summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/linux
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/linux')
-rw-r--r--src/VBox/Additions/linux/Makefile136
-rw-r--r--src/VBox/Additions/linux/Makefile.kmk449
-rw-r--r--src/VBox/Additions/linux/drm/.scm-settings35
-rw-r--r--src/VBox/Additions/linux/drm/Makefile.kmk52
-rw-r--r--src/VBox/Additions/linux/drm/Makefile.module.kms72
-rw-r--r--src/VBox/Additions/linux/drm/README.testing13
-rwxr-xr-xsrc/VBox/Additions/linux/drm/files_vboxvideo_drv59
-rw-r--r--src/VBox/Additions/linux/drm/indent.sed285
-rw-r--r--src/VBox/Additions/linux/drm/vbox_drv.c452
-rw-r--r--src/VBox/Additions/linux/drm/vbox_drv.h551
-rw-r--r--src/VBox/Additions/linux/drm/vbox_fb.c503
-rw-r--r--src/VBox/Additions/linux/drm/vbox_hgsmi.c130
-rw-r--r--src/VBox/Additions/linux/drm/vbox_irq.c225
-rw-r--r--src/VBox/Additions/linux/drm/vbox_main.c700
-rw-r--r--src/VBox/Additions/linux/drm/vbox_mode.c930
-rw-r--r--src/VBox/Additions/linux/drm/vbox_prime.c80
-rw-r--r--src/VBox/Additions/linux/drm/vbox_ttm.c820
-rwxr-xr-xsrc/VBox/Additions/linux/export_modules.sh162
-rw-r--r--src/VBox/Additions/linux/installer/.scm-settings30
-rwxr-xr-xsrc/VBox/Additions/linux/installer/autorun.sh204
-rw-r--r--src/VBox/Additions/linux/installer/deffiles80
-rwxr-xr-xsrc/VBox/Additions/linux/installer/install.sh.in641
-rw-r--r--src/VBox/Additions/linux/installer/module-autologon.sh182
-rwxr-xr-xsrc/VBox/Additions/linux/installer/vboxadd-service.sh169
-rwxr-xr-xsrc/VBox/Additions/linux/installer/vboxadd-x11.sh625
-rwxr-xr-xsrc/VBox/Additions/linux/installer/vboxadd.sh944
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/.scm-settings30
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/Config.kmk41
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk111
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/banner-dummy.pngbin0 -> 1622 bytes
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk54
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h90
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c1442
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c416
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c344
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c211
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c388
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c42
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c1655
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp1531
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop5
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui227
-rw-r--r--src/VBox/Additions/linux/sharedfolders/.scm-settings39
-rw-r--r--src/VBox/Additions/linux/sharedfolders/Makefile.kmk57
-rw-r--r--src/VBox/Additions/linux/sharedfolders/Makefile.module119
-rw-r--r--src/VBox/Additions/linux/sharedfolders/dirops.c1417
-rwxr-xr-xsrc/VBox/Additions/linux/sharedfolders/files_vboxsf107
-rw-r--r--src/VBox/Additions/linux/sharedfolders/lnkops.c305
-rw-r--r--src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c702
-rw-r--r--src/VBox/Additions/linux/sharedfolders/regops.c3881
-rw-r--r--src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c126
-rw-r--r--src/VBox/Additions/linux/sharedfolders/utils.c1263
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vbsfmount.c113
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vbsfmount.h142
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vfsmod.c1753
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vfsmod.h478
-rw-r--r--src/VBox/Additions/linux/testcase/TimesyncBackdoor.c103
57 files changed, 25721 insertions, 0 deletions
diff --git a/src/VBox/Additions/linux/Makefile b/src/VBox/Additions/linux/Makefile
new file mode 100644
index 00000000..d6067e44
--- /dev/null
+++ b/src/VBox/Additions/linux/Makefile
@@ -0,0 +1,136 @@
+#
+# Makefile for the VirtualBox Linux Guest Drivers.
+#
+
+#
+# Copyright (C) 2009-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+ifneq ($(KERNELRELEASE),)
+
+# Building from kBuild (make -C <kernel_directory> M=`pwd`)
+# or inside a kernel source tree.
+
+obj-m = vboxguest/ vboxsf/ vboxvideo/
+
+else # ! KERNELRELEASE
+
+KBUILD_VERBOSE =
+ ifeq ($(KBUILD_VERBOSE),)
+VBOX_QUIET := @
+VBOX_QUIET_SH := @
+ else
+VBOX_QUIET :=
+VBOX_QUIET_SH := set -x;
+ endif
+
+all: vboxguest vboxsf vboxvideo
+
+vboxguest:
+ @echo "=== Building 'vboxguest' module ==="
+ + $(VBOX_QUIET)$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest
+ $(VBOX_QUIET_SH)if [ -f vboxguest/vboxguest.ko ]; then \
+ cp vboxguest/vboxguest.ko .; \
+ else \
+ cp vboxguest/vboxguest.o .; \
+ fi
+ @echo
+
+vboxsf: vboxguest
+ + $(VBOX_QUIET_SH)if [ -d vboxsf ]; then \
+ if [ -f vboxguest/Module.symvers ]; then \
+ cp vboxguest/Module.symvers vboxsf; \
+ fi; \
+ echo "=== Building 'vboxsf' module ==="; \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) KBUILD_EXTRA_SYMBOLS=$(abspath vboxsf/Module.symvers) -C vboxsf || exit 1; \
+ if [ -f vboxsf/vboxsf.ko ]; then \
+ cp vboxsf/vboxsf.ko .; \
+ else \
+ cp vboxsf/vboxsf.o .; \
+ fi; \
+ echo; \
+ fi
+
+vboxvideo:
+ + $(VBOX_QUIET_SH)if [ -d vboxvideo ]; then \
+ echo "=== Building 'vboxvideo' module ==="; \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxvideo || exit 1; \
+ if [ -f vboxvideo/vboxvideo.ko ]; then \
+ cp vboxvideo/vboxvideo.ko .; \
+ elif [ -f vboxvideo/vboxvideo.o ]; then \
+ cp vboxvideo/vboxvideo.o .; \
+ fi; \
+ echo; \
+ fi
+
+install-vboxguest:
+ + $(VBOX_QUIET)$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest install
+
+install-vboxsf:
+ + $(VBOX_QUIET_SH)if [ -d vboxsf ]; then \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxsf install; \
+ fi
+
+install-vboxvideo:
+ + $(VBOX_QUIET_SH)if [ -d vboxvideo ]; then \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxvideo install; \
+ fi
+
+install: install-vboxguest install-vboxsf install-vboxvideo
+
+clean-vboxguest:
+ + $(VBOX_QUIET)$(MAKE) -C vboxguest clean
+ rm -f vboxguest.*o
+
+clean-vboxsf:
+ + $(VBOX_QUIET_SH)if [ -d vboxsf ]; then \
+ $(MAKE) -C vboxsf clean; \
+ fi
+ rm -f vboxsf.*o
+
+clean-vboxvideo:
+ + $(VBOX_QUIET_SH)if [ -d vboxvideo ]; then \
+ $(MAKE) -C vboxvideo clean; \
+ fi
+ rm -f vboxvideo.*o
+
+clean: clean-vboxguest clean-vboxsf clean-vboxvideo
+
+check:
+ $(VBOX_QUIET)$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest check
+
+unload:
+ $(VBOX_QUIET)/sbin/rmmod vboxvideo || true
+ $(VBOX_QUIET)/sbin/rmmod vboxvfs || true
+ $(VBOX_QUIET)/sbin/rmmod vboxsf || true
+ $(VBOX_QUIET)/sbin/rmmod vboxguest || true
+
+load: unload
+ $(VBOX_QUIET)/sbin/insmod vboxguest.ko
+ $(VBOX_QUIET)if [ -f vboxsf.ko ]; then /sbin/insmod vboxsf.ko; fi
+ $(VBOX_QUIET)if [ -f vboxvideo.ko ]; then /sbin/insmod vboxvideo.ko; fi
+
+.PHONY: all install clean check unload load \
+ vboxguest vboxsf vboxvideo \
+ install-vboxguest install-vboxsf install-vboxvideo \
+ clean-vboxguest clean-vboxsf clean-vboxvideo
+
+endif # ! KERNELRELEASE
diff --git a/src/VBox/Additions/linux/Makefile.kmk b/src/VBox/Additions/linux/Makefile.kmk
new file mode 100644
index 00000000..3362efa7
--- /dev/null
+++ b/src/VBox/Additions/linux/Makefile.kmk
@@ -0,0 +1,449 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for the linux guest additions base directory.
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Include sub-makefiles.
+#
+include $(PATH_SUB_CURRENT)/sharedfolders/Makefile.kmk
+include $(PATH_SUB_CURRENT)/drm/Makefile.kmk
+ifdef VBOX_WITH_LIGHTDM_GREETER
+ include $(PATH_SUB_CURRENT)/lightdm-greeter/Makefile.kmk
+endif
+
+
+#
+# Globals
+#
+
+# Basic path components
+VBOX_LNX_ADD_PACKAGE_NAME := VBoxGuestAdditions
+VBOX_LNX_ADD_INST_OUT_DIR := $(PATH_TARGET)/Additions/Installer/linux/
+VBOX_LNX_ADD_INST_DBG_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)debug/
+VBOX_LNX_ADD_INST_STAGE_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)install/
+
+# Installation paths for binaries and files
+VBOX_LNX_ADD_INST_BIN_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)bin/
+VBOX_LNX_ADD_INST_SBIN_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)sbin/
+VBOX_LNX_ADD_INST_LIB_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)lib/
+VBOX_LNX_ADD_INST_OTHER_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)other/
+VBOX_LNX_ADD_INST_KMOD_DIR_BASE := $(VBOX_LNX_ADD_INST_OUT_DIR)src/
+# This is a symbolic link, so no trailing slash.
+VBOX_LNX_ADD_INST_KMOD_PATH := $(VBOX_LNX_ADD_INST_KMOD_DIR_BASE)vboxguest-$(VBOX_VERSION_STRING)
+VBOX_LNX_ADD_INST_INIT_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)init/
+
+VBOX_LNX_ADD_ARCH_INST_DIRS := \
+ $(VBOX_LNX_ADD_INST_OUT_DIR) \
+ $(VBOX_LNX_ADD_INST_BIN_DIR) \
+ $(VBOX_LNX_ADD_INST_SBIN_DIR) \
+ $(VBOX_LNX_ADD_INST_INIT_DIR)
+
+# Installation paths for debug symbols
+VBOX_LNX_ADD_DBG_BIN_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)bin/
+VBOX_LNX_ADD_DBG_SBIN_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)sbin/
+VBOX_LNX_ADD_DBG_LIB_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)lib/
+VBOX_LNX_ADD_DBG_OTHER_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)other/
+
+VBOX_LNX_ADD_DBG_DIRS := \
+ $(VBOX_LNX_ADD_DBG_BIN_DIR) \
+ $(VBOX_LNX_ADD_DBG_SBIN_DIR) \
+ $(VBOX_LNX_ADD_DBG_OTHER_DIR)
+
+# Script source directories
+VBOX_PATH_LNX_ADD_INST := $(PATH_SUB_CURRENT)/installer/
+VBOX_REL_LNX_ADD_INST := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_LNX_ADD_INST))
+VBOX_PATH_X11_ADD_INST := $(PATH_ROOT)/src/VBox/Additions/x11/Installer/
+VBOX_REL_X11_ADD_INST := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_X11_ADD_INST))
+VBOX_PATH_LNX_INST_SRC := $(PATH_ROOT)/src/VBox/Installer/linux/
+VBOX_REL_LNX_INST_SRC := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_LNX_INST_SRC))
+VBOX_PATH_LNX_HOST_DRV := $(PATH_ROOT)/src/VBox/HostDrivers/linux/
+VBOX_REL_LNX_HOST_DRV := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_LNX_HOST_DRV))
+
+# Unset this to speed up things during makefile hacking.
+VBOX_LNX_ADD_INST_DEP_ON_MAKEFILE := $(MAKEFILE_CURRENT)
+
+
+#
+# Targets
+#
+VBOX_SELINUX_CMPLD := $(PATH_SUB_CURRENT)/selinux-fedora/vbox_x11.pp
+VBOX_LNX_ADD_ARCHIVE.x86 := $(PATH_OUT_BASE)/linux.x86/$(KBUILD_TYPE)/bin/additions/VBoxGuestAdditions-x86.tar.bz2
+VBOX_LNX_ADD_ARCHIVE.amd64 := $(PATH_OUT_BASE)/linux.amd64/$(KBUILD_TYPE)/bin/additions/VBoxGuestAdditions-amd64.tar.bz2
+ifndef VBOX_WITH_COMBINED_LINUX_GUEST_PACKAGE
+ VBOX_LNX_ADD_ARCHIVES := $(PATH_STAGE_BIN)/additions/VBoxGuestAdditions-$(KBUILD_TARGET_ARCH).tar.bz2
+else
+ VBOX_LNX_ADD_ARCHIVES := \
+ $(VBOX_LNX_ADD_ARCHIVE.x86) \
+ $(VBOX_LNX_ADD_ARCHIVE.amd64)
+endif
+BLDDIRS += \
+ $(VBOX_LNX_ADD_ARCH_INST_DIRS) \
+ $(VBOX_LNX_ADD_DBG_DIRS) \
+ $(VBOX_LNX_ADD_INST_STAGE_DIR)
+
+# Use VBOX_WITHOUT_LINUX_GUEST_PACKAGE to skip building the .run installer.
+# This will only take effect if you also use VBOX_WITHOUT_ADDITIONS_ISO.
+PACKING += \
+ $(if-expr !defined(VBOX_WITHOUT_LINUX_GUEST_PACKAGE), $(PATH_STAGE_BIN)/additions/VBoxLinuxAdditions.run,) \
+ $(VBOX_LNX_ADD_ARCHIVES) \
+ $(PATH_STAGE_BIN)/additions/VBoxGuestAdditions-dbg.tar.bz2
+OTHER_CLEAN += \
+ $(PACKING) \
+ $(VBOX_LNX_ADD_INST_KMOD_PATH) \
+ $(foreach file, $(VBOX_LNX_ADD_ARCHIVES), $(VBOX_LNX_ADD_INST_STAGE_DIR)$(subst -r$(VBOX_SVN_REV),,$(notdir $(file))))
+
+
+#
+# Files to install
+#
+VBOX_LNX_ADD_STRIP_BIN += \
+ VBoxControl \
+ VBoxDRMClient \
+ VBoxClient
+ifdef VBOX_WITH_ADDITIONS_SHIPPING_AUDIO_TEST
+ VBOX_LNX_ADD_STRIP_BIN += \
+ VBoxAudioTest
+endif
+
+VBOX_LNX_ADD_STRIP_SBIN += \
+ VBoxService \
+ $(if $(VBOX_WITH_LIGHTDM_GREETER),vbox-greeter)
+
+VBOX_LNX_ADD_STRIP_MOD = \
+ vboxmouse_drv_70.so \
+ vboxmouse_drv_71.so \
+ vboxmouse_drv_13.so \
+ vboxmouse_drv_14.so \
+ vboxmouse_drv_15.so \
+ vboxmouse_drv_16.so \
+ $(addsuffix .so,$(filter-out %_32,$(filter vboxvideo_drv_%,$(DLLS)))) \
+ $(if $(VBOX_WITH_PAM),pam_vbox.so,) \
+ mount.vboxsf
+
+VBOX_LNX_ADD_MOD = \
+ 98vboxadd-xclient \
+ x11config.sh
+
+VBOX_LNX_ADD_STRIP_OBJ = \
+ vboxmouse_drv.o \
+ vboxvideo_drv.o
+
+VBOX_LNX_ADD_INIT = \
+ vboxadd \
+ vboxadd-service \
+ vboxadd-x11
+
+#
+# All the files that go into the archive
+#
+VBOX_LNX_ADD_INST_FILES := \
+ $(addprefix $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_BIN)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR),$(VBOX_LNX_ADD_STRIP_SBIN)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_MOD)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_OBJ)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_INIT_DIR),$(VBOX_LNX_ADD_INIT))
+
+VBOX_LNX_ADD_DBG_FILES := \
+ $(addprefix $(VBOX_LNX_ADD_DBG_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)) \
+ $(addprefix $(VBOX_LNX_ADD_DBG_SBIN_DIR),$(VBOX_LNX_ADD_STRIP_SBIN)) \
+ $(addprefix $(VBOX_LNX_ADD_DBG_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)) \
+ $(addprefix $(VBOX_LNX_ADD_DBG_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD))
+
+ifdef VBOX_WITH_LIGHTDM_GREETER_PACKING
+VBOX_LNX_ADD_INST_FILES += \
+ $(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR),vbox-greeter)
+endif
+
+# Cleanup of the installer directory files
+OTHER_CLEAN += $(VBOX_LNX_ADD_INST_FILES) $(VBOX_LNX_ADD_DBG_FILES)
+
+# pattern rules for copying the debug info from the VBOX_LNX_ADD_STRIP_* files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_DBG_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)): \
+ $(VBOX_LNX_ADD_DBG_BIN_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+$(addprefix $(VBOX_LNX_ADD_DBG_SBIN_DIR),$(VBOX_LNX_ADD_STRIP_SBIN)): \
+ $(VBOX_LNX_ADD_DBG_SBIN_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+$(addprefix $(VBOX_LNX_ADD_DBG_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)): \
+ $(VBOX_LNX_ADD_DBG_LIB_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+$(addprefix $(VBOX_LNX_ADD_DBG_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD)): \
+ $(VBOX_LNX_ADD_DBG_OTHER_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_BIN files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)): \
+ $(VBOX_LNX_ADD_INST_BIN_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_BIN_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_DBG_BIN_DIR),$@) $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_SBIN files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR),\
+ $(filter-out vbox-greeter,$(VBOX_LNX_ADD_STRIP_SBIN))): \
+ $(VBOX_LNX_ADD_INST_SBIN_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_SBIN_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_SBIN_DIR),$(VBOX_LNX_ADD_DBG_SBIN_DIR),$@) $@
+
+# pattern rule for stripping and copying vbox-greeter to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR),vbox-greeter): \
+ $(VBOX_LNX_ADD_INST_SBIN_DIR)% : $(subst linux.amd64/release,linux.amd64/release/greeter,$(subst linux.x86/release,linux.x86/release/greeter,$(PATH_STAGE_BIN)))/additions/% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $< $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_LIB files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)): \
+ $(VBOX_LNX_ADD_INST_LIB_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_LIB_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_LIB_DIR),$(VBOX_LNX_ADD_DBG_LIB_DIR),$@) $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_MOD files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD)): \
+ $(VBOX_LNX_ADD_INST_OTHER_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_OTHER_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_DBG_OTHER_DIR),$@) $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_OBJ files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_OBJ)): \
+ $(VBOX_LNX_ADD_INST_OTHER_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ifeq ($(VBOX_DO_STRIP),)
+ $(QUIET)$(INSTALL) -m 0644 $< $@
+else # strip to temp file because of umask.
+ $(QUIET)objcopy --strip-unneeded -R .comment $< $@.tmp
+ $(QUIET)$(INSTALL) -m 0644 $@.tmp $@
+ $(QUIET)$(RM) -f -- $@.tmp
+endif
+
+include $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest
+VBOX_LNX_ADD_INST_FILES_VBOXGUEST=$(patsubst =>%,$(PATH_STAGE_BIN)/additions/src/vboxguest/%,$(filter =>%,$(subst =>, =>,$(subst $(DQUOTE),,$(FILES_VBOXGUEST_NOBIN)))))
+include $(PATH_ROOT)/src/VBox/Additions/linux/sharedfolders/files_vboxsf
+VBOX_LNX_ADD_INST_FILES_VBOXSF=$(patsubst =>%,$(PATH_STAGE_BIN)/additions/src/vboxsf/%,$(filter =>%,$(subst =>, =>,$(subst $(DQUOTE),,$(FILES_VBOXSF_NOBIN)))))
+include $(PATH_ROOT)/src/VBox/Additions/linux/drm/files_vboxvideo_drv
+VBOX_LNX_ADD_INST_FILES_VBOXVIDEO=$(patsubst =>%,$(PATH_STAGE_BIN)/additions/src/vboxvideo/%,$(filter =>%,$(subst =>, =>,$(subst $(DQUOTE),,$(FILES_VBOXVIDEO_DRM_NOBIN)))))
+
+# special rule for the kernel modules
+$(VBOX_LNX_ADD_INST_KMOD_DIR_BASE): \
+ $(PATH_STAGE_BIN)/additions/src/ \
+ $(PATH_ROOT)/Version.kmk \
+ | $(dir $@)
+ $(call MSG_INST_SYM,$<,$@)
+ $(QUIET)$(RM) -Rf $@
+ $(QUIET)$(MKDIR) $@
+ $(QUIET)$(LN_SYMLINK) $< $(VBOX_LNX_ADD_INST_KMOD_PATH)
+
+INSTALLS += GuestDrivers-src
+GuestDrivers-src_INST = $(INST_ADDITIONS)src/
+GuestDrivers-src_MODE = a+r,u+w
+GuestDrivers-src_SOURCES = Makefile
+
+
+INSTALLS += lnx_add_inst-exec
+lnx_add_inst-exec_INST = $(VBOX_LNX_ADD_INST_OTHER_DIR)
+lnx_add_inst-exec_INSTTYPE = stage
+lnx_add_inst-exec_EXEC_SOURCES = \
+ $(VBOX_REL_X11_ADD_INST)98vboxadd-xclient \
+ $(VBOX_REL_X11_ADD_INST)x11config.sh \
+ $(VBOX_REL_LNX_INST_SRC)check_module_dependencies.sh
+
+
+INSTALLS += lnx_add_inst-noexec
+lnx_add_inst-noexec_INST = $(VBOX_LNX_ADD_INST_OTHER_DIR)
+lnx_add_inst-noexec_INSTTYPE = stage
+lnx_add_inst-noexec_SOURCES = \
+ $(VBOX_REL_X11_ADD_INST)vboxclient.desktop \
+ $(VBOX_REL_X11_ADD_INST)vboxvideo.ids \
+ $(if $(VBOX_WITH_LIGHTDM_GREETER_PACKING),lightdm-greeter/vbox-greeter.desktop,) \
+ selinux-fedora/vbox_x11.pp \
+ selinux-fedora/vbox_accel.pp
+
+INSTALLS += lnx_add_inst-license
+lnx_add_inst-license_INST = $(VBOX_LNX_ADD_INST_OUT_DIR)
+lnx_add_inst-license_INSTTYPE = stage
+lnx_add_inst-license_SOURCES = \
+ $(VBOX_BRAND_LICENSE_TXT)=>LICENSE
+
+
+#
+# We need our routines.sh and the uninstallation scripts in the staging
+# directory too
+#
+INSTALLS += LnxAdd-scripts
+LnxAdd-scripts_INST = $(VBOX_LNX_ADD_INST_STAGE_DIR)
+LnxAdd-scripts_INSTTYPE = stage
+LnxAdd-scripts_SOURCES = \
+ $(VBOX_REL_LNX_ADD_INST)deffiles
+LnxAdd-scripts_EXEC_SOURCES = \
+ $(VBOX_REL_LNX_INST_SRC)routines.sh
+
+ifdef VBOX_WITH_LIGHTDM_GREETER_PACKING
+ LnxAdd-scripts_EXEC_SOURCES += \
+ $(VBOX_REL_LNX_ADD_INST)module-autologon.sh=>installer/module-autologon
+endif
+
+
+#
+# And the init scripts
+#
+INSTALLS += LnxAdd-init-scripts
+LnxAdd-init-scripts_INST = $(VBOX_LNX_ADD_INST_INIT_DIR)
+LnxAdd-init-scripts_INSTTYPE = stage
+LnxAdd-init-scripts_EXEC_SOURCES = \
+ $(foreach i,$(VBOX_LNX_ADD_INIT), installer/$(i).sh=>$(i))
+
+# this file needs editing before it can be included in the generic installer.
+$(VBOX_LNX_ADD_INST_STAGE_DIR)install.sh: \
+ $(VBOX_PATH_LNX_ADD_INST)install.sh.in \
+ $(VBOX_VERSION_STAMP) | $$(dir $$@)
+ $(RM) -f -- $@
+ $(QUIET)$(SED) \
+ -e "s;_VERSION_;$(VBOX_VERSION_STRING);g" \
+ -e "s;_BUILDTYPE_;$(KBUILD_TYPE);g" \
+ -e "s;_USERNAME_;$(USERNAME);g" \
+ --output $@ \
+ $<
+ $(QUIET)$(CHMOD) 0755 $@
+OTHER_CLEAN += \
+ $(VBOX_LNX_ADD_INST_OUT_DIR)install.sh \
+ $(VBOX_LNX_ADD_INST_STAGE_DIR)install.sh
+
+
+#
+# Build test for the Guest Additions kernel modules (kmk check).
+#
+$(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,vboxvideo-src,,)
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
+# All the files that go into our archive
+VBOX_LNX_ADD_ARCH_FILES = \
+ $(lnx_add_inst-noexec_2_STAGE_TARGETS) \
+ $(lnx_add_inst-exec_2_STAGE_TARGETS) \
+ $(lnx_add_inst-license_2_STAGE_TARGETS) \
+ $(VBOX_LNX_ADD_INST_FILES) \
+ $(VBOX_LNX_ADD_INST_KMOD_DIR_BASE)
+
+VBOX_LNX_ADD_INST_ARCH_DEPS := \
+ $(VBOX_LNX_ADD_ARCH_FILES) \
+ $(VBOX_LNX_ADD_INST_DEP_ON_MAKEFILE) \
+ $(VBOX_VERSION_STAMP) \
+ $(VBOX_LNX_ADD_INST_FILES_VBOXGUEST) \
+ $(VBOX_LNX_ADD_INST_FILES_VBOXSF) \
+ $(VBOX_LNX_ADD_INST_FILES_VBOXVIDEO)
+
+#
+# .tar.bz2 for converting into .run
+#
+$(VBOX_LNX_ADD_ARCHIVE.$(KBUILD_TARGET_ARCH)): \
+ $(VBOX_LNX_ADD_INST_ARCH_DEPS)
+ $(call MSG_L1,Packing $@)
+ $(QUIET)$(RM) -f -- $(wildcard $(dir $@)VBoxGuestAdditions-*r*.tar.bz2)
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(CHMOD) 0755 $(VBOX_LNX_ADD_ARCH_INST_DIRS)
+ifdef VBOX_USE_PBZIP2
+ $(QUIET)tar --dereference --owner 0 --group 0 -cRf $(patsubst %.bz2,%,$@) \
+ -C $(VBOX_LNX_ADD_INST_OUT_DIR) \
+ LICENSE bin init lib other sbin src \
+ $(if $(filter $(KBUILD_TYPE),debug),debug)
+ $(QUIET)pbzip2 $(patsubst %.bz2,%,$@)
+else
+ $(QUIET)tar --dereference --owner 0 --group 0 --ignore-failed-read -cjRf $@ \
+ -C $(VBOX_LNX_ADD_INST_OUT_DIR) \
+ LICENSE bin init lib other sbin src \
+ $(if $(filter $(KBUILD_TYPE),debug),debug)
+endif
+ $(QUIET)$(CHMOD) 0644 $@
+
+
+#
+# .tar.bz2 containing the debug information
+#
+$(PATH_STAGE_BIN)/additions/VBoxGuestAdditions-dbg.tar.bz2: \
+ $(VBOX_LNX_ADD_DBG_FILES) \
+ $(VBOX_LNX_ADD_INST_DEP_ON_MAKEFILE)
+ $(call MSG_L1,Packing $@)
+ $(QUIET)$(RM) -f -- $@ $(patsubst %.bz2,%,$@)
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(CHMOD) 0755 $(VBOX_LNX_ADD_DBG_DIRS)
+ifdef VBOX_USE_PBZIP2
+ $(QUIET)tar --dereference --owner 0 --group 0 -cRf $(patsubst %.bz2,%,$@) \
+ -C $(VBOX_LNX_ADD_INST_DBG_DIR) \
+ bin lib sbin
+ $(QUIET)pbzip2 $(patsubst %.bz2,%,$@)
+else
+ $(QUIET)tar --dereference --owner 0 --group 0 --ignore-failed-read -cjRf $@ \
+ -C $(VBOX_LNX_ADD_INST_DBG_DIR) \
+ bin lib sbin
+endif
+ $(QUIET)$(CHMOD) 0644 $@
+
+
+#
+# Build the Linux Guest Additions self extracting installer.
+#
+# Note that $(PATH_SUB_CURRENT) was changed by subfooter.kmk above and
+# any references should be made via variables assigned a know value via := .
+#
+$(PATH_STAGE_BIN)/additions/VBoxLinuxAdditions.run: \
+ $(VBOX_LNX_ADD_ARCHIVES) \
+ $(VBOX_LNX_ADD_INST_STAGE_DIR)install.sh \
+ $$(LnxAdd-scripts_2_STAGE_TARGETS) \
+ $(VBOX_VERSION_STAMP)
+ # Remove any archives left over from previous builds so that they don't
+ # end up in our installer as well.
+ $(QUIET)$(RM) -f $(foreach file, $(wildcard $(VBOX_LNX_ADD_INST_STAGE_DIR)$(VBOX_LNX_ADD_PACKAGE_NAME)-*.tar.bz2), $(file))
+ $(QUIET)$(foreach file, $(VBOX_LNX_ADD_ARCHIVES), \
+ $(CP) -f $(file) $(VBOX_LNX_ADD_INST_STAGE_DIR)$(subst -r$(VBOX_SVN_REV),,$(notdir $(file)))$(NLTAB) )
+ $(QUIET)$(VBOX_MAKESELF) --nocomp $(if $(VBOX_OSE),,--tar-no-format) $(VBOX_LNX_ADD_INST_STAGE_DIR) $@ \
+ "VirtualBox $(VBOX_VERSION_STRING) Guest Additions for Linux" \
+ /bin/sh ./install.sh "\$$0"
diff --git a/src/VBox/Additions/linux/drm/.scm-settings b/src/VBox/Additions/linux/drm/.scm-settings
new file mode 100644
index 00000000..a3794be9
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/.scm-settings
@@ -0,0 +1,35 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for linux drm driver.
+#
+
+#
+# Copyright (C) 2010-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+#Sources are MIT licensed for simplier upstreaming, several files are external.
+/*.c|/*.h: --license-based-on-mit --no-convert-tabs
+/vbox_hgsmi.c: --license-mit
+/Makefile.module.kms: --treat-as Makefile
+
+--filter-out-files /README.testing
+
diff --git a/src/VBox/Additions/linux/drm/Makefile.kmk b/src/VBox/Additions/linux/drm/Makefile.kmk
new file mode 100644
index 00000000..28bd93bc
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/Makefile.kmk
@@ -0,0 +1,52 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the vboxvideo DRM module (linux kernel OpenGL module).
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Populate FILES_VBOXVIDEO_DRM_NOBIN
+#
+INSTALLS += vboxvideo-src
+include $(PATH_SUB_CURRENT)/files_vboxvideo_drv
+vboxvideo-src_DEPS = \
+ $(PATH_ROOT)/src/VBox/Additions/linux/drm/files_vboxvideo_drv \
+ $(PATH_ROOT)/src/VBox/Additions/linux/drm/indent.sed
+vboxvideo-src_INST = $(INST_ADDITIONS)src/vboxvideo/
+vboxvideo-src_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXVIDEO_DRM_NOBIN))
+vboxvideo-src_EXEC_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXVIDEO_DRM_BIN))
+vboxvideo-src_INSTALLER = $(RM_EXT) -f -- "$2" && \
+ $(if $(filter %.c %.h,$2),$(SED) -f $(PATH_ROOT)/src/VBox/Additions/linux/drm/indent.sed \
+ --output "$2" "$1",$(CP_EXT) "$1" "$2") && \
+ $(CHMOD_EXT) "$(if $(mode),$(mode),0644)" "$2"
+
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/linux/drm/Makefile.module.kms b/src/VBox/Additions/linux/drm/Makefile.module.kms
new file mode 100644
index 00000000..0a061f22
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/Makefile.module.kms
@@ -0,0 +1,72 @@
+# $Id: Makefile.module.kms $
+## @file
+# VirtualBox Guest Additions Module Makefile.
+#
+# (For 2.6.x this file must be 'Makefile'!)
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Linux kbuild sets this to our source directory if we are called from there
+obj ?= $(CURDIR)
+include $(obj)/Makefile-header.gmk
+VBOXDRM_DIR = $(VBOX_MODULE_SRC_DIR)
+
+# We want to build on Linux 3.11 and later and on all EL 7 kernels.
+VBOX_BUILD =
+ifneq ($(filter-out 1.% 2.% 3.0 3.0.% 3.1 3.1.% 3.2 3.2.% 3.3 3.3.% 3.4 3.4.% 3.5 3.5.% 3.6 3.6.% 3.7 3.7.% 3.8 3.8.% 3.9 3.9.% 3.10 3.10.%,$(KERN_VER)),)
+ VBOX_BUILD = 1
+endif
+ifeq ($(filter-out %.el7.x86_64,$(KERN_VER)),)
+ VBOX_BUILD = 1
+endif
+
+ifneq ($(VBOX_BUILD),)
+
+VBOXMOD_NAME = vboxvideo
+VBOXMOD_OBJS = \
+ hgsmi_base.o \
+ modesetting.o \
+ vbox_drv.o \
+ vbox_fb.o \
+ vbox_irq.o \
+ vbox_main.o \
+ vbox_mode.o \
+ vbox_ttm.o \
+ vbva_base.o \
+ vbox_prime.o \
+ vbox_hgsmi.o
+VBOXMOD_INCL = \
+ $(VBOXDRM_DIR) \
+ $(KERN_INCL)/drm
+
+include $(obj)/Makefile-footer.gmk
+
+else # !VBOX_BUILD
+
+ all:
+ install:
+ clean:
+
+endif # !VBOX_BUILD
+
diff --git a/src/VBox/Additions/linux/drm/README.testing b/src/VBox/Additions/linux/drm/README.testing
new file mode 100644
index 00000000..72cd96c3
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/README.testing
@@ -0,0 +1,13 @@
+This document lists things which have been known to fail in the past with the
+drm video driver and which should be tested regularly, e.g. when making code
+changes or before releases.
+
+ * Test that "auto-resize" is enabled in the GUI if user space supports it.
+ * Test that old versions of Plymouth which do not report rectangles
+ (pre-0.9.0/2014-05-20) update the screen correctly.
+ * Having valid, non-overlapping offset hints on all screens has caused
+ mouse integration and/or screen updates to break for fbdev, X.Org or
+ GNOME Shell/Wayland.
+ * Note that if a multi-screen VM is booted with only one screen enabled
+ the fbdev console will be disabled on the others until reboot. Test this
+ configuration.
diff --git a/src/VBox/Additions/linux/drm/files_vboxvideo_drv b/src/VBox/Additions/linux/drm/files_vboxvideo_drv
new file mode 100755
index 00000000..c2af58d4
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/files_vboxvideo_drv
@@ -0,0 +1,59 @@
+#!/bin/sh
+# $Id: files_vboxvideo_drv $
+## @file
+# Shared file between Makefile.kmk and export_modules.sh.
+#
+
+#
+# Copyright (C) 2011-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+FILES_VBOXVIDEO_DRM_NOBIN=" \
+ ${PATH_OUT}/version-generated.h=>version-generated.h \
+ ${PATH_OUT}/revision-generated.h=>revision-generated.h \
+ ${PATH_OUT}/product-generated.h=>product-generated.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideo.h=>vboxvideo.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideoGuest.h=>vboxvideo_guest.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIChannels.h=>hgsmi_channels.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIChSetup.h=>hgsmi_ch_setup.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIContext.h=>hgsmi_context.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIDefs.h=>hgsmi_defs.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideoErr.h=>vbox_err.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideoVBE.h=>vboxvideo_vbe.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp=>hgsmi_base.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp=>modesetting.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp=>vbva_base.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_drv.c=>vbox_drv.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_drv.h=>vbox_drv.h \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_fb.c=>vbox_fb.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_irq.c=>vbox_irq.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_main.c=>vbox_main.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_mode.c=>vbox_mode.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_prime.c=>vbox_prime.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_ttm.c=>vbox_ttm.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_hgsmi.c=>vbox_hgsmi.c \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/Makefile.module.kms=>Makefile \
+"
+
+FILES_VBOXVIDEO_DRM_BIN=" \
+"
diff --git a/src/VBox/Additions/linux/drm/indent.sed b/src/VBox/Additions/linux/drm/indent.sed
new file mode 100644
index 00000000..7116615e
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/indent.sed
@@ -0,0 +1,285 @@
+# Oracle VM VirtualBox
+# VirtualBox to Linux kernel coding style conversion script.
+
+#
+# Copyright (C) 2017-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# This script is for converting code inside the vboxvideo module to Linux
+# kernel coding style. It assumes correct VirtualBox coding style, will break
+# break if the coding style is wrong (e.g. tab instead of spaces) and is not
+# indended to be a generic solution: for example, identifiers will be
+# translated case by case, not algorithmically. It also assumes that any
+# flexibility in either coding style will be used where possible to make the
+# code conform to both at once.
+
+# Replace up to six leading groups of four spaces with tabs.
+s/^ */\t\t\t\t\t\t/g
+s/^ /\t\t\t\t\t/g
+s/^ /\t\t\t\t/g
+s/^ /\t\t\t/g
+s/^ /\t\t/g
+s/^ /\t/g
+
+# Change various symbols and file names to fit kernel conventions.
+
+# Miscellaneous:
+# Remove @file headers.
+\|/\*\* @file| {
+:start
+ N
+ s|\*/|\*/|g
+ T start
+ N
+ d
+}
+/^\/\* \$Id:.*\*\/$/d
+/^typedef .* HGSMIOFFSET;/d
+/^typedef .* HGSMISIZE;/d
+s/^#\( *\)include <\([^/]*\)>/#\1include "\2"/g
+
+# File names:
+s/\bHGSMIBase\.h\b/vbox_drv.h/g
+s/\bHGSMIChannels\.h\b/hgsmi_channels.h/g
+s/\bHGSMIChSetup\.h\b/hgsmi_ch_setup.h/g
+s/\bHGSMIContext\.h\b/hgsmi_context.h/g
+s/\bHGSMIDefs\.h\b/hgsmi_defs.h/g
+s/\bVBoxVideoGuest\.h\b/vboxvideo_guest.h/g
+s/\bVBoxVideo\.h\b/vboxvideo.h/g
+s/\bVBoxVideoIPRT\.h\b/vbox_err.h/g
+s/\bVBoxVideoVBE\.h\b/vboxvideo_vbe.h/g
+
+# Function names:
+s/\btestQueryConf\b/hgsmi_test_query_conf/g
+s/\bVBoxHGSMIBufferAlloc\b/hgsmi_buffer_alloc/g
+s/\bVBoxHGSMIBufferFree\b/hgsmi_buffer_free/g
+s/\bVBoxHGSMIBufferSubmit\b/hgsmi_buffer_submit/g
+s/\bVBoxHGSMICursorPosition\b/hgsmi_cursor_position/g
+s/\bVBoxHGSMIGetModeHints\b/hgsmi_get_mode_hints/g
+s/\bVBoxHGSMIProcessDisplayInfo\b/hgsmi_process_display_info/g
+s/\bVBoxHGSMIReportFlagsLocation\b/hgsmi_report_flags_location/g
+s/\bVBoxHGSMISendCapsInfo\b/hgsmi_send_caps_info/g
+s/\bVBoxHGSMIUpdateInputMapping\b/hgsmi_update_input_mapping/g
+s/\bVBoxHGSMIUpdatePointerShape\b/hgsmi_update_pointer_shape/g
+s/\bvboxHwBufferAvail\b/vbva_buffer_available/g
+s/\bvboxHwBufferEndUpdate\b/vbva_buffer_end_update/g
+s/\bvboxHwBufferFlush\b/vbva_buffer_flush/g
+s/\bvboxHwBufferPlaceDataAt\b/vbva_buffer_place_data_at/g
+s/\bvboxHwBufferWrite\b/vbva_write/g
+s/\bVBoxQueryConfHGSMI\b/hgsmi_query_conf/g
+s/\bVBoxVBVABufferBeginUpdate\b/vbva_buffer_begin_update/g
+s/\bVBoxVBVABufferEndUpdate\b/vbva_buffer_end_update/g
+s/\bVBoxVBVADisable\b/vbva_disable/g
+s/\bVBoxVBVAEnable\b/vbva_enable/g
+s/\bvboxVBVAInformHost\b/vbva_inform_host/g
+s/\bvboxVBVASetupBufferContext\b/vbva_setup_buffer_context/g
+s/\bVBVO_PORT_READ_U8\b/inb/g
+s/\bVBVO_PORT_READ_U16\b/inw/g
+s/\bVBVO_PORT_READ_U32\b/inl/g
+s/\bVBVO_PORT_WRITE_U8\b *( *\(\b[^(),]*\b\) *, *\(\b[^(),]*\b\) *)/outb(\2, \1)/g
+s/\bVBVO_PORT_WRITE_U16\b *( *\(\b[^(),]*\b\) *, *\(\b[^(),]*\b\) *)/outw(\2, \1)/g
+s/\bVBVO_PORT_WRITE_U32\b *( *\(\b[^(),]*\b\) *, *\(\b[^(),]*\b\) *)/outl(\2, \1)/g
+s/\bVBVO_PORT_WRITE_U[0-9]*\b/VBVO_PORT_WRITE_statement_should_be_on_one_line/g
+
+# Macros:
+s/\b_1K\b/1024/g
+s/\b_4M\b/4*1024*1024/g
+s/\bAssert\b\([^;]*\);/WARN_ON_ONCE(!(\1));/g
+s/\bAssertCompile\b/assert_compile/g
+s/\bAssertCompileSize\b/assert_compile_size/g
+s/\bAssertPtr\b\([^;]*\);/WARN_ON_ONCE(!(\1));/g
+s/\bAssertPtrReturn\b/assert_ptr_return/g
+/AssertPtrNullReturnVoid/d
+s/\bAssertRC\b\([^;]*\);/WARN_ON_ONCE(RT_FAILURE\1);/g
+s/\bAssertRC\b/Assert_RC_statement_should_be_on_one_line/g
+s/\bDECLCALLBACK\b(\([^)]*\))/\1/g
+ s/\bDECLCALLBACKTYPE\b(\([^,)]*\), *\([^,)]*\), *(\([^;)]*\) *) *)/\1 \2(\3)/g
+s/\bDECLCALLBACKMEMBER\b(\([^,)]*\), *\([^,)]*\), *(\([^;)]*\) *) *)/\1 (*\2)(\3)/g
+s/^\bDECLHIDDEN\b(\([^)]*\))/\1/g
+s/\bDECLINLINE\b(\([^)]*\))/static inline \1/g
+s/\bRT_BIT\b/BIT/g
+s/\bRT_BOOL\b(\([^)]*\))/(!!(\1))/g
+/RT_C_DECLS/d
+s/\bUINT16_MAX\b/U16_MAX/g
+s/\bUINT32_MAX\b/U32_MAX/g
+s/\bUINT32_C\b(\(.*\))/\1u/g
+s/!RT_VALID_PTR(/WARN_ON(!/g
+s/\bRT_UNTRUSTED_VOLATILE_HOST\b//g
+s/\bRT_UNTRUSTED_VOLATILE_GUEST\b//g
+s/\bRT_UNTRUSTED_VOLATILE_HSTGST\b//g
+
+# Type names:
+s/\bint32_t\b/s32/g
+s/\buint8_t\b/u8/g
+s/\buint16_t\b/u16/g
+s/\buint32_t\b/u32/g
+s/(HGSMIBUFFERLOCATION \*)//g # Remove C++ casts from void.
+s/typedef struct HGSMIBUFFERLOCATION/struct hgsmi_buffer_location/g
+s/struct HGSMIBUFFERLOCATION/struct hgsmi_buffer_location/g
+s/} HGSMIBUFFERLOCATION/}/g
+s/\bHGSMIBUFFERLOCATION\b/struct hgsmi_buffer_location/g
+s/\([^*] *\)\bPHGSMIGUESTCOMMANDCONTEXT\b/\1struct gen_pool */g
+s/(HGSMIHOSTFLAGS \*)//g # Remove C++ casts from void.
+s/typedef struct HGSMIHOSTFLAGS/struct hgsmi_host_flags/g
+s/struct HGSMIHOSTFLAGS/struct hgsmi_host_flags/g
+s/} HGSMIHOSTFLAGS/}/g
+s/\bHGSMIHOSTFLAGS\b/struct hgsmi_host_flags/g
+s/\bHGSMIOFFSET\b/u32/g
+s/\bHGSMISIZE\b/u32/g
+s/\bRTRECT\b/void/g
+s/(VBVABUFFERCONTEXT \*)//g # Remove C++ casts from void.
+s/struct VBVABUFFERCONTEXT/struct vbva_buf_context/g
+s/} VBVABUFFERCONTEXT/} vbva_buf_context/g
+s/\bVBVABUFFERCONTEXT\b/struct vbva_buf_context/g
+s/\([^*] *\)\bPVBVABUFFERCONTEXT\b/\1struct vbva_buf_context */g
+s/(VBVACAPS \*)//g # Remove C++ casts from void.
+s/struct VBVACAPS/struct vbva_caps/g
+s/} VBVACAPS/} vbva_caps/g
+s/\bVBVACAPS\b/struct vbva_caps/g
+s/(VBVACONF32 \*)//g # Remove C++ casts from void.
+s/struct VBVACONF32/struct vbva_conf32/g
+s/} VBVACONF32/} vbva_conf32/g
+s/\bVBVACONF32\b/struct vbva_conf32/g
+s/(VBVACURSORPOSITION \*)//g # Remove C++ casts from void.
+s/struct VBVACURSORPOSITION/struct vbva_cursor_position/g
+s/} VBVACURSORPOSITION/} vbva_cursor_position/g
+s/\bVBVACURSORPOSITION\b/struct vbva_cursor_position/g
+s/(VBVAENABLE_EX \*)//g # Remove C++ casts from void.
+s/struct VBVAENABLE_EX/struct vbva_enable_ex/g
+s/} VBVAENABLE_EX/} vbva_enable_ex/g
+s/\bVBVAENABLE_EX\b/struct vbva_enable_ex/g
+s/(VBVAMOUSEPOINTERSHAPE \*)//g # Remove C++ casts from void.
+s/struct VBVAMOUSEPOINTERSHAPE/struct vbva_mouse_pointer_shape/g
+s/} VBVAMOUSEPOINTERSHAPE/} vbva_mouse_pointer_shape/g
+s/\bVBVAMOUSEPOINTERSHAPE\b/struct vbva_mouse_pointer_shape/g
+s/(VBVAMODEHINT \*)//g # Remove C++ casts from void.
+s/struct VBVAMODEHINT/struct vbva_modehint/g
+s/} VBVAMODEHINT/} vbva_modehint/g
+s/\bVBVAMODEHINT\b/struct vbva_modehint/g
+s/(VBVAQUERYMODEHINTS \*)//g # Remove C++ casts from void.
+s/struct VBVAQUERYMODEHINTS/struct vbva_query_mode_hints/g
+s/} VBVAQUERYMODEHINTS/} vbva_query_mode_hints/g
+s/\bVBVAQUERYMODEHINTS\b/struct vbva_query_mode_hints/g
+s/(VBVAREPORTINPUTMAPPING \*)//g # Remove C++ casts from void.
+s/struct VBVAREPORTINPUTMAPPING/struct vbva_report_input_mapping/g
+s/} VBVAREPORTINPUTMAPPING/} vbva_report_input_mapping/g
+s/\bVBVAREPORTINPUTMAPPING\b/struct vbva_report_input_mapping/g
+
+# Variable and parameter names:
+s/\baRecords\b/records/g
+s/\bau8Data\b/data/g
+s/\bau32Reserved\b/reserved/g
+s/\bBase\b/base/g
+s/\bbEnable\b/enable/g
+s/\bbRc\b/ret/g
+s/\bcb\b/len/g
+s/\bcbBuffer\b/buffer_length/g
+s/\bcbChunk\b/chunk/g
+s/\bcbData\b/data_len/g
+s/\bcbHintsStructureGuest\b/hints_structure_guest_size/g
+s/\bcbHwBufferAvail\b/available/g
+s/\bcbLength\b/len/g
+s/\bcbLocation\b/buf_len/g
+s/\bcbPartialWriteThreshold\b/partial_write_tresh/g ## @todo fix this?
+s/\bcbPitch\b/pitch/g
+s/\bcbPixels\b/pixel_len/g
+s/\bcBPP\b/bpp/g
+s/\bcbRecord\b/len_and_flags/g ## @todo fix this?
+s/\bcDisplay\b/display/g
+s/\bcHeight\b/height/g
+s/\bcHintsQueried\b/hints_queried_count/g
+s/\bcHotX\b/hot_x/g
+s/\bcHotY\b/hot_y/g
+s/\bcOriginX\b/origin_x/g
+s/\bcOriginY\b/origin_y/g
+s/\bcScreen\b/screen/g
+s/\bcScreens\b/screens/g
+s/\bcWidth\b/width/g
+s/\bfCaps\b/caps/g
+s/\bfFlags\b/flags/g
+s/\bfHwBufferOverflow\b/buffer_overflow/g
+s/\bfReportPosition\b/report_position/g
+s/\bfu32Flags\b/flags/g
+s/\bhostFlags\b/host_flags/g
+s/\bi32Diff\b/diff/g
+s/\bi32OriginX\b/origin_x/g
+s/\bi32OriginY\b/origin_y/g
+s/\bi32Result\b/result/g
+s/\bindexRecordFirst\b/first_record_index/g
+s/\bindexRecordFree\b/free_record_index/g
+s/\bindexRecordNext\b/next/g
+s/\boff32Data\b/data_offset/g
+s/\boff32Free\b/free_offset/g
+s/\boffLocation\b/location/g
+s/\boffStart\b/start_offset/g
+s/\boffVRAMBuffer\b/buffer_offset/g
+s/\bpaHints\b/hints/g
+s/\bpCtx\b/ctx/g
+s/\bpPixels\b/pixels/g
+s/\bpRecord\b/record/g
+s/\bpulValue\b/value_ret/g
+s/\bpVBVA\b/vbva/g
+s/\bpxHost\b/x_host/g
+s/\bpyHost\b/y_host/g
+s/\bu16BitsPerPixel\b/bits_per_pixel/g
+s/\bu16Flags\b/flags/g
+s/\bu32BytesTillBoundary\b/bytes_till_boundary/g
+s/\bu32Flags\b/flags/g
+s/\bu32Height\b/height/g
+s/\bu32HostEvents\b/host_events/g
+s/\bu32HostFlags\b/host_flags/g
+s/\bu32HotX\b/hot_x/g
+s/\bu32HotY\b/hot_y/g
+s/\bu32Index\b/index/g
+s/\bu32LineSize\b/line_size/g
+s/\bu32Offset\b/offset/g
+s/\bu32Reserved\b/reserved/g
+s/\bu32ScreenId\b/screen_id/g
+s/\bu32StartOffset\b/start_offset/g
+s/\bu32SupportedOrders\b/supported_orders/g
+s/\bu32Value\b/value/g
+s/\bu32ViewIndex\b/view_index/g
+s/\bu32Width\b/width/g
+s/\bulValue\b/value/g
+
+# Header file guard:
+s/__HGSMIChannels_h__/__HGSMI_CHANNELS_H__/g
+s/VBOX_INCLUDED_Graphics_HGSMIChSetup_h/__HGSMI_CH_SETUP_H__/g
+
+# And move braces. This must be the last expression as it jumps to the next
+# line.
+/..*$/ {
+ N
+ s/^\([\t ][\t ]*\)} *\n[\t ]*else/\1} else/g
+ t continue_else
+ b try_brace
+:continue_else
+ N
+:try_brace
+ s/^\([\t ].*\)\n[\t ][\t ]*{/\1 {/g
+ s/^\([^#()]*\)\n[\t ]*{/\1 {/g
+ t done_brace
+ P
+ D
+:done_brace
+ p
+ d
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_drv.c b/src/VBox/Additions/linux/drm/vbox_drv.c
new file mode 100644
index 00000000..cac22e94
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_drv.c
@@ -0,0 +1,452 @@
+/* $Id: vbox_drv.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ * This file is based on ast_drv.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/vt_kern.h>
+
+#include "vbox_drv.h"
+
+#include <drm/drm_crtc_helper.h>
+#if RTLNX_VER_MIN(5,1,0) || RTLNX_RHEL_MAJ_PREREQ(8,1)
+# include <drm/drm_probe_helper.h>
+#endif
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# include <drm/drm_aperture.h>
+#endif
+
+#include "version-generated.h"
+#include "revision-generated.h"
+
+/** Detect whether kernel mode setting is OFF. */
+#if defined(CONFIG_VGA_CONSOLE)
+# if RTLNX_VER_MIN(5,17,0)
+# define VBOX_VIDEO_NOMODESET() drm_firmware_drivers_only() && vbox_modeset == -1
+# elif RTLNX_VER_MIN(4,7,0)
+# define VBOX_VIDEO_NOMODESET() vgacon_text_force() && vbox_modeset == -1
+# else /* < 4.7.0 */
+# define VBOX_VIDEO_NOMODESET() 0
+# endif /* < 4.7.0 */
+#else /* !CONFIG_VGA_CONSOLE */
+# define VBOX_VIDEO_NOMODESET() 0
+#endif /* !CONFIG_VGA_CONSOLE */
+
+static int vbox_modeset = -1;
+
+MODULE_PARM_DESC(modeset, "Disable/Enable modesetting");
+module_param_named(modeset, vbox_modeset, int, 0400);
+
+static struct drm_driver driver;
+
+static const struct pci_device_id pciidlist[] = {
+ { 0x80ee, 0xbeef, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { 0, 0, 0},
+};
+MODULE_DEVICE_TABLE(pci, pciidlist);
+
+static int vbox_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MIN(8,3)
+ struct drm_device *dev = NULL;
+ int ret = 0;
+
+# if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MIN(9,1) || RTLNX_SUSE_MAJ_PREREQ(15,4)
+ ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &driver);
+# else
+ ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "vboxvideofb");
+# endif
+ if (ret)
+ {
+ printk("unable to remove conflicting framebuffer devices\n");
+ return ret;
+ }
+# endif /* >= 5.14. */
+
+ dev = drm_dev_alloc(&driver, &pdev->dev);
+ if (IS_ERR(dev)) {
+ ret = PTR_ERR(dev);
+ goto err_drv_alloc;
+ }
+# if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ dev->pdev = pdev;
+# endif
+ pci_set_drvdata(pdev, dev);
+
+ ret = vbox_driver_load(dev);
+ if (ret)
+ goto err_vbox_driver_load;
+
+ ret = drm_dev_register(dev, 0);
+ if (ret)
+ goto err_drv_dev_register;
+ return ret;
+
+err_drv_dev_register:
+ vbox_driver_unload(dev);
+err_vbox_driver_load:
+ drm_dev_put(dev);
+err_drv_alloc:
+ return ret;
+#else /* < 4.19.0 || RHEL < 8.3 */
+ return drm_get_pci_dev(pdev, ent, &driver);
+#endif
+}
+
+static void vbox_pci_remove(struct pci_dev *pdev)
+{
+ struct drm_device *dev = pci_get_drvdata(pdev);
+
+#if RTLNX_VER_MAX(4,19,0)
+ drm_put_dev(dev);
+#else
+ drm_dev_unregister(dev);
+ vbox_driver_unload(dev);
+ drm_dev_put(dev);
+#endif
+}
+
+#if RTLNX_VER_MAX(4,9,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+static void drm_fb_helper_set_suspend_unlocked(struct drm_fb_helper *fb_helper,
+ bool suspend)
+{
+ if (!fb_helper || !fb_helper->fbdev)
+ return;
+
+ console_lock();
+ fb_set_suspend(fb_helper->fbdev, suspend);
+ console_unlock();
+}
+#endif
+
+static int vbox_drm_freeze(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ drm_kms_helper_poll_disable(dev);
+
+ pci_save_state(VBOX_DRM_TO_PCI_DEV(dev));
+
+ drm_fb_helper_set_suspend_unlocked(&vbox->fbdev->helper, true);
+
+ return 0;
+}
+
+static int vbox_drm_thaw(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ drm_mode_config_reset(dev);
+ drm_helper_resume_force_mode(dev);
+ drm_fb_helper_set_suspend_unlocked(&vbox->fbdev->helper, false);
+
+ return 0;
+}
+
+static int vbox_drm_resume(struct drm_device *dev)
+{
+ int ret;
+
+ if (pci_enable_device(VBOX_DRM_TO_PCI_DEV(dev)))
+ return -EIO;
+
+ ret = vbox_drm_thaw(dev);
+ if (ret)
+ return ret;
+
+ drm_kms_helper_poll_enable(dev);
+
+ return 0;
+}
+
+static int vbox_pm_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct drm_device *ddev = pci_get_drvdata(pdev);
+ int error;
+
+ error = vbox_drm_freeze(ddev);
+ if (error)
+ return error;
+
+ pci_disable_device(pdev);
+ pci_set_power_state(pdev, PCI_D3hot);
+
+ return 0;
+}
+
+static int vbox_pm_resume(struct device *dev)
+{
+ struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+
+ return vbox_drm_resume(ddev);
+}
+
+static int vbox_pm_freeze(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct drm_device *ddev = pci_get_drvdata(pdev);
+
+ if (!ddev || !ddev->dev_private)
+ return -ENODEV;
+
+ return vbox_drm_freeze(ddev);
+}
+
+static int vbox_pm_thaw(struct device *dev)
+{
+ struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+
+ return vbox_drm_thaw(ddev);
+}
+
+static int vbox_pm_poweroff(struct device *dev)
+{
+ struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+
+ return vbox_drm_freeze(ddev);
+}
+
+static const struct dev_pm_ops vbox_pm_ops = {
+ .suspend = vbox_pm_suspend,
+ .resume = vbox_pm_resume,
+ .freeze = vbox_pm_freeze,
+ .thaw = vbox_pm_thaw,
+ .poweroff = vbox_pm_poweroff,
+ .restore = vbox_pm_resume,
+};
+
+static struct pci_driver vbox_pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ .probe = vbox_pci_probe,
+ .remove = vbox_pci_remove,
+ .driver.pm = &vbox_pm_ops,
+};
+
+#if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+/* This works around a bug in X servers prior to 1.18.4, which sometimes
+ * submit more dirty rectangles than the kernel is willing to handle and
+ * then disable dirty rectangle handling altogether when they see the
+ * EINVAL error. I do not want the code to hang around forever, which is
+ * why I am limiting it to certain kernel versions. We can increase the
+ * limit if some distributions uses old X servers with new kernels. */
+long vbox_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ long rc = drm_ioctl(filp, cmd, arg);
+
+ if (cmd == DRM_IOCTL_MODE_DIRTYFB && rc == -EINVAL)
+ return -EOVERFLOW;
+
+ return rc;
+}
+#endif /* RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4) */
+
+static const struct file_operations vbox_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+#if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+ .unlocked_ioctl = vbox_ioctl,
+#else
+ .unlocked_ioctl = drm_ioctl,
+#endif
+ .mmap = vbox_mmap,
+ .poll = drm_poll,
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,0)
+ .fasync = drm_fasync,
+#endif
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .read = drm_read,
+};
+
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+static void
+#else
+static int
+#endif
+vbox_master_set(struct drm_device *dev,
+ struct drm_file *file_priv, bool from_open)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ /*
+ * We do not yet know whether the new owner can handle hotplug, so we
+ * do not advertise dynamic modes on the first query and send a
+ * tentative hotplug notification after that to see if they query again.
+ */
+ vbox->initial_mode_queried = false;
+
+ mutex_lock(&vbox->hw_mutex);
+ /* Start the refresh timer in case the user does not provide dirty
+ * rectangles. */
+ vbox->need_refresh_timer = true;
+ schedule_delayed_work(&vbox->refresh_work, VBOX_REFRESH_PERIOD);
+ mutex_unlock(&vbox->hw_mutex);
+
+#if RTLNX_VER_MAX(5,9,0) && !RTLNX_RHEL_MAJ_PREREQ(8,4) && !RTLNX_SUSE_MAJ_PREREQ(15,3)
+ return 0;
+#endif
+}
+
+#if RTLNX_VER_MAX(4,8,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+static void vbox_master_drop(struct drm_device *dev,
+ struct drm_file *file_priv, bool from_release)
+#else
+static void vbox_master_drop(struct drm_device *dev, struct drm_file *file_priv)
+#endif
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ /* See vbox_master_set() */
+ vbox->initial_mode_queried = false;
+ vbox_report_caps(vbox);
+
+ mutex_lock(&vbox->hw_mutex);
+ vbox->need_refresh_timer = false;
+ mutex_unlock(&vbox->hw_mutex);
+}
+
+static struct drm_driver driver = {
+#if RTLNX_VER_MAX(5,4,0) && !RTLNX_RHEL_MAJ_PREREQ(8,3) && !RTLNX_SUSE_MAJ_PREREQ(15,3)
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ |
+# if RTLNX_VER_MAX(5,1,0) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ DRIVER_IRQ_SHARED |
+# endif
+ DRIVER_PRIME,
+#else /* >= 5.4.0 && RHEL >= 8.3 && SLES >= 15-SP3 */
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ,
+#endif /* < 5.4.0 */
+
+#if RTLNX_VER_MAX(4,19,0) && !RTLNX_RHEL_MAJ_PREREQ(8,3)
+ /* Legacy hooks, but still supported. */
+ .load = vbox_driver_load,
+ .unload = vbox_driver_unload,
+#endif
+ .lastclose = vbox_driver_lastclose,
+ .master_set = vbox_master_set,
+ .master_drop = vbox_master_drop,
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+# if RTLNX_VER_MAX(4,14,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ .set_busid = drm_pci_set_busid,
+# endif
+#endif
+
+ .fops = &vbox_fops,
+#if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_RANGE(8,7, 8,99) && !RTLNX_RHEL_MAJ_PREREQ(9,1)
+ .irq_handler = vbox_irq_handler,
+#endif
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+ .patchlevel = DRIVER_PATCHLEVEL,
+
+#if RTLNX_VER_MAX(4,7,0)
+ .gem_free_object = vbox_gem_free_object,
+#endif
+ .dumb_create = vbox_dumb_create,
+ .dumb_map_offset = vbox_dumb_mmap_offset,
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+ .dumb_destroy = vbox_dumb_destroy,
+#elif RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ .dumb_destroy = drm_gem_dumb_destroy,
+#endif
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+
+ .gem_prime_import = drm_gem_prime_import,
+ .gem_prime_import_sg_table = vbox_gem_prime_import_sg_table,
+ .gem_prime_mmap = vbox_gem_prime_mmap,
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ .dev_priv_size = 0,
+# if RTLNX_VER_MIN(4,7,0)
+ .gem_free_object_unlocked = vbox_gem_free_object,
+# endif
+ .gem_prime_export = drm_gem_prime_export,
+ .gem_prime_pin = vbox_gem_prime_pin,
+ .gem_prime_unpin = vbox_gem_prime_unpin,
+ .gem_prime_get_sg_table = vbox_gem_prime_get_sg_table,
+ .gem_prime_vmap = vbox_gem_prime_vmap,
+ .gem_prime_vunmap = vbox_gem_prime_vunmap,
+#endif
+};
+
+static int __init vbox_init(void)
+{
+ printk("vboxvideo: loading version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n");
+ if (VBOX_VIDEO_NOMODESET())
+ {
+ printk("vboxvideo: kernel is running with *nomodeset* parameter,\n");
+ printk("vboxvideo: please consider either to remove it or load driver\n");
+ printk("vboxvideo: with parameter modeset=1, unloading\n");
+ return -EINVAL;
+ }
+
+ if (vbox_modeset == 0)
+ {
+ printk("vboxvideo: driver loaded with modeset=0 parameter, unloading\n");
+ return -EINVAL;
+ }
+
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ return pci_register_driver(&vbox_pci_driver);
+#else
+ return drm_pci_init(&driver, &vbox_pci_driver);
+#endif
+}
+
+static void __exit vbox_exit(void)
+{
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ pci_unregister_driver(&vbox_pci_driver);
+#else
+ drm_pci_exit(&driver, &vbox_pci_driver);
+#endif
+}
+
+module_init(vbox_init);
+module_exit(vbox_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL and additional rights");
+#ifdef MODULE_VERSION
+MODULE_VERSION(VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV));
+#endif
diff --git a/src/VBox/Additions/linux/drm/vbox_drv.h b/src/VBox/Additions/linux/drm/vbox_drv.h
new file mode 100644
index 00000000..068a6742
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_drv.h
@@ -0,0 +1,551 @@
+/* $Id: vbox_drv.h $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ * This file is based on ast_drv.h
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+
+#ifndef GA_INCLUDED_SRC_linux_drm_vbox_drv_h
+#define GA_INCLUDED_SRC_linux_drm_vbox_drv_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <linux/version.h>
+
+/* iprt/linux/version.h copy - start */
+/** @def RTLNX_VER_MIN
+ * Evaluates to true if the linux kernel version is equal or higher to the
+ * one specfied. */
+#define RTLNX_VER_MIN(a_Major, a_Minor, a_Patch) \
+ (LINUX_VERSION_CODE >= KERNEL_VERSION(a_Major, a_Minor, a_Patch))
+
+/** @def RTLNX_VER_MAX
+ * Evaluates to true if the linux kernel version is less to the one specfied
+ * (exclusive). */
+#define RTLNX_VER_MAX(a_Major, a_Minor, a_Patch) \
+ (LINUX_VERSION_CODE < KERNEL_VERSION(a_Major, a_Minor, a_Patch))
+
+/** @def RTLNX_VER_RANGE
+ * Evaluates to true if the linux kernel version is equal or higher to the given
+ * minimum version and less (but not equal) to the maximum version (exclusive). */
+#define RTLNX_VER_RANGE(a_MajorMin, a_MinorMin, a_PatchMin, a_MajorMax, a_MinorMax, a_PatchMax) \
+ ( LINUX_VERSION_CODE >= KERNEL_VERSION(a_MajorMin, a_MinorMin, a_PatchMin) \
+ && LINUX_VERSION_CODE < KERNEL_VERSION(a_MajorMax, a_MinorMax, a_PatchMax) )
+
+
+/** @def RTLNX_RHEL_MIN
+ * Require a minium RedHat release.
+ * @param a_iMajor The major release number (RHEL_MAJOR).
+ * @param a_iMinor The minor release number (RHEL_MINOR).
+ * @sa RTLNX_RHEL_MAX, RTLNX_RHEL_RANGE, RTLNX_RHEL_MAJ_PREREQ
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_MIN(a_iMajor, a_iMinor) \
+ ((RHEL_MAJOR) > (a_iMajor) || ((RHEL_MAJOR) == (a_iMajor) && (RHEL_MINOR) >= (a_iMinor)))
+#else
+# define RTLNX_RHEL_MIN(a_iMajor, a_iMinor) (0)
+#endif
+
+/** @def RTLNX_RHEL_MAX
+ * Require a maximum RedHat release, true for all RHEL versions below it.
+ * @param a_iMajor The major release number (RHEL_MAJOR).
+ * @param a_iMinor The minor release number (RHEL_MINOR).
+ * @sa RTLNX_RHEL_MIN, RTLNX_RHEL_RANGE, RTLNX_RHEL_MAJ_PREREQ
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_MAX(a_iMajor, a_iMinor) \
+ ((RHEL_MAJOR) < (a_iMajor) || ((RHEL_MAJOR) == (a_iMajor) && (RHEL_MINOR) < (a_iMinor)))
+#else
+# define RTLNX_RHEL_MAX(a_iMajor, a_iMinor) (0)
+#endif
+
+/** @def RTLNX_RHEL_RANGE
+ * Check that it's a RedHat kernel in the given version range.
+ * The max version is exclusive, the minimum inclusive.
+ * @sa RTLNX_RHEL_MIN, RTLNX_RHEL_MAX, RTLNX_RHEL_MAJ_PREREQ
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_RANGE(a_iMajorMin, a_iMinorMin, a_iMajorMax, a_iMinorMax) \
+ (RTLNX_RHEL_MIN(a_iMajorMin, a_iMinorMin) && RTLNX_RHEL_MAX(a_iMajorMax, a_iMinorMax))
+#else
+# define RTLNX_RHEL_RANGE(a_iMajorMin, a_iMinorMin, a_iMajorMax, a_iMinorMax) (0)
+#endif
+
+/** @def RTLNX_RHEL_MAJ_PREREQ
+ * Require a minimum minor release number for the given RedHat release.
+ * @param a_iMajor RHEL_MAJOR must _equal_ this.
+ * @param a_iMinor RHEL_MINOR must be greater or equal to this.
+ * @sa RTLNX_RHEL_MIN, RTLNX_RHEL_MAX
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_MAJ_PREREQ(a_iMajor, a_iMinor) ((RHEL_MAJOR) == (a_iMajor) && (RHEL_MINOR) >= (a_iMinor))
+#else
+# define RTLNX_RHEL_MAJ_PREREQ(a_iMajor, a_iMinor) (0)
+#endif
+
+
+/** @def RTLNX_SUSE_MAJ_PREREQ
+ * Require a minimum minor release number for the given SUSE release.
+ * @param a_iMajor CONFIG_SUSE_VERSION must _equal_ this.
+ * @param a_iMinor CONFIG_SUSE_PATCHLEVEL must be greater or equal to this.
+ */
+#if defined(CONFIG_SUSE_VERSION) && defined(CONFIG_SUSE_PATCHLEVEL)
+# define RTLNX_SUSE_MAJ_PREREQ(a_iMajor, a_iMinor) ((CONFIG_SUSE_VERSION) == (a_iMajor) && (CONFIG_SUSE_PATCHLEVEL) >= (a_iMinor))
+#else
+# define RTLNX_SUSE_MAJ_PREREQ(a_iMajor, a_iMinor) (0)
+#endif
+/* iprt/linux/version.h copy - end */
+
+#if RTLNX_VER_MAX(4,5,0)
+# include <linux/types.h>
+# include <linux/spinlock_types.h>
+#endif
+
+#include <linux/genalloc.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/pci.h>
+
+
+#if RTLNX_VER_MAX(3,14,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+#define U8_MAX ((u8)~0U)
+#define S8_MAX ((s8)(U8_MAX>>1))
+#define S8_MIN ((s8)(-S8_MAX - 1))
+#define U16_MAX ((u16)~0U)
+#define S16_MAX ((s16)(U16_MAX>>1))
+#define S16_MIN ((s16)(-S16_MAX - 1))
+#define U32_MAX ((u32)~0U)
+#define S32_MAX ((s32)(U32_MAX>>1))
+#define S32_MIN ((s32)(-S32_MAX - 1))
+#define U64_MAX ((u64)~0ULL)
+#define S64_MAX ((s64)(U64_MAX>>1))
+#define S64_MIN ((s64)(-S64_MAX - 1))
+#endif
+
+#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+# include <drm/drm_file.h>
+# include <drm/drm_drv.h>
+# include <drm/drm_device.h>
+# include <drm/drm_ioctl.h>
+# include <drm/drm_fourcc.h>
+# if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_RANGE(8,7, 8,99) && !RTLNX_RHEL_MAJ_PREREQ(9,1)
+# include <drm/drm_irq.h>
+# endif
+# include <drm/drm_vblank.h>
+#else /* < 5.5.0 || RHEL < 8.3 || SLES < 15-SP3 */
+# include <drm/drmP.h>
+#endif
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+# include <drm/drm_encoder.h>
+#endif
+#include <drm/drm_fb_helper.h>
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+# include <drm/drm_gem.h>
+#endif
+
+#include <drm/ttm/ttm_bo_api.h>
+#include <drm/ttm/ttm_bo_driver.h>
+#include <drm/ttm/ttm_placement.h>
+#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+# include <drm/ttm/ttm_memory.h>
+#endif
+#if RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+# include <drm/ttm/ttm_module.h>
+#endif
+#if RTLNX_VER_MIN(5,10,0)
+# include <drm/ttm/ttm_resource.h>
+#endif
+
+#if RTLNX_VER_MIN(6,0,0)
+# include <drm/drm_framebuffer.h>
+#endif
+
+#include "vboxvideo_guest.h"
+#include "vboxvideo_vbe.h"
+#include "hgsmi_ch_setup.h"
+
+#include "product-generated.h"
+
+#if RTLNX_VER_MAX(4,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+static inline void drm_gem_object_put_unlocked(struct drm_gem_object *obj)
+{
+ drm_gem_object_unreference_unlocked(obj);
+}
+#endif
+
+#if RTLNX_VER_MAX(4,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+static inline void drm_gem_object_put(struct drm_gem_object *obj)
+{
+ drm_gem_object_unreference(obj);
+}
+#endif
+
+#define DRIVER_AUTHOR VBOX_VENDOR
+
+#define DRIVER_NAME "vboxvideo"
+#define DRIVER_DESC VBOX_PRODUCT " Graphics Card"
+#define DRIVER_DATE "20130823"
+
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+
+#define VBOX_MAX_CURSOR_WIDTH 64
+#define VBOX_MAX_CURSOR_HEIGHT 64
+#define CURSOR_PIXEL_COUNT (VBOX_MAX_CURSOR_WIDTH * VBOX_MAX_CURSOR_HEIGHT)
+#define CURSOR_DATA_SIZE (CURSOR_PIXEL_COUNT * 4 + CURSOR_PIXEL_COUNT / 8)
+
+#define VBOX_MAX_SCREENS 32
+
+#define GUEST_HEAP_OFFSET(vbox) ((vbox)->full_vram_size - \
+ VBVA_ADAPTER_INFORMATION_SIZE)
+#define GUEST_HEAP_SIZE VBVA_ADAPTER_INFORMATION_SIZE
+#define GUEST_HEAP_USABLE_SIZE (VBVA_ADAPTER_INFORMATION_SIZE - \
+ sizeof(HGSMIHOSTFLAGS))
+#define HOST_FLAGS_OFFSET GUEST_HEAP_USABLE_SIZE
+
+/** Field "pdev" of struct drm_device was removed in 5.14. This macro
+ * transparently handles this change. Input argument is a pointer
+ * to struct drm_device. */
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# define VBOX_DRM_TO_PCI_DEV(_dev) to_pci_dev(_dev->dev)
+#else
+# define VBOX_DRM_TO_PCI_DEV(_dev) _dev->pdev
+#endif
+
+/** Field "num_pages" of struct ttm_resource was renamed to "size" in 6.2 and
+ * now represents number of bytes. This macro handles this change. Input
+ * argument is a pointer to struct ttm_resource. */
+#if RTLNX_VER_MIN(6,2,0)
+# define VBOX_BO_RESOURCE_NUM_PAGES(_resource) PFN_UP(_resource->size)
+#else
+# define VBOX_BO_RESOURCE_NUM_PAGES(_resource) _resource->num_pages
+#endif
+
+/** How frequently we refresh if the guest is not providing dirty rectangles. */
+#define VBOX_REFRESH_PERIOD (HZ / 2)
+
+#if RTLNX_VER_MAX(3,13,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+static inline void *devm_kcalloc(struct device *dev, size_t n, size_t size,
+ gfp_t flags)
+{
+ return devm_kzalloc(dev, n * size, flags);
+}
+#endif
+
+struct vbox_fbdev;
+
+struct vbox_private {
+ struct drm_device *dev;
+
+ u8 __iomem *guest_heap;
+ u8 __iomem *vbva_buffers;
+ struct gen_pool *guest_pool;
+ struct VBVABUFFERCONTEXT *vbva_info;
+ bool any_pitch;
+ u32 num_crtcs;
+ /** Amount of available VRAM, including space used for buffers. */
+ u32 full_vram_size;
+ /** Amount of available VRAM, not including space used for buffers. */
+ u32 available_vram_size;
+ /** Array of structures for receiving mode hints. */
+ VBVAMODEHINT *last_mode_hints;
+
+ struct vbox_fbdev *fbdev;
+
+ int fb_mtrr;
+
+ struct {
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ struct drm_global_reference mem_global_ref;
+ struct ttm_bo_global_ref bo_global_ref;
+#endif
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ struct ttm_device bdev;
+#else
+ struct ttm_bo_device bdev;
+#endif
+ bool mm_initialised;
+ } ttm;
+
+ struct mutex hw_mutex; /* protects modeset and accel/vbva accesses */
+ /**
+ * We decide whether or not user-space supports display hot-plug
+ * depending on whether they react to a hot-plug event after the initial
+ * mode query.
+ */
+ bool initial_mode_queried;
+ /**
+ * Do we know that the current user can send us dirty rectangle information?
+ * If not, do periodic refreshes until we do know.
+ */
+ bool need_refresh_timer;
+ /**
+ * As long as the user is not sending us dirty rectangle information,
+ * refresh the whole screen at regular intervals.
+ */
+ struct delayed_work refresh_work;
+ struct work_struct hotplug_work;
+ u32 input_mapping_width;
+ u32 input_mapping_height;
+ /**
+ * Is user-space using an X.Org-style layout of one large frame-buffer
+ * encompassing all screen ones or is the fbdev console active?
+ */
+ bool single_framebuffer;
+ u32 cursor_width;
+ u32 cursor_height;
+ u32 cursor_hot_x;
+ u32 cursor_hot_y;
+ size_t cursor_data_size;
+ u8 cursor_data[CURSOR_DATA_SIZE];
+};
+
+#undef CURSOR_PIXEL_COUNT
+#undef CURSOR_DATA_SIZE
+
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MIN(8,3)
+int vbox_driver_load(struct drm_device *dev);
+#else
+int vbox_driver_load(struct drm_device *dev, unsigned long flags);
+#endif
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+void vbox_driver_unload(struct drm_device *dev);
+#else
+int vbox_driver_unload(struct drm_device *dev);
+#endif
+void vbox_driver_lastclose(struct drm_device *dev);
+
+struct vbox_gem_object;
+
+#ifndef VGA_PORT_HGSMI_HOST
+#define VGA_PORT_HGSMI_HOST 0x3b0
+#define VGA_PORT_HGSMI_GUEST 0x3d0
+#endif
+
+struct vbox_connector {
+ struct drm_connector base;
+ char name[32];
+ struct vbox_crtc *vbox_crtc;
+ struct {
+ u32 width;
+ u32 height;
+ bool disconnected;
+ } mode_hint;
+};
+
+struct vbox_crtc {
+ struct drm_crtc base;
+ bool blanked;
+ bool disconnected;
+ unsigned int crtc_id;
+ u32 fb_offset;
+ bool cursor_enabled;
+ u32 x_hint;
+ u32 y_hint;
+};
+
+struct vbox_encoder {
+ struct drm_encoder base;
+};
+
+struct vbox_framebuffer {
+ struct drm_framebuffer base;
+ struct drm_gem_object *obj;
+};
+
+struct vbox_fbdev {
+ struct drm_fb_helper helper;
+ struct vbox_framebuffer afb;
+ int size;
+ struct ttm_bo_kmap_obj mapping;
+ int x1, y1, x2, y2; /* dirty rect */
+ spinlock_t dirty_lock;
+};
+
+#define to_vbox_crtc(x) container_of(x, struct vbox_crtc, base)
+#define to_vbox_connector(x) container_of(x, struct vbox_connector, base)
+#define to_vbox_encoder(x) container_of(x, struct vbox_encoder, base)
+#define to_vbox_framebuffer(x) container_of(x, struct vbox_framebuffer, base)
+
+int vbox_mode_init(struct drm_device *dev);
+void vbox_mode_fini(struct drm_device *dev);
+
+#if RTLNX_VER_MAX(3,3,0)
+# define DRM_MODE_FB_CMD drm_mode_fb_cmd
+#else
+# define DRM_MODE_FB_CMD drm_mode_fb_cmd2
+#endif
+
+#if RTLNX_VER_MAX(3,15,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+# define CRTC_FB(crtc) ((crtc)->fb)
+#else
+# define CRTC_FB(crtc) ((crtc)->primary->fb)
+#endif
+
+void vbox_enable_accel(struct vbox_private *vbox);
+void vbox_disable_accel(struct vbox_private *vbox);
+void vbox_report_caps(struct vbox_private *vbox);
+
+void vbox_framebuffer_dirty_rectangles(struct drm_framebuffer *fb,
+ struct drm_clip_rect *rects,
+ unsigned int num_rects);
+
+int vbox_framebuffer_init(struct drm_device *dev,
+ struct vbox_framebuffer *vbox_fb,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ const struct DRM_MODE_FB_CMD *mode_cmd,
+#else
+ struct DRM_MODE_FB_CMD *mode_cmd,
+#endif
+ struct drm_gem_object *obj);
+
+int vbox_fbdev_init(struct drm_device *dev);
+void vbox_fbdev_fini(struct drm_device *dev);
+void vbox_fbdev_set_base(struct vbox_private *vbox, unsigned long gpu_addr);
+
+struct vbox_bo {
+ struct ttm_buffer_object bo;
+ struct ttm_placement placement;
+ struct ttm_bo_kmap_obj kmap;
+ struct drm_gem_object gem;
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ u32 placements[3];
+#else
+ struct ttm_place placements[3];
+#endif
+ int pin_count;
+};
+
+#define gem_to_vbox_bo(gobj) container_of((gobj), struct vbox_bo, gem)
+
+static inline struct vbox_bo *vbox_bo(struct ttm_buffer_object *bo)
+{
+ return container_of(bo, struct vbox_bo, bo);
+}
+
+#define to_vbox_obj(x) container_of(x, struct vbox_gem_object, base)
+
+int vbox_dumb_create(struct drm_file *file,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+int vbox_dumb_destroy(struct drm_file *file,
+ struct drm_device *dev, u32 handle);
+#endif
+
+void vbox_gem_free_object(struct drm_gem_object *obj);
+int vbox_dumb_mmap_offset(struct drm_file *file,
+ struct drm_device *dev,
+ u32 handle, u64 *offset);
+
+#define DRM_FILE_PAGE_OFFSET (0x10000000ULL >> PAGE_SHIFT)
+
+int vbox_mm_init(struct vbox_private *vbox);
+void vbox_mm_fini(struct vbox_private *vbox);
+
+int vbox_bo_create(struct drm_device *dev, int size, int align,
+ u32 flags, struct vbox_bo **pvboxbo);
+
+int vbox_gem_create(struct drm_device *dev,
+ u32 size, bool iskernel, struct drm_gem_object **obj);
+
+#define VBOX_MEM_TYPE_VRAM 0x1
+#define VBOX_MEM_TYPE_SYSTEM 0x2
+
+int vbox_bo_pin(struct vbox_bo *bo, u32 mem_type, u64 *gpu_addr);
+int vbox_bo_unpin(struct vbox_bo *bo);
+
+static inline int vbox_bo_reserve(struct vbox_bo *bo, bool no_wait)
+{
+ int ret;
+
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ ret = ttm_bo_reserve(&bo->bo, true, no_wait, NULL);
+#else
+ ret = ttm_bo_reserve(&bo->bo, true, no_wait, false, 0);
+#endif
+ if (ret) {
+ if (ret != -ERESTARTSYS && ret != -EBUSY)
+ DRM_ERROR("reserve failed %p\n", bo);
+ return ret;
+ }
+ return 0;
+}
+
+static inline void vbox_bo_unreserve(struct vbox_bo *bo)
+{
+ ttm_bo_unreserve(&bo->bo);
+}
+
+void vbox_ttm_placement(struct vbox_bo *bo, u32 mem_type);
+int vbox_bo_push_sysram(struct vbox_bo *bo);
+int vbox_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* vbox_prime.c */
+int vbox_gem_prime_pin(struct drm_gem_object *obj);
+void vbox_gem_prime_unpin(struct drm_gem_object *obj);
+struct sg_table *vbox_gem_prime_get_sg_table(struct drm_gem_object *obj);
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, size_t size, struct sg_table *table);
+#else
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, struct dma_buf_attachment *attach,
+ struct sg_table *table);
+#endif
+void *vbox_gem_prime_vmap(struct drm_gem_object *obj);
+void vbox_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
+int vbox_gem_prime_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *area);
+
+/* vbox_irq.c */
+int vbox_irq_init(struct vbox_private *vbox);
+void vbox_irq_fini(struct vbox_private *vbox);
+void vbox_report_hotplug(struct vbox_private *vbox);
+#if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_MAJ_PREREQ(9,1)
+irqreturn_t vbox_irq_handler(int irq, void *arg);
+#endif
+
+/* vbox_hgsmi.c */
+void *hgsmi_buffer_alloc(struct gen_pool *guest_pool, size_t size,
+ u8 channel, u16 channel_info);
+void hgsmi_buffer_free(struct gen_pool *guest_pool, void *buf);
+int hgsmi_buffer_submit(struct gen_pool *guest_pool, void *buf);
+
+static inline void vbox_write_ioport(u16 index, u16 data)
+{
+ outw(index, VBE_DISPI_IOPORT_INDEX);
+ outw(data, VBE_DISPI_IOPORT_DATA);
+}
+
+#endif /* !GA_INCLUDED_SRC_linux_drm_vbox_drv_h */
diff --git a/src/VBox/Additions/linux/drm/vbox_fb.c b/src/VBox/Additions/linux/drm/vbox_fb.c
new file mode 100644
index 00000000..313bdbbc
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_fb.c
@@ -0,0 +1,503 @@
+/* $Id: vbox_fb.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ * This file is based on ast_fb.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/tty.h>
+#include <linux/sysrq.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+
+#include "vbox_drv.h"
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include <VBoxVideo.h>
+
+#if RTLNX_VER_MIN(6,2,0)
+# define VBOX_FBDEV_INFO(_helper) _helper.info
+#else
+# define VBOX_FBDEV_INFO(_helper) _helper.fbdev
+#endif
+
+#if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+/**
+ * Tell the host about dirty rectangles to update.
+ */
+static void vbox_dirty_update(struct vbox_fbdev *fbdev,
+ int x, int y, int width, int height)
+{
+ struct drm_gem_object *obj;
+ struct vbox_bo *bo;
+ int ret = -EBUSY;
+ bool store_for_later = false;
+ int x2, y2;
+ unsigned long flags;
+ struct drm_clip_rect rect;
+
+ obj = fbdev->afb.obj;
+ bo = gem_to_vbox_bo(obj);
+
+ /*
+ * try and reserve the BO, if we fail with busy
+ * then the BO is being moved and we should
+ * store up the damage until later.
+ */
+ if (drm_can_sleep())
+ ret = vbox_bo_reserve(bo, true);
+ if (ret) {
+ if (ret != -EBUSY)
+ return;
+
+ store_for_later = true;
+ }
+
+ x2 = x + width - 1;
+ y2 = y + height - 1;
+ spin_lock_irqsave(&fbdev->dirty_lock, flags);
+
+ if (fbdev->y1 < y)
+ y = fbdev->y1;
+ if (fbdev->y2 > y2)
+ y2 = fbdev->y2;
+ if (fbdev->x1 < x)
+ x = fbdev->x1;
+ if (fbdev->x2 > x2)
+ x2 = fbdev->x2;
+
+ if (store_for_later) {
+ fbdev->x1 = x;
+ fbdev->x2 = x2;
+ fbdev->y1 = y;
+ fbdev->y2 = y2;
+ spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+ return;
+ }
+
+ fbdev->x1 = INT_MAX;
+ fbdev->y1 = INT_MAX;
+ fbdev->x2 = 0;
+ fbdev->y2 = 0;
+
+ spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+
+ /*
+ * Not sure why the original code subtracted 1 here, but I will keep
+ * it that way to avoid unnecessary differences.
+ */
+ rect.x1 = x;
+ rect.x2 = x2 + 1;
+ rect.y1 = y;
+ rect.y2 = y2 + 1;
+ vbox_framebuffer_dirty_rectangles(&fbdev->afb.base, &rect, 1);
+
+ vbox_bo_unreserve(bo);
+}
+#endif /* RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4) */
+
+#ifdef CONFIG_FB_DEFERRED_IO
+# if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+static void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagelist)
+{
+ struct vbox_fbdev *fbdev = info->par;
+ unsigned long start, end, min, max;
+ struct page *page;
+ int y1, y2;
+
+ min = ULONG_MAX;
+ max = 0;
+ list_for_each_entry(page, pagelist, lru) {
+ start = page->index << PAGE_SHIFT;
+ end = start + PAGE_SIZE - 1;
+ min = min(min, start);
+ max = max(max, end);
+ }
+
+ if (min < max) {
+ y1 = min / info->fix.line_length;
+ y2 = (max / info->fix.line_length) + 1;
+ DRM_INFO("%s: Calling dirty update: 0, %d, %d, %d\n",
+ __func__, y1, info->var.xres, y2 - y1 - 1);
+ vbox_dirty_update(fbdev, 0, y1, info->var.xres, y2 - y1 - 1);
+ }
+}
+# endif
+
+static struct fb_deferred_io vbox_defio = {
+ .delay = HZ / 30,
+ .deferred_io = drm_fb_helper_deferred_io,
+};
+#endif /* CONFIG_FB_DEFERRED_IO */
+
+#if RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+static void drm_fb_helper_sys_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
+{
+ struct vbox_fbdev *fbdev = info->par;
+
+ sys_fillrect(info, rect);
+ vbox_dirty_update(fbdev, rect->dx, rect->dy, rect->width, rect->height);
+}
+
+static void drm_fb_helper_sys_copyarea(struct fb_info *info, const struct fb_copyarea *area)
+{
+ struct vbox_fbdev *fbdev = info->par;
+
+ sys_copyarea(info, area);
+ vbox_dirty_update(fbdev, area->dx, area->dy, area->width, area->height);
+}
+
+static void drm_fb_helper_sys_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+ struct vbox_fbdev *fbdev = info->par;
+
+ sys_imageblit(info, image);
+ vbox_dirty_update(fbdev, image->dx, image->dy, image->width,
+ image->height);
+}
+#endif /* RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3) */
+
+static struct fb_ops vboxfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = drm_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par,
+ .fb_fillrect = drm_fb_helper_sys_fillrect,
+ .fb_copyarea = drm_fb_helper_sys_copyarea,
+ .fb_imageblit = drm_fb_helper_sys_imageblit,
+ .fb_pan_display = drm_fb_helper_pan_display,
+ .fb_blank = drm_fb_helper_blank,
+ .fb_setcmap = drm_fb_helper_setcmap,
+ .fb_debug_enter = drm_fb_helper_debug_enter,
+ .fb_debug_leave = drm_fb_helper_debug_leave,
+};
+
+static int vboxfb_create_object(struct vbox_fbdev *fbdev,
+ struct DRM_MODE_FB_CMD *mode_cmd,
+ struct drm_gem_object **gobj_p)
+{
+ struct drm_device *dev = fbdev->helper.dev;
+ u32 size;
+ struct drm_gem_object *gobj;
+#if RTLNX_VER_MAX(3,3,0)
+ u32 pitch = mode_cmd->pitch;
+#else
+ u32 pitch = mode_cmd->pitches[0];
+#endif
+
+ int ret;
+
+ size = pitch * mode_cmd->height;
+ ret = vbox_gem_create(dev, size, true, &gobj);
+ if (ret)
+ return ret;
+
+ *gobj_p = gobj;
+
+ return 0;
+}
+
+#if RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+static struct fb_info *drm_fb_helper_alloc_fbi(struct drm_fb_helper *helper)
+{
+ struct fb_info *info;
+ struct vbox_fbdev *fbdev =
+ container_of(helper, struct vbox_fbdev, helper);
+ struct drm_device *dev = fbdev->helper.dev;
+ struct device *device = &dev->pdev->dev;
+
+ info = framebuffer_alloc(0, device);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+ fbdev->helper.fbdev = info;
+
+ if (fb_alloc_cmap(&info->cmap, 256, 0))
+ return ERR_PTR(-ENOMEM);
+
+ info->apertures = alloc_apertures(1);
+ if (!info->apertures)
+ return ERR_PTR(-ENOMEM);
+
+ return info;
+}
+#endif
+
+static int vboxfb_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct vbox_fbdev *fbdev =
+ container_of(helper, struct vbox_fbdev, helper);
+ struct drm_device *dev = fbdev->helper.dev;
+ struct DRM_MODE_FB_CMD mode_cmd;
+ struct drm_framebuffer *fb;
+ struct fb_info *info;
+ struct drm_gem_object *gobj;
+ struct vbox_bo *bo;
+ int size, ret;
+ u32 pitch;
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ pitch = mode_cmd.width * ((sizes->surface_bpp + 7) / 8);
+#if RTLNX_VER_MAX(3,3,0)
+ mode_cmd.bpp = sizes->surface_bpp;
+ mode_cmd.depth = sizes->surface_depth;
+ mode_cmd.pitch = pitch;
+#else
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+ mode_cmd.pitches[0] = pitch;
+#endif
+
+ size = pitch * mode_cmd.height;
+
+ ret = vboxfb_create_object(fbdev, &mode_cmd, &gobj);
+ if (ret) {
+ DRM_ERROR("failed to create fbcon backing object %d\n", ret);
+ return ret;
+ }
+
+ ret = vbox_framebuffer_init(dev, &fbdev->afb, &mode_cmd, gobj);
+ if (ret)
+ return ret;
+
+ bo = gem_to_vbox_bo(gobj);
+
+ ret = vbox_bo_reserve(bo, false);
+ if (ret)
+ return ret;
+
+ ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_VRAM, NULL);
+ if (ret) {
+ vbox_bo_unreserve(bo);
+ return ret;
+ }
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = ttm_bo_kmap(&bo->bo, 0, VBOX_BO_RESOURCE_NUM_PAGES(bo->bo.resource), &bo->kmap);
+#elif RTLNX_VER_MIN(5,12,0) || RTLNX_RHEL_MAJ_PREREQ(8,5)
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.mem.num_pages, &bo->kmap);
+#else
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap);
+#endif
+
+ vbox_bo_unreserve(bo);
+ if (ret) {
+ DRM_ERROR("failed to kmap fbcon\n");
+ return ret;
+ }
+
+#if RTLNX_VER_MIN(6,2,0)
+ info = drm_fb_helper_alloc_info(helper);
+#else
+ info = drm_fb_helper_alloc_fbi(helper);
+#endif
+ if (IS_ERR(info))
+ return -PTR_ERR(info);
+
+ info->par = fbdev;
+
+ fbdev->size = size;
+
+ fb = &fbdev->afb.base;
+ fbdev->helper.fb = fb;
+
+ strcpy(info->fix.id, "vboxdrmfb");
+
+ /*
+ * The last flag forces a mode set on VT switches even if the kernel
+ * does not think it is needed.
+ */
+ info->flags = FBINFO_DEFAULT | FBINFO_MISC_ALWAYS_SETPAR;
+ info->fbops = &vboxfb_ops;
+
+ /*
+ * This seems to be done for safety checking that the framebuffer
+ * is not registered twice by different drivers.
+ */
+ info->apertures->ranges[0].base = pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0);
+ info->apertures->ranges[0].size = pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0);
+
+#if RTLNX_VER_MIN(5,2,0) || RTLNX_RHEL_MAJ_PREREQ(8,2)
+ /*
+ * The corresponding 5.2-rc1 Linux DRM kernel changes have been
+ * also backported to older RedHat based 4.18.0 Linux kernels.
+ */
+ drm_fb_helper_fill_info(info, &fbdev->helper, sizes);
+#elif RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7, 5)
+ drm_fb_helper_fill_fix(info, fb->pitches[0], fb->format->depth);
+#else
+ drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
+#endif
+#if RTLNX_VER_MAX(5,2,0) && !RTLNX_RHEL_MAJ_PREREQ(8,2)
+ drm_fb_helper_fill_var(info, &fbdev->helper, sizes->fb_width,
+ sizes->fb_height);
+#endif
+
+ info->screen_base = (char __iomem *)bo->kmap.virtual;
+ info->screen_size = size;
+
+#ifdef CONFIG_FB_DEFERRED_IO
+# if RTLNX_VER_MIN(5,19,0)
+ info->fix.smem_len = info->screen_size;
+# endif
+ info->fbdefio = &vbox_defio;
+ fb_deferred_io_init(info);
+#endif
+
+ info->pixmap.flags = FB_PIXMAP_SYSTEM;
+
+ DRM_DEBUG_KMS("allocated %dx%d\n", fb->width, fb->height);
+
+ return 0;
+}
+
+static struct drm_fb_helper_funcs vbox_fb_helper_funcs = {
+ .fb_probe = vboxfb_create,
+};
+
+#if RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+static void drm_fb_helper_unregister_fbi(struct drm_fb_helper *fb_helper)
+{
+ if (fb_helper && fb_helper->fbdev)
+ unregister_framebuffer(fb_helper->fbdev);
+}
+#endif
+
+void vbox_fbdev_fini(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct vbox_fbdev *fbdev = vbox->fbdev;
+ struct vbox_framebuffer *afb = &fbdev->afb;
+
+#ifdef CONFIG_FB_DEFERRED_IO
+ if (VBOX_FBDEV_INFO(fbdev->helper) && VBOX_FBDEV_INFO(fbdev->helper)->fbdefio)
+ fb_deferred_io_cleanup(VBOX_FBDEV_INFO(fbdev->helper));
+#endif
+
+#if RTLNX_VER_MIN(6,2,0)
+ drm_fb_helper_unregister_info(&fbdev->helper);
+#else
+ drm_fb_helper_unregister_fbi(&fbdev->helper);
+#endif
+
+ if (afb->obj) {
+ struct vbox_bo *bo = gem_to_vbox_bo(afb->obj);
+
+ if (!vbox_bo_reserve(bo, false)) {
+ if (bo->kmap.virtual)
+ ttm_bo_kunmap(&bo->kmap);
+ /*
+ * QXL does this, but is it really needed before
+ * freeing?
+ */
+ if (bo->pin_count)
+ vbox_bo_unpin(bo);
+ vbox_bo_unreserve(bo);
+ }
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(afb->obj);
+#else
+ drm_gem_object_put_unlocked(afb->obj);
+#endif
+ afb->obj = NULL;
+ }
+ drm_fb_helper_fini(&fbdev->helper);
+
+#if RTLNX_VER_MIN(3,9,0)
+ drm_framebuffer_unregister_private(&afb->base);
+#endif
+ drm_framebuffer_cleanup(&afb->base);
+}
+
+int vbox_fbdev_init(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct vbox_fbdev *fbdev;
+ int ret = 0;
+
+ fbdev = devm_kzalloc(dev->dev, sizeof(*fbdev), GFP_KERNEL);
+ if (!fbdev)
+ return -ENOMEM;
+
+ vbox->fbdev = fbdev;
+ spin_lock_init(&fbdev->dirty_lock);
+
+#if RTLNX_VER_MAX(3,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ fbdev->helper.funcs = &vbox_fb_helper_funcs;
+#else
+ drm_fb_helper_prepare(dev, &fbdev->helper, &vbox_fb_helper_funcs);
+#endif
+#if RTLNX_VER_MIN(5,7,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ ret = drm_fb_helper_init(dev, &fbdev->helper);
+#elif RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ ret = drm_fb_helper_init(dev, &fbdev->helper, vbox->num_crtcs);
+#else /* < 4.11.0 */
+ ret =
+ drm_fb_helper_init(dev, &fbdev->helper, vbox->num_crtcs,
+ vbox->num_crtcs);
+#endif
+ if (ret)
+ return ret;
+
+#if RTLNX_VER_MAX(5,7,0) && !RTLNX_RHEL_MAJ_PREREQ(8,4) && !RTLNX_SUSE_MAJ_PREREQ(15,3)
+ ret = drm_fb_helper_single_add_all_connectors(&fbdev->helper);
+ if (ret)
+ goto err_fini;
+#endif
+
+ /* disable all the possible outputs/crtcs before entering KMS mode */
+ drm_helper_disable_unused_functions(dev);
+
+ ret = drm_fb_helper_initial_config(&fbdev->helper, 32);
+ if (ret)
+ goto err_fini;
+
+ return 0;
+
+err_fini:
+ drm_fb_helper_fini(&fbdev->helper);
+ return ret;
+}
+
+void vbox_fbdev_set_base(struct vbox_private *vbox, unsigned long gpu_addr)
+{
+ struct fb_info *fbdev = VBOX_FBDEV_INFO(vbox->fbdev->helper);
+
+ fbdev->fix.smem_start = fbdev->apertures->ranges[0].base + gpu_addr;
+ fbdev->fix.smem_len = vbox->available_vram_size - gpu_addr;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_hgsmi.c b/src/VBox/Additions/linux/drm/vbox_hgsmi.c
new file mode 100644
index 00000000..e45d70c8
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_hgsmi.c
@@ -0,0 +1,130 @@
+/** @file
+ * VirtualBox Additions Linux kernel video driver hgsmi interface code
+ */
+
+/*
+ * Contributed by Hans de Goede <hdegoede@redhat.com>
+ *
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <HGSMIBase.h>
+#include <VBoxVideoVBE.h>
+
+/* One-at-a-Time Hash from http://www.burtleburtle.net/bob/hash/doobs.html */
+static u32 hgsmi_hash_process(u32 hash, const u8 *data, int size)
+{
+ while (size--) {
+ hash += *data++;
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+
+ return hash;
+}
+
+static u32 hgsmi_hash_end(u32 hash)
+{
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+
+ return hash;
+}
+
+/* Not really a checksum but that is the naming used in all vbox code */
+static u32 hgsmi_checksum(u32 offset,
+ const HGSMIBUFFERHEADER *header,
+ const HGSMIBUFFERTAIL *tail)
+{
+ u32 checksum;
+
+ checksum = hgsmi_hash_process(0, (u8 *)&offset, sizeof(offset));
+ checksum = hgsmi_hash_process(checksum, (u8 *)header, sizeof(*header));
+ /* 4 -> Do not checksum the checksum itself */
+ checksum = hgsmi_hash_process(checksum, (u8 *)tail, 4);
+
+ return hgsmi_hash_end(checksum);
+}
+
+#if RTLNX_VER_MAX(3,13,0)
+void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size, dma_addr_t *dma)
+{
+ unsigned long vaddr = gen_pool_alloc(pool, size);
+
+ if (vaddr)
+ *dma = gen_pool_virt_to_phys(pool, vaddr);
+ return (void *)vaddr;
+}
+#endif
+
+void *hgsmi_buffer_alloc(struct gen_pool *guest_pool, size_t size,
+ u8 channel, u16 channel_info)
+{
+ HGSMIBUFFERHEADER *h;
+ HGSMIBUFFERTAIL *t;
+ size_t total_size;
+ dma_addr_t offset;
+
+ total_size = size + sizeof(*h) + sizeof(*t);
+ h = gen_pool_dma_alloc(guest_pool, total_size, &offset);
+ if (!h)
+ return NULL;
+
+ t = (HGSMIBUFFERTAIL *)((u8 *)h + sizeof(*h) + size);
+
+ h->u8Flags = HGSMI_BUFFER_HEADER_F_SEQ_SINGLE;
+ h->u32DataSize = size;
+ h->u8Channel = channel;
+ h->u16ChannelInfo = channel_info;
+ memset(&h->u.au8Union, 0, sizeof(h->u.au8Union));
+
+ t->u32Reserved = 0;
+ t->u32Checksum = hgsmi_checksum(offset, h, t);
+
+ return (u8 *)h + sizeof(*h);
+}
+
+void hgsmi_buffer_free(struct gen_pool *guest_pool, void *buf)
+{
+ HGSMIBUFFERHEADER *h =
+ (HGSMIBUFFERHEADER *)((u8 *)buf - sizeof(*h));
+ size_t total_size = h->u32DataSize + sizeof(*h) +
+ sizeof(HGSMIBUFFERTAIL);
+
+ gen_pool_free(guest_pool, (unsigned long)h, total_size);
+}
+
+int hgsmi_buffer_submit(struct gen_pool *guest_pool, void *buf)
+{
+ phys_addr_t offset;
+
+ offset = gen_pool_virt_to_phys(guest_pool, (unsigned long)buf -
+ sizeof(HGSMIBUFFERHEADER));
+ outl(offset, VGA_PORT_HGSMI_GUEST);
+ /* Make the compiler aware that the host has changed memory. */
+ mb();
+
+ return 0;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_irq.c b/src/VBox/Additions/linux/drm/vbox_irq.c
new file mode 100644
index 00000000..885b8661
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_irq.c
@@ -0,0 +1,225 @@
+/* $Id: vbox_irq.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ * This file is based on qxl_irq.c
+ * Copyright 2013 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Dave Airlie
+ * Alon Levy
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include "vbox_drv.h"
+
+#if RTLNX_VER_MAX(5,1,0)
+# include <drm/drm_crtc_helper.h>
+# if RTLNX_RHEL_MAJ_PREREQ(8,1)
+# include <drm/drm_probe_helper.h>
+# endif
+#else
+# include <drm/drm_probe_helper.h>
+#endif
+#include <VBoxVideo.h>
+
+static void vbox_clear_irq(void)
+{
+ outl((u32)~0, VGA_PORT_HGSMI_HOST);
+}
+
+static u32 vbox_get_flags(struct vbox_private *vbox)
+{
+ return readl(vbox->guest_heap + HOST_FLAGS_OFFSET);
+}
+
+void vbox_report_hotplug(struct vbox_private *vbox)
+{
+ schedule_work(&vbox->hotplug_work);
+}
+
+irqreturn_t vbox_irq_handler(int irq, void *arg)
+{
+ struct drm_device *dev = (struct drm_device *)arg;
+ struct vbox_private *vbox = (struct vbox_private *)dev->dev_private;
+ u32 host_flags = vbox_get_flags(vbox);
+
+ if (!(host_flags & HGSMIHOSTFLAGS_IRQ))
+ return IRQ_NONE;
+
+ /*
+ * Due to a bug in the initial host implementation of hot-plug irqs,
+ * the hot-plug and cursor capability flags were never cleared.
+ * Fortunately we can tell when they would have been set by checking
+ * that the VSYNC flag is not set.
+ */
+ if (host_flags &
+ (HGSMIHOSTFLAGS_HOTPLUG | HGSMIHOSTFLAGS_CURSOR_CAPABILITIES) &&
+ !(host_flags & HGSMIHOSTFLAGS_VSYNC))
+ vbox_report_hotplug(vbox);
+
+ vbox_clear_irq();
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * Check that the position hints provided by the host are suitable for GNOME
+ * shell (i.e. all screens disjoint and hints for all enabled screens) and if
+ * not replace them with default ones. Providing valid hints improves the
+ * chances that we will get a known screen layout for pointer mapping.
+ */
+static void validate_or_set_position_hints(struct vbox_private *vbox)
+{
+ struct VBVAMODEHINT *hintsi, *hintsj;
+ bool valid = true;
+ u16 currentx = 0;
+ int i, j;
+
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ for (j = 0; j < i; ++j) {
+ hintsi = &vbox->last_mode_hints[i];
+ hintsj = &vbox->last_mode_hints[j];
+
+ if (hintsi->fEnabled && hintsj->fEnabled) {
+ if (hintsi->dx >= 0xffff ||
+ hintsi->dy >= 0xffff ||
+ hintsj->dx >= 0xffff ||
+ hintsj->dy >= 0xffff ||
+ (hintsi->dx <
+ hintsj->dx + (hintsj->cx & 0x8fff) &&
+ hintsi->dx + (hintsi->cx & 0x8fff) >
+ hintsj->dx) ||
+ (hintsi->dy <
+ hintsj->dy + (hintsj->cy & 0x8fff) &&
+ hintsi->dy + (hintsi->cy & 0x8fff) >
+ hintsj->dy))
+ valid = false;
+ }
+ }
+ }
+ if (!valid)
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ if (vbox->last_mode_hints[i].fEnabled) {
+ vbox->last_mode_hints[i].dx = currentx;
+ vbox->last_mode_hints[i].dy = 0;
+ currentx +=
+ vbox->last_mode_hints[i].cx & 0x8fff;
+ }
+ }
+}
+
+/**
+ * Query the host for the most recent video mode hints.
+ */
+static void vbox_update_mode_hints(struct vbox_private *vbox)
+{
+ struct drm_device *dev = vbox->dev;
+ struct drm_connector *connector;
+ struct vbox_connector *vbox_conn;
+ struct VBVAMODEHINT *hints;
+ u16 flags;
+ bool disconnected;
+ unsigned int crtc_id;
+ int ret;
+
+ ret = VBoxHGSMIGetModeHints(vbox->guest_pool, vbox->num_crtcs,
+ vbox->last_mode_hints);
+ if (ret) {
+ DRM_ERROR("vboxvideo: hgsmi_get_mode_hints failed: %d\n", ret);
+ return;
+ }
+
+ validate_or_set_position_hints(vbox);
+#if RTLNX_VER_MIN(3,9,0)
+ drm_modeset_lock_all(dev);
+#else
+ mutex_lock(&dev->mode_config.mutex);
+#endif
+ list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+ vbox_conn = to_vbox_connector(connector);
+
+ hints = &vbox->last_mode_hints[vbox_conn->vbox_crtc->crtc_id];
+ if (hints->magic != VBVAMODEHINT_MAGIC)
+ continue;
+
+ disconnected = !(hints->fEnabled);
+ crtc_id = vbox_conn->vbox_crtc->crtc_id;
+ vbox_conn->mode_hint.width = hints->cx;
+ vbox_conn->mode_hint.height = hints->cy;
+ vbox_conn->vbox_crtc->x_hint = hints->dx;
+ vbox_conn->vbox_crtc->y_hint = hints->dy;
+ vbox_conn->mode_hint.disconnected = disconnected;
+
+ if (vbox_conn->vbox_crtc->disconnected == disconnected)
+ continue;
+
+ if (disconnected)
+ flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_DISABLED;
+ else
+ flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_BLANK;
+
+ VBoxHGSMIProcessDisplayInfo(vbox->guest_pool, crtc_id, 0, 0, 0,
+ hints->cx * 4, hints->cx,
+ hints->cy, 0, flags);
+
+ vbox_conn->vbox_crtc->disconnected = disconnected;
+ }
+#if RTLNX_VER_MIN(3,9,0)
+ drm_modeset_unlock_all(dev);
+#else
+ mutex_unlock(&dev->mode_config.mutex);
+#endif
+}
+
+static void vbox_hotplug_worker(struct work_struct *work)
+{
+ struct vbox_private *vbox = container_of(work, struct vbox_private,
+ hotplug_work);
+
+ vbox_update_mode_hints(vbox);
+ drm_kms_helper_hotplug_event(vbox->dev);
+}
+
+int vbox_irq_init(struct vbox_private *vbox)
+{
+ INIT_WORK(&vbox->hotplug_work, vbox_hotplug_worker);
+ vbox_update_mode_hints(vbox);
+#if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,1)
+ return request_irq(VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq, vbox_irq_handler, IRQF_SHARED, vbox->dev->driver->name, vbox->dev);
+#elif RTLNX_VER_MIN(3,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+ return drm_irq_install(vbox->dev, VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq);
+#else
+ return drm_irq_install(vbox->dev);
+#endif
+}
+
+void vbox_irq_fini(struct vbox_private *vbox)
+{
+#if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,1)
+ free_irq(VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq, vbox->dev);
+#else
+ drm_irq_uninstall(vbox->dev);
+#endif
+ flush_work(&vbox->hotplug_work);
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_main.c b/src/VBox/Additions/linux/drm/vbox_main.c
new file mode 100644
index 00000000..4d70874a
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_main.c
@@ -0,0 +1,700 @@
+/* $Id: vbox_main.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ * This file is based on ast_main.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>,
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include "vbox_drv.h"
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include <VBoxVideoGuest.h>
+#include <VBoxVideoVBE.h>
+
+#include "hgsmi_channels.h"
+
+static void vbox_user_framebuffer_destroy(struct drm_framebuffer *fb)
+{
+ struct vbox_framebuffer *vbox_fb = to_vbox_framebuffer(fb);
+
+ if (vbox_fb->obj)
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(vbox_fb->obj);
+#else
+ drm_gem_object_put_unlocked(vbox_fb->obj);
+#endif
+
+ drm_framebuffer_cleanup(fb);
+ kfree(fb);
+}
+
+void vbox_enable_accel(struct vbox_private *vbox)
+{
+ unsigned int i;
+ struct VBVABUFFER *vbva;
+
+ if (!vbox->vbva_info || !vbox->vbva_buffers) {
+ /* Should never happen... */
+ DRM_ERROR("vboxvideo: failed to set up VBVA.\n");
+ return;
+ }
+
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ if (vbox->vbva_info[i].pVBVA)
+ continue;
+
+ vbva = (void __force *)vbox->vbva_buffers +
+ i * VBVA_MIN_BUFFER_SIZE;
+ if (!VBoxVBVAEnable(&vbox->vbva_info[i],
+ vbox->guest_pool, vbva, i)) {
+ /* very old host or driver error. */
+ DRM_ERROR("vboxvideo: vbva_enable failed\n");
+ return;
+ }
+ }
+}
+
+void vbox_disable_accel(struct vbox_private *vbox)
+{
+ unsigned int i;
+
+ for (i = 0; i < vbox->num_crtcs; ++i)
+ VBoxVBVADisable(&vbox->vbva_info[i], vbox->guest_pool, i);
+}
+
+void vbox_report_caps(struct vbox_private *vbox)
+{
+ u32 caps = VBVACAPS_DISABLE_CURSOR_INTEGRATION |
+ VBVACAPS_IRQ | VBVACAPS_USE_VBVA_ONLY;
+
+ if (vbox->initial_mode_queried)
+ caps |= VBVACAPS_VIDEO_MODE_HINTS;
+
+ VBoxHGSMISendCapsInfo(vbox->guest_pool, caps);
+}
+
+/**
+ * Send information about dirty rectangles to VBVA. If necessary we enable
+ * VBVA first, as this is normally disabled after a change of master in case
+ * the new master does not send dirty rectangle information (is this even
+ * allowed?)
+ */
+void vbox_framebuffer_dirty_rectangles(struct drm_framebuffer *fb,
+ struct drm_clip_rect *rects,
+ unsigned int num_rects)
+{
+ struct vbox_private *vbox = fb->dev->dev_private;
+ struct drm_crtc *crtc;
+ unsigned int i;
+
+ /* The user can send rectangles, we do not need the timer. */
+ vbox->need_refresh_timer = false;
+ mutex_lock(&vbox->hw_mutex);
+ list_for_each_entry(crtc, &fb->dev->mode_config.crtc_list, head) {
+ if (CRTC_FB(crtc) != fb)
+ continue;
+
+ for (i = 0; i < num_rects; ++i) {
+ VBVACMDHDR cmd_hdr;
+ unsigned int crtc_id = to_vbox_crtc(crtc)->crtc_id;
+
+ if ((rects[i].x1 > crtc->x + crtc->hwmode.hdisplay) ||
+ (rects[i].y1 > crtc->y + crtc->hwmode.vdisplay) ||
+ (rects[i].x2 < crtc->x) ||
+ (rects[i].y2 < crtc->y))
+ continue;
+
+ cmd_hdr.x = (s16)rects[i].x1;
+ cmd_hdr.y = (s16)rects[i].y1;
+ cmd_hdr.w = (u16)rects[i].x2 - rects[i].x1;
+ cmd_hdr.h = (u16)rects[i].y2 - rects[i].y1;
+
+ if (!VBoxVBVABufferBeginUpdate(&vbox->vbva_info[crtc_id],
+ vbox->guest_pool))
+ continue;
+
+ VBoxVBVAWrite(&vbox->vbva_info[crtc_id], vbox->guest_pool,
+ &cmd_hdr, sizeof(cmd_hdr));
+ VBoxVBVABufferEndUpdate(&vbox->vbva_info[crtc_id]);
+ }
+ }
+ mutex_unlock(&vbox->hw_mutex);
+}
+
+static int vbox_user_framebuffer_dirty(struct drm_framebuffer *fb,
+ struct drm_file *file_priv,
+ unsigned int flags, unsigned int color,
+ struct drm_clip_rect *rects,
+ unsigned int num_rects)
+{
+ vbox_framebuffer_dirty_rectangles(fb, rects, num_rects);
+
+ return 0;
+}
+
+static const struct drm_framebuffer_funcs vbox_fb_funcs = {
+ .destroy = vbox_user_framebuffer_destroy,
+ .dirty = vbox_user_framebuffer_dirty,
+};
+
+int vbox_framebuffer_init(struct drm_device *dev,
+ struct vbox_framebuffer *vbox_fb,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ const struct DRM_MODE_FB_CMD *mode_cmd,
+#else
+ struct DRM_MODE_FB_CMD *mode_cmd,
+#endif
+ struct drm_gem_object *obj)
+{
+ int ret;
+
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ drm_helper_mode_fill_fb_struct(dev, &vbox_fb->base, mode_cmd);
+#else
+ drm_helper_mode_fill_fb_struct(&vbox_fb->base, mode_cmd);
+#endif
+ vbox_fb->obj = obj;
+ ret = drm_framebuffer_init(dev, &vbox_fb->base, &vbox_fb_funcs);
+ if (ret) {
+ DRM_ERROR("framebuffer init failed %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct drm_framebuffer *vbox_user_framebuffer_create(
+ struct drm_device *dev,
+ struct drm_file *filp,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ const struct drm_mode_fb_cmd2 *mode_cmd)
+#else
+ struct drm_mode_fb_cmd2 *mode_cmd)
+#endif
+{
+ struct drm_gem_object *obj;
+ struct vbox_framebuffer *vbox_fb;
+ int ret = -ENOMEM;
+
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ obj = drm_gem_object_lookup(filp, mode_cmd->handles[0]);
+#else
+ obj = drm_gem_object_lookup(dev, filp, mode_cmd->handles[0]);
+#endif
+ if (!obj)
+ return ERR_PTR(-ENOENT);
+
+ vbox_fb = kzalloc(sizeof(*vbox_fb), GFP_KERNEL);
+ if (!vbox_fb)
+ goto err_unref_obj;
+
+ ret = vbox_framebuffer_init(dev, vbox_fb, mode_cmd, obj);
+ if (ret)
+ goto err_free_vbox_fb;
+
+ return &vbox_fb->base;
+
+err_free_vbox_fb:
+ kfree(vbox_fb);
+err_unref_obj:
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(obj);
+#else
+ drm_gem_object_put_unlocked(obj);
+#endif
+ return ERR_PTR(ret);
+}
+
+static const struct drm_mode_config_funcs vbox_mode_funcs = {
+ .fb_create = vbox_user_framebuffer_create,
+};
+
+#if RTLNX_VER_MAX(4,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+# define pci_iomap_range(dev, bar, offset, maxlen) \
+ ioremap(pci_resource_start(dev, bar) + (offset), maxlen)
+#endif
+
+/**
+ * Tell the host about the views. This design originally targeted the
+ * Windows XP driver architecture and assumed that each screen would
+ * have a dedicated frame buffer with the command buffer following it,
+ * the whole being a "view". The host works out which screen a command
+ * buffer belongs to by checking whether it is in the first view, then
+ * whether it is in the second and so on. The first match wins. We
+ * cheat around this by making the first view be the managed memory
+ * plus the first command buffer, the second the same plus the second
+ * buffer and so on.
+ */
+static int vbox_set_views(struct vbox_private *vbox)
+{
+ VBVAINFOVIEW *p;
+ int i;
+
+ p = VBoxHGSMIBufferAlloc(vbox->guest_pool, sizeof(*p),
+ HGSMI_CH_VBVA, VBVA_INFO_VIEW);
+ if (!p)
+ return -ENOMEM;
+
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ p->u32ViewIndex = i;
+ p->u32ViewOffset = 0;
+ p->u32ViewSize = vbox->available_vram_size +
+ i * VBVA_MIN_BUFFER_SIZE;
+ p->u32MaxScreenSize = vbox->available_vram_size;
+
+ VBoxHGSMIBufferSubmit(vbox->guest_pool, p);
+ }
+
+ VBoxHGSMIBufferFree(vbox->guest_pool, p);
+
+ return 0;
+}
+
+static int vbox_accel_init(struct vbox_private *vbox)
+{
+ unsigned int i, ret;
+
+ vbox->vbva_info = devm_kcalloc(vbox->dev->dev, vbox->num_crtcs,
+ sizeof(*vbox->vbva_info), GFP_KERNEL);
+ if (!vbox->vbva_info)
+ return -ENOMEM;
+
+ /* Take a command buffer for each screen from the end of usable VRAM. */
+ vbox->available_vram_size -= vbox->num_crtcs * VBVA_MIN_BUFFER_SIZE;
+
+ vbox->vbva_buffers = pci_iomap_range(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0,
+ vbox->available_vram_size,
+ vbox->num_crtcs *
+ VBVA_MIN_BUFFER_SIZE);
+ if (!vbox->vbva_buffers)
+ return -ENOMEM;
+
+ for (i = 0; i < vbox->num_crtcs; ++i)
+ VBoxVBVASetupBufferContext(&vbox->vbva_info[i],
+ vbox->available_vram_size +
+ i * VBVA_MIN_BUFFER_SIZE,
+ VBVA_MIN_BUFFER_SIZE);
+
+ vbox_enable_accel(vbox);
+ ret = vbox_set_views(vbox);
+ if (ret)
+ goto err_pci_iounmap;
+
+ return 0;
+
+err_pci_iounmap:
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->vbva_buffers);
+ return ret;
+}
+
+static void vbox_accel_fini(struct vbox_private *vbox)
+{
+ vbox_disable_accel(vbox);
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->vbva_buffers);
+}
+
+/** Do we support the 4.3 plus mode hint reporting interface? */
+static bool have_hgsmi_mode_hints(struct vbox_private *vbox)
+{
+ u32 have_hints, have_cursor;
+ int ret;
+
+ ret = VBoxQueryConfHGSMI(vbox->guest_pool,
+ VBOX_VBVA_CONF32_MODE_HINT_REPORTING,
+ &have_hints);
+ if (ret)
+ return false;
+
+ ret = VBoxQueryConfHGSMI(vbox->guest_pool,
+ VBOX_VBVA_CONF32_GUEST_CURSOR_REPORTING,
+ &have_cursor);
+ if (ret)
+ return false;
+
+ return have_hints == VINF_SUCCESS && have_cursor == VINF_SUCCESS;
+}
+
+/**
+ * Our refresh timer call-back. Only used for guests without dirty rectangle
+ * support.
+ */
+static void vbox_refresh_timer(struct work_struct *work)
+{
+ struct vbox_private *vbox = container_of(work, struct vbox_private,
+ refresh_work.work);
+ bool have_unblanked = false;
+ struct drm_crtc *crtci;
+
+ if (!vbox->need_refresh_timer)
+ return;
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list, head) {
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtci);
+ if (crtci->enabled && !vbox_crtc->blanked)
+ have_unblanked = true;
+ }
+ if (!have_unblanked)
+ return;
+ /* This forces a full refresh. */
+ vbox_enable_accel(vbox);
+ /* Schedule the next timer iteration. */
+ schedule_delayed_work(&vbox->refresh_work, VBOX_REFRESH_PERIOD);
+}
+
+static bool vbox_check_supported(u16 id)
+{
+ u16 dispi_id;
+
+ vbox_write_ioport(VBE_DISPI_INDEX_ID, id);
+ dispi_id = inw(VBE_DISPI_IOPORT_DATA);
+
+ return dispi_id == id;
+}
+
+/**
+ * Set up our heaps and data exchange buffers in VRAM before handing the rest
+ * to the memory manager.
+ */
+static int vbox_hw_init(struct vbox_private *vbox)
+{
+ int ret = -ENOMEM;
+
+ vbox->full_vram_size = inl(VBE_DISPI_IOPORT_DATA);
+ vbox->any_pitch = vbox_check_supported(VBE_DISPI_ID_ANYX);
+
+ DRM_INFO("VRAM %08x\n", vbox->full_vram_size);
+
+ /* Map guest-heap at end of vram */
+ vbox->guest_heap =
+ pci_iomap_range(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0, GUEST_HEAP_OFFSET(vbox),
+ GUEST_HEAP_SIZE);
+ if (!vbox->guest_heap)
+ return -ENOMEM;
+
+ /* Create guest-heap mem-pool use 2^4 = 16 byte chunks */
+ vbox->guest_pool = gen_pool_create(4, -1);
+ if (!vbox->guest_pool)
+ goto err_unmap_guest_heap;
+
+ ret = gen_pool_add_virt(vbox->guest_pool,
+ (unsigned long)vbox->guest_heap,
+ GUEST_HEAP_OFFSET(vbox),
+ GUEST_HEAP_USABLE_SIZE, -1);
+ if (ret)
+ goto err_destroy_guest_pool;
+
+ /* Reduce available VRAM size to reflect the guest heap. */
+ vbox->available_vram_size = GUEST_HEAP_OFFSET(vbox);
+ /* Linux drm represents monitors as a 32-bit array. */
+ VBoxQueryConfHGSMI(vbox->guest_pool, VBOX_VBVA_CONF32_MONITOR_COUNT,
+ &vbox->num_crtcs);
+ vbox->num_crtcs = clamp_t(u32, vbox->num_crtcs, 1, VBOX_MAX_SCREENS);
+
+ if (!have_hgsmi_mode_hints(vbox)) {
+ ret = -ENOTSUPP;
+ goto err_destroy_guest_pool;
+ }
+
+ vbox->last_mode_hints = devm_kcalloc(vbox->dev->dev, vbox->num_crtcs,
+ sizeof(VBVAMODEHINT),
+ GFP_KERNEL);
+ if (!vbox->last_mode_hints) {
+ ret = -ENOMEM;
+ goto err_destroy_guest_pool;
+ }
+
+ ret = vbox_accel_init(vbox);
+ if (ret)
+ goto err_destroy_guest_pool;
+
+ /* Set up the refresh timer for users which do not send dirty rectangles. */
+ INIT_DELAYED_WORK(&vbox->refresh_work, vbox_refresh_timer);
+
+ return 0;
+
+err_destroy_guest_pool:
+ gen_pool_destroy(vbox->guest_pool);
+err_unmap_guest_heap:
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->guest_heap);
+ return ret;
+}
+
+static void vbox_hw_fini(struct vbox_private *vbox)
+{
+ vbox->need_refresh_timer = false;
+ cancel_delayed_work(&vbox->refresh_work);
+ vbox_accel_fini(vbox);
+ gen_pool_destroy(vbox->guest_pool);
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->guest_heap);
+}
+
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MIN(8,3)
+int vbox_driver_load(struct drm_device *dev)
+#else
+int vbox_driver_load(struct drm_device *dev, unsigned long flags)
+#endif
+{
+ struct vbox_private *vbox;
+ int ret = 0;
+
+ if (!vbox_check_supported(VBE_DISPI_ID_HGSMI))
+ return -ENODEV;
+
+ vbox = devm_kzalloc(dev->dev, sizeof(*vbox), GFP_KERNEL);
+ if (!vbox)
+ return -ENOMEM;
+
+ dev->dev_private = vbox;
+ vbox->dev = dev;
+
+ mutex_init(&vbox->hw_mutex);
+
+ ret = vbox_hw_init(vbox);
+ if (ret)
+ return ret;
+
+ ret = vbox_mm_init(vbox);
+ if (ret)
+ goto err_hw_fini;
+
+ drm_mode_config_init(dev);
+
+ dev->mode_config.funcs = (void *)&vbox_mode_funcs;
+ dev->mode_config.min_width = 64;
+ dev->mode_config.min_height = 64;
+ dev->mode_config.preferred_depth = 24;
+ dev->mode_config.max_width = VBE_DISPI_MAX_XRES;
+ dev->mode_config.max_height = VBE_DISPI_MAX_YRES;
+
+ ret = vbox_mode_init(dev);
+ if (ret)
+ goto err_drm_mode_cleanup;
+
+ ret = vbox_irq_init(vbox);
+ if (ret)
+ goto err_mode_fini;
+
+ ret = vbox_fbdev_init(dev);
+ if (ret)
+ goto err_irq_fini;
+
+ return 0;
+
+err_irq_fini:
+ vbox_irq_fini(vbox);
+err_mode_fini:
+ vbox_mode_fini(dev);
+err_drm_mode_cleanup:
+ drm_mode_config_cleanup(dev);
+ vbox_mm_fini(vbox);
+err_hw_fini:
+ vbox_hw_fini(vbox);
+ return ret;
+}
+
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+void vbox_driver_unload(struct drm_device *dev)
+#else
+int vbox_driver_unload(struct drm_device *dev)
+#endif
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ vbox_fbdev_fini(dev);
+ vbox_irq_fini(vbox);
+ vbox_mode_fini(dev);
+ drm_mode_config_cleanup(dev);
+ vbox_mm_fini(vbox);
+ vbox_hw_fini(vbox);
+#if RTLNX_VER_MAX(4,11,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+ return 0;
+#endif
+}
+
+/**
+ * @note this is described in the DRM framework documentation. AST does not
+ * have it, but we get an oops on driver unload if it is not present.
+ */
+void vbox_driver_lastclose(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+#if RTLNX_VER_MIN(3,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+ if (vbox->fbdev)
+ drm_fb_helper_restore_fbdev_mode_unlocked(&vbox->fbdev->helper);
+#else
+ drm_modeset_lock_all(dev);
+ if (vbox->fbdev)
+ drm_fb_helper_restore_fbdev_mode(&vbox->fbdev->helper);
+ drm_modeset_unlock_all(dev);
+#endif
+}
+
+int vbox_gem_create(struct drm_device *dev,
+ u32 size, bool iskernel, struct drm_gem_object **obj)
+{
+ struct vbox_bo *vboxbo;
+ int ret;
+
+ *obj = NULL;
+
+ size = roundup(size, PAGE_SIZE);
+ if (size == 0)
+ {
+ DRM_ERROR("bad size\n");
+ return -EINVAL;
+ }
+
+ ret = vbox_bo_create(dev, size, 0, 0, &vboxbo);
+ if (ret) {
+ if (ret != -ERESTARTSYS)
+ DRM_ERROR("failed to allocate GEM object\n");
+ DRM_ERROR("failed to allocate GEM (%d)\n", ret);
+ return ret;
+ }
+
+ *obj = &vboxbo->gem;
+
+ return 0;
+}
+
+int vbox_dumb_create(struct drm_file *file,
+ struct drm_device *dev, struct drm_mode_create_dumb *args)
+{
+ int ret;
+ struct drm_gem_object *gobj;
+ u32 handle;
+
+ args->pitch = args->width * ((args->bpp + 7) / 8);
+ args->size = args->pitch * args->height;
+
+ ret = vbox_gem_create(dev, args->size, false, &gobj);
+ if (ret)
+ return ret;
+
+ ret = drm_gem_handle_create(file, gobj, &handle);
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(gobj);
+#else
+ drm_gem_object_put_unlocked(gobj);
+#endif
+ if (ret)
+ return ret;
+
+ args->handle = handle;
+
+ return 0;
+}
+
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+int vbox_dumb_destroy(struct drm_file *file,
+ struct drm_device *dev, u32 handle)
+{
+ return drm_gem_handle_delete(file, handle);
+}
+#endif
+
+#if RTLNX_VER_MAX(4,19,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+static void ttm_bo_put(struct ttm_buffer_object *bo)
+{
+ ttm_bo_unref(&bo);
+}
+#endif
+
+void vbox_gem_free_object(struct drm_gem_object *obj)
+{
+ struct vbox_bo *vbox_bo = gem_to_vbox_bo(obj);
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ /* Starting from kernel 5.14, there is a warning appears in dmesg
+ * on attempt to desroy pinned buffer object. Make sure it is unpinned. */
+ while (vbox_bo->bo.pin_count)
+ {
+ int ret;
+ ret = vbox_bo_unpin(vbox_bo);
+ if (ret)
+ {
+ DRM_ERROR("unable to unpin buffer object\n");
+ break;
+ }
+ }
+#endif
+
+ ttm_bo_put(&vbox_bo->bo);
+}
+
+static inline u64 vbox_bo_mmap_offset(struct vbox_bo *bo)
+{
+#if RTLNX_VER_MIN(5,4,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ return drm_vma_node_offset_addr(&bo->bo.base.vma_node);
+#elif RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,0)
+ return bo->bo.addr_space_offset;
+#else
+ return drm_vma_node_offset_addr(&bo->bo.vma_node);
+#endif /* >= 5.4.0 */
+}
+
+int
+vbox_dumb_mmap_offset(struct drm_file *file,
+ struct drm_device *dev,
+ u32 handle, u64 *offset)
+{
+ struct drm_gem_object *obj;
+ int ret = 0;
+ struct vbox_bo *bo;
+
+ mutex_lock(&dev->struct_mutex);
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ obj = drm_gem_object_lookup(file, handle);
+#else
+ obj = drm_gem_object_lookup(dev, file, handle);
+#endif
+ if (!obj) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ bo = gem_to_vbox_bo(obj);
+ *offset = vbox_bo_mmap_offset(bo);
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = drm_vma_node_allow(&bo->bo.base.vma_node, file);
+ if (ret)
+ {
+ DRM_ERROR("unable to grant previladges to user");
+ }
+#endif
+
+ drm_gem_object_put(obj);
+
+out_unlock:
+ mutex_unlock(&dev->struct_mutex);
+ return ret;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_mode.c b/src/VBox/Additions/linux/drm/vbox_mode.c
new file mode 100644
index 00000000..caca854b
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_mode.c
@@ -0,0 +1,930 @@
+/* $Id: vbox_mode.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ * This file is based on ast_mode.c
+ * Copyright 2012 Red Hat Inc.
+ * Parts based on xf86-video-ast
+ * Copyright (c) 2005 ASPEED Technology Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ */
+/*
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include "vbox_drv.h"
+#include <linux/export.h>
+#include <drm/drm_crtc_helper.h>
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+# include <drm/drm_plane_helper.h>
+#endif
+#if RTLNX_VER_MIN(5,1,0) || RTLNX_RHEL_MAJ_PREREQ(8,1)
+# include <drm/drm_probe_helper.h>
+#endif
+
+#if RTLNX_VER_MIN(6,0,0)
+# include <drm/drm_edid.h>
+#endif
+
+#include "VBoxVideo.h"
+
+static int vbox_cursor_set2(struct drm_crtc *crtc, struct drm_file *file_priv,
+ u32 handle, u32 width, u32 height,
+ s32 hot_x, s32 hot_y);
+static int vbox_cursor_move(struct drm_crtc *crtc, int x, int y);
+
+/**
+ * Set a graphics mode. Poke any required values into registers, do an HGSMI
+ * mode set and tell the host we support advanced graphics functions.
+ */
+static void vbox_do_modeset(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct vbox_private *vbox;
+ int width, height, bpp, pitch;
+ u16 flags;
+ s32 x_offset, y_offset;
+
+ vbox = crtc->dev->dev_private;
+ width = mode->hdisplay ? mode->hdisplay : 640;
+ height = mode->vdisplay ? mode->vdisplay : 480;
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ bpp = crtc->enabled ? CRTC_FB(crtc)->format->cpp[0] * 8 : 32;
+ pitch = crtc->enabled ? CRTC_FB(crtc)->pitches[0] : width * bpp / 8;
+#elif RTLNX_VER_MIN(3,3,0)
+ bpp = crtc->enabled ? CRTC_FB(crtc)->bits_per_pixel : 32;
+ pitch = crtc->enabled ? CRTC_FB(crtc)->pitches[0] : width * bpp / 8;
+#else
+ bpp = crtc->enabled ? CRTC_FB(crtc)->bits_per_pixel : 32;
+ pitch = crtc->enabled ? CRTC_FB(crtc)->pitch : width * bpp / 8;
+#endif
+ x_offset = vbox->single_framebuffer ? crtc->x : vbox_crtc->x_hint;
+ y_offset = vbox->single_framebuffer ? crtc->y : vbox_crtc->y_hint;
+
+ /*
+ * This is the old way of setting graphics modes. It assumed one screen
+ * and a frame-buffer at the start of video RAM. On older versions of
+ * VirtualBox, certain parts of the code still assume that the first
+ * screen is programmed this way, so try to fake it.
+ */
+ if (vbox_crtc->crtc_id == 0 && crtc->enabled &&
+ vbox_crtc->fb_offset / pitch < 0xffff - crtc->y &&
+ vbox_crtc->fb_offset % (bpp / 8) == 0)
+ VBoxVideoSetModeRegisters(
+ width, height, pitch * 8 / bpp,
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ CRTC_FB(crtc)->format->cpp[0] * 8,
+#else
+ CRTC_FB(crtc)->bits_per_pixel,
+#endif
+ 0,
+ vbox_crtc->fb_offset % pitch / bpp * 8 + crtc->x,
+ vbox_crtc->fb_offset / pitch + crtc->y);
+
+ flags = VBVA_SCREEN_F_ACTIVE;
+ flags |= (crtc->enabled && !vbox_crtc->blanked) ?
+ 0 : VBVA_SCREEN_F_BLANK;
+ flags |= vbox_crtc->disconnected ? VBVA_SCREEN_F_DISABLED : 0;
+ VBoxHGSMIProcessDisplayInfo(vbox->guest_pool, vbox_crtc->crtc_id,
+ x_offset, y_offset, vbox_crtc->fb_offset +
+ crtc->x * bpp / 8 + crtc->y * pitch,
+ pitch, width, height,
+ vbox_crtc->blanked ? 0 : bpp, flags);
+}
+
+static void vbox_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct vbox_private *vbox = crtc->dev->dev_private;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ vbox_crtc->blanked = false;
+ /* Restart the refresh timer if necessary. */
+ schedule_delayed_work(&vbox->refresh_work, VBOX_REFRESH_PERIOD);
+ break;
+ case DRM_MODE_DPMS_STANDBY:
+ case DRM_MODE_DPMS_SUSPEND:
+ case DRM_MODE_DPMS_OFF:
+ vbox_crtc->blanked = true;
+ break;
+ }
+
+ mutex_lock(&vbox->hw_mutex);
+ vbox_do_modeset(crtc, &crtc->hwmode);
+ mutex_unlock(&vbox->hw_mutex);
+}
+
+static bool vbox_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+/*
+ * Try to map the layout of virtual screens to the range of the input device.
+ * Return true if we need to re-set the crtc modes due to screen offset
+ * changes.
+ */
+static bool vbox_set_up_input_mapping(struct vbox_private *vbox)
+{
+ struct drm_crtc *crtci;
+ struct drm_connector *connectori;
+ struct drm_framebuffer *fb1 = NULL;
+ bool single_framebuffer = true;
+ bool old_single_framebuffer = vbox->single_framebuffer;
+ u16 width = 0, height = 0;
+
+ /*
+ * Are we using an X.Org-style single large frame-buffer for all crtcs?
+ * If so then screen layout can be deduced from the crtc offsets.
+ * Same fall-back if this is the fbdev frame-buffer.
+ */
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list, head) {
+ if (!fb1) {
+ fb1 = CRTC_FB(crtci);
+ if (to_vbox_framebuffer(fb1) == &vbox->fbdev->afb)
+ break;
+ } else if (CRTC_FB(crtci) && fb1 != CRTC_FB(crtci)) {
+ single_framebuffer = false;
+ }
+ }
+ if (single_framebuffer) {
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list,
+ head) {
+ if (to_vbox_crtc(crtci)->crtc_id != 0)
+ continue;
+
+ if (!CRTC_FB(crtci))
+ break;
+ vbox->single_framebuffer = true;
+ vbox->input_mapping_width = CRTC_FB(crtci)->width;
+ vbox->input_mapping_height = CRTC_FB(crtci)->height;
+ return old_single_framebuffer !=
+ vbox->single_framebuffer;
+ }
+ }
+ /* Otherwise calculate the total span of all screens. */
+ list_for_each_entry(connectori, &vbox->dev->mode_config.connector_list,
+ head) {
+ struct vbox_connector *vbox_connector =
+ to_vbox_connector(connectori);
+ struct vbox_crtc *vbox_crtc = vbox_connector->vbox_crtc;
+
+ width = max_t(u16, width, vbox_crtc->x_hint +
+ vbox_connector->mode_hint.width);
+ height = max_t(u16, height, vbox_crtc->y_hint +
+ vbox_connector->mode_hint.height);
+ }
+
+ vbox->single_framebuffer = false;
+ vbox->input_mapping_width = width;
+ vbox->input_mapping_height = height;
+
+ return old_single_framebuffer != vbox->single_framebuffer;
+}
+
+static int vbox_crtc_set_base(struct drm_crtc *crtc,
+ struct drm_framebuffer *old_fb,
+ struct drm_framebuffer *new_fb,
+ int x, int y)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct drm_gem_object *obj;
+ struct vbox_framebuffer *vbox_fb;
+ struct vbox_bo *bo;
+ int ret;
+ u64 gpu_addr;
+
+ vbox_fb = to_vbox_framebuffer(new_fb);
+ obj = vbox_fb->obj;
+ bo = gem_to_vbox_bo(obj);
+
+ ret = vbox_bo_reserve(bo, false);
+ if (ret)
+ return ret;
+
+ ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_VRAM, &gpu_addr);
+ vbox_bo_unreserve(bo);
+ if (ret)
+ return ret;
+
+ /* Unpin the previous fb. Do this after the new one has been pinned rather
+ * than before and re-pinning it on failure in case that fails too. */
+ if (old_fb) {
+ vbox_fb = to_vbox_framebuffer(old_fb);
+ obj = vbox_fb->obj;
+ bo = gem_to_vbox_bo(obj);
+ ret = vbox_bo_reserve(bo, false);
+ /* This should never fail, as no one else should be accessing it and we
+ * should be running under the modeset locks. */
+ if (!ret) {
+ vbox_bo_unpin(bo);
+ vbox_bo_unreserve(bo);
+ }
+ else
+ {
+ DRM_ERROR("unable to lock buffer object: error %d\n", ret);
+ }
+ }
+
+ if (&vbox->fbdev->afb == vbox_fb)
+ vbox_fbdev_set_base(vbox, gpu_addr);
+
+ vbox_crtc->fb_offset = gpu_addr;
+ if (vbox_set_up_input_mapping(vbox)) {
+ struct drm_crtc *crtci;
+
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list,
+ head) {
+ vbox_do_modeset(crtci, &crtci->mode);
+ }
+ }
+
+ return 0;
+}
+
+static int vbox_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y, struct drm_framebuffer *old_fb)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ int ret = vbox_crtc_set_base(crtc, old_fb, CRTC_FB(crtc), x, y);
+ if (ret)
+ return ret;
+ mutex_lock(&vbox->hw_mutex);
+ vbox_do_modeset(crtc, mode);
+ VBoxHGSMIUpdateInputMapping(vbox->guest_pool, 0, 0,
+ vbox->input_mapping_width,
+ vbox->input_mapping_height);
+ mutex_unlock(&vbox->hw_mutex);
+
+ return ret;
+}
+
+static int vbox_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+#if RTLNX_VER_MIN(4,12,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ struct drm_pending_vblank_event *event,
+ uint32_t page_flip_flags,
+ struct drm_modeset_acquire_ctx *ctx)
+#elif RTLNX_VER_MIN(3,12,0) || RTLNX_RHEL_MAJ_PREREQ(7,0)
+ struct drm_pending_vblank_event *event,
+ uint32_t page_flip_flags)
+#else
+ struct drm_pending_vblank_event *event)
+#endif
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ struct drm_device *drm = vbox->dev;
+ unsigned long flags;
+ int rc;
+
+ rc = vbox_crtc_set_base(crtc, CRTC_FB(crtc), fb, 0, 0);
+ if (rc)
+ return rc;
+
+ vbox_do_modeset(crtc, &crtc->mode);
+ mutex_unlock(&vbox->hw_mutex);
+
+ spin_lock_irqsave(&drm->event_lock, flags);
+
+ if (event)
+#if RTLNX_VER_MIN(3,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_crtc_send_vblank_event(crtc, event);
+#else
+ drm_send_vblank_event(drm, -1, event);
+#endif
+
+ spin_unlock_irqrestore(&drm->event_lock, flags);
+
+ return 0;
+}
+
+static void vbox_crtc_disable(struct drm_crtc *crtc)
+{
+}
+
+static void vbox_crtc_prepare(struct drm_crtc *crtc)
+{
+}
+
+static void vbox_crtc_commit(struct drm_crtc *crtc)
+{
+}
+
+static const struct drm_crtc_helper_funcs vbox_crtc_helper_funcs = {
+ .dpms = vbox_crtc_dpms,
+ .mode_fixup = vbox_crtc_mode_fixup,
+ .mode_set = vbox_crtc_mode_set,
+ .disable = vbox_crtc_disable,
+ .prepare = vbox_crtc_prepare,
+ .commit = vbox_crtc_commit,
+};
+
+static void vbox_crtc_reset(struct drm_crtc *crtc)
+{
+}
+
+static void vbox_crtc_destroy(struct drm_crtc *crtc)
+{
+ drm_crtc_cleanup(crtc);
+ kfree(crtc);
+}
+
+static const struct drm_crtc_funcs vbox_crtc_funcs = {
+ .cursor_move = vbox_cursor_move,
+ .cursor_set2 = vbox_cursor_set2,
+ .reset = vbox_crtc_reset,
+ .set_config = drm_crtc_helper_set_config,
+ /* .gamma_set = vbox_crtc_gamma_set, */
+ .page_flip = vbox_crtc_page_flip,
+ .destroy = vbox_crtc_destroy,
+};
+
+static struct vbox_crtc *vbox_crtc_init(struct drm_device *dev, unsigned int i)
+{
+ struct vbox_crtc *vbox_crtc;
+
+ vbox_crtc = kzalloc(sizeof(*vbox_crtc), GFP_KERNEL);
+ if (!vbox_crtc)
+ return NULL;
+
+ vbox_crtc->crtc_id = i;
+
+ drm_crtc_init(dev, &vbox_crtc->base, &vbox_crtc_funcs);
+ drm_mode_crtc_set_gamma_size(&vbox_crtc->base, 256);
+ drm_crtc_helper_add(&vbox_crtc->base, &vbox_crtc_helper_funcs);
+
+ return vbox_crtc;
+}
+
+static void vbox_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+ kfree(encoder);
+}
+
+#if RTLNX_VER_MAX(3,13,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+static struct drm_encoder *drm_encoder_find(struct drm_device *dev, u32 id)
+{
+ struct drm_mode_object *mo;
+
+ mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
+ return mo ? obj_to_encoder(mo) : NULL;
+}
+#endif
+
+static struct drm_encoder *vbox_best_single_encoder(struct drm_connector
+ *connector)
+{
+#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ struct drm_encoder *encoder;
+
+ /* There is only one encoder per connector */
+ drm_connector_for_each_possible_encoder(connector, encoder)
+ return encoder;
+#else /* < 5.5 || RHEL < 8.3 */
+ int enc_id = connector->encoder_ids[0];
+
+ /* pick the encoder ids */
+ if (enc_id)
+# if RTLNX_VER_MIN(4,15,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || (defined(CONFIG_SUSE_VERSION) && RTLNX_VER_MIN(4,12,0))
+ return drm_encoder_find(connector->dev, NULL, enc_id);
+# else
+ return drm_encoder_find(connector->dev, enc_id);
+# endif
+#endif /* < 5.5 || RHEL < 8.3 */
+ return NULL;
+}
+
+static const struct drm_encoder_funcs vbox_enc_funcs = {
+ .destroy = vbox_encoder_destroy,
+};
+
+static void vbox_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool vbox_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void vbox_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static void vbox_encoder_prepare(struct drm_encoder *encoder)
+{
+}
+
+static void vbox_encoder_commit(struct drm_encoder *encoder)
+{
+}
+
+static const struct drm_encoder_helper_funcs vbox_enc_helper_funcs = {
+ .dpms = vbox_encoder_dpms,
+ .mode_fixup = vbox_mode_fixup,
+ .prepare = vbox_encoder_prepare,
+ .commit = vbox_encoder_commit,
+ .mode_set = vbox_encoder_mode_set,
+};
+
+static struct drm_encoder *vbox_encoder_init(struct drm_device *dev,
+ unsigned int i)
+{
+ struct vbox_encoder *vbox_encoder;
+
+ vbox_encoder = kzalloc(sizeof(*vbox_encoder), GFP_KERNEL);
+ if (!vbox_encoder)
+ return NULL;
+
+ drm_encoder_init(dev, &vbox_encoder->base, &vbox_enc_funcs,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ DRM_MODE_ENCODER_DAC, NULL);
+#else
+ DRM_MODE_ENCODER_DAC);
+#endif
+ drm_encoder_helper_add(&vbox_encoder->base, &vbox_enc_helper_funcs);
+
+ vbox_encoder->base.possible_crtcs = 1 << i;
+ return &vbox_encoder->base;
+}
+
+/**
+ * Generate EDID data with a mode-unique serial number for the virtual
+ * monitor to try to persuade Unity that different modes correspond to
+ * different monitors and it should not try to force the same resolution on
+ * them.
+ */
+static void vbox_set_edid(struct drm_connector *connector, int width,
+ int height)
+{
+ enum { EDID_SIZE = 128 };
+ unsigned char edid[EDID_SIZE] = {
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, /* header */
+ 0x58, 0x58, /* manufacturer (VBX) */
+ 0x00, 0x00, /* product code */
+ 0x00, 0x00, 0x00, 0x00, /* serial number goes here */
+ 0x01, /* week of manufacture */
+ 0x00, /* year of manufacture */
+ 0x01, 0x03, /* EDID version */
+ 0x80, /* capabilities - digital */
+ 0x00, /* horiz. res in cm, zero for projectors */
+ 0x00, /* vert. res in cm */
+ 0x78, /* display gamma (120 == 2.2). */
+ 0xEE, /* features (standby, suspend, off, RGB, std */
+ /* colour space, preferred timing mode) */
+ 0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
+ /* chromaticity for standard colour space. */
+ 0x00, 0x00, 0x00, /* no default timings */
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, /* no standard timings */
+ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x02, 0x02,
+ 0x02, 0x02,
+ /* descriptor block 1 goes below */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* descriptor block 2, monitor ranges */
+ 0x00, 0x00, 0x00, 0xFD, 0x00,
+ 0x00, 0xC8, 0x00, 0xC8, 0x64, 0x00, 0x0A, 0x20, 0x20, 0x20,
+ 0x20, 0x20,
+ /* 0-200Hz vertical, 0-200KHz horizontal, 1000MHz pixel clock */
+ 0x20,
+ /* descriptor block 3, monitor name */
+ 0x00, 0x00, 0x00, 0xFC, 0x00,
+ 'V', 'B', 'O', 'X', ' ', 'm', 'o', 'n', 'i', 't', 'o', 'r',
+ '\n',
+ /* descriptor block 4: dummy data */
+ 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20,
+ 0x00, /* number of extensions */
+ 0x00 /* checksum goes here */
+ };
+ int clock = (width + 6) * (height + 6) * 60 / 10000;
+ unsigned int i, sum = 0;
+
+ edid[12] = width & 0xff;
+ edid[13] = width >> 8;
+ edid[14] = height & 0xff;
+ edid[15] = height >> 8;
+ edid[54] = clock & 0xff;
+ edid[55] = clock >> 8;
+ edid[56] = width & 0xff;
+ edid[58] = (width >> 4) & 0xf0;
+ edid[59] = height & 0xff;
+ edid[61] = (height >> 4) & 0xf0;
+ for (i = 0; i < EDID_SIZE - 1; ++i)
+ sum += edid[i];
+ edid[EDID_SIZE - 1] = (0x100 - (sum & 0xFF)) & 0xFF;
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,7) || RTLNX_RHEL_MAJ_PREREQ(8,1) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ drm_connector_update_edid_property(connector, (struct edid *)edid);
+#else
+ drm_mode_connector_update_edid_property(connector, (struct edid *)edid);
+#endif
+}
+
+static int vbox_get_modes(struct drm_connector *connector)
+{
+ struct vbox_connector *vbox_connector = NULL;
+ struct drm_display_mode *mode = NULL;
+ struct vbox_private *vbox = NULL;
+ unsigned int num_modes = 0;
+ int preferred_width, preferred_height;
+
+ vbox_connector = to_vbox_connector(connector);
+ vbox = connector->dev->dev_private;
+ /*
+ * Heuristic: we do not want to tell the host that we support dynamic
+ * resizing unless we feel confident that the user space client using
+ * the video driver can handle hot-plug events. So the first time modes
+ * are queried after a "master" switch we tell the host that we do not,
+ * and immediately after we send the client a hot-plug notification as
+ * a test to see if they will respond and query again.
+ * That is also the reason why capabilities are reported to the host at
+ * this place in the code rather than elsewhere.
+ * We need to report the flags location before reporting the IRQ
+ * capability.
+ */
+ VBoxHGSMIReportFlagsLocation(vbox->guest_pool, GUEST_HEAP_OFFSET(vbox) +
+ HOST_FLAGS_OFFSET);
+ if (vbox_connector->vbox_crtc->crtc_id == 0)
+ vbox_report_caps(vbox);
+ if (!vbox->initial_mode_queried) {
+ if (vbox_connector->vbox_crtc->crtc_id == 0) {
+ vbox->initial_mode_queried = true;
+ vbox_report_hotplug(vbox);
+ }
+ return drm_add_modes_noedid(connector, 800, 600);
+ }
+ /* Also assume that a client which supports hot-plugging also knows
+ * how to update the screen in a way we can use, the only known
+ * relevent client which cannot is Plymouth in Ubuntu 14.04. */
+ vbox->need_refresh_timer = false;
+ num_modes = drm_add_modes_noedid(connector, 2560, 1600);
+ preferred_width = vbox_connector->mode_hint.width ?
+ vbox_connector->mode_hint.width : 1024;
+ preferred_height = vbox_connector->mode_hint.height ?
+ vbox_connector->mode_hint.height : 768;
+ mode = drm_cvt_mode(connector->dev, preferred_width, preferred_height,
+ 60, false, false, false);
+ if (mode) {
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ ++num_modes;
+ }
+ vbox_set_edid(connector, preferred_width, preferred_height);
+
+#if RTLNX_VER_MIN(3,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ if (vbox_connector->vbox_crtc->x_hint != -1)
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_x_property,
+ vbox_connector->vbox_crtc->x_hint);
+ else
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_x_property, 0);
+
+ if (vbox_connector->vbox_crtc->y_hint != -1)
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_y_property,
+ vbox_connector->vbox_crtc->y_hint);
+ else
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_y_property, 0);
+#endif
+
+ return num_modes;
+}
+
+#if RTLNX_VER_MAX(3,14,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+static int vbox_mode_valid(struct drm_connector *connector,
+#else
+static enum drm_mode_status vbox_mode_valid(struct drm_connector *connector,
+#endif
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static void vbox_connector_destroy(struct drm_connector *connector)
+{
+#if RTLNX_VER_MAX(3,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_sysfs_connector_remove(connector);
+#else
+ drm_connector_unregister(connector);
+#endif
+ drm_connector_cleanup(connector);
+ kfree(connector);
+}
+
+static enum drm_connector_status
+vbox_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct vbox_connector *vbox_connector;
+
+ vbox_connector = to_vbox_connector(connector);
+
+ return vbox_connector->mode_hint.disconnected ?
+ connector_status_disconnected : connector_status_connected;
+}
+
+static int vbox_fill_modes(struct drm_connector *connector, u32 max_x,
+ u32 max_y)
+{
+ struct vbox_connector *vbox_connector;
+ struct drm_device *dev;
+ struct drm_display_mode *mode, *iterator;
+
+ vbox_connector = to_vbox_connector(connector);
+ dev = vbox_connector->base.dev;
+ list_for_each_entry_safe(mode, iterator, &connector->modes, head) {
+ list_del(&mode->head);
+ drm_mode_destroy(dev, mode);
+ }
+
+ return drm_helper_probe_single_connector_modes(connector, max_x, max_y);
+}
+
+static const struct drm_connector_helper_funcs vbox_connector_helper_funcs = {
+ .mode_valid = vbox_mode_valid,
+ .get_modes = vbox_get_modes,
+ .best_encoder = vbox_best_single_encoder,
+};
+
+static const struct drm_connector_funcs vbox_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = vbox_connector_detect,
+ .fill_modes = vbox_fill_modes,
+ .destroy = vbox_connector_destroy,
+};
+
+static int vbox_connector_init(struct drm_device *dev,
+ struct vbox_crtc *vbox_crtc,
+ struct drm_encoder *encoder)
+{
+ struct vbox_connector *vbox_connector;
+ struct drm_connector *connector;
+
+ vbox_connector = kzalloc(sizeof(*vbox_connector), GFP_KERNEL);
+ if (!vbox_connector)
+ return -ENOMEM;
+
+ connector = &vbox_connector->base;
+ vbox_connector->vbox_crtc = vbox_crtc;
+
+ drm_connector_init(dev, connector, &vbox_connector_funcs,
+ DRM_MODE_CONNECTOR_VGA);
+ drm_connector_helper_add(connector, &vbox_connector_helper_funcs);
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+#if RTLNX_VER_MIN(3,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_mode_create_suggested_offset_properties(dev);
+ drm_object_attach_property(&connector->base,
+ dev->mode_config.suggested_x_property, 0);
+ drm_object_attach_property(&connector->base,
+ dev->mode_config.suggested_y_property, 0);
+#endif
+#if RTLNX_VER_MAX(3,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_sysfs_connector_add(connector);
+#else
+ drm_connector_register(connector);
+#endif
+
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,7) || RTLNX_RHEL_MAJ_PREREQ(8,1) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ drm_connector_attach_encoder(connector, encoder);
+#else
+ drm_mode_connector_attach_encoder(connector, encoder);
+#endif
+
+ return 0;
+}
+
+int vbox_mode_init(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct drm_encoder *encoder;
+ struct vbox_crtc *vbox_crtc;
+ unsigned int i;
+ int ret;
+
+ /* vbox_cursor_init(dev); */
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ vbox_crtc = vbox_crtc_init(dev, i);
+ if (!vbox_crtc)
+ return -ENOMEM;
+ encoder = vbox_encoder_init(dev, i);
+ if (!encoder)
+ return -ENOMEM;
+ ret = vbox_connector_init(dev, vbox_crtc, encoder);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void vbox_mode_fini(struct drm_device *dev)
+{
+ /* vbox_cursor_fini(dev); */
+}
+
+/**
+ * Copy the ARGB image and generate the mask, which is needed in case the host
+ * does not support ARGB cursors. The mask is a 1BPP bitmap with the bit set
+ * if the corresponding alpha value in the ARGB image is greater than 0xF0.
+ */
+static void copy_cursor_image(u8 *src, u8 *dst, u32 width, u32 height,
+ size_t mask_size)
+{
+ size_t line_size = (width + 7) / 8;
+ u32 i, j;
+
+ memcpy(dst + mask_size, src, width * height * 4);
+ for (i = 0; i < height; ++i)
+ for (j = 0; j < width; ++j)
+ if (((u32 *)src)[i * width + j] > 0xf0000000)
+ dst[i * line_size + j / 8] |= (0x80 >> (j % 8));
+}
+
+static int vbox_cursor_set2(struct drm_crtc *crtc, struct drm_file *file_priv,
+ u32 handle, u32 width, u32 height,
+ s32 hot_x, s32 hot_y)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct ttm_bo_kmap_obj uobj_map;
+ size_t data_size, mask_size;
+ struct drm_gem_object *obj;
+ u32 flags, caps = 0;
+ struct vbox_bo *bo;
+ bool src_isiomem;
+ u8 *dst = NULL;
+ u8 *src;
+ int ret;
+
+ if (!handle) {
+ bool cursor_enabled = false;
+ struct drm_crtc *crtci;
+
+ /* Hide cursor. */
+ vbox_crtc->cursor_enabled = false;
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list,
+ head) {
+ if (to_vbox_crtc(crtci)->cursor_enabled)
+ cursor_enabled = true;
+ }
+
+ if (!cursor_enabled)
+ VBoxHGSMIUpdatePointerShape(vbox->guest_pool, 0, 0, 0,
+ 0, 0, NULL, 0);
+ return 0;
+ }
+
+ vbox_crtc->cursor_enabled = true;
+
+ if (width > VBOX_MAX_CURSOR_WIDTH || height > VBOX_MAX_CURSOR_HEIGHT ||
+ width == 0 || height == 0)
+ return -EINVAL;
+ ret = VBoxQueryConfHGSMI(vbox->guest_pool,
+ VBOX_VBVA_CONF32_CURSOR_CAPABILITIES, &caps);
+ if (ret)
+ return ret == VERR_NO_MEMORY ? -ENOMEM : -EINVAL;
+
+ if (!(caps & VBOX_VBVA_CURSOR_CAPABILITY_HARDWARE)) {
+ /*
+ * -EINVAL means cursor_set2() not supported, -EAGAIN means
+ * retry at once.
+ */
+ return -EBUSY;
+ }
+
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ obj = drm_gem_object_lookup(file_priv, handle);
+#else
+ obj = drm_gem_object_lookup(crtc->dev, file_priv, handle);
+#endif
+ if (!obj) {
+ DRM_ERROR("Cannot find cursor object %x for crtc\n", handle);
+ return -ENOENT;
+ }
+
+ bo = gem_to_vbox_bo(obj);
+ ret = vbox_bo_reserve(bo, false);
+ if (ret)
+ goto out_unref_obj;
+
+ /*
+ * The mask must be calculated based on the alpha
+ * channel, one bit per ARGB word, and must be 32-bit
+ * padded.
+ */
+ mask_size = ((width + 7) / 8 * height + 3) & ~3;
+ data_size = width * height * 4 + mask_size;
+ vbox->cursor_hot_x = hot_x;
+ vbox->cursor_hot_y = hot_y;
+ vbox->cursor_width = width;
+ vbox->cursor_height = height;
+ vbox->cursor_data_size = data_size;
+ dst = vbox->cursor_data;
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = ttm_bo_kmap(&bo->bo, 0, VBOX_BO_RESOURCE_NUM_PAGES(bo->bo.resource), &uobj_map);
+#elif RTLNX_VER_MIN(5,12,0) || RTLNX_RHEL_MAJ_PREREQ(8,5)
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.mem.num_pages, &uobj_map);
+#else
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &uobj_map);
+#endif
+ if (ret) {
+ vbox->cursor_data_size = 0;
+ goto out_unreserve_bo;
+ }
+
+ src = ttm_kmap_obj_virtual(&uobj_map, &src_isiomem);
+ if (src_isiomem) {
+ DRM_ERROR("src cursor bo not in main memory\n");
+ ret = -EIO;
+ goto out_unmap_bo;
+ }
+
+ copy_cursor_image(src, dst, width, height, mask_size);
+
+ flags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE |
+ VBOX_MOUSE_POINTER_ALPHA;
+ ret = VBoxHGSMIUpdatePointerShape(vbox->guest_pool, flags,
+ vbox->cursor_hot_x, vbox->cursor_hot_y,
+ width, height, dst, data_size);
+ ret = ret == VINF_SUCCESS ? 0 : ret == VERR_NO_MEMORY ? -ENOMEM :
+ ret == VERR_NOT_SUPPORTED ? -EBUSY : -EINVAL;
+
+out_unmap_bo:
+ ttm_bo_kunmap(&uobj_map);
+out_unreserve_bo:
+ vbox_bo_unreserve(bo);
+out_unref_obj:
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(obj);
+#else
+ drm_gem_object_put_unlocked(obj);
+#endif
+
+ return ret;
+}
+
+static int vbox_cursor_move(struct drm_crtc *crtc, int x, int y)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ s32 crtc_x =
+ vbox->single_framebuffer ? crtc->x : to_vbox_crtc(crtc)->x_hint;
+ s32 crtc_y =
+ vbox->single_framebuffer ? crtc->y : to_vbox_crtc(crtc)->y_hint;
+ int ret;
+
+ x += vbox->cursor_hot_x;
+ y += vbox->cursor_hot_y;
+ if (x + crtc_x < 0 || y + crtc_y < 0 ||
+ x + crtc_x >= vbox->input_mapping_width ||
+ y + crtc_y >= vbox->input_mapping_width ||
+ vbox->cursor_data_size == 0)
+ return 0;
+ ret = VBoxHGSMICursorPosition(vbox->guest_pool, true, x + crtc_x,
+ y + crtc_y, NULL, NULL);
+ return ret == VINF_SUCCESS ? 0 : ret == VERR_NO_MEMORY ? -ENOMEM : ret ==
+ VERR_NOT_SUPPORTED ? -EBUSY : -EINVAL;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_prime.c b/src/VBox/Additions/linux/drm/vbox_prime.c
new file mode 100644
index 00000000..19e44956
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_prime.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017-2022 Oracle and/or its affiliates.
+ * This file is based on qxl_prime.c
+ * Copyright 2017 Canonical
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Andreas Pokorny
+ */
+
+#include "vbox_drv.h"
+
+/*
+ * Based on qxl_prime.c:
+ * Empty Implementations as there should not be any other driver for a virtual
+ * device that might share buffers with vboxvideo
+ */
+
+int vbox_gem_prime_pin(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+ return -ENOSYS;
+}
+
+void vbox_gem_prime_unpin(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+}
+
+struct sg_table *vbox_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+ return ERR_PTR(-ENOSYS);
+}
+
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, size_t size, struct sg_table *table)
+#else
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, struct dma_buf_attachment *attach,
+ struct sg_table *table)
+#endif
+{
+ WARN_ONCE(1, "not implemented");
+ return ERR_PTR(-ENOSYS);
+}
+
+void *vbox_gem_prime_vmap(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+ return ERR_PTR(-ENOSYS);
+}
+
+void vbox_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
+{
+ WARN_ONCE(1, "not implemented");
+}
+
+int vbox_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *area)
+{
+ WARN_ONCE(1, "not implemented");
+ return -ENOSYS;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_ttm.c b/src/VBox/Additions/linux/drm/vbox_ttm.c
new file mode 100644
index 00000000..397262e8
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_ttm.c
@@ -0,0 +1,820 @@
+/* $Id: vbox_ttm.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2022 Oracle and/or its affiliates.
+ * This file is based on ast_ttm.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com>
+ */
+#include "vbox_drv.h"
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_MAJ_PREREQ(8,5)
+# include <drm/drm_gem.h>
+# include <drm/drm_gem_ttm_helper.h>
+# include <drm/drm_gem_vram_helper.h>
+#else
+# include <drm/ttm/ttm_page_alloc.h>
+#endif
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# include <drm/ttm/ttm_range_manager.h>
+#endif
+
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+#define PLACEMENT_FLAGS(placement) (placement)
+#else
+#define PLACEMENT_FLAGS(placement) ((placement).flags)
+#endif
+
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static inline struct vbox_private *vbox_bdev(struct ttm_device *bd)
+#else
+static inline struct vbox_private *vbox_bdev(struct ttm_bo_device *bd)
+#endif
+{
+ return container_of(bd, struct vbox_private, ttm.bdev);
+}
+
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+static int vbox_ttm_mem_global_init(struct drm_global_reference *ref)
+{
+ return ttm_mem_global_init(ref->object);
+}
+
+static void vbox_ttm_mem_global_release(struct drm_global_reference *ref)
+{
+ ttm_mem_global_release(ref->object);
+}
+
+/**
+ * Adds the vbox memory manager object/structures to the global memory manager.
+ */
+static int vbox_ttm_global_init(struct vbox_private *vbox)
+{
+ struct drm_global_reference *global_ref;
+ int ret;
+
+#if RTLNX_VER_MAX(5,0,0)
+ global_ref = &vbox->ttm.mem_global_ref;
+ global_ref->global_type = DRM_GLOBAL_TTM_MEM;
+ global_ref->size = sizeof(struct ttm_mem_global);
+ global_ref->init = &vbox_ttm_mem_global_init;
+ global_ref->release = &vbox_ttm_mem_global_release;
+ ret = drm_global_item_ref(global_ref);
+ if (ret) {
+ DRM_ERROR("Failed setting up TTM memory subsystem.\n");
+ return ret;
+ }
+
+ vbox->ttm.bo_global_ref.mem_glob = vbox->ttm.mem_global_ref.object;
+#endif
+ global_ref = &vbox->ttm.bo_global_ref.ref;
+ global_ref->global_type = DRM_GLOBAL_TTM_BO;
+ global_ref->size = sizeof(struct ttm_bo_global);
+ global_ref->init = &ttm_bo_global_init;
+ global_ref->release = &ttm_bo_global_release;
+
+ ret = drm_global_item_ref(global_ref);
+ if (ret) {
+ DRM_ERROR("Failed setting up TTM BO subsystem.\n");
+#if RTLNX_VER_MAX(5,0,0)
+ drm_global_item_unref(&vbox->ttm.mem_global_ref);
+#endif
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * Removes the vbox memory manager object from the global memory manager.
+ */
+static void vbox_ttm_global_release(struct vbox_private *vbox)
+{
+ drm_global_item_unref(&vbox->ttm.bo_global_ref.ref);
+ drm_global_item_unref(&vbox->ttm.mem_global_ref);
+}
+#endif
+
+static void vbox_bo_ttm_destroy(struct ttm_buffer_object *tbo)
+{
+ struct vbox_bo *bo;
+
+ bo = container_of(tbo, struct vbox_bo, bo);
+
+ drm_gem_object_release(&bo->gem);
+ kfree(bo);
+}
+
+static bool vbox_ttm_bo_is_vbox_bo(struct ttm_buffer_object *bo)
+{
+ if (bo->destroy == &vbox_bo_ttm_destroy)
+ return true;
+
+ return false;
+}
+
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+static int
+vbox_bo_init_mem_type(struct ttm_bo_device *bdev, u32 type,
+ struct ttm_mem_type_manager *man)
+{
+ switch (type) {
+ case TTM_PL_SYSTEM:
+ man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->available_caching = TTM_PL_MASK_CACHING;
+ man->default_caching = TTM_PL_FLAG_CACHED;
+ break;
+ case TTM_PL_VRAM:
+ man->func = &ttm_bo_manager_func;
+ man->flags = TTM_MEMTYPE_FLAG_FIXED | TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->available_caching = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC;
+ man->default_caching = TTM_PL_FLAG_WC;
+ break;
+ default:
+ DRM_ERROR("Unsupported memory type %u\n", (unsigned int)type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#endif
+
+static void
+vbox_bo_evict_flags(struct ttm_buffer_object *bo, struct ttm_placement *pl)
+{
+ struct vbox_bo *vboxbo = vbox_bo(bo);
+
+ if (!vbox_ttm_bo_is_vbox_bo(bo))
+ return;
+
+ vbox_ttm_placement(vboxbo, VBOX_MEM_TYPE_SYSTEM);
+ *pl = vboxbo->placement;
+}
+
+#if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+static int vbox_bo_verify_access(struct ttm_buffer_object *bo,
+ struct file *filp)
+{
+ return 0;
+}
+#endif
+
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_RANGE(8,5, 8,99)
+static int vbox_ttm_io_mem_reserve(struct ttm_bo_device *bdev,
+ struct ttm_mem_reg *mem)
+{
+ struct vbox_private *vbox = vbox_bdev(bdev);
+ struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type];
+
+ mem->bus.addr = NULL;
+ mem->bus.offset = 0;
+ mem->bus.size = mem->num_pages << PAGE_SHIFT;
+ mem->bus.base = 0;
+ mem->bus.is_iomem = false;
+ if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
+ return -EINVAL;
+ switch (mem->mem_type) {
+ case TTM_PL_SYSTEM:
+ /* system memory */
+ return 0;
+ case TTM_PL_VRAM:
+ mem->bus.offset = mem->start << PAGE_SHIFT;
+ mem->bus.base = pci_resource_start(vbox->dev->pdev, 0);
+ mem->bus.is_iomem = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+#else
+# if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+static int vbox_ttm_io_mem_reserve(struct ttm_bo_device *bdev,
+ struct ttm_resource *mem)
+# else /* > 5.13.0 */
+static int vbox_ttm_io_mem_reserve(struct ttm_device *bdev,
+ struct ttm_resource *mem)
+# endif /* > 5.13.0 */
+{
+ struct vbox_private *vbox = vbox_bdev(bdev);
+ mem->bus.addr = NULL;
+ mem->bus.offset = 0;
+# if RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ mem->size = mem->num_pages << PAGE_SHIFT;
+# endif
+ mem->start = 0;
+ mem->bus.is_iomem = false;
+ switch (mem->mem_type) {
+ case TTM_PL_SYSTEM:
+ /* system memory */
+ return 0;
+ case TTM_PL_VRAM:
+# if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ mem->bus.caching = ttm_write_combined;
+# endif
+# if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ mem->bus.offset = (mem->start << PAGE_SHIFT) + pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0);
+# else
+ mem->bus.offset = mem->start << PAGE_SHIFT;
+ mem->start = pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0);
+# endif
+ mem->bus.is_iomem = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+#endif
+
+
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static void vbox_ttm_io_mem_free(struct ttm_device *bdev,
+ struct ttm_resource *mem)
+{
+}
+#elif RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static void vbox_ttm_io_mem_free(struct ttm_bo_device *bdev,
+ struct ttm_resource *mem)
+{
+}
+#else
+static void vbox_ttm_io_mem_free(struct ttm_bo_device *bdev,
+ struct ttm_mem_reg *mem)
+{
+}
+#endif
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static void vbox_ttm_tt_destroy(struct ttm_device *bdev, struct ttm_tt *tt)
+{
+ ttm_tt_fini(tt);
+ kfree(tt);
+}
+#elif RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static void vbox_ttm_tt_destroy(struct ttm_bo_device *bdev, struct ttm_tt *tt)
+{
+ ttm_tt_fini(tt);
+ kfree(tt);
+}
+#else
+static void vbox_ttm_backend_destroy(struct ttm_tt *tt)
+{
+ ttm_tt_fini(tt);
+ kfree(tt);
+}
+
+static struct ttm_backend_func vbox_tt_backend_func = {
+ .destroy = &vbox_ttm_backend_destroy,
+};
+#endif
+
+#if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+static struct ttm_tt *vbox_ttm_tt_create(struct ttm_bo_device *bdev,
+ unsigned long size,
+ u32 page_flags,
+ struct page *dummy_read_page)
+#else
+static struct ttm_tt *vbox_ttm_tt_create(struct ttm_buffer_object *bo,
+ u32 page_flags)
+#endif
+{
+ struct ttm_tt *tt;
+
+ tt = kzalloc(sizeof(*tt), GFP_KERNEL);
+ if (!tt)
+ return NULL;
+
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_RANGE(8,5, 8,99)
+ tt->func = &vbox_tt_backend_func;
+#endif
+#if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ if (ttm_tt_init(tt, bdev, size, page_flags, dummy_read_page)) {
+#elif RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_RANGE(8,5, 8,99)
+ if (ttm_tt_init(tt, bo, page_flags)) {
+#elif RTLNX_VER_MAX(5,19,0)
+ if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined)) {
+#else
+ if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined, 0)) {
+#endif
+ kfree(tt);
+ return NULL;
+ }
+
+ return tt;
+}
+
+#if RTLNX_VER_MAX(4,17,0)
+# if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+static int vbox_ttm_tt_populate(struct ttm_tt *ttm)
+{
+ return ttm_pool_populate(ttm);
+}
+# else
+static int vbox_ttm_tt_populate(struct ttm_tt *ttm,
+ struct ttm_operation_ctx *ctx)
+{
+ return ttm_pool_populate(ttm, ctx);
+}
+# endif
+
+static void vbox_ttm_tt_unpopulate(struct ttm_tt *ttm)
+{
+ ttm_pool_unpopulate(ttm);
+}
+#endif
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static int vbox_bo_move(struct ttm_buffer_object *bo, bool evict,
+ struct ttm_operation_ctx *ctx, struct ttm_resource *new_mem,
+ struct ttm_place *hop)
+{
+ return ttm_bo_move_memcpy(bo, ctx, new_mem);
+}
+#endif
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static struct ttm_device_funcs vbox_bo_driver = {
+#else /* < 5.13.0 */
+static struct ttm_bo_driver vbox_bo_driver = {
+#endif /* < 5.13.0 */
+ .ttm_tt_create = vbox_ttm_tt_create,
+#if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ .ttm_tt_destroy = vbox_ttm_tt_destroy,
+#endif
+#if RTLNX_VER_MAX(4,17,0)
+ .ttm_tt_populate = vbox_ttm_tt_populate,
+ .ttm_tt_unpopulate = vbox_ttm_tt_unpopulate,
+#endif
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ .init_mem_type = vbox_bo_init_mem_type,
+#endif
+#if RTLNX_VER_MIN(4,10,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ .eviction_valuable = ttm_bo_eviction_valuable,
+#endif
+ .evict_flags = vbox_bo_evict_flags,
+#if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ .verify_access = vbox_bo_verify_access,
+#endif
+ .io_mem_reserve = &vbox_ttm_io_mem_reserve,
+ .io_mem_free = &vbox_ttm_io_mem_free,
+#if RTLNX_VER_MIN(4,12,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+# if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ .io_mem_pfn = ttm_bo_default_io_mem_pfn,
+# endif
+#endif
+#if (RTLNX_VER_RANGE(4,7,0, 4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+ .lru_tail = &ttm_bo_default_lru_tail,
+ .swap_lru_tail = &ttm_bo_default_swap_lru_tail,
+#endif
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ .move = &vbox_bo_move,
+#endif
+};
+
+int vbox_mm_init(struct vbox_private *vbox)
+{
+ int ret;
+ struct drm_device *dev = vbox->dev;
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ struct ttm_device *bdev = &vbox->ttm.bdev;
+#else
+ struct ttm_bo_device *bdev = &vbox->ttm.bdev;
+#endif
+
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ ret = vbox_ttm_global_init(vbox);
+ if (ret)
+ return ret;
+#endif
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = ttm_device_init(&vbox->ttm.bdev,
+#else
+ ret = ttm_bo_device_init(&vbox->ttm.bdev,
+#endif
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ vbox->ttm.bo_global_ref.ref.object,
+#endif
+ &vbox_bo_driver,
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ dev->dev,
+#endif
+#if RTLNX_VER_MIN(3,15,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+ dev->anon_inode->i_mapping,
+#endif
+#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ dev->vma_offset_manager,
+#elif RTLNX_VER_MAX(5,2,0) && !RTLNX_RHEL_MAJ_PREREQ(8,2)
+ DRM_FILE_PAGE_OFFSET,
+#endif
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ false,
+#endif
+ true);
+ if (ret) {
+ DRM_ERROR("Error initialising bo driver; %d\n", ret);
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ goto err_ttm_global_release;
+#else
+ return ret;
+#endif
+ }
+
+#if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ ret = ttm_range_man_init(bdev, TTM_PL_VRAM, false,
+ vbox->available_vram_size >> PAGE_SHIFT);
+#else
+ ret = ttm_bo_init_mm(bdev, TTM_PL_VRAM,
+ vbox->available_vram_size >> PAGE_SHIFT);
+#endif
+ if (ret) {
+ DRM_ERROR("Failed ttm VRAM init: %d\n", ret);
+ goto err_device_release;
+ }
+
+#ifdef DRM_MTRR_WC
+ vbox->fb_mtrr = drm_mtrr_add(pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0),
+ pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0),
+ DRM_MTRR_WC);
+#else
+ vbox->fb_mtrr = arch_phys_wc_add(pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0),
+ pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0));
+#endif
+ return 0;
+
+err_device_release:
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ttm_device_fini(&vbox->ttm.bdev);
+#else
+ ttm_bo_device_release(&vbox->ttm.bdev);
+#endif
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+err_ttm_global_release:
+ vbox_ttm_global_release(vbox);
+#endif
+ return ret;
+}
+
+void vbox_mm_fini(struct vbox_private *vbox)
+{
+#ifdef DRM_MTRR_WC
+ drm_mtrr_del(vbox->fb_mtrr,
+ pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0),
+ pci_resource_len(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0), DRM_MTRR_WC);
+#else
+ arch_phys_wc_del(vbox->fb_mtrr);
+#endif
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ttm_device_fini(&vbox->ttm.bdev);
+#else
+ ttm_bo_device_release(&vbox->ttm.bdev);
+#endif
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ vbox_ttm_global_release(vbox);
+#endif
+}
+
+void vbox_ttm_placement(struct vbox_bo *bo, u32 mem_type)
+{
+ u32 c = 0;
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ bo->placement.fpfn = 0;
+ bo->placement.lpfn = 0;
+#else
+ unsigned int i;
+#endif
+
+ bo->placement.placement = bo->placements;
+ bo->placement.busy_placement = bo->placements;
+
+ if (mem_type & VBOX_MEM_TYPE_VRAM) {
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ bo->placements[c].mem_type = TTM_PL_VRAM;
+ PLACEMENT_FLAGS(bo->placements[c++]) = 0;
+#elif RTLNX_VER_MIN(5,10,0)
+ bo->placements[c].mem_type = TTM_PL_VRAM;
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED;
+#else
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_VRAM;
+#endif
+ }
+ if (mem_type & VBOX_MEM_TYPE_SYSTEM) {
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) = 0;
+#elif RTLNX_VER_MIN(5,10,0)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING;
+#else
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+#endif
+ }
+ if (!c) {
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) = 0;
+#elif RTLNX_VER_MIN(5,10,0)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING;
+#else
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+#endif
+ }
+
+ bo->placement.num_placement = c;
+ bo->placement.num_busy_placement = c;
+
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ for (i = 0; i < c; ++i) {
+ bo->placements[i].fpfn = 0;
+ bo->placements[i].lpfn = 0;
+ }
+#endif
+}
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static const struct drm_gem_object_funcs vbox_drm_gem_object_funcs = {
+ .free = vbox_gem_free_object,
+ .print_info = drm_gem_ttm_print_info,
+# if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ .mmap = drm_gem_ttm_mmap,
+# endif
+};
+#endif
+
+int vbox_bo_create(struct drm_device *dev, int size, int align,
+ u32 flags, struct vbox_bo **pvboxbo)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct vbox_bo *vboxbo;
+#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ size_t acc_size;
+#endif
+ int ret;
+
+ vboxbo = kzalloc(sizeof(*vboxbo), GFP_KERNEL);
+ if (!vboxbo)
+ return -ENOMEM;
+
+ ret = drm_gem_object_init(dev, &vboxbo->gem, size);
+ if (ret)
+ goto err_free_vboxbo;
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ if (!vboxbo->gem.funcs) {
+ vboxbo->gem.funcs = &vbox_drm_gem_object_funcs;
+ }
+#endif
+ vboxbo->bo.bdev = &vbox->ttm.bdev;
+#if RTLNX_VER_MAX(3,15,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+ vboxbo->bo.bdev->dev_mapping = dev->dev_mapping;
+#endif
+
+ vbox_ttm_placement(vboxbo, VBOX_MEM_TYPE_VRAM | VBOX_MEM_TYPE_SYSTEM);
+
+#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ acc_size = ttm_bo_dma_acc_size(&vbox->ttm.bdev, size,
+ sizeof(struct vbox_bo));
+#endif
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ /* Initialization of the following was removed from DRM stack
+ * in 5.14, so we need to do it manually. */
+ vboxbo->bo.base.funcs = &vbox_drm_gem_object_funcs;
+ kref_init(&vboxbo->bo.base.refcount);
+ vboxbo->bo.base.size = size;
+ vboxbo->bo.base.dev = dev;
+ dma_resv_init(&vboxbo->bo.base._resv);
+ drm_vma_node_reset(&vboxbo->bo.base.vma_node);
+#endif
+
+#if RTLNX_VER_MIN(6,1,0)
+ ret = ttm_bo_init_validate(&vbox->ttm.bdev, &vboxbo->bo,
+#else
+ ret = ttm_bo_init(&vbox->ttm.bdev, &vboxbo->bo, size,
+#endif /* < 6.1.0 */
+ ttm_bo_type_device, &vboxbo->placement,
+#if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ align >> PAGE_SHIFT, false, NULL, acc_size,
+#elif RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) /* < 5.13.0, < RHEL(8.6, 8.99) */
+ align >> PAGE_SHIFT, false, acc_size,
+#else /* > 5.13.0 */
+ align >> PAGE_SHIFT, false,
+#endif /* > 5.13.0 */
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ NULL, NULL, vbox_bo_ttm_destroy);
+#else
+ NULL, vbox_bo_ttm_destroy);
+#endif
+ if (ret)
+ {
+ /* In case of failure, ttm_bo_init() supposed to call
+ * vbox_bo_ttm_destroy() which in turn will free @vboxbo. */
+ goto err_exit;
+ }
+
+ *pvboxbo = vboxbo;
+
+ return 0;
+
+err_free_vboxbo:
+ kfree(vboxbo);
+err_exit:
+ return ret;
+}
+
+static inline u64 vbox_bo_gpu_offset(struct vbox_bo *bo)
+{
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ return bo->bo.resource->start << PAGE_SHIFT;
+#elif RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ return bo->bo.mem.start << PAGE_SHIFT;
+#else
+ return bo->bo.offset;
+#endif
+}
+
+int vbox_bo_pin(struct vbox_bo *bo, u32 mem_type, u64 *gpu_addr)
+{
+#if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ struct ttm_operation_ctx ctx = { false, false };
+#endif
+ int ret;
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ int i;
+#endif
+
+ if (bo->pin_count) {
+ bo->pin_count++;
+ if (gpu_addr)
+ *gpu_addr = vbox_bo_gpu_offset(bo);
+
+ return 0;
+ }
+
+ vbox_ttm_placement(bo, mem_type);
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ for (i = 0; i < bo->placement.num_placement; i++)
+ PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT;
+#endif
+
+#if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
+#else
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+#endif
+ if (ret)
+ return ret;
+
+ bo->pin_count = 1;
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ ttm_bo_pin(&bo->bo);
+#endif
+
+ if (gpu_addr)
+ *gpu_addr = vbox_bo_gpu_offset(bo);
+
+ return 0;
+}
+
+int vbox_bo_unpin(struct vbox_bo *bo)
+{
+#if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+# if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ struct ttm_operation_ctx ctx = { false, false };
+# endif
+#endif
+ int ret = 0;
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ int i;
+#endif
+
+ if (!bo->pin_count) {
+ DRM_ERROR("unpin bad %p\n", bo);
+ return 0;
+ }
+ bo->pin_count--;
+ if (bo->pin_count)
+ return 0;
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ for (i = 0; i < bo->placement.num_placement; i++)
+ PLACEMENT_FLAGS(bo->placements[i]) &= ~TTM_PL_FLAG_NO_EVICT;
+#endif
+
+#if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
+#elif RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+#endif
+ if (ret)
+ return ret;
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ ttm_bo_unpin(&bo->bo);
+#endif
+
+ return 0;
+}
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+/*
+ * Move a vbox-owned buffer object to system memory if no one else has it
+ * pinned. The caller must have pinned it previously, and this call will
+ * release the caller's pin.
+ */
+int vbox_bo_push_sysram(struct vbox_bo *bo)
+{
+# if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ struct ttm_operation_ctx ctx = { false, false };
+# endif
+ int i, ret;
+
+ if (!bo->pin_count) {
+ DRM_ERROR("unpin bad %p\n", bo);
+ return 0;
+ }
+ bo->pin_count--;
+ if (bo->pin_count)
+ return 0;
+
+ if (bo->kmap.virtual)
+ ttm_bo_kunmap(&bo->kmap);
+
+ vbox_ttm_placement(bo, VBOX_MEM_TYPE_SYSTEM);
+
+ for (i = 0; i < bo->placement.num_placement; i++)
+ PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT;
+
+# if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
+# else
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+# endif
+ if (ret) {
+ DRM_ERROR("pushing to VRAM failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+int vbox_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_file *file_priv;
+ struct vbox_private *vbox;
+ int ret = -EINVAL;
+
+ if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET))
+ return -EINVAL;
+
+ file_priv = filp->private_data;
+ vbox = file_priv->minor->dev->dev_private;
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ (void)vbox;
+ if (drm_dev_is_unplugged(file_priv->minor->dev))
+ return -ENODEV;
+ ret = drm_gem_mmap(filp, vma);
+#else
+ ret = ttm_bo_mmap(filp, vma, &vbox->ttm.bdev);
+#endif
+ return ret;
+}
diff --git a/src/VBox/Additions/linux/export_modules.sh b/src/VBox/Additions/linux/export_modules.sh
new file mode 100755
index 00000000..411717d2
--- /dev/null
+++ b/src/VBox/Additions/linux/export_modules.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+# $Id$
+## @file
+# Create a tar archive containing the sources of the Linux guest kernel modules.
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+export LC_ALL=C
+
+# The below is GNU-specific. See VBox.sh for the longer Solaris/OS X version.
+TARGET=`readlink -e -- "${0}"` || exit 1
+MY_DIR="${TARGET%/[!/]*}"
+
+if [ -z "${1}" ] || { [ "x${1}" = x--folder ] && [ -z "${2}" ]; }; then
+ echo "Usage: $0 <filename.tar.gz>"
+ echo " Export VirtualBox kernel modules to <filename.tar.gz>."
+ echo "Usage: $0 --folder <folder>"
+ echo " Copy VirtualBox kernel module source to <folder>."
+ exit 1
+fi
+
+if test "x${1}" = x--folder; then
+ PATH_OUT="${2}"
+else
+ PATH_OUT="`cd \`dirname $1\`; pwd`/.vbox_modules"
+ FILE_OUT="`cd \`dirname $1\`; pwd`/`basename $1`"
+fi
+PATH_ROOT="`cd ${MY_DIR}/../../../..; pwd`"
+PATH_LOG=/tmp/vbox-export-guest.log
+PATH_LINUX="$PATH_ROOT/src/VBox/Additions/linux"
+PATH_VBOXGUEST="$PATH_ROOT/src/VBox/Additions/common/VBoxGuest"
+PATH_VBOXSF="$PATH_ROOT/src/VBox/Additions/linux/sharedfolders"
+PATH_VBOXVIDEO="$PATH_ROOT/src/VBox/Additions/linux/drm"
+
+VBOX_VERSION_MAJOR=`sed -e "s/^ *VBOX_VERSION_MAJOR *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk`
+VBOX_VERSION_MINOR=`sed -e "s/^ *VBOX_VERSION_MINOR *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk`
+VBOX_VERSION_BUILD=`sed -e "s/^ *VBOX_VERSION_BUILD *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk`
+VBOX_SVN_CONFIG_REV=`sed -e 's/^ *VBOX_SVN_REV_CONFIG_FALLBACK *:= \+\$(patsubst *%:,, *\$Rev: *\([0-9]\+\) *\$ *) */\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_SVN_VERSION_REV=`sed -e 's/^ *VBOX_SVN_REV_VERSION_FALLBACK *:= \+\$(patsubst *%:,, *\$Rev: *\([0-9]\+\) *\$ *) */\1/;t;d' $PATH_ROOT/Version.kmk`
+if [ "$VBOX_SVN_CONFIG_REV" -gt "$VBOX_SVN_VERSION_REV" ]; then
+ VBOX_SVN_REV=$VBOX_SVN_CONFIG_REV
+else
+ VBOX_SVN_REV=$VBOX_SVN_VERSION_REV
+fi
+VBOX_VENDOR=`sed -e 's/^ *VBOX_VENDOR *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_VENDOR_SHORT=`sed -e 's/^ *VBOX_VENDOR_SHORT *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_PRODUCT=`sed -e 's/^ *VBOX_PRODUCT *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_C_YEAR=`date +%Y`
+
+. $PATH_VBOXGUEST/linux/files_vboxguest
+. $PATH_VBOXSF/files_vboxsf
+. $PATH_VBOXVIDEO/files_vboxvideo_drv
+
+# Temporary path for creating the modules, will be removed later
+mkdir -p $PATH_OUT || exit 1
+
+# Create auto-generated version file, needed by all modules
+echo "#ifndef ___version_generated_h___" > $PATH_OUT/version-generated.h
+echo "#define ___version_generated_h___" >> $PATH_OUT/version-generated.h
+echo "" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_MAJOR $VBOX_VERSION_MAJOR" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_MINOR $VBOX_VERSION_MINOR" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_BUILD $VBOX_VERSION_BUILD" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_STRING_RAW \"$VBOX_VERSION_MAJOR.$VBOX_VERSION_MINOR.$VBOX_VERSION_BUILD\"" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_STRING \"$VBOX_VERSION_MAJOR.$VBOX_VERSION_MINOR.$VBOX_VERSION_BUILD\"" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_API_VERSION_STRING \"${VBOX_VERSION_MAJOR}_${VBOX_VERSION_MINOR}\"" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_PRIVATE_BUILD_DESC \"Private build with export_modules\"" >> $PATH_OUT/version-generated.h
+echo "" >> $PATH_OUT/version-generated.h
+echo "#endif" >> $PATH_OUT/version-generated.h
+
+# Create auto-generated revision file, needed by all modules
+echo "#ifndef __revision_generated_h__" > $PATH_OUT/revision-generated.h
+echo "#define __revision_generated_h__" >> $PATH_OUT/revision-generated.h
+echo "" >> $PATH_OUT/revision-generated.h
+echo "#define VBOX_SVN_REV $VBOX_SVN_REV" >> $PATH_OUT/revision-generated.h
+echo "" >> $PATH_OUT/revision-generated.h
+echo "#endif" >> $PATH_OUT/revision-generated.h
+
+# Create auto-generated product file, needed by all modules
+echo "#ifndef ___product_generated_h___" > $PATH_OUT/product-generated.h
+echo "#define ___product_generated_h___" >> $PATH_OUT/product-generated.h
+echo "" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_VENDOR \"$VBOX_VENDOR\"" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_VENDOR_SHORT \"$VBOX_VENDOR_SHORT\"" >> $PATH_OUT/product-generated.h
+echo "" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_PRODUCT \"$VBOX_PRODUCT\"" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_C_YEAR \"$VBOX_C_YEAR\"" >> $PATH_OUT/product-generated.h
+echo "" >> $PATH_OUT/product-generated.h
+echo "#endif" >> $PATH_OUT/product-generated.h
+
+# vboxguest (VirtualBox guest kernel module)
+mkdir $PATH_OUT/vboxguest || exit 1
+for f in $FILES_VBOXGUEST_NOBIN; do
+ install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxguest/`echo $f|cut -d'>' -f2`"
+done
+for f in $FILES_VBOXGUEST_BIN; do
+ install -D -m 0755 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxguest/`echo $f|cut -d'>' -f2`"
+done
+
+# vboxsf (VirtualBox guest kernel module for shared folders)
+mkdir $PATH_OUT/vboxsf || exit 1
+for f in $FILES_VBOXSF_NOBIN; do
+ install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxsf/`echo $f|cut -d'>' -f2`"
+done
+for f in $FILES_VBOXSF_BIN; do
+ install -D -m 0755 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxsf/`echo $f|cut -d'>' -f2`"
+done
+
+# vboxvideo (VirtualBox guest kernel module for drm support)
+mkdir $PATH_OUT/vboxvideo || exit 1
+for f in $FILES_VBOXVIDEO_DRM_NOBIN; do
+ install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxvideo/`echo $f|cut -d'>' -f2`"
+done
+for f in $FILES_VBOXVIDEO_DRM_BIN; do
+ install -D -m 0755 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxvideo/`echo $f|cut -d'>' -f2`"
+done
+sed -f $PATH_VBOXVIDEO/indent.sed -i $PATH_OUT/vboxvideo/*.[ch]
+
+# convenience Makefile
+install -D -m 0644 $PATH_LINUX/Makefile "$PATH_OUT/Makefile"
+
+# Only temporary, omit from archive
+rm $PATH_OUT/version-generated.h
+rm $PATH_OUT/revision-generated.h
+rm $PATH_OUT/product-generated.h
+
+# If we are exporting to a folder then stop now.
+test "x${1}" = x--folder && exit 0
+
+# Do a test build
+echo Doing a test build, this may take a while.
+make -C $PATH_OUT > $PATH_LOG 2>&1 &&
+ make -C $PATH_OUT clean >> $PATH_LOG 2>&1 ||
+ echo "Warning: test build failed. Please check $PATH_LOG"
+
+# Create the archive
+tar -czf $FILE_OUT -C $PATH_OUT . || exit 1
+
+# Remove the temporary directory
+rm -r $PATH_OUT
+
diff --git a/src/VBox/Additions/linux/installer/.scm-settings b/src/VBox/Additions/linux/installer/.scm-settings
new file mode 100644
index 00000000..96d4dbd8
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for linux guest additions installer.
+#
+
+#
+# Copyright (C) 2010-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+/install.sh.in: --treat-as .sh
+/deffiles: --treat-as files_
diff --git a/src/VBox/Additions/linux/installer/autorun.sh b/src/VBox/Additions/linux/installer/autorun.sh
new file mode 100755
index 00000000..7b260935
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/autorun.sh
@@ -0,0 +1,204 @@
+#!/bin/sh
+# $Id: autorun.sh $
+## @file
+# VirtualBox Guest Additions installation script for *nix guests
+#
+
+#
+# Copyright (C) 2009-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+
+# Deal with differing "which" semantics
+mywhich() {
+ which "$1" 2>/dev/null | grep -v "no $1"
+}
+
+# Get the name and execute switch for a useful terminal emulator
+#
+# Sets $gxtpath to the emulator path or empty
+# Sets $gxttitle to the "title" switch for that emulator
+# Sets $gxtexec to the "execute" switch for that emulator
+# May clobber $gtx*
+# Calls mywhich
+getxterm() {
+ # gnome-terminal and mate-terminal use -e differently to other emulators
+ for gxti in "konsole --title -e" "gnome-terminal --title -x" "mate-terminal --title -x" "xterm -T -e"; do
+ set $gxti
+ gxtpath="`mywhich $1`"
+ case "$gxtpath" in ?*)
+ gxttitle=$2
+ gxtexec=$3
+ return
+ ;;
+ esac
+ done
+}
+
+# Quotes its argument by inserting '\' in front of every character save
+# for 'A-Za-z0-9/'. Prints the result to stdout.
+quotify() {
+ echo "$1" | sed -e 's/\([^a-zA-Z0-9/]\)/\\\1/g'
+}
+
+ostype=`uname -s`
+if test "$ostype" != "Linux" && test "$ostype" != "SunOS" ; then
+ echo "Linux/Solaris not detected."
+ exit 1
+fi
+
+# The below is GNU-specific. See VBox.sh for the longer Solaris/OS X version.
+TARGET=`readlink -e -- "${0}"` || exit 1
+path="${TARGET%/[!/]*}"
+# 32-bit or 64-bit?
+case `uname -m` in
+ i[3456789]86|x86|i86pc)
+ arch='x86'
+ ;;
+ x86_64|amd64|AMD64)
+ arch='amd64'
+ ;;
+ *)
+ echo "Unknown architecture `uname -m`."
+ exit 1
+ ;;
+esac
+
+# execute the installer
+if test "$ostype" = "Linux"; then
+ for i in "$path/VBoxLinuxAdditions.run" \
+ "$path/VBoxLinuxAdditions-$arch.run"; do
+ if test -f "$i"; then
+ getxterm
+ case "$gxtpath" in ?*)
+ TITLE="VirtualBox Guest Additions installation"
+ BINARY="`quotify "$i"`"
+ exec "$gxtpath" "$gxttitle" "$TITLE" "$gxtexec" /bin/sh "$path/runasroot.sh" --has-terminal "$TITLE" "/bin/sh $BINARY --xwin" "Please try running "\""$i"\"" manually."
+ exit
+ ;;
+ *)
+ echo "Unable to start installation process with elevated privileges automatically. Please try running "\""$i"\"" manually."
+ exit
+ ;;
+ esac
+ fi
+ done
+
+ # else: unknown failure
+ echo "Linux guest additions installer not found -- try to start it manually."
+ exit 1
+
+elif test "$ostype" = "SunOS"; then
+
+ # check for combined package
+ installfile="$path/VBoxSolarisAdditions.pkg"
+ if test -f "$installfile"; then
+
+ # check for pkgadd bin
+ pkgaddbin=pkgadd
+ found=`which pkgadd | grep "no pkgadd"`
+ if test ! -z "$found"; then
+ if test -f "/usr/sbin/pkgadd"; then
+ pkgaddbin=/usr/sbin/pkgadd
+ else
+ echo "Could not find pkgadd."
+ exit 1
+ fi
+ fi
+
+ # check for pfexec
+ pfexecbin=pfexec
+ found=`which pfexec | grep "no pfexec"`
+ if test ! -z "$found"; then
+ # Use su and prompt for password
+ echo "Could not find pfexec."
+ subin=`which su`
+ else
+ idbin=/usr/xpg4/bin/id
+ if test ! -x "$idbin"; then
+ found=`which id 2> /dev/null`
+ if test ! -x "$found"; then
+ echo "Failed to find a suitable user id executable."
+ exit 1
+ else
+ idbin=$found
+ fi
+ fi
+
+ # check if pfexec can get the job done
+ if test "$idbin" = "/usr/xpg4/bin/id"; then
+ userid=`$pfexecbin $idbin -u`
+ else
+ userid=`$pfexecbin $idbin | cut -f1 -d'(' | cut -f2 -d'='`
+ fi
+ if test $userid != "0"; then
+ # pfexec exists but user has no pfexec privileges, switch to using su and prompting password
+ subin=`which su`
+ fi
+ fi
+
+ # create temporary admin file for autoinstall
+ TMPFILE=`mktemp -q /tmp/vbox.XXXXXX`
+ if [ -z $TMPFILE ]; then
+ echo "Unable to create a temporary file"
+ exit 1
+ fi
+ echo "basedir=default
+runlevel=nocheck
+conflict=quit
+setuid=nocheck
+action=nocheck
+partial=quit
+instance=unique
+idepend=quit
+rdepend=quit
+space=quit
+mail=
+" > $TMPFILE
+
+ # check gnome-terminal, use it if it exists.
+ if test -f "/usr/bin/gnome-terminal"; then
+ # use su/pfexec
+ if test -z "$subin"; then
+ /usr/bin/gnome-terminal --title "Installing VirtualBox Additions" --command "/bin/sh -c '$pfexecbin $pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest; /bin/echo press ENTER to close this window; /bin/read'"
+ else
+ /usr/bin/gnome-terminal --title "Installing VirtualBox Additions: Root password required." --command "/bin/sh -c '$subin - root -c \"$pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest\"; /bin/echo press ENTER to close this window; /bin/read'"
+ fi
+ elif test -f "/usr/X11/bin/xterm"; then
+ # use xterm
+ if test -z "$subin"; then
+ /usr/X11/bin/xterm -title "Installing VirtualBox Additions" -e "$pfexecbin $pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest; /bin/echo press ENTER to close this window; /bin/read"
+ else
+ /usr/X11/bin/xterm -title "Installing VirtualBox Additions: Root password required." -e "$subin - root -c \"$pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest\"; /bin/echo press ENTER to close this window; /bin/read"
+ fi
+ else
+ echo "No suitable terminal not found. -- install additions using pkgadd -d."
+ fi
+ rm -r $TMPFILE
+
+ exit 0
+ fi
+
+ echo "Solaris guest additions installer not found -- try to start them manually."
+ exit 1
+fi
+
diff --git a/src/VBox/Additions/linux/installer/deffiles b/src/VBox/Additions/linux/installer/deffiles
new file mode 100644
index 00000000..97278f74
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/deffiles
@@ -0,0 +1,80 @@
+# $Id: deffiles $
+## @file
+# VirtualBox linux Guest Additions default files list
+#
+
+#
+# Copyright (C) 2009-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# This file contains the names of all files that the Guest Additions for
+# Linux guests have ever installed into the main filesystem tree (/usr and
+# /sbin). No entries should ever be removed from this file, as it is used
+# for uninstalling old installations of VirtualBox which did not keep track
+# of the names of their files, or damaged installations which lost track of
+# them.
+
+DEFAULT_FILE_NAMES=" \
+ /usr/bin/vboxadd-xclient \
+ /usr/bin/VBoxAudioTest \
+ /usr/bin/VBoxClient \
+ /usr/bin/VBoxControl \
+ /usr/sbin/vbox-greeter \
+ /usr/sbin/vboxadd-timesync \
+ /usr/sbin/vboxadd-service \
+ /usr/sbin/VBoxService \
+ /sbin/mount.vboxsf \
+ /usr/share/xgreeters/vbox-greeter.desktop \
+ /etc/X11/Xsession.d/98vboxadd-xclient \
+ /etc/X11/xinit.d/98vboxadd-xclient \
+ /etc/X11/xinit/xinitrc.d/98vboxadd-xclient.sh \
+ /usr/bin/VBoxRandR \
+ /usr/bin/VBoxClient-all \
+ /etc/xdg/autostart/vboxclient.desktop \
+ /usr/share/autostart/vboxclient.desktop \
+ /etc/udev/rules.d/60-vboxadd.rules \
+ /usr/share/xserver-xorg/pci/vboxvideo.ids \
+ /etc/hal/fdi/policy/90-vboxguest.fdi \
+ /etc/udev/rules.d/70-xorg-vboxmouse.rules \
+ /usr/share/hal/fdi/policy/20thirdparty/90-vboxguest.fdi \
+ /usr/lib/X11/xorg.conf.d/50-vboxmouse.conf \
+ /usr/share/X11/xorg.conf.d/50-vboxmouse.conf \
+ /usr/lib/dri/vboxvideo_dri.so \
+ /usr/lib64/dri/vboxvideo_dri.so \
+ /usr/lib64/xorg/modules/drivers/vboxvideo_drv.so \
+ /usr/lib/xorg/modules/drivers/vboxvideo_drv.so \
+ /usr/X11R6/lib64/modules/drivers/vboxvideo_drv.so \
+ /usr/X11R6/lib/modules/drivers/vboxvideo_drv.so \
+ /usr/X11R6/lib/X11/modules/drivers/vboxvideo_drv.so \
+ /usr/lib64/xorg/modules/input/vboxmouse_drv.so \
+ /usr/lib/xorg/modules/input/vboxmouse_drv.so \
+ /usr/X11R6/lib64/modules/input/vboxmouse_drv.so \
+ /usr/X11R6/lib/modules/input/vboxmouse_drv.so \
+ /usr/X11R6/lib/X11/modules/input/vboxmouse_drv.so
+"
+
+DEFAULT_VERSIONED_FILE_NAMES=" \
+ /usr/src/vboxadd \
+ /usr/src/vboxguest \
+ /usr/src/vboxvfs \
+ /usr/src/vboxsf \
+ /usr/src/vboxvideo \
+"
diff --git a/src/VBox/Additions/linux/installer/install.sh.in b/src/VBox/Additions/linux/installer/install.sh.in
new file mode 100755
index 00000000..00097cf3
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/install.sh.in
@@ -0,0 +1,641 @@
+#!/bin/sh
+#
+# Oracle VM VirtualBox
+# VirtualBox Makeself installation starter script
+# for Linux Guest Additions
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Testing:
+# * After successful installation, 0 is returned if the vboxguest module version
+# built matches the one loaded and 2 is returned otherwise. E.g. VBoxClient
+# running will prevent vboxguest reloading.
+# * If the kernel modules cannot be built (run the installer with KERN_VER=none)
+# or loaded (run with KERN_VER=<installed non-current version>) then 1 is
+# returned.
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+
+# Note: These variable names must *not* clash with variables in $CONFIG_DIR/$CONFIG!
+PACKAGE="VBoxGuestAdditions"
+PACKAGE_NAME="VirtualBox Guest Additions"
+UNINSTALL="uninstall.sh"
+PUBLIC_UNINSTALL_HOOK="/usr/sbin/vbox-uninstall-guest-additions"
+ROUTINES="routines.sh"
+INSTALLATION_VER="_VERSION_"
+BUILD_VBOX_KBUILD_TYPE="_BUILDTYPE_"
+BUILD_USERNAME="_USERNAME_"
+UNINSTALL_SCRIPTS="vboxadd-x11 vboxvfs vboxadd-timesync vboxadd-service vboxadd"
+
+INSTALLATION_DIR="/opt/$PACKAGE-$INSTALLATION_VER"
+CONFIG_DIR="/var/lib/$PACKAGE"
+CONFIG="config"
+CONFIG_FILES="filelist"
+SELF=$1
+LOGFILE="/var/log/vboxadd-install.log"
+
+## Were we able to stop any previously running Additions kernel modules?
+MODULES_STOPPED=1
+
+. "./$ROUTINES"
+
+check_root
+
+check_deps bzip2 tar
+
+create_log "$LOGFILE"
+
+usage()
+{
+ catinfo << EOF
+
+Usage: $SELF install [<installation directory>]
+ [--with-<module>]
+ [--package-base <base> |
+ uninstall
+ [--force] [--no-setup]
+
+Options:
+ --package-base <base> For use when building distribution packages.
+ Installs relative to <base> instead of to "/",
+ does not run setup, installs to "<base>/usr/lib"
+ instead of to "/opt" and does not create uninstall.
+ --force Forces an upgrade. Not recommended.
+
+Example:
+$SELF install
+EOF
+ exit 1
+}
+
+# Create a symlink in the filesystem and add it to the list of package files
+add_symlink()
+{
+ self=add_symlink
+ ## Parameters:
+ # The file the link should point to
+ target="$1"
+ # The name of the actual symlink file. Must be an absolute path to a
+ # non-existing file in an existing directory.
+ link="$2"
+ link_dir="`dirname "$link"`"
+ test -n "$target" ||
+ { echo 1>&2 "$self: no target specified"; return 1; }
+ test -d "$link_dir" ||
+ { echo 1>&2 "$self: link directory $link_dir does not exist"; return 1; }
+ expr "$link" : "/.*" > /dev/null ||
+ { echo 1>&2 "$self: link file name is not absolute"; return 1; }
+ rm -f "$link"
+ ln -s "$target" "$link"
+ echo "$link" >> "$CONFIG_DIR/$CONFIG_FILES"
+}
+
+# Create symbolic links targeting all files in a directory in another
+# directory in the filesystem
+link_into_fs()
+{
+ ## Parameters:
+ # Directory containing the link target files
+ target_branch="$1"
+ # Directory to create the link files in
+ directory="$2"
+ for i in "$INSTALLATION_DIR/$target_branch"/*; do
+ test -e "$i" &&
+ add_symlink "$i" "$directory/`basename $i`"
+ done
+}
+
+# Look for broken installations or installations without a known uninstaller
+# and try to clean them up, asking the user first.
+def_uninstall()
+{
+ ## Parameters:
+ # Whether to force cleanup without asking the user
+ force="$1"
+
+ . ./deffiles
+ found=0
+ for i in "/opt/$PACKAGE-"*; do
+ test -e "$i" && found=1
+ done
+ for i in $DEFAULT_FILE_NAMES; do
+ test "$found" = 0 && test -e "$i" && found=1
+ done
+ test "$found" = 0 &&
+ for i in $DEFAULT_VERSIONED_FILE_NAMES; do
+ for j in $i-*; do
+ test "$found" = 0 && test -e "$j" && found=1
+ done
+ done
+ test "$found" = 0 && return 0
+ if ! test "$1" = "force" ; then
+ # Try to make the promised notification appear on next start.
+ VBoxControl guestproperty delete \
+ /VirtualBox/GuestAdd/HostVerLastChecked 2>&1 > /dev/null
+ cat 1>&2 << EOF
+This system appears to have a version of the VirtualBox Guest Additions
+already installed. If it is part of the operating system and kept up-to-date,
+there is most likely no need to replace it. If it is not up-to-date, you
+should get a notification when you start the system. If you wish to replace
+it with this version, please do not continue with this installation now, but
+instead remove the current version first, following the instructions for the
+operating system.
+
+If your system simply has the remains of a version of the Additions you could
+not remove you should probably continue now, and these will be removed during
+installation.
+
+Do you wish to continue? [yes or no]
+EOF
+ read reply dummy
+ if ! expr "$reply" : [yY] > /dev/null &&
+ ! expr "$reply" : [yY][eE][sS] > /dev/null
+ then
+ info
+ info "Cancelling installation."
+ return 1
+ fi
+ fi
+ # Inhibit rebuilding of any installed kernels.
+ for i in /lib/modules/*; do
+ ver="${i##*/}"
+ test ! -d "$i"/build || touch /var/lib/VBoxGuestAdditions/skip-"$ver"
+ done
+ # Stop what we can in the way of services and remove them from the
+ # system
+ for i in $UNINSTALL_SCRIPTS; do
+ stop_init_script "$i" 2>> "${LOGFILE}"
+ test -z "$NO_CLEANUP" && test -x "./$i" && "./$i" cleanup 1>&2 2>> "$LOGFILE"
+ delrunlevel "$i" 2>> "${LOGFILE}"
+ remove_init_script "$i" 2>> "${LOGFILE}"
+ done
+ for i in "/opt/$PACKAGE-"*/init; do
+ for j in $UNINSTALL_SCRIPTS; do
+ script="${i}/${j}"
+ test -x "${script}" && test -z "$NO_CLEANUP" &&
+ grep -q '^# *cleanup_script *$' "${script}" &&
+ "${script}" cleanup 1>&2 2>> "$LOGFILE"
+ done
+ done
+
+ # Get rid of any remaining files
+ for i in $DEFAULT_FILE_NAMES; do
+ rm -f "$i" 2> /dev/null
+ done
+ for i in $DEFAULT_VERSIONED_FILE_NAMES; do
+ rm -f "$i-"* 2> /dev/null
+ done
+ rm -f "/usr/lib/$PACKAGE" "/usr/lib64/$PACKAGE" "/usr/share/$PACKAGE" \
+ "/usr/lib/i386-linux-gnu/$PACKAGE" "/usr/lib/x86_64-linux-gnu/$PACKAGE"
+
+ # And any packages left under /opt
+ for i in "/opt/$PACKAGE-"*; do
+ test -d "$i" && rm -rf "$i"
+ done
+ return 0
+}
+
+info "$PACKAGE_NAME installer"
+
+# Sensible default actions
+ACTION="install"
+NO_CLEANUP=""
+FORCE_UPGRADE=""
+PACKAGE_BASE=""
+
+while [ $# -ge 2 ];
+do
+ ARG=$2
+ shift
+
+ if [ -z "$MY_END_OF_OPTIONS" ]; then
+ case "$ARG" in
+
+ install)
+ ACTION="install"
+ ;;
+
+ uninstall)
+ ACTION="uninstall"
+ ;;
+
+ package)
+ ACTION="package"
+ INSTALLATION_DIR=/usr/lib
+ PACKAGE_BASE="$2"
+ shift
+ if test ! -d "${PACKAGE_BASE}"; then
+ info "Package base directory not found."
+ usage
+ fi
+ ;;
+
+ ## @todo Add per-module options handling, e.g. --lightdm-greeter-dir
+ # or --lightdm-config
+
+ ## @todo Add listing all available modules (+ their options, e.g.
+ # with callback mod_mymod_show_options?)
+
+ --with-*)
+ MODULE_CUR=`expr "$ARG" : '--with-\(.*\)'`
+ # Check if corresponding module in installer/module-$1 exists.
+ # Note: Module names may not contain spaces or other funny things.
+ if [ ! -f "./installer/module-${MODULE_CUR}" ]; then
+ info "Error: Module \"${MODULE_CUR}\" does not exist."
+ usage
+ fi
+ # Give the module the chance of doing initialization work / checks.
+ . "./installer/module-${MODULE_CUR}"
+ mod_${MODULE_CUR}_init
+ if test $? -ne 0; then
+ echo 1>&2 "Module '${MODULE_CUR}' failed to initialize"
+ if ! test "$FORCE_UPGRADE" = "force"; then
+ return 1
+ fi
+ # Continue initialization.
+ fi
+ # Add module to the list of modules to handle later.
+ if test -z "${INSTALLATION_MODULES_LIST}"; then
+ INSTALLATION_MODULES_LIST="${MODULE_CUR}"
+ else
+ INSTALLATION_MODULES_LIST="${INSTALLATION_MODULES_LIST} ${MODULE_CUR}"
+ fi
+ shift
+ ;;
+
+ --force|force) # Keep "force" for backwards compatibility.
+ FORCE_UPGRADE="force"
+ ;;
+
+ --no-setup|no_setup)
+ # Deprecated; keep this setting for backwards compatibility.
+ ;;
+
+ --no-cleanup|no_cleanup) # Keep "no_cleanup" for backwards compatibility.
+ # Do not do cleanup of old modules when removing them. For
+ # testing purposes only.
+ NO_CLEANUP="no_cleanup"
+ ;;
+
+ --)
+ MY_END_OF_OPTIONS="1"
+ ;;
+
+ *)
+ if [ "`echo $1|cut -c1`" != "/" ]; then
+ info "Please specify an absolute path"
+ usage
+ fi
+ INSTALLATION_DIR="$1"
+ shift
+ ;;
+ esac
+ fi
+done
+
+# Check architecture
+cpu=`uname -m`;
+case "$cpu" in
+ i[3456789]86|x86)
+ cpu="x86"
+ lib_candidates="/usr/lib/i386-linux-gnu /usr/lib /lib"
+ ;;
+ x86_64|amd64)
+ cpu="amd64"
+ lib_candidates="/usr/lib/x86_64-linux-gnu /usr/lib64 /usr/lib /lib64 /lib"
+ ;;
+ *)
+ cpu="unknown"
+esac
+ARCH_PACKAGE="$PACKAGE-$cpu.tar.bz2"
+if [ ! -r "$ARCH_PACKAGE" ]; then
+ info "Detected unsupported $cpu machine type."
+ exit 1
+fi
+# Find the most appropriate libary folder by seeing which of the candidate paths
+# are actually in the shared linker path list and choosing the first. We look
+# for Debian-specific paths first, then LSB ones, then the new RedHat ones.
+libs=`ldconfig -v 2>/dev/null | grep -v ^$'\t'`
+for i in $lib_candidates; do
+ if echo $libs | grep -q $i; then
+ lib_path=$i
+ break
+ fi
+done
+if [ ! -x "$lib_path" ]; then
+ info "Unable to determine correct library path."
+ exit 1
+fi
+
+# uninstall any previous installation
+# If the currently installed Additions have provided an uninstallation hook, try
+# that first.
+if test -x "${PUBLIC_UNINSTALL_HOOK}"; then
+ "${PUBLIC_UNINSTALL_HOOK}" 1>&2 ||
+ abort "Failed to remove existing installation. Aborting..."
+fi
+
+INSTALL_DIR=""
+uninstalled=0
+test -r "$CONFIG_DIR/$CONFIG" && . "$CONFIG_DIR/$CONFIG"
+if test -n "$INSTALL_DIR" && test -n "$UNINSTALLER" &&
+ test -x "$INSTALL_DIR/$UNINSTALLER"; then
+ "$INSTALL_DIR/$UNINSTALLER" $NO_CLEANUP 1>&2 ||
+ abort "Failed to remove existing installation. Aborting..."
+ uninstalled=1
+fi
+test "$uninstalled" = 0 && def_uninstall "$FORCE_UPGRADE" && uninstalled=1
+test "$uninstalled" = 0 && exit 1
+rm -f "$CONFIG_DIR/$CONFIG"
+rm -f "$CONFIG_DIR/$CONFIG_FILES"
+rmdir "$CONFIG_DIR" 2>/dev/null || true
+test "$ACTION" = "install" || exit 0
+
+# Now check whether the kernel modules were stopped.
+lsmod | grep -q vboxguest && MODULES_STOPPED=
+
+# Choose a proper umask
+umask 022
+
+# Set installer modules directory
+INSTALLATION_MODULES_DIR="$INSTALLATION_DIR/installer/"
+
+# install the new version
+mkdir -p -m 755 "$CONFIG_DIR"
+test ! -d "$INSTALLATION_DIR" && REMOVE_INSTALLATION_DIR=1
+mkdir -p -m 755 "$INSTALLATION_DIR"
+
+# install and load installer modules
+if [ -d installer ]; then
+ info "Copying additional installer modules ..."
+ mkdir -p -m 755 "$INSTALLATION_MODULES_DIR"
+ for CUR_FILE in `ls installer/*`; do
+ install -p -m 755 "$CUR_FILE" "$INSTALLATION_MODULES_DIR"
+ if [ $? -ne 0 ]; then
+ info "Error: Failed to copy installer module \"$CUR_FILE\""
+ if ! test "$FORCE_UPGRADE" = "force"; then
+ exit 1
+ fi
+ fi
+ done
+fi
+
+# Create a list of the files in the archive, skipping any directories which
+# already exist in the filesystem.
+bzip2 -d -c "$ARCH_PACKAGE" | tar -tf - |
+ while read name; do
+ fullname="$INSTALLATION_DIR/$name"
+ case "$fullname" in
+ */)
+ test ! -d "$fullname" &&
+ echo "$fullname" >> "$CONFIG_DIR/$CONFIG_FILES"
+ ;;
+ *)
+ echo "$fullname" >> "$CONFIG_DIR/$CONFIG_FILES"
+ ;;
+ esac
+ done
+bzip2 -d -c "$ARCH_PACKAGE" | tar -xf - -C "$INSTALLATION_DIR" || exit 1
+
+# Set symlinks into /usr and /sbin
+link_into_fs "bin" "${PACKAGE_BASE}/usr/bin"
+link_into_fs "sbin" "${PACKAGE_BASE}/usr/sbin"
+link_into_fs "lib" "$lib_path"
+link_into_fs "src" "${PACKAGE_BASE}/usr/src"
+
+if [ -d "$INSTALLATION_MODULES_DIR" ]; then
+ info "Installing additional modules ..."
+ for CUR_MODULE in `find "$INSTALLATION_MODULES_DIR" 2>/dev/null`
+ do
+ echo "$CUR_MODULE" >> "$CONFIG_DIR/$CONFIG_FILES"
+ done
+fi
+
+for CUR_MODULE in ${INSTALLATION_MODULES_LIST}
+do
+ mod_${CUR_MODULE}_install
+ if [ $? -ne 0 ]; then
+ info "Error: Failed to install module \"$CUR_MODULE\""
+ if ! test "$FORCE_UPGRADE" = "force"; then
+ exit 1
+ fi
+ fi
+done
+
+# Remember our installation configuration before we call any init scripts
+cat > "$CONFIG_DIR/$CONFIG" << EOF
+# $PACKAGE installation record.
+# Package installation directory
+INSTALL_DIR='$INSTALLATION_DIR'
+# Additional installation modules
+INSTALL_MODULES_DIR='$INSTALLATION_MODULES_DIR'
+INSTALL_MODULES_LIST='$INSTALLATION_MODULES_LIST'
+# Package uninstaller. If you repackage this software, please make sure
+# that this prints a message and returns an error so that the default
+# uninstaller does not attempt to delete the files installed by your
+# package.
+UNINSTALLER='$UNINSTALL'
+# Package version
+INSTALL_VER='$INSTALLATION_VER'
+# Build type and user name for logging purposes
+BUILD_VBOX_KBUILD_TYPE='$BUILD_BUILDTYPE'
+BUILD_USERNAME='$USERNAME'
+EOF
+
+# Give the modules the chance to write their stuff
+# to the installation config as well.
+if [ -n "${INSTALLATION_MODULES_LIST}" ]; then
+ info "Saving modules configuration ..."
+ for CUR_MODULE in ${INSTALLATION_MODULES_LIST}
+ do
+ echo "`mod_${CUR_MODULE}_config_save`" >> "$CONFIG_DIR/$CONFIG"
+ done
+fi
+
+"$INSTALLATION_DIR"/init/vboxadd setup 2>&1 | tee -a "$LOGFILE"
+
+# Install, set up and start init scripts
+install_init_script "$INSTALLATION_DIR"/init/vboxadd vboxadd 2>> "$LOGFILE"
+install_init_script "$INSTALLATION_DIR"/init/vboxadd-service vboxadd-service \
+ 2>> "$LOGFILE"
+finish_init_script_install
+addrunlevel vboxadd 2>> "$LOGFILE"
+addrunlevel vboxadd-service 2>> "$LOGFILE"
+start_init_script vboxadd 2>> "$LOGFILE"
+start_init_script vboxadd-service 2>> "$LOGFILE"
+
+cp $ROUTINES $INSTALLATION_DIR
+echo $INSTALLATION_DIR/$ROUTINES >> "$CONFIG_DIR/$CONFIG_FILES"
+cat > $INSTALLATION_DIR/$UNINSTALL << EOF
+#!/bin/sh
+# Auto-generated uninstallation file
+
+PATH=\$PATH:/bin:/sbin:/usr/sbin
+LOGFILE="/var/log/vboxadd-uninstall.log"
+
+# Read routines.sh
+if ! test -r "$INSTALLATION_DIR/$ROUTINES"; then
+ echo 1>&2 "Required file $ROUTINES not found. Aborting..."
+ return 1
+fi
+. "$INSTALLATION_DIR/$ROUTINES"
+
+# We need to be run as root
+check_root
+
+create_log "\$LOGFILE"
+
+echo 1>&2 "Removing installed version $INSTALLATION_VER of $PACKAGE_NAME..."
+
+NO_CLEANUP=""
+if test "\$1" = "no_cleanup"; then
+ shift
+ NO_CLEANUP="no_cleanup"
+fi
+
+test -r "$CONFIG_DIR/$CONFIG_FILES" || abort "Required file $CONFIG_FILES not found. Aborting..."
+
+# Stop and clean up all services
+if test -r "$INSTALLATION_DIR"/init/vboxadd-service; then
+ stop_init_script vboxadd-service 2>> "\$LOGFILE"
+ delrunlevel vboxadd-service 2>> "\$LOGFILE"
+ remove_init_script vboxadd-service 2>> "\$LOGFILE"
+fi
+if test -r "$INSTALLATION_DIR"/init/vboxadd; then
+ stop_init_script vboxadd 2>> "\$LOGFILE"
+ test -n "\$NO_CLEANUP" || "$INSTALLATION_DIR"/init/vboxadd cleanup 2>> "\$LOGFILE"
+ delrunlevel vboxadd 2>> "\$LOGFILE"
+ remove_init_script vboxadd 2>> "\$LOGFILE"
+fi
+finish_init_script_install
+
+# Load all modules
+# Important: This needs to be done before loading the configuration
+# value below to not override values which are set to a default
+# value in the modules itself.
+for CUR_MODULE in `find "$INSTALLATION_MODULES_DIR" -name "module-*" 2>/dev/null`
+ do
+ . "\$CUR_MODULE"
+ done
+
+# Load configuration values
+test -r "$CONFIG_DIR/$CONFIG" && . "$CONFIG_DIR/$CONFIG"
+
+# Call uninstallation initialization of all modules
+for CUR_MODULE in "$INSTALLATION_MODULES_LIST"
+ do
+ if test -z "\$CUR_MODULE"; then
+ continue
+ fi
+ mod_\${CUR_MODULE}_pre_uninstall
+ if [ $? -ne 0 ]; then
+ echo 1>&2 "Module \"\$CUR_MODULE\" failed to initialize uninstallation"
+ # Continue initialization.
+ fi
+ done
+
+# Call uninstallation of all modules
+for CUR_MODULE in "$INSTALLATION_MODULES_LIST"
+ do
+ if test -z "\$CUR_MODULE"; then
+ continue
+ fi
+ mod_\${CUR_MODULE}_uninstall
+ if [ $? -ne 0 ]; then
+ echo 1>&2 "Module \"\$CUR_MODULE\" failed to uninstall"
+ # Continue uninstallation.
+ fi
+ done
+
+# And remove all files and empty installation directories
+# Remove any non-directory entries
+cat "$CONFIG_DIR/$CONFIG_FILES" | xargs rm 2>/dev/null
+# Remove any empty (of files) directories in the file list
+cat "$CONFIG_DIR/$CONFIG_FILES" |
+ while read file; do
+ case "\$file" in
+ */)
+ test -d "\$file" &&
+ find "\$file" -depth -type d -exec rmdir '{}' ';' 2>/dev/null
+ ;;
+ esac
+ done
+
+# Remove configuration files
+rm "$CONFIG_DIR/$CONFIG_FILES" 2>/dev/null
+rm "$CONFIG_DIR/$CONFIG" 2>/dev/null
+rmdir "$CONFIG_DIR" 2>/dev/null
+exit 0
+EOF
+chmod 0755 $INSTALLATION_DIR/$UNINSTALL
+echo $INSTALLATION_DIR/$UNINSTALL >> "$CONFIG_DIR/$CONFIG_FILES"
+test -n "$REMOVE_INSTALLATION_DIR" &&
+ echo "$INSTALLATION_DIR/" >> "$CONFIG_DIR/$CONFIG_FILES"
+
+cat > "${PUBLIC_UNINSTALL_HOOK}" << EOF
+#!/bin/sh
+# This executable provides a well-known way to uninstall VirtualBox Guest
+# Additions in order to re-install them from a different source. A common case
+# is uninstalling distribution-provide Additions to install the version provided
+# by VirtualBox. Distributions should put the right command in here to do the
+# removal, e.g. "dnf remove VirtualBox-guest-additions". Leaving kernel modules
+# provided by the distribution kernel package in place is acceptable if the
+# location does not clash with the VirtualBox-provided module location (misc).
+$INSTALLATION_DIR/$UNINSTALL
+EOF
+chmod 0755 "${PUBLIC_UNINSTALL_HOOK}"
+echo "$PUBLIC_UNINSTALL_HOOK" >> "$CONFIG_DIR/$CONFIG_FILES"
+
+# Test for a problem with old Mesa versions which stopped our 3D libraries
+# from working. Known to affect Debian 7.11, probably OL/RHEL 5.
+# The problem was that the system Mesa library had an ABI note, which caused
+# ldconfig to always prefer it to ours.
+if ldconfig -p | grep -q "libGL.so.1.*Linux 2.4"; then
+ gl_with_abi=`ldconfig -p | grep "libGL.so.1.*Linux 2.4" | sed 's/.*=> //'`
+ cat >&2 << EOF
+This system appears to be running a version of Mesa with a known problem
+which will prevent VirtualBox 3D pass-through from working. See
+ https://bugs.freedesktop.org/show_bug.cgi?id=26663
+The following, run as root should fix this, though you will have to run it
+again if the system version of Mesa is updated:
+EOF
+ for i in ${gl_with_abi}; do
+ echo >&2 " strip -R .note.ABI-tag ${i}"
+ done
+ echo >&2 " ldconfig"
+fi
+
+# setuid bit of our drm client as drm ioctl calls
+# need to be done by elevated privileges
+chmod 4755 "$INSTALLATION_DIR"/bin/VBoxDRMClient
+
+# And do a final test as to whether the kernel modules were properly created
+# and loaded. Return 0 if both are true, 1 if the modules could not be built
+# or loaded (except due to already running older modules) and 2 if already
+# running modules probably prevented new ones from loading. vboxvideo is
+# currently not tested.
+# Assume that we have already printed enough messages by now and stay silent.
+modinfo vboxguest >/dev/null 2>&1 || exit 1
+lsmod | grep -q vboxguest || exit 1
+test -n "${MODULES_STOPPED}" || exit 2
+exit 0
diff --git a/src/VBox/Additions/linux/installer/module-autologon.sh b/src/VBox/Additions/linux/installer/module-autologon.sh
new file mode 100644
index 00000000..8fdf792b
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/module-autologon.sh
@@ -0,0 +1,182 @@
+# Oracle VM VirtualBox
+# $Id: module-autologon.sh $
+## @file
+# VirtualBox Linux Guest Additions installer - autologon module
+#
+
+#
+# Copyright (C) 2012-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# @todo Document functions and their usage!
+
+MOD_AUTOLOGON_DEFAULT_LIGHTDM_CONFIG="/etc/lightdm/lightdm.conf"
+MOD_AUTOLOGON_DEFAULT_LIGHTDM_GREETER_DIR="/usr/share/xgreeters"
+
+mod_autologon_init()
+{
+ echo "Initializing auto-logon support ..."
+ return 0
+}
+
+mod_autologon_install_ex()
+{
+ info "Installing auto-logon support ..."
+
+ ## Parameters:
+ # Greeter directory. Defaults to /usr/share/xgreeters.
+ greeter_dir="$1"
+ # LightDM config. Defaults to /etc/lightdm/lightdm.conf.
+ lightdm_config="$2"
+ # Whether to force installation if non-compatible distribution
+ # is detected.
+ force="$3"
+
+ # Check for Ubuntu and derivates. @todo Debian?
+ distros="Ubuntu UbuntuStudio Edubuntu Kubuntu Lubuntu Mythbuntu Xubuntu"
+ ## @todo Map Linux Mint versions to Ubuntu ones.
+
+ ## @todo Move the distro check to a routine / globals as soon as
+ ## we have other distribution-dependent stuff.
+ which lsb_release &>/dev/null
+ if test "$?" -ne "0"; then
+ info "Error: lsb_release not found (path set?), skipping auto-logon installation"
+ return 1
+ fi
+ distro_name=$(lsb_release -si)
+ distro_ver=$(lsb_release -sr)
+
+ for distro_cur in ${distros}; do
+ if test "$distro_name" = "$distro_cur"; then
+ distro_found="true"
+ break
+ fi
+ done
+
+ if test -z "$distro_found"; then
+ if ! test "$force" = "force"; then
+ info "Error: Unsupported distribution \"$distro_name\" found, skipping auto-logon installation"
+ return 1
+ fi
+ info "Warning: Unsupported distribution \"$distro_name\" found"
+ else
+ # Do we have Ubuntu 11.10 or greater?
+ # Use AWK for comparison since we run on plan sh.
+ echo | awk 'END { exit ( !('"$distro_ver >= 11.10"') ); }'
+ if test "$?" -ne "0"; then
+ if ! test "$force" = "force"; then
+ info "Error: Version $distro_ver of \"$distro_name\" not supported, skipping auto-logon installation"
+ return 1
+ fi
+ info "Warning: Unsupported \"$distro_name\" version $distro_ver found"
+ fi
+ fi
+
+ # Install dependencies (lightdm and FLTK 1.3+) using apt-get.
+ which apt-get &>/dev/null
+ if test "$?" -ne "0"; then
+ info "Error: apt-get not found (path set?), skipping auto-logon installation"
+ return 1
+ fi
+ info "Checking and installing necessary dependencies ..."
+ apt-get -qqq -y install libfltk1.3 libfltk-images1.3 || return 1
+ apt-get -qqq -y install lightdm || return 1
+
+ # Check for LightDM config.
+ if ! test -f "$lightdm_config"; then
+ info "Error: LightDM config \"$lightdm_config\" not found (LightDM installed?), skipping auto-logon installation"
+ return 1
+ fi
+
+ # Check for /usr/share/xgreeters.
+ if ! test -d "$greeter_dir"; then
+ if ! test "$force" = "force"; then
+ info "Error: Directory \"$greeter_dir\" does not exist, skipping auto-logon installation"
+ return 1
+ fi
+ info "Warning: Directory \"$greeter_dir\" does not exist, creating it"
+ mkdir -p -m 755 "$greeter_dir" || return 1
+ fi
+
+ # Link to required greeter files into $greeter_dir.
+ add_symlink "$INSTALLATION_DIR/other/vbox-greeter.desktop" "$greeter_dir/vbox-greeter.desktop"
+
+ # Backup and activate greeter config.
+ if ! test -f "$lightdm_config.vbox-backup"; then
+ info "Backing up LightDM configuration file ..."
+ cp "$lightdm_config" "$lightdm_config.vbox-backup" || return 1
+ chmod 644 "$lightdm_config.vbox-backup" || return 1
+ fi
+ sed -i -e 's/^\s*greeter-session\s*=.*/greeter-session=vbox-greeter/g' "$lightdm_config" || return 1
+ chmod 644 "$lightdm_config" || return 1
+
+ info "Auto-logon installation successful"
+ return 0
+}
+
+mod_autologon_install()
+{
+ if [ -z "$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR" ]; then
+ MOD_AUTOLOGON_LIGHTDM_GREETER_DIR=$MOD_AUTOLOGON_DEFAULT_LIGHTDM_GREETER_DIR
+ fi
+ if [ -z "$MOD_AUTOLOGON_LIGHTDM_CONFIG" ]; then
+ MOD_AUTOLOGON_LIGHTDM_CONFIG=$MOD_AUTOLOGON_DEFAULT_LIGHTDM_CONFIG
+ fi
+
+ mod_autologon_install_ex "$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR" "$MOD_AUTOLOGON_LIGHTDM_CONFIG" "$MOD_AUTOLOGON_FORCE"
+ return $?
+}
+
+mod_autologon_pre_uninstall()
+{
+ echo "Preparing to uninstall auto-logon support ..."
+ return 0
+}
+
+mod_autologon_uninstall()
+{
+ if test -z "$MOD_AUTOLOGON_LIGHTDM_CONFIG"; then
+ return 0
+ fi
+ info "Un-installing auto-logon support ..."
+
+ # Switch back to original greeter.
+ if test -f "$MOD_AUTOLOGON_LIGHTDM_CONFIG.vbox-backup"; then
+ mv "$MOD_AUTOLOGON_LIGHTDM_CONFIG.vbox-backup" "$MOD_AUTOLOGON_LIGHTDM_CONFIG"
+ if test "$?" -ne "0"; then
+ info "Warning: Could not restore original LightDM config \"$MOD_AUTOLOGON_LIGHTDM_CONFIG\""
+ fi
+ fi
+
+ # Remove greeter directory (if not empty).
+ rm "$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR" 2>/dev/null
+
+ info "Auto-logon uninstallation successful"
+ return 0
+}
+
+mod_autologon_config_save()
+{
+ echo "
+MOD_AUTOLOGON_LIGHTDM_CONFIG='$MOD_AUTOLOGON_LIGHTDM_CONFIG'
+MOD_AUTOLOGON_LIGHTDM_GREETER_DIR='$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR'"
+}
+
diff --git a/src/VBox/Additions/linux/installer/vboxadd-service.sh b/src/VBox/Additions/linux/installer/vboxadd-service.sh
new file mode 100755
index 00000000..f8ed86cd
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/vboxadd-service.sh
@@ -0,0 +1,169 @@
+#!/bin/sh
+# $Id: vboxadd-service.sh $
+## @file
+# Linux Additions Guest Additions service daemon init script.
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# X-Conflicts-With is our own invention, which we use when converting to
+# a systemd unit.
+
+# chkconfig: 345 35 65
+# description: VirtualBox Additions service
+#
+### BEGIN INIT INFO
+# Provides: vboxadd-service
+# Required-Start: vboxadd
+# Required-Stop: vboxadd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# X-Conflicts-With: systemd-timesyncd.service
+# Description: VirtualBox Additions Service
+### END INIT INFO
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+SCRIPTNAME=vboxadd-service.sh
+
+PIDFILE="/var/run/${SCRIPTNAME}"
+
+# Preamble for Gentoo
+if [ "`which $0`" = "/sbin/rc" ]; then
+ shift
+fi
+
+begin()
+{
+ test -n "${2}" && echo "${SCRIPTNAME}: ${1}."
+ logger -t "${SCRIPTNAME}" "${1}."
+}
+
+succ_msg()
+{
+ logger -t "${SCRIPTNAME}" "${1}."
+}
+
+fail_msg()
+{
+ echo "${SCRIPTNAME}: ${1}." >&2
+ logger -t "${SCRIPTNAME}" "${1}."
+}
+
+daemon() {
+ $1 $2 $3
+}
+
+killproc() {
+ killall $1
+ rm -f $PIDFILE
+}
+
+if which start-stop-daemon >/dev/null 2>&1; then
+ daemon() {
+ start-stop-daemon --start --exec $1 -- $2 $3
+ }
+
+ killproc() {
+ start-stop-daemon --stop --retry 2 --exec $@
+ }
+fi
+
+binary=/usr/sbin/VBoxService
+
+testbinary() {
+ test -x "$binary" || {
+ echo "Cannot run $binary"
+ exit 1
+ }
+}
+
+vboxaddrunning() {
+ lsmod | grep -q "vboxguest[^_-]"
+}
+
+start() {
+ if ! test -f $PIDFILE; then
+ begin "Starting VirtualBox Guest Addition service" console;
+ vboxaddrunning || {
+ echo "VirtualBox Additions module not loaded!"
+ exit 1
+ }
+ testbinary
+ daemon $binary --pidfile $PIDFILE > /dev/null
+ RETVAL=$?
+ succ_msg "VirtualBox Guest Addition service started"
+ fi
+ return $RETVAL
+}
+
+stop() {
+ if test -f $PIDFILE; then
+ begin "Stopping VirtualBox Guest Addition service" console;
+ killproc $binary
+ RETVAL=$?
+ if ! pidof VBoxService > /dev/null 2>&1; then
+ rm -f $PIDFILE
+ succ_msg "VirtualBox Guest Addition service stopped"
+ else
+ fail_msg "VirtualBox Guest Addition service failed to stop"
+ fi
+ fi
+ return $RETVAL
+}
+
+restart() {
+ stop && start
+}
+
+status() {
+ echo -n "Checking for VBoxService"
+ if [ -f $PIDFILE ]; then
+ echo " ...running"
+ else
+ echo " ...not running"
+ fi
+}
+
+case "$1" in
+start)
+ start
+ ;;
+stop)
+ stop
+ ;;
+restart)
+ restart
+ ;;
+status)
+ status
+ ;;
+setup)
+ ;;
+cleanup)
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|status}"
+ exit 1
+esac
+
+exit $RETVAL
diff --git a/src/VBox/Additions/linux/installer/vboxadd-x11.sh b/src/VBox/Additions/linux/installer/vboxadd-x11.sh
new file mode 100755
index 00000000..fcd2aa25
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/vboxadd-x11.sh
@@ -0,0 +1,625 @@
+#! /bin/sh
+# $Id: vboxadd-x11.sh $
+## @file
+# Linux Additions X11 setup init script ($Revision: 153224 $)
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+# chkconfig: 35 30 70
+# description: VirtualBox Linux Additions kernel modules
+#
+### BEGIN INIT INFO
+# Provides: vboxadd-x11
+# Required-Start:
+# Required-Stop:
+# Default-Start:
+# Default-Stop:
+# Description: VirtualBox Linux Additions X11 setup
+### END INIT INFO
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+CONFIG_DIR="/var/lib/VBoxGuestAdditions"
+CONFIG="${CONFIG_DIR}/config"
+MODPROBE=/sbin/modprobe
+
+if $MODPROBE -c 2>/dev/null | grep -q '^allow_unsupported_modules *0'; then
+ MODPROBE="$MODPROBE --allow-unsupported-modules"
+fi
+
+# Find the version of X installed
+# The last of the three is for the X.org 6.7 included in Fedora Core 2
+xver=`X -version 2>&1`
+x_version=`echo "$xver" | sed -n 's/^X Window System Version \([0-9.]\+\)/\1/p'``echo "$xver" | sed -n 's/^XFree86 Version \([0-9.]\+\)/\1/p'``echo "$xver" | sed -n 's/^X Protocol Version 11, Revision 0, Release \([0-9.]\+\)/\1/p'``echo "$xver" | sed -n 's/^X.Org X Server \([0-9.]\+\)/\1/p'`
+x_version_short=`echo "${x_version}" | sed 's/\([0-9]*\.[0-9]*\)\..*/\1/'`
+# Version of Redhat or Fedora installed. Needed for setting up selinux policy.
+redhat_release=`cat /etc/redhat-release 2> /dev/null`
+# Version of OL installed. Needed for blacklisting vboxvideo.
+oracle_release=`cat /etc/oracle-release 2> /dev/null`
+# All the different possible locations for XFree86/X.Org configuration files
+# - how many of these have ever been used?
+x11conf_files="/etc/X11/xorg.conf /etc/X11/xorg.conf-4 /etc/X11/.xorg.conf \
+ /etc/xorg.conf /usr/etc/X11/xorg.conf-4 /usr/etc/X11/xorg.conf \
+ /usr/lib/X11/xorg.conf-4 /usr/lib/X11/xorg.conf /etc/X11/XF86Config-4 \
+ /etc/X11/XF86Config /etc/XF86Config /usr/X11R6/etc/X11/XF86Config-4 \
+ /usr/X11R6/etc/X11/XF86Config /usr/X11R6/lib/X11/XF86Config-4 \
+ /usr/X11R6/lib/X11/XF86Config"
+
+# Preamble for Gentoo
+if [ "`which $0`" = "/sbin/rc" ]; then
+ shift
+fi
+
+dev=/dev/vboxguest
+userdev=/dev/vboxuser
+owner=vboxadd
+group=1
+
+fail()
+{
+ echo "${1}" >&2
+ exit 1
+}
+
+# Install an X11 desktop startup application. The application should be a shell script.
+# Its name should be purely alphanumeric and should start with a two digit number (preferably
+# 98 or thereabouts) to indicate its place in the Debian Xsession startup order.
+#
+# syntax: install_x11_startup_app app desktop service_name
+install_x11_startup_app() {
+ self="install_x11_startup_app"
+ app_src=$1
+ desktop_src=$2
+ service_name=$3
+ alt_command=$4
+ test -r "$app_src" || fail "$self: no script given"
+ test -r "$desktop_src" || fail "$self: no desktop file given"
+ test -n "$service_name" || fail "$self: no service given"
+ test -n "$alt_command" || fail "$self: no service given"
+ app_dest=`basename $app_src sh`
+ app_dest_sh=`basename $app_src sh`.sh
+ desktop_dest=`basename $desktop_src`
+ found=0
+ x11_autostart="/etc/xdg/autostart"
+ kde_autostart="/usr/share/autostart"
+ redhat_dir=/etc/X11/Xsession.d
+ mandriva_dir=/etc/X11/xinit.d
+ debian_dir=/etc/X11/xinit/xinitrc.d
+ if [ -d "$mandriva_dir" -a -w "$mandriva_dir" -a -x "$mandriva_dir" ]
+ then
+ install -m 0644 $app_src "$mandriva_dir/$app_dest"
+ found=1
+ fi
+ if [ -d "$x11_autostart" -a -w "$x11_autostart" -a -x "$x11_autostart" ]
+ then
+ install -m 0644 $desktop_src "$x11_autostart/$desktop_dest"
+ found=1
+ fi
+ if [ -d "$kde_autostart" -a -w "$kde_autostart" -a -x "$kde_autostart" ]
+ then
+ install -m 0644 $desktop_src "$kde_autostart/$desktop_dest"
+ found=1
+ fi
+ if [ -d "$redhat_dir" -a -w "$redhat_dir" -a -x "$redhat_dir" ]
+ then
+ install -m 0644 $app_src "$redhat_dir/$app_dest"
+ found=1
+ fi
+ if [ -d "$debian_dir" -a -w "$debian_dir" -a -x "$debian_dir" ]
+ then
+ install -m 0755 $app_src "$debian_dir/$app_dest_sh"
+ found=1
+ fi
+ if [ $found -eq 1 ]; then
+ return 0
+ fi
+ cat >&2 << EOF
+Could not set up the $service_name desktop service.
+To start it at log-in for a given user, add the command $alt_command
+to the file .xinitrc in their home directory.
+EOF
+ return 1
+}
+
+
+start()
+{
+ # Todo: check configuration and correct it if necessary
+ return 0
+}
+
+stop()
+{
+ return 0
+}
+
+restart()
+{
+ stop && start
+ return 0
+}
+
+setup_opengl()
+{
+ # Install the guest OpenGL drivers. For now we don't support
+ # multi-architecture installations
+ rm -f /etc/ld.so.conf.d/00vboxvideo.conf
+ rm -Rf /var/lib/VBoxGuestAdditions/lib
+ if /usr/bin/VBoxClient --check3d 2>/dev/null; then
+ mkdir -p /var/lib/VBoxGuestAdditions/lib
+ ln -sf "${INSTALL_DIR}/lib/VBoxOGL.so" /var/lib/VBoxGuestAdditions/lib/libGL.so.1
+ # SELinux for the OpenGL libraries, so that gdm can load them during the
+ # acceleration support check. This prevents an "Oh no, something has gone
+ # wrong!" error when starting EL7 guests.
+ if test -e /etc/selinux/config; then
+ if command -v semanage > /dev/null; then
+ semanage fcontext -a -t lib_t "/var/lib/VBoxGuestAdditions/lib/libGL.so.1"
+ fi
+ # This is needed on old Fedora/Redhat systems. No one remembers which.
+ chcon -h -t lib_t "/var/lib/VBoxGuestAdditions/lib/libGL.so.1" 2>/dev/null
+ fi
+ echo "/var/lib/VBoxGuestAdditions/lib" > /etc/ld.so.conf.d/00vboxvideo.conf
+ fi
+ ldconfig
+}
+
+setup()
+{
+ if test -r "${CONFIG}"; then
+ . "${CONFIG}"
+ else
+ fail "Configuration file ${CONFIG} not found"
+ fi
+ test -n "$INSTALL_DIR" -a -n "$INSTALL_VER" ||
+ fail "Configuration file ${CONFIG} not complete"
+ lib_dir="${INSTALL_DIR}/other"
+ test -x "${lib_dir}" ||
+ fail "Invalid Guest Additions configuration found."
+ # By default we want to configure X
+ dox11config="true"
+ # By default, we want to run our xorg.conf setup script
+ setupxorgconf="true"
+ # All but the oldest supported X servers can automatically set up the
+ # keyboard driver.
+ autokeyboard="--autoKeyboard"
+ # On more recent servers our kernel mouse driver will be used
+ # automatically
+ automouse="--autoMouse"
+ # We need to tell our xorg.conf hacking script whether /dev/psaux exists
+ nopsaux="--nopsaux"
+ case "`uname -r`" in 2.4.*)
+ test -c /dev/psaux && nopsaux="";;
+ esac
+ # Should we use the VMSVGA driver instead of VBoxVideo?
+ if grep 80eebeef /proc/bus/pci/devices > /dev/null; then
+ vmsvga=""
+ elif grep 15ad0405 /proc/bus/pci/devices > /dev/null; then
+ vmsvga="--vmsvga"
+ else
+ dox11config=""
+ fi
+ # The video driver to install for X.Org 6.9+
+ vboxvideo_src=
+ # The mouse driver to install for X.Org 6.9+
+ vboxmouse_src=
+ # The driver extension
+ driver_ext=".so"
+ # The configuration file we generate if no original was found but we need
+ # one.
+ main_cfg="/etc/X11/xorg.conf"
+
+ modules_dir=`X -showDefaultModulePath 2>&1` || modules_dir=
+ if [ -z "$modules_dir" ]; then
+ for dir in /usr/lib64/xorg/modules /usr/lib/xorg/modules /usr/X11R6/lib64/modules /usr/X11R6/lib/modules /usr/X11R6/lib/X11/modules; do
+ if [ -d $dir ]; then
+ modules_dir=$dir
+ break
+ fi
+ done
+ fi
+
+ case "${x_version}" in
+ 4.* | 6.* | 7.* | 1.?.* | 1.1[0-6].* )
+ blacklist_vboxvideo="yes"
+ ;;
+ esac
+ case "$oracle_release" in
+ Oracle*release\ 6.* )
+ # relevant for OL6/UEK4 but cannot hurt for other kernels
+ blacklist_vboxvideo="yes"
+ esac
+ if test -n "${blacklist_vboxvideo}"; then
+ test -d /etc/modprobe.d &&
+ echo "blacklist vboxvideo" > /etc/modprobe.d/blacklist-vboxvideo.conf
+ else
+ test -f /etc/modprobe.d/blacklist-vboxvideo.conf &&
+ rm -f /etc/modprobe.d/blacklist-vboxvideo.conf
+ # We do not want to load the driver if X.Org Server is already
+ # running, as without a driver the server will touch the hardware
+ # directly, causing problems.
+ ps -Af | grep -q '[X]org' || ${MODPROBE} -q vboxvideo
+ fi
+
+ test -z "$x_version" -o -z "$modules_dir" &&
+ {
+ echo "Could not find the X.Org or XFree86 Window System, skipping." >&2
+ exit 0
+ }
+
+ # openSUSE 10.3 shipped X.Org 7.2 with X.Org Server 1.3, but didn't
+ # advertise the fact.
+ if grep -q '10\.3' /etc/SuSE-release 2>/dev/null; then
+ case $x_version in 7.2.*)
+ x_version=1.3.0;;
+ esac
+ fi
+ case $x_version in
+ 1.*.99.* )
+ echo "Warning: unsupported pre-release version of X.Org Server installed. Not installing the X.Org drivers." >&2
+ dox11config=""
+ ;;
+ 1.11.* )
+ xserver_version="X.Org Server 1.11"
+ vboxvideo_src=vboxvideo_drv_111.so
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.10.* )
+ xserver_version="X.Org Server 1.10"
+ vboxvideo_src=vboxvideo_drv_110.so
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.9.* )
+ xserver_version="X.Org Server 1.9"
+ vboxvideo_src=vboxvideo_drv_19.so
+ # Fedora 14 to 16 patched out vboxvideo detection
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.8.* )
+ xserver_version="X.Org Server 1.8"
+ vboxvideo_src=vboxvideo_drv_18.so
+ # Fedora 13 shipped without vboxvideo detection
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.7.* )
+ xserver_version="X.Org Server 1.7"
+ vboxvideo_src=vboxvideo_drv_17.so
+ setupxorgconf=""
+ ;;
+ 1.6.* )
+ xserver_version="X.Org Server 1.6"
+ vboxvideo_src=vboxvideo_drv_16.so
+ vboxmouse_src=vboxmouse_drv_16.so
+ # SUSE SLE* with X.Org 1.6 does not do input autodetection;
+ # openSUSE does.
+ if grep -q -E '^SLE[^ ]' /etc/SuSE-brand 2>/dev/null; then
+ automouse=""
+ else
+ test "$system" = "suse" && setupxorgconf=""
+ fi
+ ;;
+ 1.5.* )
+ xserver_version="X.Org Server 1.5"
+ vboxvideo_src=vboxvideo_drv_15.so
+ vboxmouse_src=vboxmouse_drv_15.so
+ # Historical note: SUSE with X.Org Server 1.5 disabled automatic
+ # mouse configuration and was handled specially. However since our
+ # kernel driver seems to have problems with X.Org Server 1.5 anyway
+ # we just create an X.Org configuration file and use the user space
+ # one generally, no longer just for SUSE.
+ automouse=""
+ ;;
+ 1.4.* )
+ xserver_version="X.Org Server 1.4"
+ vboxvideo_src=vboxvideo_drv_14.so
+ vboxmouse_src=vboxmouse_drv_14.so
+ automouse=""
+ ;;
+ 1.3.* )
+ # This was the first release which gave the server version number
+ # rather than the X11 release version when you did 'X -version'.
+ xserver_version="X.Org Server 1.3"
+ vboxvideo_src=vboxvideo_drv_13.so
+ vboxmouse_src=vboxmouse_drv_13.so
+ automouse=""
+ ;;
+ 7.1.* | 7.2.* )
+ xserver_version="X.Org 7.1"
+ vboxvideo_src=vboxvideo_drv_71.so
+ vboxmouse_src=vboxmouse_drv_71.so
+ automouse=""
+ ;;
+ 6.9.* | 7.0.* )
+ xserver_version="X.Org 6.9/7.0"
+ vboxvideo_src=vboxvideo_drv_70.so
+ vboxmouse_src=vboxmouse_drv_70.so
+ automouse=""
+ ;;
+ 6.7* | 6.8.* | 4.2.* | 4.3.* )
+ # As the module binaries are the same we use one text for these
+ # four server versions.
+ xserver_version="XFree86 4.2/4.3 and X.Org 6.7/6.8"
+ driver_ext=.o
+ vboxvideo_src=vboxvideo_drv.o
+ vboxmouse_src=vboxmouse_drv.o
+ automouse=""
+ autokeyboard=""
+ case $x_version in
+ 6.8.* )
+ autokeyboard="--autoKeyboard"
+ ;;
+ 4.2.* | 4.3.* )
+ main_cfg="/etc/X11/XF86Config"
+ ;;
+ esac
+ ;;
+ 1.12.* | 1.13.* | 1.14.* | 1.15.* | 1.16.* | 1.17.* | 1.18.* )
+ xserver_version="X.Org Server ${x_version_short}"
+ vboxvideo_src=vboxvideo_drv_`echo ${x_version_short} | sed 's/\.//'`.so
+ setupxorgconf=""
+ test -f "${lib_dir}/${vboxvideo_src}" ||
+ {
+ echo "Warning: unknown version of the X Window System installed. Not installing X Window System drivers." >&2
+ dox11config=""
+ vboxvideo_src=""
+ }
+ ;;
+ * )
+ # For anything else, assume kernel drivers.
+ dox11config=""
+ ;;
+ esac
+ test -n "${dox11config}" &&
+ echo "Installing $xserver_version modules" >&2
+ case "$vboxvideo_src" in
+ ?*)
+ ln -s "${lib_dir}/$vboxvideo_src" "$modules_dir/drivers/vboxvideo_drv$driver_ext.new" &&
+ mv "$modules_dir/drivers/vboxvideo_drv$driver_ext.new" "$modules_dir/drivers/vboxvideo_drv$driver_ext";;
+ *)
+ rm "$modules_dir/drivers/vboxvideo_drv$driver_ext" 2>/dev/null
+ esac
+ case "$vboxmouse_src" in
+ ?*)
+ ln -s "${lib_dir}/$vboxmouse_src" "$modules_dir/input/vboxmouse_drv$driver_ext.new" &&
+ mv "$modules_dir/input/vboxmouse_drv$driver_ext.new" "$modules_dir/input/vboxmouse_drv$driver_ext";;
+ *)
+ rm "$modules_dir/input/vboxmouse_drv$driver_ext" 2>/dev/null
+ esac
+
+ if test -n "$dox11config"; then
+ # Certain Ubuntu/Debian versions use a special PCI-id file to identify
+ # video drivers. Some versions have the directory and don't use it.
+ # Those versions can autoload vboxvideo though, so we don't need to
+ # hack the configuration file for them.
+ test "$system" = "debian" -a -d /usr/share/xserver-xorg/pci &&
+ {
+ rm -f "/usr/share/xserver-xorg/pci/vboxvideo.ids"
+ ln -s "${lib_dir}/vboxvideo.ids" /usr/share/xserver-xorg/pci 2>/dev/null
+ test -n "$automouse" && setupxorgconf=""
+ }
+
+ # Do the XF86Config/xorg.conf hack for those versions that require it
+ configured=""
+ generated=""
+ if test -n "$setupxorgconf"; then
+ for i in $x11conf_files; do
+ if test -r "$i"; then
+ if grep -q "VirtualBox generated" "$i"; then
+ generated="$generated `printf "$i\n"`"
+ else
+ "${lib_dir}/x11config.sh" $autokeyboard $automouse $nopsaux $vmsvga "$i"
+ fi
+ configured="true"
+ fi
+ # Timestamp, so that we can see if the config file is changed
+ # by someone else later
+ test -r "$i.vbox" && touch "$i.vbox"
+ done
+ # X.Org Server 1.5 and 1.6 can detect hardware they know, but they
+ # need a configuration file for VBoxVideo.
+ nobak_cfg="`expr "${main_cfg}" : '\([^.]*\)'`.vbox.nobak"
+ if test -z "$configured"; then
+ touch "$main_cfg"
+ "${lib_dir}/x11config.sh" $autokeyboard $automouse $nopsaux $vmsvga --noBak "$main_cfg"
+ touch "${nobak_cfg}"
+ fi
+ fi
+ test -n "$generated" &&
+ cat >&2 << EOF
+The following X.Org/XFree86 configuration files were originally generated by
+the VirtualBox Guest Additions and were not modified:
+
+$generated
+
+EOF
+ tty >/dev/null && cat << EOF
+You may need to restart the Window System (or just restart the guest system)
+to enable the Guest Additions.
+
+EOF
+ fi
+
+ case "$redhat_release" in
+ # Install selinux policy for Fedora 7 and 8 to allow the X server to
+ # open device files
+ Fedora\ release\ 7* | Fedora\ release\ 8* )
+ semodule -i "${lib_dir}/vbox_x11.pp" > /dev/null 2>&1
+ ;;
+ # Similar for the accelerated graphics check on Fedora 15
+ Fedora\ release\ 15* )
+ semodule -i "${lib_dir}/vbox_accel.pp" > /dev/null 2>&1
+ ;;
+ esac
+
+ # Install selinux policy for Fedora 8 to allow the X server to
+ # open our drivers
+ case "$redhat_release" in
+ Fedora\ release\ 8* )
+ chcon -u system_u -t lib_t "${lib_dir}"/*.so 2>/dev/null
+ ;;
+ esac
+
+ # Our logging code generates some glue code on 32-bit systems. At least F10
+ # needs a rule to allow this. Send all output to /dev/null in case this is
+ # completely irrelevant on the target system.
+ # chcon is needed on old Fedora/Redhat systems. No one remembers which.
+ chcon -t unconfined_execmem_exec_t '/usr/bin/VBoxClient' > /dev/null 2>&1
+ semanage fcontext -a -t unconfined_execmem_exec_t '/usr/bin/VBoxClient' > /dev/null 2>&1
+
+ # And set up VBoxClient to start when the X session does
+ install_x11_startup_app "${lib_dir}/98vboxadd-xclient" "${lib_dir}/vboxclient.desktop" VBoxClient VBoxClient-all ||
+ fail "Failed to set up VBoxClient to start automatically."
+ ln -s "${lib_dir}/98vboxadd-xclient" /usr/bin/VBoxClient-all 2>/dev/null
+ case "${x_version}" in 4.* | 6.* | 7.* | 1.?.* | 1.1* )
+ setup_opengl
+ esac
+ # Try enabling VMSVGA drm device resizing.
+ #VBoxClient --vmsvga
+}
+
+cleanup()
+{
+ # Restore xorg.conf files as far as possible
+ # List of generated files which have been changed since we generated them
+ newer=""
+ # Are we dealing with a legacy information which didn't support
+ # uninstallation?
+ legacy=""
+ # Do any of the restored configuration files still reference our drivers?
+ failed=""
+ # Have we encountered a "nobak" configuration file which means that there
+ # is no original file to restore?
+ nobak=""
+ test -r "$CONFIG_DIR/$CONFIG" || legacy="true"
+ for main_cfg in "/etc/X11/xorg.conf" "/etc/X11/XF86Config"; do
+ nobak_cfg="`expr "${main_cfg}" : '\([^.]*\)'`.vbox.nobak"
+ if test -r "${nobak_cfg}"; then
+ test -r "${main_cfg}" &&
+ if test -n "${legacy}" -o ! "${nobak_cfg}" -ot "${main_cfg}"; then
+ rm -f "${nobak_cfg}" "${main_cfg}"
+ else
+ newer="${newer}`printf " ${main_cfg} (no original)\n"`"
+ fi
+ nobak="true"
+ fi
+ done
+ if test -z "${nobak}"; then
+ for i in $x11conf_files; do
+ if test -r "$i.vbox"; then
+ if test ! "$i" -nt "$i.vbox" -o -n "$legacy"; then
+ mv -f "$i.vbox" "$i"
+ grep -q -E 'vboxvideo|vboxmouse' "$i" &&
+ failed="$failed`printf " $i\n"`"
+ else
+ newer="$newer`printf " $i ($i.vbox)\n"`"
+ fi
+ fi
+ done
+ fi
+ test -n "$newer" && cat >&2 << EOF
+
+The following X.Org/XFree86 configuration files were not restored, as they may
+have been changed since they were generated by the VirtualBox Guest Additions.
+You may wish to restore these manually. The file name in brackets is the
+original version.
+
+$newer
+
+EOF
+ test -n "$failed" && cat >&2 << EOF
+
+The following X.Org/XFree86 configuration files were restored, but still
+contain references to the Guest Additions drivers. You may wish to check and
+possibly correct the restored configuration files to be sure that the server
+will continue to work after it is restarted.
+
+$failed
+
+EOF
+
+ # Remove X.Org drivers
+ modules_dir=`X -showDefaultModulePath 2>&1` || modules_dir=
+ if [ -z "$modules_dir" ]; then
+ for dir in /usr/lib64/xorg/modules /usr/lib/xorg/modules /usr/X11R6/lib64/modules /usr/X11R6/lib/modules /usr/X11R6/lib/X11/modules; do
+ if [ -d $dir ]; then
+ modules_dir=$dir
+ break
+ fi
+ done
+ fi
+ rm -f "$modules_dir/drivers/vboxvideo_drv"* 2>/dev/null
+ rm -f "$modules_dir/input/vboxmouse_drv"* 2>/dev/null
+
+ # Remove the link to vboxvideo_dri.so
+ for dir in /usr/lib/dri /usr/lib32/dri /usr/lib64/dri \
+ /usr/lib/xorg/modules/dri /usr/lib32/xorg/modules/dri \
+ /usr/lib64/xorg/modules/dri /usr/lib/i386-linux-gnu/dri \
+ /usr/lib/x86_64-linux-gnu/dri; do
+ if [ -d $dir ]; then
+ rm -f "$dir/vboxvideo_dri.so" 2>/dev/null
+ fi
+ done
+
+ # Remove VBoxClient autostart files
+ rm /etc/X11/Xsession.d/98vboxadd-xclient 2>/dev/null
+ rm /etc/X11/xinit.d/98vboxadd-xclient 2>/dev/null
+ rm /etc/X11/xinit/xinitrc.d/98vboxadd-xclient.sh 2>/dev/null
+ rm /etc/xdg/autostart/vboxclient.desktop 2>/dev/null
+ rm /usr/share/autostart/vboxclient.desktop 2>/dev/null
+ rm /usr/bin/VBoxClient-all 2>/dev/null
+
+ # Remove other files
+ rm /usr/share/xserver-xorg/pci/vboxvideo.ids 2>/dev/null
+ return 0
+}
+
+dmnstatus()
+{
+ /bin/true
+}
+
+case "$1" in
+start)
+ start
+ ;;
+stop)
+ stop
+ ;;
+restart)
+ restart
+ ;;
+setup)
+ setup
+ ;;
+cleanup)
+ cleanup
+ ;;
+status)
+ dmnstatus
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|status}"
+ exit 1
+esac
+
+exit
diff --git a/src/VBox/Additions/linux/installer/vboxadd.sh b/src/VBox/Additions/linux/installer/vboxadd.sh
new file mode 100755
index 00000000..4275228f
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/vboxadd.sh
@@ -0,0 +1,944 @@
+#! /bin/sh
+# $Id: vboxadd.sh $
+## @file
+# Linux Additions kernel module init script ($Revision: 154702 $)
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# X-Start-Before is a Debian Addition which we use when converting to
+# a systemd unit. X-Service-Type is our own invention, also for systemd.
+
+# chkconfig: 345 10 90
+# description: VirtualBox Linux Additions kernel modules
+#
+### BEGIN INIT INFO
+# Provides: vboxadd
+# Required-Start:
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# X-Start-Before: display-manager
+# X-Service-Type: oneshot
+# Description: VirtualBox Linux Additions kernel modules
+### END INIT INFO
+
+## @todo This file duplicates a lot of script with vboxdrv.sh. When making
+# changes please try to reduce differences between the two wherever possible.
+
+# Testing:
+# * Should fail if the configuration file is missing or missing INSTALL_DIR or
+# INSTALL_VER entries.
+# * vboxadd, vboxsf and vboxdrmipc user groups should be created if they do not exist - test
+# by removing them before installing.
+# * Shared folders can be mounted and auto-mounts accessible to vboxsf group,
+# including on recent Fedoras with SELinux.
+# * Setting INSTALL_NO_MODULE_BUILDS inhibits modules and module automatic
+# rebuild script creation; otherwise modules, user, group, rebuild script,
+# udev rule and shared folder mount helper should be created/set up.
+# * Setting INSTALL_NO_MODULE_BUILDS inhibits module load and unload on start
+# and stop.
+# * Uninstalling the Additions and re-installing them does not trigger warnings.
+
+export LC_ALL=C
+PATH=$PATH:/bin:/sbin:/usr/sbin
+PACKAGE=VBoxGuestAdditions
+MODPROBE=/sbin/modprobe
+OLDMODULES="vboxguest vboxadd vboxsf vboxvfs vboxvideo"
+SERVICE="VirtualBox Guest Additions"
+## systemd logs information about service status, otherwise do that ourselves.
+QUIET=
+test -z "${TARGET_VER}" && TARGET_VER=`uname -r`
+
+# Marker to ignore a particular kernel version which was already installed.
+#
+# This is needed in order to prevent modules rebuild on system start and do
+# that on system shutdown instead. Modern Linux distributions might attempt
+# to run Additions service in async mode. As a result, on system boot, modules
+# not-by-us will be loaded while we will try to build our modules. This marker is:
+#
+# created -- in scope of setup_modules() when actual modules are built.
+# checked -- in scope of stop() when system goes shutdown and if marker
+# for certain kernel version does not exist, modules rebuild
+# will be triggered for this kernel version.
+# removed -- in scope of cleanup_modules() when modules are removed from
+# system for all installed kernels.
+SKIPFILE_BASE=/var/lib/VBoxGuestAdditions/skip
+
+export VBOX_KBUILD_TYPE
+export USERNAME
+
+setup_log()
+{
+ test -z "${LOG}" || return 0
+ # Rotate log files
+ LOG="/var/log/vboxadd-setup.log"
+ mv "${LOG}.3" "${LOG}.4" 2>/dev/null
+ mv "${LOG}.2" "${LOG}.3" 2>/dev/null
+ mv "${LOG}.1" "${LOG}.2" 2>/dev/null
+ mv "${LOG}" "${LOG}.1" 2>/dev/null
+}
+
+if $MODPROBE -c 2>/dev/null | grep -q '^allow_unsupported_modules *0'; then
+ MODPROBE="$MODPROBE --allow-unsupported-modules"
+fi
+
+# Preamble for Gentoo
+if [ "`which $0`" = "/sbin/rc" ]; then
+ shift
+fi
+
+begin()
+{
+ test -n "${QUIET}" || echo "${SERVICE}: ${1}"
+}
+
+info()
+{
+ if test -z "${QUIET}"; then
+ echo "${SERVICE}: $1" | fold -s
+ else
+ echo "$1" | fold -s
+ fi
+}
+
+fail()
+{
+ log "${1}"
+ echo "$1" >&2
+ echo "The log file $LOG may contain further information." >&2
+ exit 1
+}
+
+log()
+{
+ setup_log
+ echo "${1}" >> "${LOG}"
+}
+
+module_build_log()
+{
+ log "Error building the module. Build output follows."
+ echo ""
+ echo "${1}" >> "${LOG}"
+}
+
+dev=/dev/vboxguest
+userdev=/dev/vboxuser
+config=/var/lib/VBoxGuestAdditions/config
+owner=vboxadd
+group=1
+
+if test -r $config; then
+ . $config
+else
+ fail "Configuration file $config not found"
+fi
+test -n "$INSTALL_DIR" -a -n "$INSTALL_VER" ||
+ fail "Configuration file $config not complete"
+MODULE_SRC="$INSTALL_DIR/src/vboxguest-$INSTALL_VER"
+BUILDINTMP="$MODULE_SRC/build_in_tmp"
+
+# Attempt to detect VirtualBox Guest Additions version and revision information.
+VBOXCLIENT="${INSTALL_DIR}/bin/VBoxClient"
+VBOX_VERSION="`"$VBOXCLIENT" --version | cut -d r -f1`"
+[ -n "$VBOX_VERSION" ] || VBOX_VERSION='unknown'
+VBOX_REVISION="r`"$VBOXCLIENT" --version | cut -d r -f2`"
+[ "$VBOX_REVISION" != "r" ] || VBOX_REVISION='unknown'
+
+running_vboxguest()
+{
+ lsmod | grep -q "vboxguest[^_-]"
+}
+
+running_vboxadd()
+{
+ lsmod | grep -q "vboxadd[^_-]"
+}
+
+running_vboxsf()
+{
+ lsmod | grep -q "vboxsf[^_-]"
+}
+
+running_vboxvideo()
+{
+ lsmod | grep -q "vboxvideo[^_-]"
+}
+
+do_vboxguest_non_udev()
+{
+ if [ ! -c $dev ]; then
+ maj=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/devices`
+ if [ ! -z "$maj" ]; then
+ min=0
+ else
+ min=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/misc`
+ if [ ! -z "$min" ]; then
+ maj=10
+ fi
+ fi
+ test -n "$maj" || {
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot locate the VirtualBox device"
+ }
+
+ mknod -m 0664 $dev c $maj $min || {
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot create device $dev with major $maj and minor $min"
+ }
+ fi
+ chown $owner:$group $dev 2>/dev/null || {
+ rm -f $dev 2>/dev/null
+ rm -f $userdev 2>/dev/null
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot change owner $owner:$group for device $dev"
+ }
+
+ if [ ! -c $userdev ]; then
+ maj=10
+ min=`sed -n 's;\([0-9]\+\) vboxuser;\1;p' /proc/misc`
+ if [ ! -z "$min" ]; then
+ mknod -m 0666 $userdev c $maj $min || {
+ rm -f $dev 2>/dev/null
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot create device $userdev with major $maj and minor $min"
+ }
+ chown $owner:$group $userdev 2>/dev/null || {
+ rm -f $dev 2>/dev/null
+ rm -f $userdev 2>/dev/null
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot change owner $owner:$group for device $userdev"
+ }
+ fi
+ fi
+}
+
+restart()
+{
+ stop && start
+ return 0
+}
+
+## Update the initramfs. Debian and Ubuntu put the graphics driver in, and
+# need the touch(1) command below. Everyone else that I checked just need
+# the right module alias file from depmod(1) and only use the initramfs to
+# load the root filesystem, not the boot splash. update-initramfs works
+# for the first two and dracut for every one else I checked. We are only
+# interested in distributions recent enough to use the KMS vboxvideo driver.
+update_initramfs()
+{
+ ## kernel version to update for.
+ version="${1}"
+ depmod "${version}"
+ rm -f "/lib/modules/${version}/initrd/vboxvideo"
+ test ! -d "/lib/modules/${version}/initrd" ||
+ test ! -f "/lib/modules/${version}/misc/vboxvideo.ko" ||
+ touch "/lib/modules/${version}/initrd/vboxvideo"
+
+ # Systems without systemd-inhibit probably don't need their initramfs
+ # rebuild here anyway.
+ type systemd-inhibit >/dev/null 2>&1 || return
+ if type dracut >/dev/null 2>&1; then
+ systemd-inhibit --why="Installing VirtualBox Guest Additions" \
+ dracut -f --kver "${version}"
+ elif type update-initramfs >/dev/null 2>&1; then
+ systemd-inhibit --why="Installing VirtualBox Guest Additions" \
+ update-initramfs -u -k "${version}"
+ fi
+}
+
+# Remove any existing VirtualBox guest kernel modules from the disk, but not
+# from the kernel as they may still be in use
+cleanup_modules()
+{
+ # Needed for Ubuntu and Debian, see update_initramfs
+ rm -f /lib/modules/*/initrd/vboxvideo
+ for i in /lib/modules/*/misc; do
+ KERN_VER="${i%/misc}"
+ KERN_VER="${KERN_VER#/lib/modules/}"
+ unset do_update
+ for j in ${OLDMODULES}; do
+ for mod_ext in ko ko.gz ko.xz ko.zst; do
+ test -f "${i}/${j}.${mod_ext}" && do_update=1 && rm -f "${i}/${j}.${mod_ext}"
+ done
+ done
+ test -z "$do_update" || update_initramfs "$KERN_VER"
+ # Remove empty /lib/modules folders which may have been kept around
+ rmdir -p "${i}" 2>/dev/null || true
+ unset keep
+ for j in /lib/modules/"${KERN_VER}"/*; do
+ name="${j##*/}"
+ test -d "${name}" || test "${name%%.*}" != modules && keep=1
+ done
+ if test -z "${keep}"; then
+ rm -rf /lib/modules/"${KERN_VER}"
+ rm -f /boot/initrd.img-"${KERN_VER}"
+ fi
+ done
+ for i in ${OLDMODULES}; do
+ # We no longer support DKMS, remove any leftovers.
+ rm -rf "/var/lib/dkms/${i}"*
+ done
+ rm -f /etc/depmod.d/vboxvideo-upstream.conf
+ rm -f "$SKIPFILE_BASE"-*
+}
+
+# Secure boot state.
+case "`mokutil --sb-state 2>/dev/null`" in
+ *"disabled in shim"*) unset HAVE_SEC_BOOT;;
+ *"SecureBoot enabled"*) HAVE_SEC_BOOT=true;;
+ *) unset HAVE_SEC_BOOT;;
+esac
+# So far we can only sign modules on Ubuntu and on Debian 10 and later.
+DEB_PUB_KEY=/var/lib/shim-signed/mok/MOK.der
+DEB_PRIV_KEY=/var/lib/shim-signed/mok/MOK.priv
+# Check if key already enrolled.
+unset HAVE_DEB_KEY
+case "`mokutil --test-key "$DEB_PUB_KEY" 2>/dev/null`" in
+ *"is already"*) DEB_KEY_ENROLLED=true;;
+ *) unset DEB_KEY_ENROLLED;;
+esac
+
+# Check if update-secureboot-policy tool supports required commandline options.
+update_secureboot_policy_supports()
+{
+ opt_name="$1"
+ [ -n "$opt_name" ] || return
+
+ [ -z "$(update-secureboot-policy --help 2>&1 | grep "$opt_name")" ] && return
+ echo "1"
+}
+
+HAVE_UPDATE_SECUREBOOT_POLICY_TOOL=
+if type update-secureboot-policy >/dev/null 2>&1; then
+ [ "$(update_secureboot_policy_supports new-key)" = "1" -a "$(update_secureboot_policy_supports enroll-key)" = "1" ] && \
+ HAVE_UPDATE_SECUREBOOT_POLICY_TOOL=true
+fi
+
+# Reads kernel configuration option.
+kernel_get_config_opt()
+{
+ opt_name="$1"
+ [ -n "$opt_name" ] || return
+
+ # Check if there is a kernel tool which can extract config option.
+ if test -x /lib/modules/"$KERN_VER"/build/scripts/config; then
+ /lib/modules/"$KERN_VER"/build/scripts/config \
+ --file /lib/modules/"$KERN_VER"/build/.config \
+ --state "$opt_name" 2>/dev/null
+ elif test -f /lib/modules/"$KERN_VER"/build/.config; then
+ # Extract config option manually.
+ grep "$opt_name" /lib/modules/"$KERN_VER"/build/.config | sed -e "s/^$opt_name=//" -e "s/\"//g"
+ fi
+}
+
+# Reads CONFIG_MODULE_SIG_HASH from kernel config.
+kernel_module_sig_hash()
+{
+ kernel_get_config_opt "CONFIG_MODULE_SIG_HASH"
+}
+
+# Returns "1" if kernel module signature hash algorithm
+# is supported by us. Or empty string otherwise.
+module_sig_hash_supported()
+{
+ sig_hashalgo="$1"
+ [ -n "$sig_hashalgo" ] || return
+
+ # Go through supported list.
+ [ "$sig_hashalgo" = "sha1" \
+ -o "$sig_hashalgo" = "sha224" \
+ -o "$sig_hashalgo" = "sha256" \
+ -o "$sig_hashalgo" = "sha384" \
+ -o "$sig_hashalgo" = "sha512" ] || return
+
+ echo "1"
+}
+
+sign_modules()
+{
+ KERN_VER="$1"
+ test -n "$KERN_VER" || return 1
+
+ # Make list of mudules to sign.
+ MODULE_LIST="vboxguest vboxsf"
+ # vboxvideo might not present on for older kernels.
+ [ -f "/lib/modules/"$KERN_VER"/misc/vboxvideo.ko" ] && MODULE_LIST="$MODULE_LIST vboxvideo"
+
+ # Secure boot on Ubuntu, Debian and Oracle Linux.
+ if test -n "$HAVE_SEC_BOOT"; then
+ begin "Signing VirtualBox Guest Additions kernel modules"
+
+ # Generate new signing key if needed.
+ [ -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL" ] && SHIM_NOTRIGGER=y update-secureboot-policy --new-key
+
+ # Check if signing keys are in place.
+ if test ! -f "$DEB_PUB_KEY" || ! test -f "$DEB_PRIV_KEY"; then
+ # update-secureboot-policy tool present in the system, but keys were not generated.
+ [ -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL" ] && info "
+
+update-secureboot-policy tool does not generate signing keys
+in your distribution, see below on how to generate them manually."
+ # update-secureboot-policy not present in the system, recommend generate keys manually.
+ fail "
+
+System is running in Secure Boot mode, however your distribution
+does not provide tools for automatic generation of keys needed for
+modules signing. Please consider to generate and enroll them manually:
+
+ sudo mkdir -p /var/lib/shim-signed/mok
+ sudo openssl req -nodes -new -x509 -newkey rsa:2048 -outform DER -addext \"extendedKeyUsage=codeSigning\" -keyout $DEB_PRIV_KEY -out $DEB_PUB_KEY
+ sudo mokutil --import $DEB_PUB_KEY
+ sudo reboot
+
+Restart \"rcvboxadd setup\" after system is rebooted.
+"
+ fi
+
+ # Get kernel signature hash algorithm from kernel config and validate it.
+ sig_hashalgo=$(kernel_module_sig_hash)
+ [ "$(module_sig_hash_supported $sig_hashalgo)" = "1" ] \
+ || fail "Unsupported kernel signature hash algorithm $sig_hashalgo"
+
+ # Sign modules.
+ for i in $MODULE_LIST; do
+
+ # Try to find a tool for modules signing.
+ SIGN_TOOL=$(which kmodsign 2>/dev/null)
+ # Attempt to use in-kernel signing tool if kmodsign not found.
+ if test -z "$SIGN_TOOL"; then
+ if test -x "/lib/modules/$KERN_VER/build/scripts/sign-file"; then
+ SIGN_TOOL="/lib/modules/$KERN_VER/build/scripts/sign-file"
+ fi
+ fi
+
+ # Check if signing tool is available.
+ [ -n "$SIGN_TOOL" ] || fail "Unable to find signing tool"
+
+ "$SIGN_TOOL" "$sig_hashalgo" "$DEB_PRIV_KEY" "$DEB_PUB_KEY" \
+ /lib/modules/"$KERN_VER"/misc/"$i".ko || fail "Unable to sign $i.ko"
+ done
+ # Enroll signing key if needed.
+ if test -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL"; then
+ # update-secureboot-policy "expects" DKMS modules.
+ # Work around this and talk to the authors as soon
+ # as possible to fix it.
+ mkdir -p /var/lib/dkms/vbox-temp
+ update-secureboot-policy --enroll-key 2>/dev/null ||
+ fail "Failed to enroll secure boot key."
+ rmdir -p /var/lib/dkms/vbox-temp 2>/dev/null
+
+ # Indicate that key has been enrolled and reboot is needed.
+ HAVE_DEB_KEY=true
+ fi
+ fi
+}
+
+# Build and install the VirtualBox guest kernel modules
+setup_modules()
+{
+ KERN_VER="$1"
+ test -n "$KERN_VER" || return 1
+ # Match (at least): vboxguest.o; vboxguest.ko; vboxguest.ko.xz
+ set /lib/modules/"$KERN_VER"/misc/vboxguest.*o*
+ #test ! -f "$1" || return 0
+ test -d /lib/modules/"$KERN_VER"/build || return 0
+ export KERN_VER
+ info "Building the modules for kernel $KERN_VER."
+
+ # Prepend PATH for building UEK7 on OL8 distribution.
+ case "$KERN_VER" in
+ 5.15.0-*.el8uek*) PATH="/opt/rh/gcc-toolset-11/root/usr/bin:$PATH";;
+ esac
+
+ # Detect if kernel was built with clang.
+ unset LLVM
+ vbox_cc_is_clang=$(kernel_get_config_opt "CONFIG_CC_IS_CLANG")
+ if test "${vbox_cc_is_clang}" = "y"; then
+ info "Using clang compiler."
+ export LLVM=1
+ fi
+
+ log "Building the main Guest Additions $INSTALL_VER module for kernel $KERN_VER."
+ if ! myerr=`$BUILDINTMP \
+ --save-module-symvers /tmp/vboxguest-Module.symvers \
+ --module-source $MODULE_SRC/vboxguest \
+ --no-print-directory install 2>&1`; then
+ # If check_module_dependencies.sh fails it prints a message itself.
+ module_build_log "$myerr"
+ "${INSTALL_DIR}"/other/check_module_dependencies.sh 2>&1 &&
+ info "Look at $LOG to find out what went wrong"
+ return 0
+ fi
+ log "Building the shared folder support module."
+ if ! myerr=`$BUILDINTMP \
+ --use-module-symvers /tmp/vboxguest-Module.symvers \
+ --module-source $MODULE_SRC/vboxsf \
+ --no-print-directory install 2>&1`; then
+ module_build_log "$myerr"
+ info "Look at $LOG to find out what went wrong"
+ return 0
+ fi
+ log "Building the graphics driver module."
+ if ! myerr=`$BUILDINTMP \
+ --use-module-symvers /tmp/vboxguest-Module.symvers \
+ --module-source $MODULE_SRC/vboxvideo \
+ --no-print-directory install 2>&1`; then
+ module_build_log "$myerr"
+ info "Look at $LOG to find out what went wrong"
+ fi
+ [ -d /etc/depmod.d ] || mkdir /etc/depmod.d
+ echo "override vboxguest * misc" > /etc/depmod.d/vboxvideo-upstream.conf
+ echo "override vboxsf * misc" >> /etc/depmod.d/vboxvideo-upstream.conf
+ echo "override vboxvideo * misc" >> /etc/depmod.d/vboxvideo-upstream.conf
+
+ sign_modules "${KERN_VER}"
+
+ update_initramfs "${KERN_VER}"
+
+ # We have just built modules for KERN_VER kernel. Create a marker to indicate
+ # that modules for this kernel version should not be rebuilt on system shutdown.
+ touch "$SKIPFILE_BASE"-"$KERN_VER"
+
+ return 0
+}
+
+create_vbox_user()
+{
+ # This is the LSB version of useradd and should work on recent
+ # distributions
+ useradd -d /var/run/vboxadd -g 1 -r -s /bin/false vboxadd >/dev/null 2>&1 || true
+ # And for the others, we choose a UID ourselves
+ useradd -d /var/run/vboxadd -g 1 -u 501 -o -s /bin/false vboxadd >/dev/null 2>&1 || true
+
+}
+
+create_udev_rule()
+{
+ # Create udev description file
+ if [ -d /etc/udev/rules.d ]; then
+ udev_call=""
+ udev_app=`which udevadm 2> /dev/null`
+ if [ $? -eq 0 ]; then
+ udev_call="${udev_app} version 2> /dev/null"
+ else
+ udev_app=`which udevinfo 2> /dev/null`
+ if [ $? -eq 0 ]; then
+ udev_call="${udev_app} -V 2> /dev/null"
+ fi
+ fi
+ udev_fix="="
+ if [ "${udev_call}" != "" ]; then
+ udev_out=`${udev_call}`
+ udev_ver=`expr "$udev_out" : '[^0-9]*\([0-9]*\)'`
+ if [ "$udev_ver" = "" -o "$udev_ver" -lt 55 ]; then
+ udev_fix=""
+ fi
+ fi
+ ## @todo 60-vboxadd.rules -> 60-vboxguest.rules ?
+ echo "KERNEL=${udev_fix}\"vboxguest\", NAME=\"vboxguest\", OWNER=\"vboxadd\", MODE=\"0660\"" > /etc/udev/rules.d/60-vboxadd.rules
+ echo "KERNEL=${udev_fix}\"vboxuser\", NAME=\"vboxuser\", OWNER=\"vboxadd\", MODE=\"0666\"" >> /etc/udev/rules.d/60-vboxadd.rules
+ # Make sure the new rule is noticed.
+ udevadm control --reload >/dev/null 2>&1 || true
+ udevcontrol reload_rules >/dev/null 2>&1 || true
+ fi
+}
+
+create_module_rebuild_script()
+{
+ # And a post-installation script for rebuilding modules when a new kernel
+ # is installed.
+ mkdir -p /etc/kernel/postinst.d /etc/kernel/prerm.d
+ cat << EOF > /etc/kernel/postinst.d/vboxadd
+#!/bin/sh
+# This only works correctly on Debian derivatives - Red Hat calls it before
+# installing the right header files.
+/sbin/rcvboxadd quicksetup "\${1}"
+exit 0
+EOF
+ cat << EOF > /etc/kernel/prerm.d/vboxadd
+#!/bin/sh
+for i in ${OLDMODULES}; do rm -f /lib/modules/"\${1}"/misc/"\${i}".ko; done
+rmdir -p /lib/modules/"\$1"/misc 2>/dev/null || true
+exit 0
+EOF
+ chmod 0755 /etc/kernel/postinst.d/vboxadd /etc/kernel/prerm.d/vboxadd
+}
+
+shared_folder_setup()
+{
+ # Add a group "vboxsf" for Shared Folders access
+ # All users which want to access the auto-mounted Shared Folders have to
+ # be added to this group.
+ groupadd -r -f vboxsf >/dev/null 2>&1
+
+ # Put the mount.vboxsf mount helper in the right place.
+ ## @todo It would be nicer if the kernel module just parsed parameters
+ # itself instead of needing a separate binary to do that.
+ ln -sf "${INSTALL_DIR}/other/mount.vboxsf" /sbin
+ # SELinux security context for the mount helper.
+ if test -e /etc/selinux/config; then
+ # This is correct. semanage maps this to the real path, and it aborts
+ # with an error, telling you what you should have typed, if you specify
+ # the real path. The "chcon" is there as a back-up for old guests.
+ command -v semanage > /dev/null &&
+ semanage fcontext -a -t mount_exec_t "${INSTALL_DIR}/other/mount.vboxsf"
+ chcon -t mount_exec_t "${INSTALL_DIR}/other/mount.vboxsf" 2>/dev/null
+ fi
+}
+
+# Returns path to module file as seen by modinfo(8) or empty string.
+module_path()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ modinfo "$mod" 2>/dev/null | grep -e "^filename:" | tr -s ' ' | cut -d " " -f2
+}
+
+# Returns module version if module is available or empty string.
+module_version()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f2
+}
+
+# Returns module revision if module is available in the system or empty string.
+module_revision()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f3
+}
+
+
+# Returns "1" if module is signed and signature can be verified
+# with public key provided in DEB_PUB_KEY. Or empty string otherwise.
+module_signed()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ extraction_tool=/lib/modules/"$(uname -r)"/build/scripts/extract-module-sig.pl
+ mod_path=$(module_path "$mod" 2>/dev/null)
+ openssl_tool=$(which openssl 2>/dev/null)
+ # Do not use built-in printf!
+ printf_tool=$(which printf 2>/dev/null)
+
+ # Make sure all the tools required for signature validation are available.
+ [ -x "$extraction_tool" ] || return
+ [ -n "$mod_path" ] || return
+ [ -n "$openssl_tool" ] || return
+ [ -n "$printf_tool" ] || return
+
+ # Make sure openssl can handle hash algorithm.
+ sig_hashalgo=$(modinfo -F sig_hashalgo "$mod" 2>/dev/null)
+ [ "$(module_sig_hash_supported $sig_hashalgo)" = "1" ] || return
+
+ # Generate file names for temporary stuff.
+ mod_pub_key=$(mktemp -u)
+ mod_signature=$(mktemp -u)
+ mod_unsigned=$(mktemp -u)
+
+ # Convert public key in DER format into X509 certificate form.
+ "$openssl_tool" x509 -pubkey -inform DER -in "$DEB_PUB_KEY" -out "$mod_pub_key" 2>/dev/null
+ # Extract raw module signature and convert it into binary format.
+ "$printf_tool" \\x$(modinfo -F signature "$mod" | sed -z 's/[ \t\n]//g' | sed -e "s/:/\\\x/g") 2>/dev/null > "$mod_signature"
+ # Extract unsigned module for further digest calculation.
+ "$extraction_tool" -0 "$mod_path" 2>/dev/null > "$mod_unsigned"
+
+ # Verify signature.
+ rc=""
+ "$openssl_tool" dgst "-$sig_hashalgo" -binary -verify "$mod_pub_key" -signature "$mod_signature" "$mod_unsigned" 2>&1 >/dev/null && rc="1"
+ # Clean up.
+ rm -f $mod_pub_key $mod_signature $mod_unsigned
+
+ # Check result.
+ [ "$rc" = "1" ] || return
+
+ echo "1"
+}
+
+# Returns "1" if externally built module is available in the system and its
+# version and revision number do match to current VirtualBox installation.
+# Or empty string otherwise.
+module_available()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ [ "$VBOX_VERSION" = "$(module_version "$mod")" ] || return
+ [ "$VBOX_REVISION" = "$(module_revision "$mod")" ] || return
+
+ # Check if module belongs to VirtualBox installation.
+ #
+ # We have a convention that only modules from /lib/modules/*/misc
+ # belong to us. Modules from other locations are treated as
+ # externally built.
+ mod_path="$(module_path "$mod")"
+
+ # If module path points to a symbolic link, resolve actual file location.
+ [ -L "$mod_path" ] && mod_path="$(readlink -e -- "$mod_path")"
+
+ # File exists?
+ [ -f "$mod_path" ] || return
+
+ # Extract last component of module path and check whether it is located
+ # outside of /lib/modules/*/misc.
+ mod_dir="$(dirname "$mod_path" | sed 's;^.*/;;')"
+ [ "$mod_dir" = "misc" ] || return
+
+ # In case if system is running in Secure Boot mode, check if module is signed.
+ if test -n "$HAVE_SEC_BOOT"; then
+ [ "$(module_signed "$mod")" = "1" ] || return
+ fi
+
+ echo "1"
+}
+
+# Check if required modules are installed in the system and versions match.
+setup_complete()
+{
+ [ "$(module_available vboxguest)" = "1" ] || return
+ [ "$(module_available vboxsf)" = "1" ] || return
+
+ # All modules are in place.
+ echo "1"
+}
+
+# setup_script
+setup()
+{
+ info "Setting up modules"
+
+ # chcon is needed on old Fedora/Redhat systems. No one remembers which.
+ test ! -e /etc/selinux/config ||
+ chcon -t bin_t "$BUILDINTMP" 2>/dev/null
+
+ if test -z "$INSTALL_NO_MODULE_BUILDS"; then
+ # Check whether modules setup is already complete for currently running kernel.
+ # Prevent unnecessary rebuilding in order to speed up booting process.
+ if test "$(setup_complete)" = "1"; then
+ info "VirtualBox Guest Additions kernel modules $VBOX_VERSION $VBOX_REVISION are \
+already available for kernel $TARGET_VER and do not require to be rebuilt."
+ else
+ info "Building the VirtualBox Guest Additions kernel modules. This may take a while."
+ info "To build modules for other installed kernels, run"
+ info " /sbin/rcvboxadd quicksetup <version>"
+ info "or"
+ info " /sbin/rcvboxadd quicksetup all"
+ if test -d /lib/modules/"$TARGET_VER"/build; then
+ setup_modules "$TARGET_VER"
+ depmod
+ else
+ info "Kernel headers not found for target kernel $TARGET_VER. \
+Please install them and execute
+ /sbin/rcvboxadd setup"
+ fi
+ fi
+ fi
+ create_vbox_user
+ create_udev_rule
+ test -n "${INSTALL_NO_MODULE_BUILDS}" || create_module_rebuild_script
+ shared_folder_setup
+ # Create user group which will have permissive access to DRP IPC server socket.
+ groupadd -r -f vboxdrmipc >/dev/null 2>&1
+
+ if running_vboxguest || running_vboxadd; then
+ info "Running kernel modules will not be replaced until the system is restarted"
+ fi
+
+ # Put the X.Org driver in place. This is harmless if it is not needed.
+ # Also set up the OpenGL library.
+ myerr=`"${INSTALL_DIR}/init/vboxadd-x11" setup 2>&1`
+ test -z "${myerr}" || log "${myerr}"
+
+ return 0
+}
+
+# cleanup_script
+cleanup()
+{
+ if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
+ # Delete old versions of VBox modules.
+ cleanup_modules
+ depmod
+
+ # Remove old module sources
+ for i in $OLDMODULES; do
+ rm -rf /usr/src/$i-*
+ done
+ fi
+
+ # Clean-up X11-related bits
+ "${INSTALL_DIR}/init/vboxadd-x11" cleanup
+
+ # Remove other files
+ if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
+ rm -f /etc/kernel/postinst.d/vboxadd /etc/kernel/prerm.d/vboxadd
+ rmdir -p /etc/kernel/postinst.d /etc/kernel/prerm.d 2>/dev/null || true
+ fi
+ rm -f /sbin/mount.vboxsf 2>/dev/null
+ rm -f /etc/udev/rules.d/60-vboxadd.rules 2>/dev/null
+ udevadm control --reload >/dev/null 2>&1 || true
+ udevcontrol reload_rules >/dev/null 2>&1 || true
+}
+
+start()
+{
+ begin "Starting."
+
+ # Check if kernel modules for currently running kernel are ready
+ # and rebuild them if needed.
+ setup
+
+ # Warn if Secure Boot setup not yet complete.
+ if test -n "$HAVE_SEC_BOOT" && test -z "$DEB_KEY_ENROLLED"; then
+ if test -n "$HAVE_DEB_KEY"; then
+ info "You must re-start your system to finish secure boot set-up."
+ else
+ info "You must sign vboxguest, vboxsf and
+vboxvideo (if present) kernel modules before using
+VirtualBox Guest Additions. See the documentation
+for your Linux distribution."
+ fi
+ fi
+
+ if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
+ test -d /sys &&
+ ps -A -o comm | grep -q '/*udevd$' 2>/dev/null ||
+ no_udev=1
+ running_vboxguest || {
+ rm -f $dev || {
+ fail "Cannot remove $dev"
+ }
+ rm -f $userdev || {
+ fail "Cannot remove $userdev"
+ }
+ $MODPROBE vboxguest >/dev/null 2>&1 ||
+ fail "modprobe vboxguest failed"
+ case "$no_udev" in 1)
+ sleep .5;;
+ esac
+ $MODPROBE vboxsf > /dev/null 2>&1 ||
+ info "modprobe vboxsf failed"
+ }
+ case "$no_udev" in 1)
+ do_vboxguest_non_udev;;
+ esac
+ fi # INSTALL_NO_MODULE_BUILDS
+
+ return 0
+}
+
+stop()
+{
+ begin "Stopping."
+ if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
+ # We want to build modules for newly installed kernels on shutdown, so
+ # check which we marked at start-up.
+ for setupi in /lib/modules/*; do
+ KERN_VER="${setupi##*/}"
+ # For a full setup, mark kernels we do not want to build.
+ test -f "$SKIPFILE_BASE"-"$KERN_VER" || setup_modules "$KERN_VER"
+ done
+ fi
+ if test -r /etc/ld.so.conf.d/00vboxvideo.conf; then
+ rm /etc/ld.so.conf.d/00vboxvideo.conf
+ ldconfig
+ fi
+ if ! umount -a -t vboxsf 2>/dev/null; then
+ # Make sure we only fail, if there are truly no more vboxsf
+ # mounts in the system.
+ [ -n "$(findmnt -t vboxsf)" ] && fail "Cannot unmount vboxsf folders"
+ fi
+ test -n "${INSTALL_NO_MODULE_BUILDS}" ||
+ info "You may need to restart your guest system to finish removing guest drivers."
+ return 0
+}
+
+dmnstatus()
+{
+ if running_vboxguest; then
+ echo "The VirtualBox Additions are currently running."
+ else
+ echo "The VirtualBox Additions are not currently running."
+ fi
+}
+
+for i; do
+ case "$i" in quiet) QUIET=yes;; esac
+done
+case "$1" in
+# Does setup without clean-up first and marks all kernels currently found on the
+# system so that we can see later if any were added.
+start)
+ start
+ ;;
+# Tries to build kernel modules for kernels added since start. Tries to unmount
+# shared folders. Uninstalls our Chromium 3D libraries since we can't always do
+# this fast enough at start time if we discover we do not want to use them.
+stop)
+ stop
+ ;;
+restart)
+ restart
+ ;;
+# Setup does a clean-up (see below) and re-does all Additions-specific
+# configuration of the guest system, including building kernel modules for the
+# current kernel.
+setup)
+ cleanup && start
+ ;;
+# Builds kernel modules for the specified kernels if they are not already built.
+quicksetup)
+ if test x"$2" = xall; then
+ for topi in /lib/modules/*; do
+ KERN_VER="${topi%/misc}"
+ KERN_VER="${KERN_VER#/lib/modules/}"
+ setup_modules "$KERN_VER"
+ done
+ elif test -n "$2"; then
+ setup_modules "$2"
+ else
+ setup_modules "$TARGET_VER"
+ fi
+ ;;
+# Clean-up removes all Additions-specific configuration of the guest system,
+# including all kernel modules.
+cleanup)
+ cleanup
+ ;;
+status)
+ dmnstatus
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|status|setup|quicksetup|cleanup} [quiet]"
+ exit 1
+esac
+
+exit
diff --git a/src/VBox/Additions/linux/lightdm-greeter/.scm-settings b/src/VBox/Additions/linux/lightdm-greeter/.scm-settings
new file mode 100644
index 00000000..45dbbfc4
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for light-dm greeter.
+#
+
+#
+# Copyright (C) 2010-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+/liblightdm-gobject-*/*.c|/liblightdm-gobject-*/*.h: --external-copyright --no-strip-trailing-blanks --no-convert-tabs --lgpl-disclaimer --no-fix-header-guards
+
diff --git a/src/VBox/Additions/linux/lightdm-greeter/Config.kmk b/src/VBox/Additions/linux/lightdm-greeter/Config.kmk
new file mode 100644
index 00000000..51a68060
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/Config.kmk
@@ -0,0 +1,41 @@
+# $Id: Config.kmk $
+## @file
+# kBuild Configuration file for the lightdm-greeter
+#
+
+#
+# Copyright (C) 2016-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+VBOX_LIGHTDM_GREETER_CONFIG_KMK_INCLUDED = 1
+
+# Include the top-level configure file.
+ifndef VBOX_ROOT_CONFIG_KMK_INCLUDED
+ include $(PATH_ROOT)/Config.kmk
+endif
+
+SDK_VBoxGlib20WithIo = glib-2.0 and gio-2.0
+SDK_VBoxGlib20WithIo_VBOX_PKG_CONFIG_CFLAGS := $(shell pkg-config gio-2.0 glib-2.0 --cflags)
+SDK_VBoxGlib20WithIo_INCS = $(patsubst -I%,%,$(filter -I%,$(SDK_VBoxGlib20WithIo_VBOX_PKG_CONFIG_CFLAGS)))
+SDK_VBoxGlib20WithIo_CFLAGS = $(filter-out -I%,$(SDK_VBoxGlib20WithIo_VBOX_PKG_CONFIG_CFLAGS))
+SDK_VBoxGlib20WithIo_LDFLAGS := $(shell pkg-config gio-2.0 glib-2.0 --libs)
+
diff --git a/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk b/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk
new file mode 100644
index 00000000..d53c2021
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk
@@ -0,0 +1,111 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for VBox LightDM greeter for providing automated logons.
+#
+
+#
+# Copyright (C) 2012-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifndef VBOX_LIGHTDM_GREETER_CONFIG_KMK_INCLUDED
+ include $(PATH_SUB_CURRENT)/Config.kmk
+endif
+
+ifndef VBOX_OSE
+ include $(PATH_SUB_CURRENT)/liblightdm-gobject-1.5.0/Makefile.kmk
+endif
+
+# Enable building with FLTK UI + PNG support.
+VBOX_WITH_FLTK := 1
+VBOX_GREETER_WITH_PNG_SUPPORT := 1
+
+# The greeter module.
+PROGRAMS += vbox-greeter
+vbox-greeter_TEMPLATE = VBOXGUESTR3EXE
+vbox-greeter_SDKS = VBoxGlib20WithIo
+vbox-greeter_DEFS = LOG_TO_BACKDOOR VBOX_WITH_HGCM
+vbox-greeter_DEFS += \
+ VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+vbox-greeter_DEFS += \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_GREETER_WITH_PNG_SUPPORT),VBOX_GREETER_WITH_PNG_SUPPORT,)
+ifdef VBOX_WITH_FLTK
+ vbox-greeter_DEFS += \
+ VBOX_WITH_FLTK
+else
+ vbox-greeter_DEFS += \
+ GTK_DISABLE_SINGLE_INCLUDES \
+ GDK_DISABLE_DEPRECATED
+endif
+vbox-greeter_CFLAGS := $(if $(VBOX_OSE),%(filter-out -I%,$(shell pkg-config --cflags liblightdm-gobject-1)),)
+## @todo r=bird: Why are we cooking our own lightdm-gobject-1 but using system headers?
+## That sounds like a very risky business to me. I've added liblightdm-gobject-1.5.0
+## to the INCS, however lightdm.h is missing and will be taken from the system.
+vbox-greeter_INCS := \
+ /usr/lib/i386-linux-gnu/glib-2.0/include \
+ /usr/lib/x86_64-linux-gnu/glib-2.0/include \
+ /usr/include/glib-2.0 \
+ $(if $(VBOX_OSE),,liblightdm-gobject-1.5.0) \
+ /usr/include/lightdm-gobject-1 \
+ $(if $(VBOX_OSE),$(patsubst -I%,%,%(filter -I%,$(shell pkg-config --cflags liblightdm-gobject-1))),)
+ifndef VBOX_WITH_FLTK
+ vbox-greeter_INCS += \
+ /usr/include/glib-2.0 \
+ /usr/include/gtk-3.0 \
+ /usr/include/pango-1.0 \
+ /usr/include/cairo \
+ /usr/include/gdk-pixbuf-2.0 \
+ /usr/include/atk-1.0
+endif
+
+vbox-greeter_SOURCES = \
+ vbox-greeter.cpp
+
+vbox-greeter_LIBS := \
+ $(if $(VBOX_OSE),lightdm-gobject-1,$(VBOX_PATH_ADDITIONS_LIB)/VBox-liblightdm-gobject$(VBOX_SUFF_LIB)) \
+ glib-2.0 \
+ gio-2.0 \
+ gobject-2.0 \
+ $(VBOX_LIB_IPRT_GUEST_R3_SHARED) \
+ $(VBOX_LIB_VBGL_R3_SHARED) \
+ $(VBOX_LIB_IPRT_GUEST_R3_SHARED)
+ifdef VBOX_WITH_FLTK
+ vbox-greeter_LIBS += fltk
+ ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ vbox-greeter_LIBS += fltk_images
+ endif
+ if $(HOSTNAME) == "3960x.dev" && $(USER) == "bird" # whatever.
+ vbox-greeter_LIBS += stdc++
+ endif
+else
+ vbox-greeter_LIBS += gtk-3
+endif
+
+vbox-greeter_LDFLAGS = $(if $(VBOX_OSE),$(shell pkg-config --libs liblightdm-gobject-1),)
+ifdef VBOX_WITH_FLTK
+ #vbox-greeter_LDFLAGS = -Wl,-Bsymbolic-functions -Wl,-z,relro /usr/lib/i386-linux-gnu/libfltk.a -lXext -lXft -lfontconfig -lfontconfig -lXinerama -ldl -lm -lX11
+ vbox-greeter_LDFLAGS += -s
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/linux/lightdm-greeter/banner-dummy.png b/src/VBox/Additions/linux/lightdm-greeter/banner-dummy.png
new file mode 100644
index 00000000..d74e9c1a
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/banner-dummy.png
Binary files differ
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk
new file mode 100644
index 00000000..abc902d3
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk
@@ -0,0 +1,54 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for liblighdm-gobject 1.5.0
+#
+
+#
+# Copyright (C) 2013-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# The greeter module.
+LIBRARIES += VBox-liblightdm-gobject
+
+VBox-liblightdm-gobject_TEMPLATE = VBOXGUESTR3NPLIB
+VBox-liblightdm-gobject_SDKS = VBoxGlib20WithIo
+VBox-liblightdm-gobject_INCS = \
+ /usr/include/glib-2.0 \
+ /usr/lib/i386-linux-gnu/glib-2.0/include \
+ /usr/lib/x86_64-linux-gnu/glib-2.0/include \
+ /usr/include/gio-unix-2.0
+VBox-liblightdm-gobject_DEFS = \
+ CONFIG_DIR=\"/etc/lightdm\" \
+ XSESSIONS_DIR=\"/usr/share/xsessions\" \
+ REMOTE_SESSIONS_DIR=\"/usr/share/lightdm/remote-sessions\"
+VBox-liblightdm-gobject_SOURCES = \
+ greeter.c \
+ language.c \
+ layout.c \
+ power.c \
+ session.c \
+ system.c \
+ user.c
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h
new file mode 100644
index 00000000..c06fc08d
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h
@@ -0,0 +1,90 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Gettext package */
+#define GETTEXT_PACKAGE "lightdm"
+
+/* Greeter session */
+#define GREETER_SESSION "default"
+
+/* User to run greeter as */
+#define GREETER_USER "lightdm"
+
+/* Define to 1 if you have the `clearenv' function. */
+#define HAVE_CLEARENV 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <security/pam_appl.h> header file. */
+#define HAVE_SECURITY_PAM_APPL_H 1
+
+/* Define to 1 if you have the `setresgid' function. */
+#define HAVE_SETRESGID 1
+
+/* Define to 1 if you have the `setresuid' function. */
+#define HAVE_SETRESUID 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "lightdm"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "lightdm"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "lightdm 1.5.0"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "lightdm"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.5.0"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* User session */
+#define USER_SESSION "default"
+
+/* Version number of package */
+#define VERSION "1.5.0"
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c
new file mode 100644
index 00000000..3d44b562
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c
@@ -0,0 +1,1442 @@
+/*
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <security/pam_appl.h>
+
+#include "lightdm/greeter.h"
+
+enum {
+ PROP_0,
+ PROP_DEFAULT_SESSION_HINT,
+ PROP_HIDE_USERS_HINT,
+ PROP_SHOW_MANUAL_LOGIN_HINT,
+ PROP_SHOW_REMOTE_LOGIN_HINT,
+ PROP_LOCK_HINT,
+ PROP_HAS_GUEST_ACCOUNT_HINT,
+ PROP_SELECT_USER_HINT,
+ PROP_SELECT_GUEST_HINT,
+ PROP_AUTOLOGIN_USER_HINT,
+ PROP_AUTOLOGIN_GUEST_HINT,
+ PROP_AUTOLOGIN_TIMEOUT_HINT,
+ PROP_AUTHENTICATION_USER,
+ PROP_IN_AUTHENTICATION,
+ PROP_IS_AUTHENTICATED,
+};
+
+enum {
+ SHOW_PROMPT,
+ SHOW_MESSAGE,
+ AUTHENTICATION_COMPLETE,
+ AUTOLOGIN_TIMER_EXPIRED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct
+{
+ gboolean connected;
+
+ GIOChannel *to_server_channel, *from_server_channel;
+ guint8 *read_buffer;
+ gsize n_read;
+
+ gsize n_responses_waiting;
+ GList *responses_received;
+
+ GHashTable *hints;
+ guint autologin_timeout;
+
+ gchar *authentication_user;
+ gboolean in_authentication;
+ gboolean is_authenticated;
+ guint32 authenticate_sequence_number;
+ gboolean cancelling_authentication;
+} LightDMGreeterPrivate;
+
+G_DEFINE_TYPE (LightDMGreeter, lightdm_greeter, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_GREETER, LightDMGreeterPrivate)
+
+#define HEADER_SIZE 8
+#define MAX_MESSAGE_LENGTH 1024
+
+/* Messages from the greeter to the server */
+typedef enum
+{
+ GREETER_MESSAGE_CONNECT = 0,
+ GREETER_MESSAGE_AUTHENTICATE,
+ GREETER_MESSAGE_AUTHENTICATE_AS_GUEST,
+ GREETER_MESSAGE_CONTINUE_AUTHENTICATION,
+ GREETER_MESSAGE_START_SESSION,
+ GREETER_MESSAGE_CANCEL_AUTHENTICATION,
+ GREETER_MESSAGE_SET_LANGUAGE,
+ GREETER_MESSAGE_AUTHENTICATE_REMOTE
+} GreeterMessage;
+
+/* Messages from the server to the greeter */
+typedef enum
+{
+ SERVER_MESSAGE_CONNECTED = 0,
+ SERVER_MESSAGE_PROMPT_AUTHENTICATION,
+ SERVER_MESSAGE_END_AUTHENTICATION,
+ SERVER_MESSAGE_SESSION_RESULT
+} ServerMessage;
+
+/**
+ * lightdm_greeter_new:
+ *
+ * Create a new greeter.
+ *
+ * Return value: the new #LightDMGreeter
+ **/
+LightDMGreeter *
+lightdm_greeter_new ()
+{
+ return g_object_new (LIGHTDM_TYPE_GREETER, NULL);
+}
+
+static gboolean
+timed_login_cb (gpointer data)
+{
+ LightDMGreeter *greeter = data;
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+
+ priv->autologin_timeout = 0;
+ g_signal_emit (G_OBJECT (greeter), signals[AUTOLOGIN_TIMER_EXPIRED], 0);
+
+ return FALSE;
+}
+
+static guint32
+int_length ()
+{
+ return 4;
+}
+
+static void
+write_int (guint8 *buffer, gint buffer_length, guint32 value, gsize *offset)
+{
+ if (*offset + 4 >= buffer_length)
+ return;
+ buffer[*offset] = value >> 24;
+ buffer[*offset+1] = (value >> 16) & 0xFF;
+ buffer[*offset+2] = (value >> 8) & 0xFF;
+ buffer[*offset+3] = value & 0xFF;
+ *offset += 4;
+}
+
+static void
+write_string (guint8 *buffer, gint buffer_length, const gchar *value, gsize *offset)
+{
+ gint length = 0;
+
+ if (value)
+ length = strlen (value);
+ write_int (buffer, buffer_length, length, offset);
+ if (*offset + length >= buffer_length)
+ return;
+ memcpy (buffer + *offset, value, length);
+ *offset += length;
+}
+
+static guint32
+read_int (guint8 *message, gsize message_length, gsize *offset)
+{
+ guint32 value;
+ guint8 *buffer;
+
+ if (message_length - *offset < int_length ())
+ {
+ g_warning ("Not enough space for int, need %i, got %zi", int_length (), message_length - *offset);
+ return 0;
+ }
+
+ buffer = message + *offset;
+ value = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
+ *offset += int_length ();
+
+ return value;
+}
+
+static gchar *
+read_string (guint8 *message, gsize message_length, gsize *offset)
+{
+ guint32 length;
+ gchar *value;
+
+ length = read_int (message, message_length, offset);
+ if (message_length - *offset < length)
+ {
+ g_warning ("Not enough space for string, need %u, got %zu", length, message_length - *offset);
+ return g_strdup ("");
+ }
+
+ value = g_malloc (sizeof (gchar) * (length + 1));
+ memcpy (value, message + *offset, length);
+ value[length] = '\0';
+ *offset += length;
+
+ return value;
+}
+
+static guint32
+string_length (const gchar *value)
+{
+ if (value)
+ return int_length () + strlen (value);
+ else
+ return int_length ();
+}
+
+static void
+write_header (guint8 *buffer, gint buffer_length, guint32 id, guint32 length, gsize *offset)
+{
+ write_int (buffer, buffer_length, id, offset);
+ write_int (buffer, buffer_length, length, offset);
+}
+
+static guint32
+get_message_length (guint8 *message, gsize message_length)
+{
+ gsize offset = 4;
+ return read_int (message, message_length, &offset);
+}
+
+static void
+write_message (LightDMGreeter *greeter, guint8 *message, gsize message_length)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ GIOStatus status;
+ GError *error = NULL;
+ guint32 stated_length;
+
+ /* Double check that we're sending well-formed messages. If we say we're
+ sending more than we do, we end up DOS'ing lightdm as it waits for the
+ rest. If we say we're sending less than we do, we confuse the heck out
+ of lightdm, as it starts reading headers from the middle of our
+ messages. */
+ stated_length = HEADER_SIZE + get_message_length (message, message_length);
+ if (stated_length != message_length)
+ {
+ g_warning ("Refusing to write malformed packet to daemon: declared size is %u, but actual size is %zu", stated_length, message_length);
+ return;
+ }
+
+ status = g_io_channel_write_chars (priv->to_server_channel, (gchar *) message, message_length, NULL, &error);
+ if (error)
+ g_warning ("Error writing to daemon: %s", error->message);
+ g_clear_error (&error);
+ if (status == G_IO_STATUS_NORMAL)
+ g_debug ("Wrote %zi bytes to daemon", message_length);
+ g_io_channel_flush (priv->to_server_channel, NULL);
+}
+
+static void
+handle_connected (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ gchar *version;
+ GString *hint_string;
+ int timeout;
+
+ version = read_string (message, message_length, offset);
+ hint_string = g_string_new ("");
+ while (*offset < message_length)
+ {
+ gchar *name, *value;
+
+ name = read_string (message, message_length, offset);
+ value = read_string (message, message_length, offset);
+ g_hash_table_insert (priv->hints, name, value);
+ g_string_append_printf (hint_string, " %s=%s", name, value);
+ }
+
+ g_debug ("Connected version=%s%s", version, hint_string->str);
+ g_free (version);
+ g_string_free (hint_string, TRUE);
+
+ /* Set timeout for default login */
+ timeout = lightdm_greeter_get_autologin_timeout_hint (greeter);
+ if (timeout)
+ {
+ g_debug ("Setting autologin timer for %d seconds", timeout);
+ priv->autologin_timeout = g_timeout_add (timeout * 1000, timed_login_cb, greeter);
+ }
+}
+
+static void
+handle_prompt_authentication (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ guint32 sequence_number, n_messages, i;
+ gchar *username;
+
+ sequence_number = read_int (message, message_length, offset);
+ if (sequence_number != priv->authenticate_sequence_number)
+ {
+ g_debug ("Ignoring prompt authentication with invalid sequence number %d", sequence_number);
+ return;
+ }
+
+ if (priv->cancelling_authentication)
+ {
+ g_debug ("Ignoring prompt authentication as waiting for it to cancel");
+ return;
+ }
+
+ /* Update username */
+ username = read_string (message, message_length, offset);
+ if (strcmp (username, "") == 0)
+ {
+ g_free (username);
+ username = NULL;
+ }
+ g_free (priv->authentication_user);
+ priv->authentication_user = username;
+
+ g_list_free_full (priv->responses_received, g_free);
+ priv->responses_received = NULL;
+ priv->n_responses_waiting = 0;
+
+ n_messages = read_int (message, message_length, offset);
+ g_debug ("Prompt user with %d message(s)", n_messages);
+
+ for (i = 0; i < n_messages; i++)
+ {
+ int style;
+ gchar *text;
+
+ style = read_int (message, message_length, offset);
+ text = read_string (message, message_length, offset);
+
+ // FIXME: Should stop on prompts?
+ switch (style)
+ {
+ case PAM_PROMPT_ECHO_OFF:
+ priv->n_responses_waiting++;
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_PROMPT], 0, text, LIGHTDM_PROMPT_TYPE_SECRET);
+ break;
+ case PAM_PROMPT_ECHO_ON:
+ priv->n_responses_waiting++;
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_PROMPT], 0, text, LIGHTDM_PROMPT_TYPE_QUESTION);
+ break;
+ case PAM_ERROR_MSG:
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_MESSAGE], 0, text, LIGHTDM_MESSAGE_TYPE_ERROR);
+ break;
+ case PAM_TEXT_INFO:
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_MESSAGE], 0, text, LIGHTDM_MESSAGE_TYPE_INFO);
+ break;
+ }
+
+ g_free (text);
+ }
+}
+
+static void
+handle_end_authentication (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ guint32 sequence_number, return_code;
+ gchar *username;
+
+ sequence_number = read_int (message, message_length, offset);
+
+ if (sequence_number != priv->authenticate_sequence_number)
+ {
+ g_debug ("Ignoring end authentication with invalid sequence number %d", sequence_number);
+ return;
+ }
+
+ username = read_string (message, message_length, offset);
+ return_code = read_int (message, message_length, offset);
+
+ g_debug ("Authentication complete for user %s with return code %d", username, return_code);
+
+ /* Update username */
+ if (strcmp (username, "") == 0)
+ {
+ g_free (username);
+ username = NULL;
+ }
+ g_free (priv->authentication_user);
+ priv->authentication_user = username;
+
+ priv->cancelling_authentication = FALSE;
+ priv->is_authenticated = (return_code == 0);
+
+ priv->in_authentication = FALSE;
+ g_signal_emit (G_OBJECT (greeter), signals[AUTHENTICATION_COMPLETE], 0);
+}
+
+static guint8 *
+read_message (LightDMGreeter *greeter, gsize *length, gboolean block)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ gsize n_to_read, n_read;
+ guint8 *buffer;
+ GError *error = NULL;
+
+ /* Read the header, or the whole message if we already have that */
+ n_to_read = HEADER_SIZE;
+ if (priv->n_read >= HEADER_SIZE)
+ n_to_read += get_message_length (priv->read_buffer, priv->n_read);
+
+ do
+ {
+ GIOStatus status;
+ status = g_io_channel_read_chars (priv->from_server_channel,
+ (gchar *) priv->read_buffer + priv->n_read,
+ n_to_read - priv->n_read,
+ &n_read,
+ &error);
+ if (error)
+ g_warning ("Error reading from server: %s", error->message);
+ g_clear_error (&error);
+ if (status != G_IO_STATUS_NORMAL)
+ break;
+
+ g_debug ("Read %zi bytes from daemon", n_read);
+
+ priv->n_read += n_read;
+ } while (priv->n_read < n_to_read && block);
+
+ /* Stop if haven't got all the data we want */
+ if (priv->n_read != n_to_read)
+ return FALSE;
+
+ /* If have header, rerun for content */
+ if (priv->n_read == HEADER_SIZE)
+ {
+ n_to_read = get_message_length (priv->read_buffer, priv->n_read);
+ if (n_to_read > 0)
+ {
+ priv->read_buffer = g_realloc (priv->read_buffer, HEADER_SIZE + n_to_read);
+ return read_message (greeter, length, block);
+ }
+ }
+
+ buffer = priv->read_buffer;
+ *length = priv->n_read;
+
+ priv->read_buffer = g_malloc (priv->n_read);
+ priv->n_read = 0;
+
+ return buffer;
+}
+
+static gboolean
+from_server_cb (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ LightDMGreeter *greeter = data;
+ guint8 *message;
+ gsize message_length, offset;
+ guint32 id;
+
+ message = read_message (greeter, &message_length, FALSE);
+ if (!message)
+ return TRUE;
+
+ offset = 0;
+ id = read_int (message, message_length, &offset);
+ read_int (message, message_length, &offset);
+ switch (id)
+ {
+ case SERVER_MESSAGE_PROMPT_AUTHENTICATION:
+ handle_prompt_authentication (greeter, message, message_length, &offset);
+ break;
+ case SERVER_MESSAGE_END_AUTHENTICATION:
+ handle_end_authentication (greeter, message, message_length, &offset);
+ break;
+ default:
+ g_warning ("Unknown message from server: %d", id);
+ break;
+ }
+ g_free (message);
+
+ return TRUE;
+}
+
+/**
+ * lightdm_greeter_connect_sync:
+ * @greeter: The greeter to connect
+ * @error: return location for a #GError, or %NULL
+ *
+ * Connects the greeter to the display manager. Will block until connected.
+ *
+ * Return value: #TRUE if successfully connected
+ **/
+gboolean
+lightdm_greeter_connect_sync (LightDMGreeter *greeter, GError **error)
+{
+ LightDMGreeterPrivate *priv;
+ const gchar *fd;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ guint8 *response;
+ gsize response_length, offset = 0;
+ guint32 id;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+
+ priv = GET_PRIVATE (greeter);
+
+ fd = g_getenv ("LIGHTDM_TO_SERVER_FD");
+ if (!fd)
+ {
+ g_warning ("No LIGHTDM_TO_SERVER_FD environment variable");
+ return FALSE;
+ }
+ priv->to_server_channel = g_io_channel_unix_new (atoi (fd));
+ g_io_channel_set_encoding (priv->to_server_channel, NULL, NULL);
+
+ fd = g_getenv ("LIGHTDM_FROM_SERVER_FD");
+ if (!fd)
+ {
+ g_warning ("No LIGHTDM_FROM_SERVER_FD environment variable");
+ return FALSE;
+ }
+ priv->from_server_channel = g_io_channel_unix_new (atoi (fd));
+ g_io_channel_set_encoding (priv->from_server_channel, NULL, NULL);
+ g_io_add_watch (priv->from_server_channel, G_IO_IN, from_server_cb, greeter);
+
+ g_debug ("Connecting to display manager...");
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CONNECT, string_length (VERSION), &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, VERSION, &offset);
+ write_message (greeter, message, offset);
+
+ response = read_message (greeter, &response_length, TRUE);
+ if (!response)
+ return FALSE;
+
+ offset = 0;
+ id = read_int (response, response_length, &offset);
+ read_int (response, response_length, &offset);
+ if (id == SERVER_MESSAGE_CONNECTED)
+ handle_connected (greeter, response, response_length, &offset);
+ g_free (response);
+ if (id != SERVER_MESSAGE_CONNECTED)
+ {
+ g_warning ("Expected CONNECTED message, got %d", id);
+ return FALSE;
+ }
+
+ priv->connected = TRUE;
+
+ return TRUE;
+}
+
+/**
+ * lightdm_greeter_get_hint:
+ * @greeter: A #LightDMGreeter
+ * @name: The hint name to query.
+ *
+ * Get a hint.
+ *
+ * Return value: The value for this hint or #NULL if not set.
+ **/
+const gchar *
+lightdm_greeter_get_hint (LightDMGreeter *greeter, const gchar *name)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return g_hash_table_lookup (GET_PRIVATE (greeter)->hints, name);
+}
+
+/**
+ * lightdm_greeter_get_default_session_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the default session to use.
+ *
+ * Return value: The session name
+ **/
+const gchar *
+lightdm_greeter_get_default_session_hint (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return lightdm_greeter_get_hint (greeter, "default-session");
+}
+
+/**
+ * lightdm_greeter_get_hide_users_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if user accounts should be shown. If this is TRUE then the list of
+ * accounts should be taken from #LightDMUserList and displayed in the greeter
+ * for the user to choose from. Note that this list can be empty and it is
+ * recommended you show a method for the user to enter a username manually.
+ *
+ * If this option is shown the greeter should only allow these users to be
+ * chosen for login unless the manual login hint is set.
+ *
+ * Return value: #TRUE if the available users should not be shown.
+ */
+gboolean
+lightdm_greeter_get_hide_users_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "hide-users");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_show_manual_login_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if a manual login option should be shown. If set the GUI
+ * should provide a way for a username to be entered manually.
+ * Without this hint a greeter which is showing a user list can
+ * limit logins to only those users.
+ *
+ * Return value: #TRUE if a manual login option should be shown.
+ */
+gboolean
+lightdm_greeter_get_show_manual_login_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "show-manual-login");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_show_remote_login_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if a remote login option should be shown. If set the GUI
+ * should provide a way for a user to log into a remote desktop server.
+ *
+ * Return value: #TRUE if a remote login option should be shown.
+ */
+gboolean
+lightdm_greeter_get_show_remote_login_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "show-remote-login");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_lock_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if the greeter is acting as a lock screen.
+ *
+ * Return value: #TRUE if the greeter was triggered by locking the seat.
+ */
+gboolean
+lightdm_greeter_get_lock_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "lock-screen");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_has_guest_account_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if guest sessions are supported.
+ *
+ * Return value: #TRUE if guest sessions are supported.
+ */
+gboolean
+lightdm_greeter_get_has_guest_account_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "has-guest-account");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_select_user_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the user to select by default.
+ *
+ * Return value: A username
+ */
+const gchar *
+lightdm_greeter_get_select_user_hint (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return lightdm_greeter_get_hint (greeter, "select-user");
+}
+
+/**
+ * lightdm_greeter_get_select_guest_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if the guest account should be selected by default.
+ *
+ * Return value: #TRUE if the guest account should be selected by default.
+ */
+gboolean
+lightdm_greeter_get_select_guest_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "select-guest");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_autologin_user_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the user account to automatically logg into when the timer expires.
+ *
+ * Return value: The user account to automatically log into.
+ */
+const gchar *
+lightdm_greeter_get_autologin_user_hint (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return lightdm_greeter_get_hint (greeter, "autologin-user");
+}
+
+/**
+ * lightdm_greeter_get_autologin_guest_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if the guest account should be automatically logged into when the timer expires.
+ *
+ * Return value: #TRUE if the guest account should be automatically logged into.
+ */
+gboolean
+lightdm_greeter_get_autologin_guest_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "autologin-guest");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_autologin_timeout_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the number of seconds to wait before automaitcally logging in.
+ *
+ * Return value: The number of seconds to wait before automatically logging in or 0 for no timeout.
+ */
+gint
+lightdm_greeter_get_autologin_timeout_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+ gint timeout = 0;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "autologin-timeout");
+ if (value)
+ timeout = atoi (value);
+ if (timeout < 0)
+ timeout = 0;
+
+ return timeout;
+}
+
+/**
+ * lightdm_greeter_cancel_autologin:
+ * @greeter: A #LightDMGreeter
+ *
+ * Cancel the automatic login.
+ */
+void
+lightdm_greeter_cancel_autologin (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ if (priv->autologin_timeout)
+ g_source_remove (priv->autologin_timeout);
+ priv->autologin_timeout = 0;
+}
+
+/**
+ * lightdm_greeter_authenticate:
+ * @greeter: A #LightDMGreeter
+ * @username: (allow-none): A username or #NULL to prompt for a username.
+ *
+ * Starts the authentication procedure for a user.
+ **/
+void
+lightdm_greeter_authenticate (LightDMGreeter *greeter, const gchar *username)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = FALSE;
+ priv->authenticate_sequence_number++;
+ priv->in_authentication = TRUE;
+ priv->is_authenticated = FALSE;
+ if (username != priv->authentication_user)
+ {
+ g_free (priv->authentication_user);
+ priv->authentication_user = g_strdup (username);
+ }
+
+ g_debug ("Starting authentication for user %s...", username);
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE, int_length () + string_length (username), &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, username, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_authenticate_as_guest:
+ * @greeter: A #LightDMGreeter
+ *
+ * Starts the authentication procedure for the guest user.
+ **/
+void
+lightdm_greeter_authenticate_as_guest (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = FALSE;
+ priv->authenticate_sequence_number++;
+ priv->in_authentication = TRUE;
+ priv->is_authenticated = FALSE;
+ g_free (priv->authentication_user);
+ priv->authentication_user = NULL;
+
+ g_debug ("Starting authentication for guest account...");
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE_AS_GUEST, int_length (), &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_authenticate_autologin:
+ * @greeter: A #LightDMGreeter
+ *
+ * Starts the authentication procedure for the automatic login user.
+ **/
+void
+lightdm_greeter_authenticate_autologin (LightDMGreeter *greeter)
+{
+ const gchar *user;
+
+ user = lightdm_greeter_get_autologin_user_hint (greeter);
+ if (lightdm_greeter_get_autologin_guest_hint (greeter))
+ lightdm_greeter_authenticate_as_guest (greeter);
+ else if (user)
+ lightdm_greeter_authenticate (greeter, user);
+}
+
+/**
+ * lightdm_greeter_authenticate_remote:
+ * @greeter: A #LightDMGreeter
+ * @session: The name of a remote session
+ * @username: (allow-none): A username of #NULL to prompt for a username.
+ *
+ * Start authentication for a remote session type.
+ **/
+void
+lightdm_greeter_authenticate_remote (LightDMGreeter *greeter, const gchar *session, const gchar *username)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = FALSE;
+ priv->authenticate_sequence_number++;
+ priv->in_authentication = TRUE;
+ priv->is_authenticated = FALSE;
+ g_free (priv->authentication_user);
+ priv->authentication_user = NULL;
+
+ if (username)
+ g_debug ("Starting authentication for remote session %s as user %s...", session, username);
+ else
+ g_debug ("Starting authentication for remote session %s...", session);
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE_REMOTE, int_length () + string_length (session) + string_length (username), &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, session, &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, username, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_respond:
+ * @greeter: A #LightDMGreeter
+ * @response: Response to a prompt
+ *
+ * Provide response to a prompt. May be one in a series.
+ **/
+void
+lightdm_greeter_respond (LightDMGreeter *greeter, const gchar *response)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+ g_return_if_fail (response != NULL);
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+ g_return_if_fail (priv->n_responses_waiting > 0);
+
+ priv->n_responses_waiting--;
+ priv->responses_received = g_list_append (priv->responses_received, g_strdup (response));
+
+ if (priv->n_responses_waiting == 0)
+ {
+ guint32 msg_length;
+ GList *iter;
+
+ g_debug ("Providing response to display manager");
+
+ msg_length = int_length ();
+ for (iter = priv->responses_received; iter; iter = iter->next)
+ {
+ msg_length += string_length ((gchar *)iter->data);
+ }
+
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CONTINUE_AUTHENTICATION, msg_length, &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, g_list_length (priv->responses_received), &offset);
+ for (iter = priv->responses_received; iter; iter = iter->next)
+ {
+ write_string (message, MAX_MESSAGE_LENGTH, (gchar *)iter->data, &offset);
+ }
+ write_message (greeter, message, offset);
+
+ g_list_free_full (priv->responses_received, g_free);
+ priv->responses_received = NULL;
+ }
+}
+
+/**
+ * lightdm_greeter_cancel_authentication:
+ * @greeter: A #LightDMGreeter
+ *
+ * Cancel the current user authentication.
+ **/
+void
+lightdm_greeter_cancel_authentication (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = TRUE;
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CANCEL_AUTHENTICATION, 0, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_get_in_authentication:
+ * @greeter: A #LightDMGreeter
+ *
+ * Checks if the greeter is in the process of authenticating.
+ *
+ * Return value: #TRUE if the greeter is authenticating a user.
+ **/
+gboolean
+lightdm_greeter_get_in_authentication (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ return GET_PRIVATE (greeter)->in_authentication;
+}
+
+/**
+ * lightdm_greeter_get_is_authenticated:
+ * @greeter: A #LightDMGreeter
+ *
+ * Checks if the greeter has successfully authenticated.
+ *
+ * Return value: #TRUE if the greeter is authenticated for login.
+ **/
+gboolean
+lightdm_greeter_get_is_authenticated (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ return GET_PRIVATE (greeter)->is_authenticated;
+}
+
+/**
+ * lightdm_greeter_get_authentication_user:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the user that is being authenticated.
+ *
+ * Return value: The username of the authentication user being authenticated or #NULL if no authentication in progress.
+ */
+const gchar *
+lightdm_greeter_get_authentication_user (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return GET_PRIVATE (greeter)->authentication_user;
+}
+
+/**
+ * lightdm_greeter_set_language:
+ * @greeter: A #LightDMGreeter
+ * @language: The language to use for this user.
+ *
+ * Set the language for the currently authenticated user.
+ **/
+void
+lightdm_greeter_set_language (LightDMGreeter *greeter, const gchar *language)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_SET_LANGUAGE, string_length (language), &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, language, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_start_session_sync:
+ * @greeter: A #LightDMGreeter
+ * @session: (allow-none): The session to log into or #NULL to use the default.
+ * @error: return location for a #GError, or %NULL
+ *
+ * Start a session for the authenticated user.
+ *
+ * Return value: TRUE if the session was started.
+ **/
+gboolean
+lightdm_greeter_start_session_sync (LightDMGreeter *greeter, const gchar *session, GError **error)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ guint8 *response;
+ gsize response_length, offset = 0;
+ guint32 id, return_code = 1;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_val_if_fail (priv->connected, FALSE);
+ g_return_val_if_fail (priv->is_authenticated, FALSE);
+
+ if (session)
+ g_debug ("Starting session %s", session);
+ else
+ g_debug ("Starting default session");
+
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_START_SESSION, string_length (session), &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, session, &offset);
+ write_message (greeter, message, offset);
+
+ response = read_message (greeter, &response_length, TRUE);
+ if (!response)
+ return FALSE;
+
+ offset = 0;
+ id = read_int (response, response_length, &offset);
+ read_int (response, response_length, &offset);
+ if (id == SERVER_MESSAGE_SESSION_RESULT)
+ return_code = read_int (response, response_length, &offset);
+ else
+ g_warning ("Expected SESSION_RESULT message, got %d", id);
+
+ g_free (response);
+
+ return return_code == 0;
+}
+
+static void
+lightdm_greeter_init (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+
+ priv->read_buffer = g_malloc (HEADER_SIZE);
+ priv->hints = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+}
+
+static void
+lightdm_greeter_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_greeter_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMGreeter *self;
+
+ self = LIGHTDM_GREETER (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_SESSION_HINT:
+ g_value_set_string (value, lightdm_greeter_get_default_session_hint (self));
+ break;
+ case PROP_HIDE_USERS_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_hide_users_hint (self));
+ break;
+ case PROP_SHOW_MANUAL_LOGIN_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_show_manual_login_hint (self));
+ break;
+ case PROP_SHOW_REMOTE_LOGIN_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_show_remote_login_hint (self));
+ break;
+ case PROP_LOCK_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_lock_hint (self));
+ break;
+ case PROP_HAS_GUEST_ACCOUNT_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_has_guest_account_hint (self));
+ break;
+ case PROP_SELECT_USER_HINT:
+ g_value_set_string (value, lightdm_greeter_get_select_user_hint (self));
+ break;
+ case PROP_SELECT_GUEST_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_select_guest_hint (self));
+ break;
+ case PROP_AUTOLOGIN_USER_HINT:
+ g_value_set_string (value, lightdm_greeter_get_autologin_user_hint (self));
+ break;
+ case PROP_AUTOLOGIN_GUEST_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_autologin_guest_hint (self));
+ break;
+ case PROP_AUTOLOGIN_TIMEOUT_HINT:
+ g_value_set_int (value, lightdm_greeter_get_autologin_timeout_hint (self));
+ break;
+ case PROP_AUTHENTICATION_USER:
+ g_value_set_string (value, lightdm_greeter_get_authentication_user (self));
+ break;
+ case PROP_IN_AUTHENTICATION:
+ g_value_set_boolean (value, lightdm_greeter_get_in_authentication (self));
+ break;
+ case PROP_IS_AUTHENTICATED:
+ g_value_set_boolean (value, lightdm_greeter_get_is_authenticated (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+marshal_VOID__STRING_INT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__STRING_INT) (gpointer data1,
+ gpointer arg_1,
+ gint arg_2,
+ gpointer data2);
+ register GMarshalFunc_VOID__STRING_INT callback;
+ register GCClosure *cc = (GCClosure*) closure;
+ register gpointer data1, data2;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__STRING_INT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ (param_values + 1)->data[0].v_pointer,
+ (param_values + 2)->data[0].v_int,
+ data2);
+}
+
+static void
+lightdm_greeter_finalize (GObject *object)
+{
+ LightDMGreeter *self = LIGHTDM_GREETER (object);
+ LightDMGreeterPrivate *priv = GET_PRIVATE (self);
+
+ if (priv->to_server_channel)
+ g_io_channel_unref (priv->to_server_channel);
+ if (priv->from_server_channel)
+ g_io_channel_unref (priv->from_server_channel);
+ g_free (priv->authentication_user);
+ g_hash_table_unref (priv->hints);
+
+ G_OBJECT_CLASS (lightdm_greeter_parent_class)->finalize (object);
+}
+
+static void
+lightdm_greeter_class_init (LightDMGreeterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMGreeterPrivate));
+
+ object_class->set_property = lightdm_greeter_set_property;
+ object_class->get_property = lightdm_greeter_get_property;
+ object_class->finalize = lightdm_greeter_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_DEFAULT_SESSION_HINT,
+ g_param_spec_string ("default-session-hint",
+ "default-session-hint",
+ "Default session hint",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_HIDE_USERS_HINT,
+ g_param_spec_boolean ("hide-users-hint",
+ "hide-users-hint",
+ "Hide users hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_MANUAL_LOGIN_HINT,
+ g_param_spec_boolean ("show-manual-login-hint",
+ "show-manual-login-hint",
+ "Show manual login hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_REMOTE_LOGIN_HINT,
+ g_param_spec_boolean ("show-remote-login-hint",
+ "show-remote-login-hint",
+ "Show remote login hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_LOCK_HINT,
+ g_param_spec_boolean ("lock-hint",
+ "lock-hint",
+ "Lock hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_HAS_GUEST_ACCOUNT_HINT,
+ g_param_spec_boolean ("has-guest-account-hint",
+ "has-guest-account-hint",
+ "Has guest account hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SELECT_USER_HINT,
+ g_param_spec_string ("select-user-hint",
+ "select-user-hint",
+ "Select user hint",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SELECT_GUEST_HINT,
+ g_param_spec_boolean ("select-guest-hint",
+ "select-guest-hint",
+ "Select guest account hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTOLOGIN_USER_HINT,
+ g_param_spec_string ("autologin-user-hint",
+ "autologin-user-hint",
+ "Autologin user hint",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTOLOGIN_GUEST_HINT,
+ g_param_spec_boolean ("autologin-guest-hint",
+ "autologin-guest-hint",
+ "Autologin guest account hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTOLOGIN_TIMEOUT_HINT,
+ g_param_spec_int ("autologin-timeout-hint",
+ "autologin-timeout-hint",
+ "Autologin timeout hint",
+ 0, G_MAXINT, 0,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTHENTICATION_USER,
+ g_param_spec_string ("authentication-user",
+ "authentication-user",
+ "The user being authenticated",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_IN_AUTHENTICATION,
+ g_param_spec_boolean ("in-authentication",
+ "in-authentication",
+ "TRUE if a user is being authenticated",
+ FALSE,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_IS_AUTHENTICATED,
+ g_param_spec_boolean ("is-authenticated",
+ "is-authenticated",
+ "TRUE if the selected user is authenticated",
+ FALSE,
+ G_PARAM_READABLE));
+
+ /**
+ * LightDMGreeter::show-prompt:
+ * @greeter: A #LightDMGreeter
+ * @text: Prompt text
+ * @type: Prompt type
+ *
+ * The ::show-prompt signal gets emitted when the greeter should show a
+ * prompt to the user. The given text should be displayed and an input
+ * field for the user to provide a response.
+ *
+ * Call lightdm_greeter_respond() with the resultant input or
+ * lightdm_greeter_cancel_authentication() to abort the authentication.
+ **/
+ signals[SHOW_PROMPT] =
+ g_signal_new ("show-prompt",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, show_prompt),
+ NULL, NULL,
+ marshal_VOID__STRING_INT,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
+
+ /**
+ * LightDMGreeter::show-message:
+ * @greeter: A #LightDMGreeter
+ * @text: Message text
+ * @type: Message type
+ *
+ * The ::show-message signal gets emitted when the greeter
+ * should show a message to the user.
+ **/
+ signals[SHOW_MESSAGE] =
+ g_signal_new ("show-message",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, show_message),
+ NULL, NULL,
+ marshal_VOID__STRING_INT,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
+
+ /**
+ * LightDMGreeter::authentication-complete:
+ * @greeter: A #LightDMGreeter
+ *
+ * The ::authentication-complete signal gets emitted when the greeter
+ * has completed authentication.
+ *
+ * Call lightdm_greeter_get_is_authenticated() to check if the authentication
+ * was successful.
+ **/
+ signals[AUTHENTICATION_COMPLETE] =
+ g_signal_new ("authentication-complete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, authentication_complete),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * LightDMGreeter::autologin-timer-expired:
+ * @greeter: A #LightDMGreeter
+ *
+ * The ::timed-login signal gets emitted when the automatic login timer has expired.
+ * The application should then call lightdm_greeter_login().
+ **/
+ signals[AUTOLOGIN_TIMER_EXPIRED] =
+ g_signal_new ("autologin-timer-expired",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, autologin_timer_expired),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c
new file mode 100644
index 00000000..9709619e
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <string.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <stdio.h>
+#include <glib/gi18n.h>
+
+#include "lightdm/language.h"
+
+enum {
+ PROP_0,
+ PROP_CODE,
+ PROP_NAME,
+ PROP_TERRITORY
+};
+
+typedef struct
+{
+ gchar *code;
+ gchar *name;
+ gchar *territory;
+} LightDMLanguagePrivate;
+
+G_DEFINE_TYPE (LightDMLanguage, lightdm_language, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_LANGUAGE, LightDMLanguagePrivate)
+
+static gboolean have_languages = FALSE;
+static GList *languages = NULL;
+
+static void
+update_languages (void)
+{
+ gchar *command = "locale -a";
+ gchar *stdout_text = NULL, *stderr_text = NULL;
+ gint exit_status;
+ gboolean result;
+ GError *error = NULL;
+
+ if (have_languages)
+ return;
+
+ result = g_spawn_command_line_sync (command, &stdout_text, &stderr_text, &exit_status, &error);
+ if (error)
+ {
+ g_warning ("Failed to run '%s': %s", command, error->message);
+ g_clear_error (&error);
+ }
+ else if (exit_status != 0)
+ g_warning ("Failed to get languages, '%s' returned %d", command, exit_status);
+ else if (result)
+ {
+ gchar **tokens;
+ int i;
+
+ tokens = g_strsplit_set (stdout_text, "\n\r", -1);
+ for (i = 0; tokens[i]; i++)
+ {
+ LightDMLanguage *language;
+ gchar *code;
+
+ code = g_strchug (tokens[i]);
+ if (code[0] == '\0')
+ continue;
+
+ /* Ignore the non-interesting languages */
+ if (strcmp (command, "locale -a") == 0 && !g_strrstr (code, ".utf8"))
+ continue;
+
+ language = g_object_new (LIGHTDM_TYPE_LANGUAGE, "code", code, NULL);
+ languages = g_list_append (languages, language);
+ }
+
+ g_strfreev (tokens);
+ }
+
+ g_free (stdout_text);
+ g_free (stderr_text);
+
+ have_languages = TRUE;
+}
+
+static gboolean
+is_utf8 (const gchar *code)
+{
+ return g_strrstr (code, ".utf8") || g_strrstr (code, ".UTF-8");
+}
+
+/* Get a valid locale name that can be passed to setlocale(), so we always can use nl_langinfo() to get language and country names. */
+static gchar *
+get_locale_name (const gchar *code)
+{
+ gchar *locale = NULL, *language;
+ char *at;
+ static gchar **avail_locales;
+ gint i;
+
+ if (is_utf8 (code))
+ return (gchar *) code;
+
+ if ((at = strchr (code, '@')))
+ language = g_strndup (code, at - code);
+ else
+ language = g_strdup (code);
+
+ if (!avail_locales)
+ {
+ gchar *locales;
+ GError *error = NULL;
+
+ if (g_spawn_command_line_sync ("locale -a", &locales, NULL, NULL, &error))
+ {
+ avail_locales = g_strsplit (g_strchomp (locales), "\n", -1);
+ g_free (locales);
+ }
+ else
+ {
+ g_warning ("Failed to run 'locale -a': %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ if (avail_locales)
+ {
+ for (i = 0; avail_locales[i]; i++)
+ {
+ gchar *loc = avail_locales[i];
+ if (!g_strrstr (loc, ".utf8"))
+ continue;
+ if (g_str_has_prefix (loc, language))
+ {
+ locale = g_strdup (loc);
+ break;
+ }
+ }
+ }
+
+ g_free (language);
+
+ return locale;
+}
+
+/**
+ * lightdm_get_language:
+ *
+ * Get the current language.
+ *
+ * Return value: (transfer none): The current language or #NULL if no language.
+ **/
+LightDMLanguage *
+lightdm_get_language (void)
+{
+ const gchar *lang;
+ GList *link;
+
+ lang = g_getenv ("LANG");
+ if (!lang)
+ return NULL;
+
+ for (link = lightdm_get_languages (); link; link = link->next)
+ {
+ LightDMLanguage *language = link->data;
+ if (lightdm_language_matches (language, lang))
+ return language;
+ }
+
+ return NULL;
+}
+
+/**
+ * lightdm_get_languages:
+ *
+ * Get a list of languages to present to the user.
+ *
+ * Return value: (element-type LightDMLanguage) (transfer none): A list of #LightDMLanguage that should be presented to the user.
+ **/
+GList *
+lightdm_get_languages (void)
+{
+ update_languages ();
+ return languages;
+}
+
+/**
+ * lightdm_language_get_code:
+ * @language: A #LightDMLanguage
+ *
+ * Get the code of a language.
+ *
+ * Return value: The code of the language
+ **/
+const gchar *
+lightdm_language_get_code (LightDMLanguage *language)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL);
+ return GET_PRIVATE (language)->code;
+}
+
+/**
+ * lightdm_language_get_name:
+ * @language: A #LightDMLanguage
+ *
+ * Get the name of a language.
+ *
+ * Return value: The name of the language
+ **/
+const gchar *
+lightdm_language_get_name (LightDMLanguage *language)
+{
+ LightDMLanguagePrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL);
+
+ priv = GET_PRIVATE (language);
+
+ if (!priv->name)
+ {
+ gchar *locale = get_locale_name (priv->code);
+ if (locale)
+ {
+ gchar *current = setlocale (LC_ALL, NULL);
+ setlocale (LC_IDENTIFICATION, locale);
+ setlocale (LC_MESSAGES, "");
+
+ gchar *language_en = nl_langinfo (_NL_IDENTIFICATION_LANGUAGE);
+ if (language_en && strlen (language_en) > 0)
+ priv->name = g_strdup (dgettext ("iso_639_3", language_en));
+
+ setlocale (LC_ALL, current);
+ }
+ if (!priv->name)
+ {
+ gchar **tokens = g_strsplit_set (priv->code, "_.@", 2);
+ priv->name = g_strdup (tokens[0]);
+ g_strfreev (tokens);
+ }
+ }
+
+ return priv->name;
+}
+
+/**
+ * lightdm_language_get_territory:
+ * @language: A #LightDMLanguage
+ *
+ * Get the territory the language is used in.
+ *
+ * Return value: The territory the language is used in.
+ **/
+const gchar *
+lightdm_language_get_territory (LightDMLanguage *language)
+{
+ LightDMLanguagePrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL);
+
+ priv = GET_PRIVATE (language);
+
+ if (!priv->territory && strchr (priv->code, '_'))
+ {
+ gchar *locale = get_locale_name (priv->code);
+ if (locale)
+ {
+ gchar *current = setlocale (LC_ALL, NULL);
+ setlocale (LC_IDENTIFICATION, locale);
+ setlocale (LC_MESSAGES, "");
+
+ gchar *country_en = nl_langinfo (_NL_IDENTIFICATION_TERRITORY);
+ if (country_en && strlen (country_en) > 0 && g_strcmp0 (country_en, "ISO") != 0)
+ priv->territory = g_strdup (dgettext ("iso_3166", country_en));
+
+ setlocale (LC_ALL, current);
+ }
+ if (!priv->territory)
+ {
+ gchar **tokens = g_strsplit_set (priv->code, "_.@", 3);
+ priv->territory = g_strdup (tokens[1]);
+ g_strfreev (tokens);
+ }
+ }
+
+ return priv->territory;
+}
+
+/**
+ * lightdm_language_matches:
+ * @language: A #LightDMLanguage
+ * @code: A language code
+ *
+ * Check if a language code matches this language.
+ *
+ * Return value: #TRUE if the code matches this language.
+ **/
+gboolean
+lightdm_language_matches (LightDMLanguage *language, const gchar *code)
+{
+ LightDMLanguagePrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), FALSE);
+ g_return_val_if_fail (code != NULL, FALSE);
+
+ priv = GET_PRIVATE (language);
+
+ /* Handle the fact the UTF-8 is specified both as '.utf8' and '.UTF-8' */
+ if (is_utf8 (priv->code) && is_utf8 (code))
+ {
+ /* Match the characters before the '.' */
+ int i;
+ for (i = 0; priv->code[i] && code[i] && priv->code[i] == code[i] && code[i] != '.' ; i++);
+ return priv->code[i] == '.' && code[i] == '.';
+ }
+
+ return g_str_equal (priv->code, code);
+}
+
+static void
+lightdm_language_init (LightDMLanguage *language)
+{
+}
+
+static void
+lightdm_language_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLanguage *self = LIGHTDM_LANGUAGE (object);
+ LightDMLanguagePrivate *priv = GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_CODE:
+ g_free (priv->name);
+ priv->code = g_strdup (g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_language_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLanguage *self;
+
+ self = LIGHTDM_LANGUAGE (object);
+
+ switch (prop_id) {
+ case PROP_CODE:
+ g_value_set_string (value, lightdm_language_get_code (self));
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, lightdm_language_get_name (self));
+ break;
+ case PROP_TERRITORY:
+ g_value_set_string (value, lightdm_language_get_territory (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_language_class_init (LightDMLanguageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMLanguagePrivate));
+
+ object_class->set_property = lightdm_language_set_property;
+ object_class->get_property = lightdm_language_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_CODE,
+ g_param_spec_string ("code",
+ "code",
+ "Language code",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Name of the language",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_TERRITORY,
+ g_param_spec_string ("territory",
+ "territory",
+ "Territory the language is from",
+ NULL,
+ G_PARAM_READABLE));
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c
new file mode 100644
index 00000000..d5057450
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c
@@ -0,0 +1,344 @@
+/* -*- Mode: C; indent-tabs-mode:nil; tab-width:4 -*-
+ *
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <libxklavier/xklavier.h>
+
+#include "lightdm/layout.h"
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_SHORT_DESCRIPTION,
+ PROP_DESCRIPTION
+};
+
+typedef struct
+{
+ gchar *name;
+ gchar *short_description;
+ gchar *description;
+} LightDMLayoutPrivate;
+
+G_DEFINE_TYPE (LightDMLayout, lightdm_layout, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_LAYOUT, LightDMLayoutPrivate)
+
+static gboolean have_layouts = FALSE;
+static Display *display = NULL;
+static XklEngine *xkl_engine = NULL;
+static XklConfigRec *xkl_config = NULL;
+static GList *layouts = NULL;
+static LightDMLayout *default_layout = NULL;
+
+static gchar *
+make_layout_string (const gchar *layout, const gchar *variant)
+{
+ if (!layout || layout[0] == 0)
+ return NULL;
+ else if (!variant || variant[0] == 0)
+ return g_strdup (layout);
+ else
+ return g_strdup_printf ("%s\t%s", layout, variant);
+}
+
+static void
+parse_layout_string (const gchar *name, gchar **layout, gchar **variant)
+{
+ gchar **split;
+
+ *layout = NULL;
+ *variant = NULL;
+
+ if (!name)
+ return;
+
+ split = g_strsplit (name, "\t", 2);
+ if (split[0])
+ {
+ *layout = g_strdup (split[0]);
+ if (split[1])
+ *variant = g_strdup (split[1]);
+ }
+ g_strfreev (split);
+}
+
+static void
+variant_cb (XklConfigRegistry *config,
+ const XklConfigItem *item,
+ gpointer data)
+{
+ LightDMLayout *layout;
+ gchar *full_name;
+
+ full_name = make_layout_string (data, item->name);
+
+ layout = g_object_new (LIGHTDM_TYPE_LAYOUT, "name", full_name, "short-description", item->short_description, "description", item->description, NULL);
+ layouts = g_list_append (layouts, layout);
+
+ g_free (full_name);
+}
+
+static void
+layout_cb (XklConfigRegistry *config,
+ const XklConfigItem *item,
+ gpointer data)
+{
+ LightDMLayout *layout;
+
+ layout = g_object_new (LIGHTDM_TYPE_LAYOUT, "name", item->name, "short-description", item->short_description, "description", item->description, NULL);
+ layouts = g_list_append (layouts, layout);
+
+ xkl_config_registry_foreach_layout_variant (config, item->name, variant_cb, (gpointer) item->name);
+}
+
+/**
+ * lightdm_get_layouts:
+ *
+ * Get a list of keyboard layouts to present to the user.
+ *
+ * Return value: (element-type LightDMLayout) (transfer none): A list of #LightDMLayout that should be presented to the user.
+ **/
+GList *
+lightdm_get_layouts (void)
+{
+ XklConfigRegistry *registry;
+
+ if (have_layouts)
+ return layouts;
+
+ display = XOpenDisplay (NULL);
+ xkl_engine = xkl_engine_get_instance (display);
+ xkl_config = xkl_config_rec_new ();
+ if (!xkl_config_rec_get_from_server (xkl_config, xkl_engine))
+ g_warning ("Failed to get Xkl configuration from server");
+
+ registry = xkl_config_registry_get_instance (xkl_engine);
+ xkl_config_registry_load (registry, FALSE);
+ xkl_config_registry_foreach_layout (registry, layout_cb, NULL);
+ g_object_unref (registry);
+
+ have_layouts = TRUE;
+
+ return layouts;
+}
+
+/**
+ * lightdm_set_layout:
+ * @layout: The layout to use
+ *
+ * Set the layout for this session.
+ **/
+void
+lightdm_set_layout (LightDMLayout *dmlayout)
+{
+ XklConfigRec *config;
+ gchar *layout, *variant;
+
+ g_return_if_fail (dmlayout != NULL);
+
+ g_debug ("Setting keyboard layout to '%s'", lightdm_layout_get_name (dmlayout));
+
+ parse_layout_string (lightdm_layout_get_name (dmlayout), &layout, &variant);
+
+ config = xkl_config_rec_new ();
+ config->layouts = g_malloc (sizeof (gchar *) * 2);
+ config->variants = g_malloc (sizeof (gchar *) * 2);
+ config->model = g_strdup (xkl_config->model);
+ config->layouts[0] = layout;
+ config->layouts[1] = NULL;
+ config->variants[0] = variant;
+ config->variants[1] = NULL;
+ if (!xkl_config_rec_activate (config, xkl_engine))
+ g_warning ("Failed to activate XKL config");
+ g_object_unref (config);
+}
+
+/**
+ * lightdm_get_layout:
+ *
+ * Get the current keyboard layout.
+ *
+ * Return value: (transfer none): The currently active layout for this user.
+ **/
+LightDMLayout *
+lightdm_get_layout (void)
+{
+ lightdm_get_layouts ();
+
+ if (layouts && xkl_config && !default_layout)
+ {
+ gchar *full_name;
+ GList *item;
+
+ full_name = make_layout_string (xkl_config->layouts ? xkl_config->layouts[0] : NULL,
+ xkl_config->variants ? xkl_config->variants[0] : NULL);
+
+ for (item = layouts; item; item = item->next)
+ {
+ LightDMLayout *iter_layout = (LightDMLayout *) item->data;
+ if (g_strcmp0 (lightdm_layout_get_name (iter_layout), full_name) == 0)
+ {
+ default_layout = iter_layout;
+ break;
+ }
+ }
+
+ g_free (full_name);
+ }
+
+ return default_layout;
+}
+
+/**
+ * lightdm_layout_get_name:
+ * @layout: A #LightDMLayout
+ *
+ * Get the name of a layout.
+ *
+ * Return value: The name of the layout
+ **/
+const gchar *
+lightdm_layout_get_name (LightDMLayout *layout)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL);
+ return GET_PRIVATE (layout)->name;
+}
+
+/**
+ * lightdm_layout_get_short_description:
+ * @layout: A #LightDMLayout
+ *
+ * Get the short description of a layout.
+ *
+ * Return value: A short description of the layout
+ **/
+const gchar *
+lightdm_layout_get_short_description (LightDMLayout *layout)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL);
+ return GET_PRIVATE (layout)->short_description;
+}
+
+/**
+ * lightdm_layout_get_description:
+ * @layout: A #LightDMLayout
+ *
+ * Get the long description of a layout.
+ *
+ * Return value: A long description of the layout
+ **/
+const gchar *
+lightdm_layout_get_description (LightDMLayout *layout)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL);
+ return GET_PRIVATE (layout)->description;
+}
+
+static void
+lightdm_layout_init (LightDMLayout *layout)
+{
+}
+
+static void
+lightdm_layout_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLayout *self = LIGHTDM_LAYOUT (object);
+ LightDMLayoutPrivate *priv = GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_free (priv->name);
+ priv->name = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_SHORT_DESCRIPTION:
+ g_free (priv->short_description);
+ priv->short_description = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_DESCRIPTION:
+ g_free (priv->description);
+ priv->description = g_strdup (g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_layout_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLayout *self;
+
+ self = LIGHTDM_LAYOUT (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_value_set_string (value, lightdm_layout_get_name (self));
+ break;
+ case PROP_SHORT_DESCRIPTION:
+ g_value_set_string (value, lightdm_layout_get_short_description (self));
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, lightdm_layout_get_description (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_layout_class_init (LightDMLayoutClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMLayoutPrivate));
+
+ object_class->set_property = lightdm_layout_set_property;
+ object_class->get_property = lightdm_layout_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Name of the layout",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_SHORT_DESCRIPTION,
+ g_param_spec_string ("short-description",
+ "short-description",
+ "Short description of the layout",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string ("description",
+ "description",
+ "Long description of the layout",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c
new file mode 100644
index 00000000..f7830ff2
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2010-2011 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gio/gio.h>
+
+#include "lightdm/power.h"
+
+static GDBusProxy *upower_proxy = NULL;
+static GDBusProxy *ck_proxy = NULL;
+
+static gboolean
+upower_call_function (const gchar *function, gboolean default_result, GError **error)
+{
+ GVariant *result;
+ gboolean function_result = FALSE;
+
+ if (!upower_proxy)
+ {
+ upower_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.UPower",
+ "/org/freedesktop/UPower",
+ "org.freedesktop.UPower",
+ NULL,
+ error);
+ if (!upower_proxy)
+ return FALSE;
+ }
+
+ result = g_dbus_proxy_call_sync (upower_proxy,
+ function,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ error);
+ if (!result)
+ return default_result;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(b)")))
+ g_variant_get (result, "(b)", &function_result);
+
+ g_variant_unref (result);
+ return function_result;
+}
+
+/**
+ * lightdm_get_can_suspend:
+ *
+ * Checks if authorized to do a system suspend.
+ *
+ * Return value: #TRUE if can suspend the system
+ **/
+gboolean
+lightdm_get_can_suspend (void)
+{
+ return upower_call_function ("SuspendAllowed", FALSE, NULL);
+}
+
+/**
+ * lightdm_suspend:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system suspend.
+ *
+ * Return value: #TRUE if suspend initiated.
+ **/
+gboolean
+lightdm_suspend (GError **error)
+{
+ return upower_call_function ("Suspend", TRUE, error);
+}
+
+/**
+ * lightdm_get_can_hibernate:
+ *
+ * Checks if is authorized to do a system hibernate.
+ *
+ * Return value: #TRUE if can hibernate the system
+ **/
+gboolean
+lightdm_get_can_hibernate (void)
+{
+ return upower_call_function ("HibernateAllowed", FALSE, NULL);
+}
+
+/**
+ * lightdm_hibernate:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system hibernate.
+ *
+ * Return value: #TRUE if hibernate initiated.
+ **/
+gboolean
+lightdm_hibernate (GError **error)
+{
+ return upower_call_function ("Hibernate", TRUE, error);
+}
+
+static gboolean
+ck_call_function (const gchar *function, gboolean default_result, GError **error)
+{
+ GVariant *result;
+ gboolean function_result = FALSE;
+
+ if (!ck_proxy)
+ {
+ ck_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.ConsoleKit",
+ "/org/freedesktop/ConsoleKit/Manager",
+ "org.freedesktop.ConsoleKit.Manager",
+ NULL,
+ error);
+ if (!ck_proxy)
+ return FALSE;
+ }
+
+ result = g_dbus_proxy_call_sync (ck_proxy,
+ function,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ error);
+
+ if (!result)
+ return default_result;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(b)")))
+ g_variant_get (result, "(b)", &function_result);
+
+ g_variant_unref (result);
+ return function_result;
+}
+
+/**
+ * lightdm_get_can_restart:
+ *
+ * Checks if is authorized to do a system restart.
+ *
+ * Return value: #TRUE if can restart the system
+ **/
+gboolean
+lightdm_get_can_restart (void)
+{
+ return ck_call_function ("CanRestart", FALSE, NULL);
+}
+
+/**
+ * lightdm_restart:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system restart.
+ *
+ * Return value: #TRUE if restart initiated.
+ **/
+gboolean
+lightdm_restart (GError **error)
+{
+ return ck_call_function ("Restart", TRUE, error);
+}
+
+/**
+ * lightdm_get_can_shutdown:
+ *
+ * Checks if is authorized to do a system shutdown.
+ *
+ * Return value: #TRUE if can shutdown the system
+ **/
+gboolean
+lightdm_get_can_shutdown (void)
+{
+ return ck_call_function ("CanStop", FALSE, NULL);
+}
+
+/**
+ * lightdm_shutdown:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system shutdown.
+ *
+ * Return value: #TRUE if shutdown initiated.
+ **/
+gboolean
+lightdm_shutdown (GError **error)
+{
+ return ck_call_function ("Stop", TRUE, error);
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c
new file mode 100644
index 00000000..2df3a1af
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <string.h>
+#include <gio/gdesktopappinfo.h>
+
+#include "lightdm/session.h"
+
+enum {
+ PROP_0,
+ PROP_KEY,
+ PROP_NAME,
+ PROP_COMMENT
+};
+
+typedef struct
+{
+ gchar *key;
+ gchar *name;
+ gchar *comment;
+} LightDMSessionPrivate;
+
+G_DEFINE_TYPE (LightDMSession, lightdm_session, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_SESSION, LightDMSessionPrivate)
+
+static gboolean have_sessions = FALSE;
+static GList *local_sessions = NULL;
+static GList *remote_sessions = NULL;
+
+static gint
+compare_session (gconstpointer a, gconstpointer b)
+{
+ LightDMSessionPrivate *priv_a = GET_PRIVATE (a);
+ LightDMSessionPrivate *priv_b = GET_PRIVATE (b);
+ return strcmp (priv_a->name, priv_b->name);
+}
+
+static LightDMSession *
+load_session (GKeyFile *key_file, const gchar *key)
+{
+ gchar *domain, *name;
+ LightDMSession *session;
+ LightDMSessionPrivate *priv;
+ gchar *try_exec;
+
+ if (g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) ||
+ g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL))
+ return NULL;
+
+#ifdef G_KEY_FILE_DESKTOP_KEY_GETTEXT_DOMAIN
+ domain = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_GETTEXT_DOMAIN, NULL);
+#else
+ domain = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL);
+#endif
+ name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, domain, NULL);
+ if (!name)
+ {
+ g_warning ("Ignoring session without name");
+ g_free (domain);
+ return NULL;
+ }
+
+ try_exec = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, domain, NULL);
+ if (try_exec)
+ {
+ gchar *full_path;
+
+ full_path = g_find_program_in_path (try_exec);
+ g_free (try_exec);
+
+ if (!full_path)
+ {
+ g_free (name);
+ g_free (domain);
+ return NULL;
+ }
+ g_free (full_path);
+ }
+
+ session = g_object_new (LIGHTDM_TYPE_SESSION, NULL);
+ priv = GET_PRIVATE (session);
+
+ g_free (priv->key);
+ priv->key = g_strdup (key);
+
+ g_free (priv->name);
+ priv->name = name;
+
+ g_free (priv->comment);
+ priv->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, domain, NULL);
+ if (!priv->comment)
+ priv->comment = g_strdup ("");
+
+ g_free (domain);
+
+ return session;
+}
+
+static GList *
+load_sessions (const gchar *sessions_dir)
+{
+ GDir *directory;
+ GList *sessions = NULL;
+ GError *error = NULL;
+
+ directory = g_dir_open (sessions_dir, 0, &error);
+ if (error)
+ g_warning ("Failed to open sessions directory: %s", error->message);
+ g_clear_error (&error);
+ if (!directory)
+ return NULL;
+
+ while (TRUE)
+ {
+ const gchar *filename;
+ gchar *path;
+ GKeyFile *key_file;
+ gboolean result;
+
+ filename = g_dir_read_name (directory);
+ if (filename == NULL)
+ break;
+
+ if (!g_str_has_suffix (filename, ".desktop"))
+ continue;
+
+ path = g_build_filename (sessions_dir, filename, NULL);
+
+ key_file = g_key_file_new ();
+ result = g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, &error);
+ if (error)
+ g_warning ("Failed to load session file %s: %s:", path, error->message);
+ g_clear_error (&error);
+
+ if (result)
+ {
+ gchar *key;
+ LightDMSession *session;
+
+ key = g_strndup (filename, strlen (filename) - strlen (".desktop"));
+ session = load_session (key_file, key);
+ if (session)
+ {
+ g_debug ("Loaded session %s (%s, %s)", path, GET_PRIVATE (session)->name, GET_PRIVATE (session)->comment);
+ sessions = g_list_insert_sorted (sessions, session, compare_session);
+ }
+ else
+ g_debug ("Ignoring session %s", path);
+ g_free (key);
+ }
+
+ g_free (path);
+ g_key_file_free (key_file);
+ }
+
+ g_dir_close (directory);
+
+ return sessions;
+}
+
+static void
+update_sessions (void)
+{
+ GKeyFile *config_key_file = NULL;
+ gchar *config_path = NULL;
+ gchar *xsessions_dir;
+ gchar *remote_sessions_dir;
+ gboolean result;
+ GError *error = NULL;
+
+ if (have_sessions)
+ return;
+
+ xsessions_dir = g_strdup (XSESSIONS_DIR);
+ remote_sessions_dir = g_strdup (REMOTE_SESSIONS_DIR);
+
+ /* Use session directory from configuration */
+ /* FIXME: This should be sent in the greeter connection */
+ config_path = g_build_filename (CONFIG_DIR, "lightdm.conf", NULL);
+ config_key_file = g_key_file_new ();
+ result = g_key_file_load_from_file (config_key_file, config_path, G_KEY_FILE_NONE, &error);
+ if (error)
+ g_warning ("Failed to open configuration file: %s", error->message);
+ g_clear_error (&error);
+ if (result)
+ {
+ gchar *value;
+
+ value = g_key_file_get_string (config_key_file, "LightDM", "xsessions-directory", NULL);
+ if (value)
+ {
+ g_free (xsessions_dir);
+ xsessions_dir = value;
+ }
+
+ value = g_key_file_get_string (config_key_file, "LightDM", "remote-sessions-directory", NULL);
+ if (value)
+ {
+ g_free (remote_sessions_dir);
+ remote_sessions_dir = value;
+ }
+ }
+ g_key_file_free (config_key_file);
+ g_free (config_path);
+
+ local_sessions = load_sessions (xsessions_dir);
+ remote_sessions = load_sessions (remote_sessions_dir);
+
+ g_free (xsessions_dir);
+ g_free (remote_sessions_dir);
+
+ have_sessions = TRUE;
+}
+
+/**
+ * lightdm_get_sessions:
+ *
+ * Get the available sessions.
+ *
+ * Return value: (element-type LightDMSession) (transfer none): A list of #LightDMSession
+ **/
+GList *
+lightdm_get_sessions (void)
+{
+ update_sessions ();
+ return local_sessions;
+}
+
+/**
+ * lightdm_get_remote_sessions:
+ *
+ * Get the available remote sessions.
+ *
+ * Return value: (element-type LightDMSession) (transfer none): A list of #LightDMSession
+ **/
+GList *
+lightdm_get_remote_sessions (void)
+{
+ update_sessions ();
+ return remote_sessions;
+}
+
+/**
+ * lightdm_session_get_key:
+ * @session: A #LightDMSession
+ *
+ * Get the key for a session
+ *
+ * Return value: The session key
+ **/
+const gchar *
+lightdm_session_get_key (LightDMSession *session)
+{
+ g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL);
+ return GET_PRIVATE (session)->key;
+}
+
+/**
+ * lightdm_session_get_name:
+ * @session: A #LightDMSession
+ *
+ * Get the name for a session
+ *
+ * Return value: The session name
+ **/
+const gchar *
+lightdm_session_get_name (LightDMSession *session)
+{
+ g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL);
+ return GET_PRIVATE (session)->name;
+}
+
+/**
+ * lightdm_session_get_comment:
+ * @session: A #LightDMSession
+ *
+ * Get the comment for a session
+ *
+ * Return value: The session comment
+ **/
+const gchar *
+lightdm_session_get_comment (LightDMSession *session)
+{
+ g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL);
+ return GET_PRIVATE (session)->comment;
+}
+
+static void
+lightdm_session_init (LightDMSession *session)
+{
+}
+
+static void
+lightdm_session_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_session_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMSession *self;
+
+ self = LIGHTDM_SESSION (object);
+
+ switch (prop_id) {
+ case PROP_KEY:
+ g_value_set_string (value, lightdm_session_get_key (self));
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, lightdm_session_get_name (self));
+ break;
+ case PROP_COMMENT:
+ g_value_set_string (value, lightdm_session_get_comment (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_session_finalize (GObject *object)
+{
+ LightDMSession *self = LIGHTDM_SESSION (object);
+ LightDMSessionPrivate *priv = GET_PRIVATE (self);
+
+ g_free (priv->key);
+ g_free (priv->name);
+ g_free (priv->comment);
+}
+
+static void
+lightdm_session_class_init (LightDMSessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMSessionPrivate));
+
+ object_class->set_property = lightdm_session_set_property;
+ object_class->get_property = lightdm_session_get_property;
+ object_class->finalize = lightdm_session_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_KEY,
+ g_param_spec_string ("key",
+ "key",
+ "Session key",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Session name",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_COMMENT,
+ g_param_spec_string ("comment",
+ "comment",
+ "Session comment",
+ NULL,
+ G_PARAM_READABLE));
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c
new file mode 100644
index 00000000..77f8a182
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010-2011 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <sys/utsname.h>
+
+#include "lightdm/system.h"
+
+static gchar *hostname = NULL;
+
+/**
+ * lightdm_get_hostname:
+ *
+ * Return value: The name of the host we are running on.
+ **/
+const gchar *
+lightdm_get_hostname (void)
+{
+ if (!hostname)
+ {
+ struct utsname info;
+ uname (&info);
+ hostname = g_strdup (info.nodename);
+ }
+
+ return hostname;
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c
new file mode 100644
index 00000000..8df19079
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c
@@ -0,0 +1,1655 @@
+/* -*- Mode: C; indent-tabs-mode:nil; tab-width:4 -*-
+ *
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <pwd.h>
+#include <gio/gio.h>
+
+#include "lightdm/user.h"
+
+enum
+{
+ LIST_PROP_0,
+ LIST_PROP_NUM_USERS,
+ LIST_PROP_USERS,
+};
+
+enum
+{
+ USER_PROP_0,
+ USER_PROP_NAME,
+ USER_PROP_REAL_NAME,
+ USER_PROP_DISPLAY_NAME,
+ USER_PROP_HOME_DIRECTORY,
+ USER_PROP_IMAGE,
+ USER_PROP_BACKGROUND,
+ USER_PROP_LANGUAGE,
+ USER_PROP_LAYOUT,
+ USER_PROP_LAYOUTS,
+ USER_PROP_SESSION,
+ USER_PROP_LOGGED_IN,
+ USER_PROP_HAS_MESSAGES
+};
+
+enum
+{
+ USER_ADDED,
+ USER_CHANGED,
+ USER_REMOVED,
+ LAST_LIST_SIGNAL
+};
+static guint list_signals[LAST_LIST_SIGNAL] = { 0 };
+
+enum
+{
+ CHANGED,
+ LAST_USER_SIGNAL
+};
+static guint user_signals[LAST_USER_SIGNAL] = { 0 };
+
+typedef struct
+{
+ /* Connection to AccountsService */
+ GDBusProxy *accounts_service_proxy;
+ GList *user_account_objects;
+
+ /* Connection to DisplayManager */
+ GDBusProxy *display_manager_proxy;
+
+ /* File monitor for password file */
+ GFileMonitor *passwd_monitor;
+
+ /* TRUE if have scanned users */
+ gboolean have_users;
+
+ /* List of users */
+ GList *users;
+
+ /* List of sessions */
+ GList *sessions;
+} LightDMUserListPrivate;
+
+typedef struct
+{
+ GDBusProxy *proxy;
+ LightDMUser *user;
+} UserAccountObject;
+
+typedef struct
+{
+ LightDMUserList *user_list;
+
+ gchar *name;
+ gchar *real_name;
+ gchar *home_directory;
+ gchar *image;
+ gchar *background;
+ gboolean has_messages;
+
+ GKeyFile *dmrc_file;
+ gchar *language;
+ gchar **layouts;
+ gchar *session;
+} LightDMUserPrivate;
+
+typedef struct
+{
+ GObject parent_instance;
+ gchar *path;
+ gchar *username;
+} Session;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} SessionClass;
+
+G_DEFINE_TYPE (LightDMUserList, lightdm_user_list, G_TYPE_OBJECT);
+G_DEFINE_TYPE (LightDMUser, lightdm_user, G_TYPE_OBJECT);
+#define SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), session_get_type (), Session))
+GType session_get_type (void);
+G_DEFINE_TYPE (Session, session, G_TYPE_OBJECT);
+
+#define GET_LIST_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER_LIST, LightDMUserListPrivate)
+#define GET_USER_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER, LightDMUserPrivate)
+
+#define PASSWD_FILE "/etc/passwd"
+#define USER_CONFIG_FILE "/etc/lightdm/users.conf"
+
+static LightDMUserList *singleton = NULL;
+
+/**
+ * lightdm_user_list_get_instance:
+ *
+ * Get the user list.
+ *
+ * Return value: (transfer none): the #LightDMUserList
+ **/
+LightDMUserList *
+lightdm_user_list_get_instance (void)
+{
+ if (!singleton)
+ singleton = g_object_new (LIGHTDM_TYPE_USER_LIST, NULL);
+ return singleton;
+}
+
+static LightDMUser *
+get_user_by_name (LightDMUserList *user_list, const gchar *username)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GList *link;
+
+ for (link = priv->users; link; link = link->next)
+ {
+ LightDMUser *user = link->data;
+ if (strcmp (lightdm_user_get_name (user), username) == 0)
+ return user;
+ }
+
+ return NULL;
+}
+
+static gint
+compare_user (gconstpointer a, gconstpointer b)
+{
+ LightDMUser *user_a = (LightDMUser *) a, *user_b = (LightDMUser *) b;
+ return strcmp (lightdm_user_get_display_name (user_a), lightdm_user_get_display_name (user_b));
+}
+
+static gboolean
+update_passwd_user (LightDMUser *user, const gchar *real_name, const gchar *home_directory, const gchar *image)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+
+ if (g_strcmp0 (lightdm_user_get_real_name (user), real_name) == 0 &&
+ g_strcmp0 (lightdm_user_get_home_directory (user), home_directory) == 0 &&
+ g_strcmp0 (lightdm_user_get_image (user), image) == 0)
+ return FALSE;
+
+ g_free (priv->real_name);
+ priv->real_name = g_strdup (real_name);
+ g_free (priv->home_directory);
+ priv->home_directory = g_strdup (home_directory);
+ g_free (priv->image);
+ priv->image = g_strdup (image);
+
+ return TRUE;
+}
+
+static void
+user_changed_cb (LightDMUser *user, LightDMUserList *user_list)
+{
+ g_signal_emit (user_list, list_signals[USER_CHANGED], 0, user);
+}
+
+static void
+load_passwd_file (LightDMUserList *user_list, gboolean emit_add_signal)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GKeyFile *config;
+ gchar *value;
+ gint minimum_uid;
+ gchar **hidden_users, **hidden_shells;
+ GList *users = NULL, *old_users, *new_users = NULL, *changed_users = NULL, *link;
+ GError *error = NULL;
+
+ g_debug ("Loading user config from %s", USER_CONFIG_FILE);
+
+ config = g_key_file_new ();
+ g_key_file_load_from_file (config, USER_CONFIG_FILE, G_KEY_FILE_NONE, &error);
+ if (error && !g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ g_warning ("Failed to load configuration from %s: %s", USER_CONFIG_FILE, error->message); // FIXME: Don't make warning on no file, just info
+ g_clear_error (&error);
+
+ if (g_key_file_has_key (config, "UserList", "minimum-uid", NULL))
+ minimum_uid = g_key_file_get_integer (config, "UserList", "minimum-uid", NULL);
+ else
+ minimum_uid = 500;
+
+ value = g_key_file_get_string (config, "UserList", "hidden-users", NULL);
+ if (!value)
+ value = g_strdup ("nobody nobody4 noaccess");
+ hidden_users = g_strsplit (value, " ", -1);
+ g_free (value);
+
+ value = g_key_file_get_string (config, "UserList", "hidden-shells", NULL);
+ if (!value)
+ value = g_strdup ("/bin/false /usr/sbin/nologin");
+ hidden_shells = g_strsplit (value, " ", -1);
+ g_free (value);
+
+ g_key_file_free (config);
+
+ setpwent ();
+
+ while (TRUE)
+ {
+ struct passwd *entry;
+ LightDMUser *user;
+ LightDMUserPrivate *user_priv;
+ char **tokens;
+ gchar *real_name, *image;
+ int i;
+
+ errno = 0;
+ entry = getpwent ();
+ if (!entry)
+ break;
+
+ /* Ignore system users */
+ if (entry->pw_uid < minimum_uid)
+ continue;
+
+ /* Ignore users disabled by shell */
+ if (entry->pw_shell)
+ {
+ for (i = 0; hidden_shells[i] && strcmp (entry->pw_shell, hidden_shells[i]) != 0; i++);
+ if (hidden_shells[i])
+ continue;
+ }
+
+ /* Ignore certain users */
+ for (i = 0; hidden_users[i] && strcmp (entry->pw_name, hidden_users[i]) != 0; i++);
+ if (hidden_users[i])
+ continue;
+
+ tokens = g_strsplit (entry->pw_gecos, ",", -1);
+ if (tokens[0] != NULL && tokens[0][0] != '\0')
+ real_name = g_strdup (tokens[0]);
+ else
+ real_name = g_strdup ("");
+ g_strfreev (tokens);
+
+ image = g_build_filename (entry->pw_dir, ".face", NULL);
+ if (!g_file_test (image, G_FILE_TEST_EXISTS))
+ {
+ g_free (image);
+ image = g_build_filename (entry->pw_dir, ".face.icon", NULL);
+ if (!g_file_test (image, G_FILE_TEST_EXISTS))
+ {
+ g_free (image);
+ image = NULL;
+ }
+ }
+
+ user = g_object_new (LIGHTDM_TYPE_USER, NULL);
+ user_priv = GET_USER_PRIVATE (user);
+ user_priv->user_list = user_list;
+ g_free (user_priv->name);
+ user_priv->name = g_strdup (entry->pw_name);
+ g_free (user_priv->real_name);
+ user_priv->real_name = real_name;
+ g_free (user_priv->home_directory);
+ user_priv->home_directory = g_strdup (entry->pw_dir);
+ g_free (user_priv->image);
+ user_priv->image = image;
+
+ /* Update existing users if have them */
+ for (link = priv->users; link; link = link->next)
+ {
+ LightDMUser *info = link->data;
+ if (strcmp (lightdm_user_get_name (info), lightdm_user_get_name (user)) == 0)
+ {
+ if (update_passwd_user (info, lightdm_user_get_real_name (user), lightdm_user_get_home_directory (user), lightdm_user_get_image (user)))
+ changed_users = g_list_insert_sorted (changed_users, info, compare_user);
+ g_object_unref (user);
+ user = info;
+ break;
+ }
+ }
+ if (!link)
+ {
+ /* Only notify once we have loaded the user list */
+ if (priv->have_users)
+ new_users = g_list_insert_sorted (new_users, user, compare_user);
+ }
+ users = g_list_insert_sorted (users, user, compare_user);
+ }
+ g_strfreev (hidden_users);
+ g_strfreev (hidden_shells);
+
+ if (errno != 0)
+ g_warning ("Failed to read password database: %s", strerror (errno));
+
+ endpwent ();
+
+ /* Use new user list */
+ old_users = priv->users;
+ priv->users = users;
+
+ /* Notify of changes */
+ for (link = new_users; link; link = link->next)
+ {
+ LightDMUser *info = link->data;
+ g_debug ("User %s added", lightdm_user_get_name (info));
+ g_signal_connect (info, "changed", G_CALLBACK (user_changed_cb), user_list);
+ if (emit_add_signal)
+ g_signal_emit (user_list, list_signals[USER_ADDED], 0, info);
+ }
+ g_list_free (new_users);
+ for (link = changed_users; link; link = link->next)
+ {
+ LightDMUser *info = link->data;
+ g_debug ("User %s changed", lightdm_user_get_name (info));
+ g_signal_emit (info, user_signals[CHANGED], 0);
+ }
+ g_list_free (changed_users);
+ for (link = old_users; link; link = link->next)
+ {
+ GList *new_link;
+
+ /* See if this user is in the current list */
+ for (new_link = priv->users; new_link; new_link = new_link->next)
+ {
+ if (new_link->data == link->data)
+ break;
+ }
+
+ if (!new_link)
+ {
+ LightDMUser *info = link->data;
+ g_debug ("User %s removed", lightdm_user_get_name (info));
+ g_signal_emit (user_list, list_signals[USER_REMOVED], 0, info);
+ g_object_unref (info);
+ }
+ }
+ g_list_free (old_users);
+}
+
+static void
+passwd_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, LightDMUserList *user_list)
+{
+ if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+ {
+ g_debug ("%s changed, reloading user list", g_file_get_path (file));
+ load_passwd_file (user_list, TRUE);
+ }
+}
+
+static gboolean
+update_user (UserAccountObject *object)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (object->user);
+ GVariant *result, *value;
+ GVariantIter *iter;
+ gchar *name;
+ GError *error = NULL;
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (object->proxy),
+ "org.freedesktop.Accounts",
+ g_dbus_proxy_get_object_path (object->proxy),
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ g_variant_new ("(s)", "org.freedesktop.Accounts.User"),
+ G_VARIANT_TYPE ("(a{sv})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error updating user %s: %s", g_dbus_proxy_get_object_path (object->proxy), error->message);
+ g_clear_error (&error);
+ if (!result)
+ return FALSE;
+
+ g_variant_get (result, "(a{sv})", &iter);
+ while (g_variant_iter_loop (iter, "{&sv}", &name, &value))
+ {
+ if (strcmp (name, "UserName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *user_name;
+ g_variant_get (value, "&s", &user_name);
+ g_free (priv->name);
+ priv->name = g_strdup (user_name);
+ }
+ else if (strcmp (name, "RealName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *real_name;
+ g_variant_get (value, "&s", &real_name);
+ g_free (priv->real_name);
+ priv->real_name = g_strdup (real_name);
+ }
+ else if (strcmp (name, "HomeDirectory") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *home_directory;
+ g_variant_get (value, "&s", &home_directory);
+ g_free (priv->home_directory);
+ priv->home_directory = g_strdup (home_directory);
+ }
+ else if (strcmp (name, "IconFile") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *icon_file;
+ g_variant_get (value, "&s", &icon_file);
+ g_free (priv->image);
+ if (strcmp (icon_file, "") == 0)
+ priv->image = NULL;
+ else
+ priv->image = g_strdup (icon_file);
+ }
+ else if (strcmp (name, "BackgroundFile") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *background_file;
+ g_variant_get (value, "&s", &background_file);
+ g_free (priv->background);
+ if (strcmp (background_file, "") == 0)
+ priv->background = NULL;
+ else
+ priv->background = g_strdup (background_file);
+ }
+ }
+ g_variant_iter_free (iter);
+
+ g_variant_unref (result);
+
+ return TRUE;
+}
+
+static void
+user_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UserAccountObject *object)
+{
+ if (strcmp (signal_name, "Changed") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("()")))
+ {
+ g_debug ("User %s changed", g_dbus_proxy_get_object_path (object->proxy));
+ update_user (object);
+ g_signal_emit (object->user, user_signals[CHANGED], 0);
+ }
+ else
+ g_warning ("Got org.freedesktop.Accounts.User signal Changed with unknown parameters %s", g_variant_get_type_string (parameters));
+ }
+}
+
+static UserAccountObject *
+user_account_object_new (LightDMUserList *user_list, const gchar *path)
+{
+ GDBusProxy *proxy;
+ UserAccountObject *object;
+ GError *error = NULL;
+
+ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.Accounts",
+ path,
+ "org.freedesktop.Accounts.User",
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting user %s: %s", path, error->message);
+ g_clear_error (&error);
+ if (!proxy)
+ return NULL;
+
+ object = g_malloc0 (sizeof (UserAccountObject));
+ object->user = g_object_new (LIGHTDM_TYPE_USER, NULL);
+ GET_USER_PRIVATE (object->user)->user_list = user_list;
+ object->proxy = proxy;
+ g_signal_connect (proxy, "g-signal", G_CALLBACK (user_signal_cb), object);
+
+ return object;
+}
+
+static void
+user_account_object_free (UserAccountObject *object)
+{
+ if (!object)
+ return;
+ g_object_unref (object->user);
+ g_object_unref (object->proxy);
+ g_free (object);
+}
+
+static UserAccountObject *
+find_user_account_object (LightDMUserList *user_list, const gchar *path)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GList *link;
+
+ for (link = priv->user_account_objects; link; link = link->next)
+ {
+ UserAccountObject *object = link->data;
+ if (strcmp (g_dbus_proxy_get_object_path (object->proxy), path) == 0)
+ return object;
+ }
+
+ return NULL;
+}
+
+static void
+user_accounts_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+
+ if (strcmp (signal_name, "UserAdded") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ UserAccountObject *object;
+
+ g_variant_get (parameters, "(&o)", &path);
+
+ /* Ignore duplicate requests */
+ object = find_user_account_object (user_list, path);
+ if (object)
+ return;
+
+ object = user_account_object_new (user_list, path);
+ if (object && update_user (object))
+ {
+ g_debug ("User %s added", path);
+ priv->user_account_objects = g_list_append (priv->user_account_objects, object);
+ priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user);
+ g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list);
+ g_signal_emit (user_list, list_signals[USER_ADDED], 0, object->user);
+ }
+ else
+ user_account_object_free (object);
+ }
+ else
+ g_warning ("Got UserAccounts signal UserAdded with unknown parameters %s", g_variant_get_type_string (parameters));
+ }
+ else if (strcmp (signal_name, "UserDeleted") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ UserAccountObject *object;
+
+ g_variant_get (parameters, "(&o)", &path);
+
+ object = find_user_account_object (user_list, path);
+ if (!object)
+ return;
+
+ g_debug ("User %s deleted", path);
+ priv->users = g_list_remove (priv->users, object->user);
+ g_object_unref (object->user);
+
+ g_signal_emit (user_list, list_signals[USER_REMOVED], 0, object->user);
+
+ priv->user_account_objects = g_list_remove (priv->user_account_objects, object);
+ user_account_object_free (object);
+ }
+ else
+ g_warning ("Got UserAccounts signal UserDeleted with unknown parameters %s", g_variant_get_type_string (parameters));
+ }
+}
+
+static Session *
+load_session (LightDMUserList *user_list, const gchar *path)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ Session *session = NULL;
+ GVariant *result, *username;
+ GError *error = NULL;
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy),
+ "org.freedesktop.DisplayManager",
+ path,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)", "org.freedesktop.DisplayManager.Session", "UserName"),
+ G_VARIANT_TYPE ("(v)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting UserName from org.freedesktop.DisplayManager.Session: %s", error->message);
+ g_clear_error (&error);
+ if (!result)
+ return NULL;
+
+ g_variant_get (result, "(v)", &username);
+ if (g_variant_is_of_type (username, G_VARIANT_TYPE_STRING))
+ {
+ gchar *name;
+
+ g_variant_get (username, "&s", &name);
+
+ g_debug ("Loaded session %s (%s)", path, name);
+ session = g_object_new (session_get_type (), NULL);
+ session->username = g_strdup (name);
+ session->path = g_strdup (path);
+ priv->sessions = g_list_append (priv->sessions, session);
+ }
+ g_variant_unref (username);
+ g_variant_unref (result);
+
+ return session;
+}
+
+static void
+display_manager_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+
+ if (strcmp (signal_name, "SessionAdded") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ Session *session;
+ LightDMUser *user = NULL;
+
+ g_variant_get (parameters, "(&o)", &path);
+ session = load_session (user_list, path);
+ if (session)
+ user = get_user_by_name (user_list, session->username);
+ if (user)
+ g_signal_emit (user, user_signals[CHANGED], 0);
+ }
+ }
+ else if (strcmp (signal_name, "SessionRemoved") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ GList *link;
+
+ g_variant_get (parameters, "(&o)", &path);
+
+ for (link = priv->sessions; link; link = link->next)
+ {
+ Session *session = link->data;
+ if (strcmp (session->path, path) == 0)
+ {
+ LightDMUser *user;
+
+ g_debug ("Session %s removed", path);
+ priv->sessions = g_list_remove_link (priv->sessions, link);
+ user = get_user_by_name (user_list, session->username);
+ if (user)
+ g_signal_emit (user, user_signals[CHANGED], 0);
+ g_object_unref (session);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+update_users (LightDMUserList *user_list)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GError *error = NULL;
+
+ if (priv->have_users)
+ return;
+ priv->have_users = TRUE;
+
+ priv->accounts_service_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.Accounts",
+ "/org/freedesktop/Accounts",
+ "org.freedesktop.Accounts",
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error contacting org.freedesktop.Accounts: %s", error->message);
+ g_clear_error (&error);
+
+ /* Check if the service exists */
+ if (priv->accounts_service_proxy)
+ {
+ gchar *name;
+
+ name = g_dbus_proxy_get_name_owner (priv->accounts_service_proxy);
+ if (!name)
+ {
+ g_debug ("org.freedesktop.Accounts does not exist, falling back to passwd file");
+ g_object_unref (priv->accounts_service_proxy);
+ priv->accounts_service_proxy = NULL;
+ }
+ g_free (name);
+ }
+
+ if (priv->accounts_service_proxy)
+ {
+ GVariant *result;
+
+ g_signal_connect (priv->accounts_service_proxy, "g-signal", G_CALLBACK (user_accounts_signal_cb), user_list);
+
+ result = g_dbus_proxy_call_sync (priv->accounts_service_proxy,
+ "ListCachedUsers",
+ g_variant_new ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting user list from org.freedesktop.Accounts: %s", error->message);
+ g_clear_error (&error);
+ if (!result)
+ return;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(ao)")))
+ {
+ GVariantIter *iter;
+ const gchar *path;
+
+ g_debug ("Loading users from org.freedesktop.Accounts");
+ g_variant_get (result, "(ao)", &iter);
+ while (g_variant_iter_loop (iter, "&o", &path))
+ {
+ UserAccountObject *object;
+
+ g_debug ("Loading user %s", path);
+
+ object = user_account_object_new (user_list, path);
+ if (object && update_user (object))
+ {
+ priv->user_account_objects = g_list_append (priv->user_account_objects, object);
+ priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user);
+ g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list);
+ }
+ else
+ user_account_object_free (object);
+ }
+ g_variant_iter_free (iter);
+ }
+ else
+ g_warning ("Unexpected type from ListCachedUsers: %s", g_variant_get_type_string (result));
+
+ g_variant_unref (result);
+ }
+ else
+ {
+ GFile *passwd_file;
+
+ load_passwd_file (user_list, FALSE);
+
+ /* Watch for changes to user list */
+
+ passwd_file = g_file_new_for_path (PASSWD_FILE);
+ priv->passwd_monitor = g_file_monitor (passwd_file, G_FILE_MONITOR_NONE, NULL, &error);
+ g_object_unref (passwd_file);
+ if (error)
+ g_warning ("Error monitoring %s: %s", PASSWD_FILE, error->message);
+ else
+ g_signal_connect (priv->passwd_monitor, "changed", G_CALLBACK (passwd_changed_cb), user_list);
+ g_clear_error (&error);
+ }
+
+ priv->display_manager_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.DisplayManager",
+ "/org/freedesktop/DisplayManager",
+ "org.freedesktop.DisplayManager",
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error contacting org.freedesktop.DisplayManager: %s", error->message);
+ g_clear_error (&error);
+
+ if (priv->display_manager_proxy)
+ {
+ GVariant *result;
+
+ g_signal_connect (priv->display_manager_proxy, "g-signal", G_CALLBACK (display_manager_signal_cb), user_list);
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy),
+ "org.freedesktop.DisplayManager",
+ "/org/freedesktop/DisplayManager",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)", "org.freedesktop.DisplayManager", "Sessions"),
+ G_VARIANT_TYPE ("(v)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting session list from org.freedesktop.DisplayManager: %s", error->message);
+ g_clear_error (&error);
+ if (!result)
+ return;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(v)")))
+ {
+ GVariant *value;
+ GVariantIter *iter;
+ const gchar *path;
+
+ g_variant_get (result, "(v)", &value);
+
+ g_debug ("Loading sessions from org.freedesktop.DisplayManager");
+ g_variant_get (value, "ao", &iter);
+ while (g_variant_iter_loop (iter, "&o", &path))
+ load_session (user_list, path);
+ g_variant_iter_free (iter);
+
+ g_variant_unref (value);
+ }
+ else
+ g_warning ("Unexpected type from org.freedesktop.DisplayManager.Sessions: %s", g_variant_get_type_string (result));
+
+ g_variant_unref (result);
+ }
+}
+
+/**
+ * lightdm_user_list_get_length:
+ * @user_list: a #LightDMUserList
+ *
+ * Return value: The number of users able to log in
+ **/
+gint
+lightdm_user_list_get_length (LightDMUserList *user_list)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), 0);
+ update_users (user_list);
+ return g_list_length (GET_LIST_PRIVATE (user_list)->users);
+}
+
+/**
+ * lightdm_user_list_get_users:
+ * @user_list: A #LightDMUserList
+ *
+ * Get a list of users to present to the user. This list may be a subset of the
+ * available users and may be empty depending on the server configuration.
+ *
+ * Return value: (element-type LightDMUser) (transfer none): A list of #LightDMUser that should be presented to the user.
+ **/
+GList *
+lightdm_user_list_get_users (LightDMUserList *user_list)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL);
+ update_users (user_list);
+ return GET_LIST_PRIVATE (user_list)->users;
+}
+
+/**
+ * lightdm_user_list_get_user_by_name:
+ * @user_list: A #LightDMUserList
+ * @username: Name of user to get.
+ *
+ * Get infomation about a given user or #NULL if this user doesn't exist.
+ *
+ * Return value: (transfer none): A #LightDMUser entry for the given user.
+ **/
+LightDMUser *
+lightdm_user_list_get_user_by_name (LightDMUserList *user_list, const gchar *username)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL);
+ g_return_val_if_fail (username != NULL, NULL);
+
+ update_users (user_list);
+
+ return get_user_by_name (user_list, username);
+}
+
+static void
+lightdm_user_list_init (LightDMUserList *user_list)
+{
+}
+
+static void
+lightdm_user_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_user_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMUserList *self;
+
+ self = LIGHTDM_USER_LIST (object);
+
+ switch (prop_id)
+ {
+ case LIST_PROP_NUM_USERS:
+ g_value_set_int (value, lightdm_user_list_get_length (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_user_list_finalize (GObject *object)
+{
+ LightDMUserList *self = LIGHTDM_USER_LIST (object);
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (self);
+
+ if (priv->accounts_service_proxy)
+ g_object_unref (priv->accounts_service_proxy);
+ g_list_free_full (priv->user_account_objects, (GDestroyNotify) user_account_object_free);
+ if (priv->passwd_monitor)
+ g_object_unref (priv->passwd_monitor);
+ g_list_free_full (priv->users, g_object_unref);
+ g_list_free_full (priv->sessions, g_object_unref);
+
+ G_OBJECT_CLASS (lightdm_user_list_parent_class)->finalize (object);
+}
+
+static void
+lightdm_user_list_class_init (LightDMUserListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMUserListPrivate));
+
+ object_class->set_property = lightdm_user_list_set_property;
+ object_class->get_property = lightdm_user_list_get_property;
+ object_class->finalize = lightdm_user_list_finalize;
+
+ g_object_class_install_property (object_class,
+ LIST_PROP_NUM_USERS,
+ g_param_spec_int ("num-users",
+ "num-users",
+ "Number of login users",
+ 0, G_MAXINT, 0,
+ G_PARAM_READABLE));
+ /**
+ * LightDMUserList::user-added:
+ * @user_list: A #LightDMUserList
+ * @user: The #LightDM user that has been added.
+ *
+ * The ::user-added signal gets emitted when a user account is created.
+ **/
+ list_signals[USER_ADDED] =
+ g_signal_new ("user-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserListClass, user_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
+
+ /**
+ * LightDMUserList::user-changed:
+ * @user_list: A #LightDMUserList
+ * @user: The #LightDM user that has been changed.
+ *
+ * The ::user-changed signal gets emitted when a user account is modified.
+ **/
+ list_signals[USER_CHANGED] =
+ g_signal_new ("user-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserListClass, user_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
+
+ /**
+ * LightDMUserList::user-removed:
+ * @user_list: A #LightDMUserList
+ * @user: The #LightDM user that has been removed.
+ *
+ * The ::user-removed signal gets emitted when a user account is removed.
+ **/
+ list_signals[USER_REMOVED] =
+ g_signal_new ("user-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserListClass, user_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
+}
+
+/**
+ * lightdm_user_get_name:
+ * @user: A #LightDMUser
+ *
+ * Get the name of a user.
+ *
+ * Return value: The name of the given user
+ **/
+const gchar *
+lightdm_user_get_name (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->name;
+}
+
+/**
+ * lightdm_user_get_real_name:
+ * @user: A #LightDMUser
+ *
+ * Get the real name of a user.
+ *
+ * Return value: The real name of the given user
+ **/
+const gchar *
+lightdm_user_get_real_name (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->real_name;
+}
+
+/**
+ * lightdm_user_get_display_name:
+ * @user: A #LightDMUser
+ *
+ * Get the display name of a user.
+ *
+ * Return value: The display name of the given user
+ **/
+const gchar *
+lightdm_user_get_display_name (LightDMUser *user)
+{
+ LightDMUserPrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+
+ priv = GET_USER_PRIVATE (user);
+ if (strcmp (priv->real_name, ""))
+ return priv->real_name;
+ else
+ return priv->name;
+}
+
+/**
+ * lightdm_user_get_home_directory:
+ * @user: A #LightDMUser
+ *
+ * Get the home directory for a user.
+ *
+ * Return value: The users home directory
+ */
+const gchar *
+lightdm_user_get_home_directory (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->home_directory;
+}
+
+/**
+ * lightdm_user_get_image:
+ * @user: A #LightDMUser
+ *
+ * Get the image URI for a user.
+ *
+ * Return value: The image URI for the given user or #NULL if no URI
+ **/
+const gchar *
+lightdm_user_get_image (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->image;
+}
+
+/**
+ * lightdm_user_get_background:
+ * @user: A #LightDMUser
+ *
+ * Get the background file path for a user.
+ *
+ * Return value: The background file path for the given user or #NULL if no path
+ **/
+const gchar *
+lightdm_user_get_background (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->background;
+}
+
+static void
+load_dmrc (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+ gchar *path;
+ //gboolean have_dmrc;
+
+ if (!priv->dmrc_file)
+ priv->dmrc_file = g_key_file_new ();
+
+ /* Load from the user directory */
+ path = g_build_filename (priv->home_directory, ".dmrc", NULL);
+ /*have_dmrc = */g_key_file_load_from_file (priv->dmrc_file, path, G_KEY_FILE_KEEP_COMMENTS, NULL);
+ g_free (path);
+
+ /* If no ~/.dmrc, then load from the cache */
+ // FIXME
+
+ // FIXME: Watch for changes
+
+ /* The Language field is actually a locale, strip the codeset off it to get the language */
+ if (priv->language)
+ g_free (priv->language);
+ priv->language = g_key_file_get_string (priv->dmrc_file, "Desktop", "Language", NULL);
+ if (priv->language)
+ {
+ gchar *codeset = strchr (priv->language, '.');
+ if (codeset)
+ *codeset = '\0';
+ }
+
+ if (priv->layouts)
+ {
+ g_strfreev (priv->layouts);
+ priv->layouts = NULL;
+ }
+ if (g_key_file_has_key (priv->dmrc_file, "Desktop", "Layout", NULL))
+ {
+ priv->layouts = g_malloc (sizeof (gchar *) * 2);
+ priv->layouts[0] = g_key_file_get_string (priv->dmrc_file, "Desktop", "Layout", NULL);
+ priv->layouts[1] = NULL;
+ }
+
+ if (priv->session)
+ g_free (priv->session);
+ priv->session = g_key_file_get_string (priv->dmrc_file, "Desktop", "Session", NULL);
+}
+
+static GVariant *
+get_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+
+ if (!proxy)
+ return NULL;
+
+ answer = g_dbus_proxy_get_cached_property (proxy, property);
+
+ if (!answer)
+ {
+ g_warning ("Could not get accounts property %s", property);
+ return NULL;
+ }
+
+ return answer;
+}
+
+static gboolean
+get_boolean_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+ gboolean rv;
+
+ answer = get_property (proxy, property);
+ if (!g_variant_is_of_type (answer, G_VARIANT_TYPE_BOOLEAN))
+ {
+ g_warning ("Unexpected accounts property type for %s: %s",
+ property, g_variant_get_type_string (answer));
+ g_variant_unref (answer);
+ return FALSE;
+ }
+
+ rv = g_variant_get_boolean (answer);
+ g_variant_unref (answer);
+
+ return rv;
+}
+
+static gchar *
+get_string_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+ gchar *rv;
+
+ answer = get_property (proxy, property);
+ if (!g_variant_is_of_type (answer, G_VARIANT_TYPE_STRING))
+ {
+ g_warning ("Unexpected accounts property type for %s: %s",
+ property, g_variant_get_type_string (answer));
+ g_variant_unref (answer);
+ return NULL;
+ }
+
+ rv = g_strdup (g_variant_get_string (answer, NULL));
+ if (strcmp (rv, "") == 0)
+ {
+ g_free (rv);
+ rv = NULL;
+ }
+ g_variant_unref (answer);
+
+ return rv;
+}
+
+static gchar **
+get_string_array_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+ gchar **rv;
+
+ if (!proxy)
+ return NULL;
+
+ answer = g_dbus_proxy_get_cached_property (proxy, property);
+
+ if (!answer)
+ {
+ g_warning ("Could not get accounts property %s", property);
+ return NULL;
+ }
+
+ if (!g_variant_is_of_type (answer, G_VARIANT_TYPE ("as")))
+ {
+ g_warning ("Unexpected accounts property type for %s: %s",
+ property, g_variant_get_type_string (answer));
+ g_variant_unref (answer);
+ return NULL;
+ }
+
+ rv = g_variant_dup_strv (answer, NULL);
+
+ g_variant_unref (answer);
+ return rv;
+}
+
+static gboolean
+load_accounts_service (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+ LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list);
+ UserAccountObject *account = NULL;
+ GList *iter;
+ gchar **value;
+
+ /* First, find AccountObject proxy */
+ for (iter = list_priv->user_account_objects; iter; iter = iter->next)
+ {
+ UserAccountObject *a = iter->data;
+ if (a->user == user)
+ {
+ account = a;
+ break;
+ }
+ }
+ if (!account)
+ return FALSE;
+
+ /* We have proxy, let's grab some properties */
+ if (priv->language)
+ g_free (priv->language);
+ priv->language = get_string_property (account->proxy, "Language");
+ if (priv->session)
+ g_free (priv->session);
+ priv->session = get_string_property (account->proxy, "XSession");
+
+ value = get_string_array_property (account->proxy, "XKeyboardLayouts");
+ if (value)
+ {
+ if (value[0])
+ {
+ g_strfreev (priv->layouts);
+ priv->layouts = value;
+ }
+ else
+ g_strfreev (value);
+ }
+
+ priv->has_messages = get_boolean_property (account->proxy, "XHasMessages");
+
+ return TRUE;
+}
+
+/* Loads language/layout/session info for user */
+static void
+load_user_values (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+
+ load_dmrc (user);
+ load_accounts_service (user); // overrides dmrc values
+
+ /* Ensure a few guarantees */
+ if (priv->layouts == NULL)
+ {
+ priv->layouts = g_malloc (sizeof (gchar *) * 1);
+ priv->layouts[0] = NULL;
+ }
+}
+
+/**
+ * lightdm_user_get_language:
+ * @user: A #LightDMUser
+ *
+ * Get the language for a user.
+ *
+ * Return value: The language for the given user or #NULL if using system defaults.
+ **/
+const gchar *
+lightdm_user_get_language (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->language;
+}
+
+/**
+ * lightdm_user_get_layout:
+ * @user: A #LightDMUser
+ *
+ * Get the keyboard layout for a user.
+ *
+ * Return value: The keyboard layout for the given user or #NULL if using system defaults. Copy the value if you want to use it long term.
+ **/
+const gchar *
+lightdm_user_get_layout (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->layouts[0];
+}
+
+/**
+ * lightdm_user_get_layouts:
+ * @user: A #LightDMUser
+ *
+ * Get the configured keyboard layouts for a user.
+ *
+ * Return value: (transfer none): A NULL-terminated array of keyboard layouts for the given user. Copy the values if you want to use them long term.
+ **/
+const gchar * const *
+lightdm_user_get_layouts (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return (const gchar * const *) GET_USER_PRIVATE (user)->layouts;
+}
+
+/**
+ * lightdm_user_get_session:
+ * @user: A #LightDMUser
+ *
+ * Get the session for a user.
+ *
+ * Return value: The session for the given user or #NULL if using system defaults.
+ **/
+const gchar *
+lightdm_user_get_session (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->session;
+}
+
+/**
+ * lightdm_user_get_logged_in:
+ * @user: A #LightDMUser
+ *
+ * Check if a user is logged in.
+ *
+ * Return value: #TRUE if the user is currently logged in.
+ **/
+gboolean
+lightdm_user_get_logged_in (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+ LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list);
+ GList *link;
+
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), FALSE);
+
+ for (link = list_priv->sessions; link; link = link->next)
+ {
+ Session *session = link->data;
+ if (strcmp (session->username, priv->name) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * lightdm_user_get_has_messages:
+ * @user: A #LightDMUser
+ *
+ * Check if a user has waiting messages.
+ *
+ * Return value: #TRUE if the user has waiting messages.
+ **/
+gboolean
+lightdm_user_get_has_messages (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), FALSE);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->has_messages;
+}
+
+static void
+lightdm_user_init (LightDMUser *user)
+{
+}
+
+static void
+lightdm_user_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_user_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMUser *self;
+
+ self = LIGHTDM_USER (object);
+
+ switch (prop_id)
+ {
+ case USER_PROP_NAME:
+ g_value_set_string (value, lightdm_user_get_name (self));
+ break;
+ case USER_PROP_REAL_NAME:
+ g_value_set_string (value, lightdm_user_get_real_name (self));
+ break;
+ case USER_PROP_DISPLAY_NAME:
+ g_value_set_string (value, lightdm_user_get_display_name (self));
+ break;
+ case USER_PROP_HOME_DIRECTORY:
+ g_value_set_string (value, lightdm_user_get_home_directory (self));
+ break;
+ case USER_PROP_IMAGE:
+ g_value_set_string (value, lightdm_user_get_image (self));
+ break;
+ case USER_PROP_BACKGROUND:
+ g_value_set_string (value, lightdm_user_get_background (self));
+ break;
+ case USER_PROP_LANGUAGE:
+ g_value_set_string (value, lightdm_user_get_language (self));
+ break;
+ case USER_PROP_LAYOUT:
+ g_value_set_string (value, lightdm_user_get_layout (self));
+ break;
+ case USER_PROP_LAYOUTS:
+ g_value_set_boxed (value, g_strdupv ((gchar **) lightdm_user_get_layouts (self)));
+ break;
+ case USER_PROP_SESSION:
+ g_value_set_string (value, lightdm_user_get_session (self));
+ break;
+ case USER_PROP_LOGGED_IN:
+ g_value_set_boolean (value, lightdm_user_get_logged_in (self));
+ break;
+ case USER_PROP_HAS_MESSAGES:
+ g_value_set_boolean (value, lightdm_user_get_has_messages (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_user_finalize (GObject *object)
+{
+ LightDMUser *self = LIGHTDM_USER (object);
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (self);
+
+ g_free (priv->name);
+ g_free (priv->real_name);
+ g_free (priv->home_directory);
+ g_free (priv->image);
+ g_free (priv->background);
+ g_strfreev (priv->layouts);
+ if (priv->dmrc_file)
+ g_key_file_free (priv->dmrc_file);
+}
+
+static void
+lightdm_user_class_init (LightDMUserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMUserPrivate));
+
+ object_class->set_property = lightdm_user_set_property;
+ object_class->get_property = lightdm_user_get_property;
+ object_class->finalize = lightdm_user_finalize;
+
+ g_object_class_install_property (object_class,
+ USER_PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Username",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_REAL_NAME,
+ g_param_spec_string ("real-name",
+ "real-name",
+ "Users real name",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_DISPLAY_NAME,
+ g_param_spec_string ("display-name",
+ "display-name",
+ "Users display name",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_HOME_DIRECTORY,
+ g_param_spec_string ("home-directory",
+ "home-directory",
+ "Home directory",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_IMAGE,
+ g_param_spec_string ("image",
+ "image",
+ "Avatar image",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_BACKGROUND,
+ g_param_spec_string ("background",
+ "background",
+ "User background",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LANGUAGE,
+ g_param_spec_string ("language",
+ "language",
+ "Language used by this user",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LAYOUT,
+ g_param_spec_string ("layout",
+ "layout",
+ "Keyboard layout used by this user",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LAYOUTS,
+ g_param_spec_boxed ("layouts",
+ "layouts",
+ "Keyboard layouts used by this user",
+ G_TYPE_STRV,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_SESSION,
+ g_param_spec_string ("session",
+ "session",
+ "Session used by this user",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LOGGED_IN,
+ g_param_spec_boolean ("logged-in",
+ "logged-in",
+ "TRUE if the user is currently in a session",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LOGGED_IN,
+ g_param_spec_boolean ("has-messages",
+ "has-messages",
+ "TRUE if the user is has waiting messages",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * LightDMUser::changed:
+ * @user: A #LightDMUser
+ *
+ * The ::changed signal gets emitted this user account is modified.
+ **/
+ user_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+session_init (Session *session)
+{
+}
+
+static void
+session_finalize (GObject *object)
+{
+ Session *self = SESSION (object);
+
+ g_free (self->path);
+ g_free (self->username);
+}
+
+static void
+session_class_init (SessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = session_finalize;
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp
new file mode 100644
index 00000000..7a66d9e9
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp
@@ -0,0 +1,1531 @@
+/* $Id: vbox-greeter.cpp $ */
+/** @file
+ * vbox-greeter - an own LightDM greeter module supporting auto-logons
+ * controlled by the host.
+ */
+
+/*
+ * Copyright (C) 2012-2022 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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define GLIB_DISABLE_DEPRECATION_WARNINGS 1 /* g_type_init() is deprecated */
+#include <pwd.h>
+#include <syslog.h>
+#include <stdlib.h>
+
+#include <lightdm.h>
+#ifdef VBOX_WITH_FLTK
+# include <FL/Fl.H>
+# include <FL/fl_ask.H> /* Yes, the casing is correct for 1.3.0 -- d'oh. */
+# include <FL/Fl_Box.H>
+# include <FL/Fl_Button.H>
+# include <FL/fl_draw.H> /* Same as above. */
+# include <FL/Fl_Double_Window.H>
+# include <FL/Fl_Input.H>
+# include <FL/Fl_Menu_Button.H>
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+# include <FL/Fl_PNG_Image.H>
+# include <FL/Fl_Shared_Image.H>
+# endif
+# include <FL/Fl_Secret_Input.H>
+#else
+# include <cairo-xlib.h>
+# include <gtk/gtk.h>
+# include <gdk/gdkx.h>
+#endif
+
+#include <package-generated.h>
+#include "product-generated.h"
+
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+
+/* The greeter's full name for logging. */
+#define VBOX_MODULE_NAME "vbox-lightdm-greeter"
+
+/* UI elements used in this greeter. */
+#define VBOX_GREETER_UI_WND_GREETER "wnd_greeter"
+
+#define VBOX_GREETER_UI_EDT_USER "edt_username"
+#define VBOX_GREETER_UI_EDT_PASSWORD "edt_password"
+#define VBOX_GREETER_UI_BTN_LOGIN "btn_login"
+#define VBOX_GREETER_UI_LBL_INFO "lbl_info"
+
+/* UI display options. */
+/** Show the restart menu entry / button. */
+#define VBOX_GREETER_UI_SHOW_RESTART RT_BIT(0)
+/** Show the shutdown menu entry / button. */
+#define VBOX_GREETER_UI_SHOW_SHUTDOWN RT_BIT(1)
+/** Show the (customized) top banner. */
+#define VBOX_GREETER_UI_SHOW_BANNER RT_BIT(2)
+/** Enable custom colors */
+#define VBOX_GREETER_UI_USE_THEMING RT_BIT(3)
+
+/** Extracts the 8-bit red component from an uint32_t. */
+#define VBOX_RGB_COLOR_RED(uColor) uColor & 0xFF
+/** Extracts the 8-bit green component from an uint32_t. */
+#define VBOX_RGB_COLOR_GREEN(uColor) (uColor >> 8) & 0xFF
+/** Extracts the 8-bit blue component from an uint32_t. */
+#define VBOX_RGB_COLOR_BLUE(uColor) (uColor >> 16) & 0xFF
+
+#include <VBox/log.h>
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+#endif
+
+/** The program name (derived from argv[0]). */
+char *g_pszProgName = (char *)"";
+/** For debugging. */
+#ifdef DEBUG
+ static int g_iVerbosity = 99;
+#else
+ static int g_iVerbosity = 0;
+#endif
+static bool g_fRunning = true;
+
+/** Logging parameters. */
+/** @todo Make this configurable later. */
+static PRTLOGGER g_pLoggerRelease = NULL;
+static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */
+static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */
+static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */
+
+/**
+ * Context structure which contains all needed
+ * data within callbacks.
+ */
+typedef struct VBOXGREETERCTX
+{
+ /** Pointer to this greeter instance. */
+ LightDMGreeter *pGreeter;
+#ifdef VBOX_WITH_FLTK
+ Fl_Button *pBtnLogin;
+ Fl_Input *pEdtUsername;
+ Fl_Secret_Input *pEdtPassword;
+ Fl_Box *pLblInfo;
+#else
+ /** The GTK builder instance for accessing
+ * the UI elements. */
+ GtkBuilder *pBuilder;
+#endif
+ /** The timeout (in ms) to wait for credentials. */
+ uint32_t uTimeoutMS;
+ /** The starting timestamp (in ms) to calculate
+ * the timeout. */
+ uint64_t uStartMS;
+ /** Timestamp of last abort message. */
+ uint64_t uTsAbort;
+ /** The HGCM client ID. */
+ uint32_t uClientId;
+ /** The credential password. */
+ char *pszPassword;
+} VBOXGREETERCTX, *PVBOXGREETERCTX;
+
+static void vboxGreeterError(const char *pszFormat, ...)
+{
+ va_list va;
+ char *buf;
+ va_start(va, pszFormat);
+ if (RTStrAPrintfV(&buf, pszFormat, va))
+ {
+ RTLogRelPrintf("%s: error: %s", VBOX_MODULE_NAME, buf);
+ RTStrFree(buf);
+ }
+ va_end(va);
+}
+
+static void vboxGreeterLog(const char *pszFormat, ...)
+{
+ if (g_iVerbosity)
+ {
+ va_list va;
+ char *buf;
+ va_start(va, pszFormat);
+ if (RTStrAPrintfV(&buf, pszFormat, va))
+ {
+ /* Only do normal logging in debug mode; could contain
+ * sensitive data! */
+ RTLogRelPrintf("%s: %s", VBOX_MODULE_NAME, buf);
+ RTStrFree(buf);
+ }
+ va_end(va);
+ }
+}
+
+/** @tood Move the following two functions to VbglR3 (also see pam_vbox). */
+#ifdef VBOX_WITH_GUEST_PROPS
+
+/**
+ * Reads a guest property.
+ *
+ * @return IPRT status code.
+ * @param hPAM PAM handle.
+ * @param uClientID Guest property service client ID.
+ * @param pszKey Key (name) of guest property to read.
+ * @param fReadOnly Indicates whether this key needs to be
+ * checked if it only can be read (and *not* written)
+ * by the guest.
+ * @param pszValue Buffer where to store the key's value.
+ * @param cbValue Size of buffer (in bytes).
+ * @param puTimestamp Timestamp of the value
+ * retrieved. Optional.
+ */
+static int vbox_read_prop(uint32_t uClientID,
+ const char *pszKey, bool fReadOnly,
+ char *pszValue, size_t cbValue, uint64_t *puTimestamp)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ /* puTimestamp is optional. */
+
+ int rc;
+
+ uint64_t u64Timestamp = 0;
+ char *pszValTemp = NULL;
+ char *pszFlags = NULL;
+ /* The buffer for storing the data and its initial size. We leave a bit
+ * of space here in case the maximum values are raised. */
+ void *pvBuf = NULL;
+ uint32_t cbBuf = GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + _1K;
+
+ /* Because there is a race condition between our reading the size of a
+ * property and the guest updating it, we loop a few times here and
+ * hope. Actually this should never go wrong, as we are generous
+ * enough with buffer space. */
+ for (unsigned i = 0; i < 10; i++)
+ {
+ pvBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvBuf)
+ {
+ rc = VbglR3GuestPropRead(uClientID, pszKey, pvBuf, cbBuf,
+ &pszValTemp, &u64Timestamp, &pszFlags,
+ &cbBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ switch (rc)
+ {
+ case VERR_BUFFER_OVERFLOW:
+ {
+ /* Buffer too small, try it with a bigger one next time. */
+ cbBuf += _1K;
+ continue; /* Try next round. */
+ }
+
+ default:
+ break;
+ }
+
+ /* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Check security bits. */
+ if (pszFlags)
+ {
+ if ( fReadOnly
+ && !RTStrStr(pszFlags, "RDONLYGUEST"))
+ {
+ /* If we want a property which is read-only on the guest
+ * and it is *not* marked as such, deny access! */
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else /* No flags, no access! */
+ rc = VERR_ACCESS_DENIED;
+
+ if (RT_SUCCESS(rc))
+ {
+ /* If everything went well copy property value to our destination buffer. */
+ if (!RTStrPrintf(pszValue, cbValue, "%s", pszValTemp))
+ rc = VERR_BUFFER_OVERFLOW;
+
+ if (puTimestamp)
+ *puTimestamp = u64Timestamp;
+ }
+ }
+
+#ifdef DEBUG
+ vboxGreeterLog("Read guest property \"%s\"=\"%s\" (Flags: %s, TS: %RU64): %Rrc\n",
+ pszKey, pszValTemp ? pszValTemp : "<None>",
+ pszFlags ? pszFlags : "<None>", u64Timestamp, rc);
+#endif
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+
+ return rc;
+}
+
+# if 0 /* unused */
+/**
+ * Waits for a guest property to be changed.
+ *
+ * @return IPRT status code.
+ * @param hPAM PAM handle.
+ * @param uClientID Guest property service client ID.
+ * @param pszKey Key (name) of guest property to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait for the change. Specify
+ * RT_INDEFINITE_WAIT to wait indefinitly.
+ */
+static int vbox_wait_prop(uint32_t uClientID,
+ const char *pszKey, uint32_t uTimeoutMS)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+
+ int rc;
+
+ /* The buffer for storing the data and its initial size. We leave a bit
+ * of space here in case the maximum values are raised. */
+ void *pvBuf = NULL;
+ uint32_t cbBuf = MAX_NAME_LEN + MAX_VALUE_LEN + MAX_FLAGS_LEN + _1K;
+
+ for (int i = 0; i < 10; i++)
+ {
+ void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvTmpBuf)
+ {
+ char *pszName = NULL;
+ char *pszValue = NULL;
+ uint64_t u64TimestampOut = 0;
+ char *pszFlags = NULL;
+
+ pvBuf = pvTmpBuf;
+ rc = VbglR3GuestPropWait(uClientID, pszKey, pvBuf, cbBuf,
+ 0 /* Last timestamp; just wait for next event */, uTimeoutMS,
+ &pszName, &pszValue, &u64TimestampOut,
+ &pszFlags, &cbBuf, NULL);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* Buffer too small, try it with a bigger one next time. */
+ cbBuf += _1K;
+ continue; /* Try next round. */
+ }
+
+ /* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */
+ break;
+ }
+
+ return rc;
+}
+# endif /* unused */
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+/**
+ * Checks for credentials provided by the host / HGCM.
+ *
+ * @return IPRT status code. VERR_NOT_FOUND if no credentials are available,
+ * VINF_SUCCESS on successful retrieval or another IPRT error.
+ * @param pCtx Greeter context.
+ */
+static int vboxGreeterCheckCreds(PVBOXGREETERCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ static bool s_fCredsNotFoundMsgShown = false;
+ int rc = VbglR3CredentialsQueryAvailability();
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_NOT_FOUND)
+ vboxGreeterError("vboxGreeterCheckCreds: could not query for credentials! rc=%Rrc. Aborting\n", rc);
+ else if (!s_fCredsNotFoundMsgShown)
+ {
+ vboxGreeterLog("vboxGreeterCheckCreds: no credentials available\n");
+ s_fCredsNotFoundMsgShown = true;
+ }
+ }
+ else
+ {
+ /** @todo Domain handling needed? */
+ char *pszUsername; /* User name only is kept local. */
+ char *pszDomain = NULL;
+ rc = VbglR3CredentialsRetrieve(&pszUsername, &pCtx->pszPassword, &pszDomain);
+ if (RT_FAILURE(rc))
+ {
+ vboxGreeterError("vboxGreeterCheckCreds: could not retrieve credentials! rc=%Rrc. Aborting\n", rc);
+ }
+ else
+ {
+ vboxGreeterLog("vboxGreeterCheckCreds: credentials retrieved: user=%s, password=%s, domain=%s\n",
+ pszUsername,
+#ifdef DEBUG
+ pCtx->pszPassword,
+#else
+ "XXX",
+#endif
+ pszDomain);
+ /* Trigger LightDM authentication with the user name just retrieved. */
+ lightdm_greeter_authenticate(pCtx->pGreeter, pszUsername); /* Must be the real user name from host! */
+
+ /* Securely wipe the user name + domain again. */
+ VbglR3CredentialsDestroy(pszUsername, NULL /* pszPassword */, pszDomain,
+ 3 /* Three wipe passes */);
+ }
+ }
+
+#ifdef DEBUG
+ vboxGreeterLog("vboxGreeterCheckCreds: returned with rc=%Rrc\n", rc);
+#endif
+ return rc;
+}
+
+/**
+ * Called by LightDM when greeter is not needed anymore.
+ *
+ * @param signum Signal number.
+ */
+static void cb_sigterm(int signum)
+{
+ RT_NOREF(signum);
+
+ /* Note: This handler must be reentrant-safe. */
+#ifdef VBOX_WITH_FLTK
+ g_fRunning = false;
+#else
+ exit(RTEXITCODE_SUCCESS);
+#endif
+}
+
+/**
+ * Callback for showing a user prompt, issued by the LightDM server.
+ *
+ * @param pGreeter Pointer to this greeter instance.
+ * @param pszText Text to display.
+ * @param enmType Type of prompt to display.
+ * @param pvData Pointer to user-supplied data.
+ */
+static void cb_lightdm_show_prompt(LightDMGreeter *pGreeter,
+ const gchar *pszText, LightDMPromptType enmType,
+ gpointer pvData)
+{
+ vboxGreeterLog("cb_lightdm_show_prompt: text=%s, type=%d\n", pszText, enmType);
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+
+ switch (enmType)
+ {
+ case 1: /* Password. */
+ {
+ if (pCtx->pszPassword)
+ {
+ lightdm_greeter_respond(pGreeter, pCtx->pszPassword);
+ }
+ else
+ {
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pEdtPassword);
+ const char *pszPwd = pCtx->pEdtPassword->value();
+#else
+ GtkEntry *pEdtPwd = GTK_ENTRY(gtk_builder_get_object(pCtx->pBuilder, "edt_password"));
+ AssertPtr(pEdtPwd);
+ const gchar *pszPwd = gtk_entry_get_text(pEdtPwd);
+#endif
+ lightdm_greeter_respond(pGreeter, pszPwd);
+ }
+ break;
+ }
+ /** @todo Other fields? */
+
+ default:
+ break;
+ }
+
+ VbglR3CredentialsDestroy(NULL /* pszUsername */, pCtx->pszPassword, NULL /* pszDomain */,
+ 3 /* Three wipe passes */);
+ pCtx->pszPassword = NULL;
+}
+
+/**
+ * Callback for showing a message, issued by the LightDM server.
+ *
+ * @param pGreeter Pointer to this greeter instance.
+ * @param pszText Text to display.
+ * @param enmType Type of message to display.
+ * @param pvData Pointer to user-supplied data.
+ */
+static void cb_lightdm_show_message(LightDMGreeter *pGreeter,
+ const gchar *pszText, LightDMPromptType enmType,
+ gpointer pvData)
+{
+ RT_NOREF(pGreeter);
+ vboxGreeterLog("cb_lightdm_show_message: text=%s, type=%d\n", pszText, enmType);
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtrReturnVoid(pCtx);
+
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pLblInfo);
+ pCtx->pLblInfo->copy_label(pszText);
+#else
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pCtx->pBuilder, "lbl_info"));
+ AssertPtr(pLblInfo);
+ gtk_label_set_text(pLblInfo, pszText);
+#endif
+}
+
+/**
+ * Callback for authentication completion, issued by the LightDM server.
+ *
+ * @param pGreeter Pointer to this greeter instance.
+ */
+static void cb_lightdm_auth_complete(LightDMGreeter *pGreeter)
+{
+ vboxGreeterLog("cb_lightdm_auth_complete\n");
+
+ const gchar *pszUser = lightdm_greeter_get_authentication_user(pGreeter);
+ vboxGreeterLog("authenticating user: %s\n", pszUser ? pszUser : "<NULL>");
+
+ if (lightdm_greeter_get_is_authenticated(pGreeter))
+ {
+ /** @todo Add non-default session support. */
+ gchar *pszSession = g_strdup(lightdm_greeter_get_default_session_hint(pGreeter));
+ if (pszSession)
+ {
+ vboxGreeterLog("starting session: %s\n", pszSession);
+ GError *pError = NULL;
+ if (!lightdm_greeter_start_session_sync(pGreeter, pszSession, &pError))
+ {
+ vboxGreeterError("unable to start session '%s': %s\n",
+ pszSession, pError ? pError->message : "Unknown error");
+ }
+ else
+ {
+ AssertPtr(pszSession);
+ vboxGreeterLog("session '%s' successfully started\n", pszSession);
+ }
+ if (pError)
+ g_error_free(pError);
+ g_free(pszSession);
+ }
+ else
+ vboxGreeterError("unable to get default session\n");
+ }
+ else
+ vboxGreeterLog("user not authenticated successfully (yet)\n");
+}
+
+/**
+ * Callback for clicking on the "Login" button.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_login(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_login(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ RT_NOREF(pWidget);
+ AssertPtr(pCtx);
+
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pEdtUsername);
+ const char *pszUser = pCtx->pEdtUsername->value();
+ AssertPtr(pCtx->pEdtPassword);
+ const char *pszPwd = pCtx->pEdtPassword->value();
+#else
+ GtkEntry *pEdtUser = GTK_ENTRY(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_EDT_USER));
+ AssertPtr(pEdtUser);
+ const gchar *pszUser = gtk_entry_get_text(pEdtUser);
+
+ GtkEntry *pEdtPwd = GTK_ENTRY(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_EDT_PASSWORD));
+ AssertPtr(pEdtPwd);
+ const gchar *pszPwd = gtk_entry_get_text(pEdtPwd);
+#endif
+
+ /** @todo Add domain handling? */
+ vboxGreeterLog("login button pressed: greeter=%p, user=%s, password=%s\n",
+ pCtx->pGreeter,
+ pszUser ? pszUser : "<NONE>",
+#ifdef DEBUG
+ pszPwd ? pszPwd : "<NONE>");
+#else
+ /* Don't log passwords in release mode! */
+ "XXX");
+#endif
+ if (strlen(pszUser)) /* Only authenticate if username is given. */
+ {
+ lightdm_greeter_respond(pCtx->pGreeter, pszPwd);
+ lightdm_greeter_authenticate(pCtx->pGreeter, pszUser);
+ }
+}
+
+/**
+ * Callback for clicking on the "Menu" button.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_menu(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_menu(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("menu button pressed\n");
+}
+
+/**
+ * Callback for clicking on the "Restart" button / menu entry.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_restart(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_restart(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("restart button pressed\n");
+
+ bool fRestart = true;
+#ifdef VBOX_WITH_FLTK
+ int rc = fl_choice("Really restart the system?", "Yes", "No", NULL);
+ fRestart = rc == 0;
+#endif
+
+ if (fRestart)
+ {
+ vboxGreeterLog("restart requested\n");
+#ifndef DEBUG
+ lightdm_restart(NULL);
+#endif
+ }
+}
+
+/**
+ * Callback for clicking on the "Shutdown" button / menu entry.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_shutdown(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_shutdown(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("shutdown button pressed\n");
+
+ bool fShutdown = true;
+#ifdef VBOX_WITH_FLTK
+ int rc = fl_choice("Really shutdown the system?", "Yes", "No", NULL);
+ fShutdown = rc == 0;
+#endif
+
+ if (fShutdown)
+ {
+ vboxGreeterLog("shutdown requested\n");
+#ifndef DEBUG
+ lightdm_shutdown(NULL);
+#endif
+ }
+}
+
+#ifdef VBOX_WITH_FLTK
+void cb_edt_username(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_edt_username(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget);
+ vboxGreeterLog("cb_edt_username called\n");
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pEdtPassword);
+ Fl::focus(pCtx->pEdtPassword);
+#endif
+}
+
+#ifdef VBOX_WITH_FLTK
+void cb_edt_password(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_edt_password(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("cb_edt_password called\n");
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pBtnLogin);
+ cb_btn_login(pCtx->pBtnLogin, pvData);
+#endif
+}
+
+/**
+ * Callback for the timer event which is checking for new credentials
+ * from the host.
+ *
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+static void cb_check_creds(void *pvData)
+#else
+static gboolean cb_check_creds(gpointer pvData)
+#endif
+{
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+
+#ifdef DEBUG
+ vboxGreeterLog("cb_check_creds called, clientId=%RU32, timeoutMS=%RU32\n",
+ pCtx->uClientId, pCtx->uTimeoutMS);
+#endif
+
+ int rc = VINF_SUCCESS;
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ bool fAbort = false;
+ char szVal[255];
+ if (pCtx->uClientId)
+ {
+ uint64_t tsAbort;
+ rc = vbox_read_prop(pCtx->uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitAbort",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), &tsAbort);
+ switch (rc)
+ {
+ case VINF_SUCCESS:
+# ifdef DEBUG
+ vboxGreeterLog("cb_check_creds: tsAbort %RU64 <-> %RU64\n",
+ pCtx->uTsAbort, tsAbort);
+# endif
+ if (tsAbort != pCtx->uTsAbort)
+ fAbort = true; /* Timestamps differs, abort. */
+ pCtx->uTsAbort = tsAbort;
+ break;
+
+ case VERR_TOO_MUCH_DATA:
+ vboxGreeterError("cb_check_creds: temporarily unable to get abort notification\n");
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Value not found, continue checking for credentials. */
+ break;
+
+ default:
+ vboxGreeterError("cb_check_creds: the abort notification request failed with rc=%Rrc\n", rc);
+ fAbort = true; /* Abort on error. */
+ break;
+ }
+ }
+
+ if (fAbort)
+ {
+ /* Get optional message. */
+ szVal[0] = '\0';
+ int rc2 = vbox_read_prop(pCtx->uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaitAbort",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_NOT_FOUND)
+ vboxGreeterError("cb_check_creds: getting wait abort message failed with rc=%Rrc\n", rc2);
+# ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pLblInfo);
+ pCtx->pLblInfo->copy_label(szVal);
+# else /* !VBOX_WITH_FLTK */
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_LBL_INFO));
+ AssertPtr(pLblInfo);
+ gtk_label_set_text(pLblInfo, szVal);
+# endif /* !VBOX_WITH_FLTK */
+ vboxGreeterLog("cb_check_creds: got notification from host to abort waiting\n");
+ }
+ else
+ {
+#endif /* VBOX_WITH_GUEST_PROPS */
+ rc = vboxGreeterCheckCreds(pCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Credentials retrieved. */
+ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ /* No credentials found, but try next round (if there's
+ * time left for) ... */
+ }
+#ifdef VBOX_WITH_GUEST_PROPS
+ }
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ if (rc == VERR_NOT_FOUND) /* No credential found this round. */
+ {
+ /* Calculate timeout value left after process has been started. */
+ uint64_t u64Elapsed = RTTimeMilliTS() - pCtx->uStartMS;
+ /* Is it time to bail out? */
+ if (pCtx->uTimeoutMS < u64Elapsed)
+ {
+#ifdef VBOX_WITH_GUEST_PROPS
+ szVal[0] = '\0';
+ int rc2 = vbox_read_prop(pCtx->uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaitTimeout",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_NOT_FOUND)
+ vboxGreeterError("cb_check_creds: getting wait timeout message failed with rc=%Rrc\n", rc2);
+# ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pLblInfo);
+ pCtx->pLblInfo->copy_label(szVal);
+# else
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_LBL_INFO));
+ AssertPtr(pLblInfo);
+ gtk_label_set_text(pLblInfo, szVal);
+# endif
+#endif /* VBOX_WITH_GUEST_PROPS */
+ vboxGreeterLog("cb_check_creds: no credentials retrieved within time (%RU32ms), giving up\n",
+ pCtx->uTimeoutMS);
+ rc = VERR_TIMEOUT;
+ }
+ }
+
+#ifdef DEBUG
+ vboxGreeterLog("cb_check_creds returned with rc=%Rrc\n", rc);
+#endif
+
+ /* At the moment we only allow *one* shot from the host,
+ * so setting credentials in a second attempt won't be possible
+ * intentionally. */
+
+ if (rc == VERR_NOT_FOUND)
+#ifdef VBOX_WITH_FLTK
+ Fl::repeat_timeout(0.5 /* 500 ms */, cb_check_creds, pvData);
+#else
+ return TRUE; /* No credentials found, do another round. */
+
+ return FALSE; /* Remove timer source on every other error / status. */
+#endif
+}
+
+/**
+ * Release logger callback.
+ *
+ * @return IPRT status code.
+ * @param pLoggerRelease
+ * @param enmPhase
+ * @param pfnLog
+ */
+static DECLCALLBACK(void) vboxGreeterLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
+{
+ /* Some introductory information. */
+ static RTTIMESPEC s_TimeSpec;
+ char szTmp[256];
+ if (enmPhase == RTLOGPHASE_BEGIN)
+ RTTimeNow(&s_TimeSpec);
+ RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
+
+ switch (enmPhase)
+ {
+ case RTLOGPHASE_BEGIN:
+ {
+ pfnLog(pLoggerRelease,
+ "vbox-greeter %s r%s (verbosity: %d) %s (%s %s) release log\n"
+ "Log opened %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_iVerbosity, VBOX_BUILD_TARGET,
+ __DATE__, __TIME__, szTmp);
+
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp);
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp);
+
+ /* the package type is interesting for Linux distributions */
+ char szExecName[RTPATH_MAX];
+ char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
+ pfnLog(pLoggerRelease,
+ "Executable: %s\n"
+ "Process ID: %u\n"
+ "Package type: %s"
+#ifdef VBOX_OSE
+ " (OSE)"
+#endif
+ "\n",
+ pszExecName ? pszExecName : "unknown",
+ RTProcSelf(),
+ VBOX_PACKAGE_STRING);
+ break;
+ }
+
+ case RTLOGPHASE_PREROTATE:
+ pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_POSTROTATE:
+ pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_END:
+ pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp);
+ break;
+
+ default:
+ /* nothing */;
+ }
+}
+
+/**
+ * Creates the default release logger outputting to the specified file.
+ *
+ * @return IPRT status code.
+ * @param pszLogFile Filename for log output. Optional.
+ */
+static int vboxGreeterLogCreate(const char *pszLogFile)
+{
+ /* Create release logger (stdout + file). */
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXGREETER_RELEASE_LOG", fFlags, "all",
+ RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT,
+ vboxGreeterLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, pszLogFile);
+ if (RT_SUCCESS(rc))
+ {
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(g_pLoggerRelease);
+
+ /* Explicitly flush the log in case of VBOXGREETER_RELEASE_LOG_FLAGS=buffered. */
+ RTLogFlush(g_pLoggerRelease);
+ }
+
+ return rc;
+}
+
+static void vboxGreeterLogDestroy(void)
+{
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+}
+
+static int vboxGreeterUsage(void)
+{
+ RTPrintf("Usage:\n"
+ " %-12s [-h|-?|--help] [-F|--logfile <file>]\n"
+ " [-v|--verbose] [-V|--version]\n", g_pszProgName);
+
+ RTPrintf("\n"
+ " Copyright (C) 2012-" VBOX_C_YEAR " " VBOX_VENDOR "\n");
+
+ return RTEXITCODE_SYNTAX;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ g_pszProgName = RTPathFilename(argv[0]);
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--logfile", 'F', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING }
+ };
+
+ char szLogFile[RTPATH_MAX + 128] = "";
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv,
+ s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'F':
+ if (!RTStrPrintf(szLogFile, sizeof(szLogFile), "%s", ValueUnion.psz))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to get prepare log file name");
+ break;
+
+ case 'h':
+ case '?':
+ return vboxGreeterUsage();
+
+ case 'v': /* Raise verbosity. */
+ g_iVerbosity++;
+ break;
+
+ case 'V': /* Print version and exit. */
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+ break; /* Never reached. */
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_SYNTAX;
+
+ rc = VbglR3InitUser();
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to init Vbgl (%Rrc)", rc);
+
+ rc = vboxGreeterLogCreate(strlen(szLogFile) ? szLogFile : NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log (%s, %Rrc)",
+ strlen(szLogFile) ? szLogFile : "<None>", rc);
+
+ vboxGreeterLog("init\n");
+
+ signal(SIGTERM, cb_sigterm);
+
+ /** @todo This function already is too long. Move code into
+ * functions. */
+
+ VBOXGREETERCTX ctx;
+ RT_ZERO(ctx);
+
+ /* UI parameters. */
+ uint32_t uBgColor = 0; /* The background color. */
+ uint32_t uLogonDlgHdrColor = 0;
+ uint32_t uLogonDlgBgColor = 0; /* The greeter's dialog color. */
+ uint32_t uLogonDlgBtnColor = 0; /* The greeter's button color. */
+
+#ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ char szBannerPath[RTPATH_MAX];
+#endif
+
+ /* By default most UI elements are shown. */
+ uint32_t uOptsUI = VBOX_GREETER_UI_SHOW_RESTART
+ | VBOX_GREETER_UI_SHOW_SHUTDOWN;
+#ifdef VBOX_WITH_GUEST_PROPS
+ uint32_t uClientId = 0;
+ rc = VbglR3GuestPropConnect(&uClientId);
+ if (RT_SUCCESS(rc))
+ {
+ vboxGreeterLog("clientId=%RU32\n", uClientId);
+
+ ctx.uClientId = uClientId;
+
+ char szVal[256];
+ int rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/HideRestart",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_SUCCESS(rc2)
+ && !RTStrICmp(szVal, "1"))
+ {
+ uOptsUI &= ~VBOX_GREETER_UI_SHOW_RESTART;
+ }
+
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/HideShutdown",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_SUCCESS(rc2)
+ && !RTStrICmp(szVal, "1"))
+ {
+ uOptsUI &= ~VBOX_GREETER_UI_SHOW_SHUTDOWN;
+ }
+
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ /* Load the banner. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/BannerPath",
+ true /* Read-only on guest */,
+ szBannerPath, sizeof(szBannerPath), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ if (RTFileExists(szBannerPath))
+ {
+ vboxGreeterLog("showing banner from '%s'\n", szBannerPath);
+ uOptsUI |= VBOX_GREETER_UI_SHOW_BANNER;
+ }
+ else
+ vboxGreeterLog("warning: unable to find banner at '%s', skipping\n", szBannerPath);
+ }
+# endif /* VBOX_GREETER_WITH_PNG_SUPPORT */
+
+ /* Use theming?. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/UseTheming",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_SUCCESS(rc2)
+ && !RTStrICmp(szVal, "1"))
+ {
+ vboxGreeterLog("custom theming enabled\n");
+ uOptsUI |= VBOX_GREETER_UI_USE_THEMING;
+ }
+
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ {
+ /* Get background color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/BackgroundColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uBgColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+
+ /* Logon dialog. */
+
+ /* Get header color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/LogonDialog/HeaderColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uLogonDlgHdrColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+
+ /* Get dialog color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/LogonDialog/BackgroundColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uLogonDlgBgColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+
+ /* Get button color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/LogonDialog/ButtonColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uLogonDlgBtnColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+ }
+ }
+ else
+ vboxGreeterError("unable to connect to guest property service, rc=%Rrc\n", rc);
+#endif
+ vboxGreeterLog("UI options are: %RU32\n", uOptsUI);
+
+#ifdef VBOX_WITH_FLTK
+ int rc2 = Fl::scheme("plastic");
+ if (!rc2)
+ vboxGreeterLog("warning: unable to set visual scheme\n");
+
+ Fl::visual(FL_DOUBLE | FL_INDEX);
+ Fl_Double_Window *pWndMain = new Fl_Double_Window(Fl::w(), Fl::h(), "VirtualBox Guest Additions");
+ AssertPtr(pWndMain);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pWndMain->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uBgColor),
+ VBOX_RGB_COLOR_GREEN(uBgColor),
+ VBOX_RGB_COLOR_BLUE(uBgColor)));
+ else /* Default colors. */
+ pWndMain->color(fl_rgb_color(0x73, 0x7F, 0x8C));
+
+ Fl_Double_Window *pWndGreeter = new Fl_Double_Window(500, 350);
+ AssertPtr(pWndGreeter);
+ pWndGreeter->set_modal();
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pWndGreeter->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgBgColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgBgColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgBgColor)));
+ else /* Default colors. */
+ pWndGreeter->color(fl_rgb_color(255, 255, 255));
+
+ uint32_t uOffsetX = 130;
+ /**
+ * For now we're using a simple Y offset for moving all elements
+ * down if a banner needs to be shown on top of the greeter. Not
+ * very clean but does the job. Use some more layouting stuff
+ * when this gets more complex.
+ */
+ uint32_t uOffsetY = 80;
+
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ fl_register_images();
+
+ /** @todo Add basic image type detection based on file
+ * extension. */
+
+ Fl_PNG_Image *pImgBanner = NULL;
+ if (uOptsUI & VBOX_GREETER_UI_SHOW_BANNER)
+ {
+ pImgBanner = new Fl_PNG_Image(szBannerPath);
+ AssertPtr(pImgBanner);
+
+ /** @todo Make the banner size configurable via guest
+ * properties. For now it's hardcoded to 460 x 90px. */
+ Fl_Box *pBoxBanner = new Fl_Box(20, uOffsetY, 460, 90, "");
+ AssertPtr(pBoxBanner);
+ pBoxBanner->image(pImgBanner);
+
+ uOffsetY = 120;
+ }
+# endif
+
+ Fl_Box *pLblHeader = new Fl_Box(FL_NO_BOX, 242, uOffsetY, 300, 20,
+ "Desktop Login");
+ AssertPtr(pLblHeader);
+
+ /** Note to use an own font:
+ * Fl_Font myfnt = FL_FREE_FONT + 1;
+ * Fl::set_font(myfnt, "MyFont"); */
+ Fl_Font fntHeader = FL_FREE_FONT;
+ Fl::set_font(fntHeader, "Courier");
+
+ pLblHeader->align(FL_ALIGN_LEFT);
+ pLblHeader->labelfont(FL_BOLD);
+ pLblHeader->labelsize(24);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pLblHeader->labelcolor(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgHdrColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgHdrColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgHdrColor)));
+ else /* Default color. */
+ pLblHeader->labelcolor(fl_rgb_color(0x51, 0x5F, 0x77));
+ uOffsetY += 40;
+
+ /** @todo Add basic NLS support. */
+
+ Fl_Input *pEdtUsername = new Fl_Input(uOffsetX, uOffsetY,
+ 300, 20, "User Name");
+ AssertPtr(pEdtUsername);
+ pEdtUsername->callback(cb_edt_username, &ctx);
+ pEdtUsername->when(FL_WHEN_ENTER_KEY_ALWAYS);
+ Fl::focus(pEdtUsername);
+ ctx.pEdtUsername = pEdtUsername;
+
+ Fl_Secret_Input *pEdtPassword = new Fl_Secret_Input(uOffsetX, uOffsetY + 40,
+ 300, 20, "Password");
+ AssertPtr(pEdtPassword);
+ pEdtPassword->callback(cb_edt_password, &ctx);
+ pEdtPassword->when(FL_WHEN_ENTER_KEY_ALWAYS);
+ ctx.pEdtPassword = pEdtPassword;
+
+ Fl_Button *pBtnLogin = new Fl_Button(uOffsetX, uOffsetY + 70,
+ 100, 40, "Log In");
+ AssertPtr(pBtnLogin);
+ pBtnLogin->callback(cb_btn_login, &ctx);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pBtnLogin->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgBtnColor)));
+ else /* Default color. */
+ pBtnLogin->color(fl_rgb_color(255, 255, 255));
+ ctx.pBtnLogin = pBtnLogin;
+
+ Fl_Menu_Button *pBtnMenu = new Fl_Menu_Button(uOffsetX + 120, uOffsetY + 70,
+ 100, 40, "Options");
+ AssertPtr(pBtnMenu);
+ pBtnMenu->callback(cb_btn_menu, &ctx);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pBtnMenu->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgBtnColor)));
+ else /* Default color. */
+ pBtnMenu->color(fl_rgb_color(255, 255, 255));
+
+ if (uOptsUI & VBOX_GREETER_UI_SHOW_RESTART)
+ pBtnMenu->add("Restart", "" /* Shortcut */, cb_btn_restart, &ctx, 0 /* Flags */);
+ if (uOptsUI & VBOX_GREETER_UI_SHOW_SHUTDOWN)
+ pBtnMenu->add("Shutdown", "" /* Shortcut */, cb_btn_shutdown, &ctx, 0 /* Flags */);
+
+ char szLabel[255];
+ RTStrPrintf(szLabel, sizeof(szLabel), "Oracle VM VirtualBox Guest Additions %sr%s",
+ RTBldCfgVersion(), RTBldCfgRevisionStr());
+ Fl_Box *pLblInfo = new Fl_Box(FL_NO_BOX , 50, uOffsetY + 150,
+ 400, 20, szLabel);
+ AssertPtr(pLblInfo);
+ ctx.pLblInfo = pLblInfo;
+
+ pWndGreeter->end();
+ pWndGreeter->position((Fl::w() - pWndGreeter->w()) / 2,
+ (Fl::h() - pWndGreeter->h()) / 2);
+
+ pWndMain->fullscreen();
+ pWndMain->show(argc, argv);
+ pWndMain->end();
+
+ pWndGreeter->show();
+#else /* !VBOX_WITH_FLTK */
+ gtk_init(&argc, &argv);
+
+ /* Set default cursor */
+ gdk_window_set_cursor(gdk_get_default_root_window(), gdk_cursor_new(GDK_LEFT_PTR));
+
+ GError *pError = NULL;
+ GtkBuilder *pBuilder = gtk_builder_new();
+ AssertPtr(pBuilder);
+ if (!gtk_builder_add_from_file(pBuilder, "/usr/share/xgreeters/vbox-greeter.ui", &pError))
+ {
+ AssertPtr(pError);
+ vboxGreeterError("unable to load UI: %s", pError->message);
+ return RTEXITCODE_FAILURE;
+ }
+
+ GtkWindow *pWndGreeter = GTK_WINDOW(gtk_builder_get_object(pBuilder, VBOX_GREETER_UI_WND_GREETER));
+ AssertPtr(pWndGreeter);
+ GtkButton *pBtnLogin = GTK_BUTTON(gtk_builder_get_object(pBuilder, VBOX_GREETER_UI_BTN_LOGIN));
+ AssertPtr(pBtnLogin);
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pBuilder, VBOX_GREETER_UI_LBL_INFO));
+ AssertPtr(pLblInfo);
+
+ ctx.pBuilder = pBuilder;
+
+ g_signal_connect(G_OBJECT(pBtnLogin), "clicked", G_CALLBACK(cb_btn_login), &ctx);
+
+ GdkRectangle rectScreen;
+ gdk_screen_get_monitor_geometry(gdk_screen_get_default(), gdk_screen_get_primary_monitor(gdk_screen_get_default()), &rectScreen);
+ vboxGreeterLog("monitor (default) is %dx%d\n", rectScreen.width, rectScreen.height);
+
+ gint iWndX, iWndY;
+ gtk_window_get_default_size(pWndGreeter, &iWndX, &iWndY);
+ vboxGreeterLog("greeter is %dx%d\n", iWndX, iWndY);
+
+ gtk_window_move(pWndGreeter,
+ (rectScreen.width / 2) - (iWndX / 2),
+ (rectScreen.height / 2) - (iWndY / 2));
+ gtk_widget_show(GTK_WIDGET(pWndGreeter));
+
+ g_clear_error(&pError);
+#endif /* !VBOX_WITH_FLTK */
+
+ /* GType is needed in any case (for LightDM), whether we
+ * use GTK3 or not. */
+ g_type_init();
+
+ GMainLoop *pMainLoop = g_main_loop_new(NULL, FALSE /* Not yet running */);
+ AssertPtr(pMainLoop); NOREF(pMainLoop);
+
+ LightDMGreeter *pGreeter = lightdm_greeter_new();
+ AssertPtr(pGreeter);
+
+ g_signal_connect(pGreeter, "show-prompt", G_CALLBACK(cb_lightdm_show_prompt), &ctx);
+ g_signal_connect(pGreeter, "show-message", G_CALLBACK(cb_lightdm_show_message), &ctx);
+ g_signal_connect(pGreeter, "authentication-complete", G_CALLBACK(cb_lightdm_auth_complete), &ctx);
+
+ ctx.pGreeter = pGreeter;
+
+ if (!lightdm_greeter_connect_sync(pGreeter, NULL))
+ {
+ vboxGreeterError("unable to connect to LightDM server, aborting\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ vboxGreeterLog("connected to LightDM server\n");
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ bool fCheckCreds = false;
+ if (uClientId) /* Connected to guest property service? */
+ {
+ char szVal[256];
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWait",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uint32_t uTimeoutMS = RT_INDEFINITE_WAIT; /* Wait infinite by default. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitTimeout",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uTimeoutMS = RTStrToUInt32(szVal);
+ if (!uTimeoutMS)
+ {
+ vboxGreeterError("pam_vbox_authenticate: invalid waiting timeout value specified, defaulting to infinite timeout\n");
+ uTimeoutMS = RT_INDEFINITE_WAIT;
+ }
+ else
+ uTimeoutMS = uTimeoutMS * 1000; /* Make ms out of s. */
+ }
+
+ ctx.uTimeoutMS = uTimeoutMS;
+
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaiting",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+# ifdef VBOX_WITH_FLTK
+ Assert(pLblInfo);
+ pLblInfo->copy_label(szVal);
+# else
+ gtk_label_set_text(pLblInfo, szVal);
+# endif
+ }
+
+ /* Get initial timestamp so that we can compare the time
+ * whether the value has been changed or not in our event callback. */
+ vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitAbort",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), &ctx.uTsAbort);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Before we actuall wait for credentials just make sure we didn't already get credentials
+ * set so that we can skip waiting for them ... */
+ rc2 = vboxGreeterCheckCreds(&ctx);
+ if (rc2 == VERR_NOT_FOUND)
+ {
+ /* Get current time stamp to later calculate rest of timeout left. */
+ ctx.uStartMS = RTTimeMilliTS();
+
+ fCheckCreds = true;
+ }
+ }
+ }
+
+ /* Start the timer to check credentials availability. */
+ if (fCheckCreds)
+ {
+ vboxGreeterLog("No credentials available on startup, starting to check periodically ...\n");
+# ifdef VBOX_WITH_FLTK
+ Fl::add_timeout(0.5 /* 500 ms */, cb_check_creds, &ctx);
+# else
+ g_timeout_add(500 /* ms */, (GSourceFunc)cb_check_creds, &ctx);
+# endif
+ }
+ }
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+#ifdef VBOX_WITH_FLTK
+ /*
+ * Do own GDK main loop processing because FLTK also needs
+ * to have the chance of processing its events.
+ */
+ GMainContext *pMainCtx = g_main_context_default();
+ AssertPtr(pMainCtx);
+
+ while (g_fRunning)
+ {
+ g_main_context_iteration(pMainCtx,
+ FALSE /* No blocking */);
+ Fl::check();
+ RTThreadSleep(10); /* Wait a bit, don't hog the CPU too much. */
+ }
+
+ g_main_context_unref(pMainCtx);
+
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ if (pImgBanner)
+ {
+ delete pImgBanner; /* Call destructor to free bitmap data. */
+ pImgBanner = NULL;
+ }
+# endif /* VBOX_GREETER_WITH_PNG_SUPPORT */
+#else /* !VBOX_WITH_FLTK */
+ gtk_main();
+ /** @todo Never reached so far. LightDM sends a SIGTERM. */
+#endif /* !VBOX_WITH_FLTK */
+
+ vboxGreeterLog("terminating\n");
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (uClientId)
+ {
+ rc2 = VbglR3GuestPropDisconnect(uClientId);
+ AssertRC(rc2);
+ }
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ VbglR3Term();
+
+ RTEXITCODE rcExit = RT_SUCCESS(rc)
+ ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+
+ vboxGreeterLog("terminated with exit code %ld (rc=%Rrc)\n",
+ rcExit, rc);
+
+ vboxGreeterLogDestroy();
+
+ return rcExit;
+}
+
+#ifdef DEBUG
+DECLEXPORT(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ RTAssertMsg1(pszExpr, uLine, pszFile, pszFunction);
+}
+#endif
+
diff --git a/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop
new file mode 100644
index 00000000..b3946b38
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=VirtualBox Greeter
+Comment=Provides Single Sign-On (SSO) support
+Exec=/usr/sbin/vbox-greeter
+Type=Application
diff --git a/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui
new file mode 100644
index 00000000..76719b39
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkWindow" id="wnd_greeter">
+ <property name="width_request">512</property>
+ <property name="height_request">348</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">VirtualBox Guest Additions Login</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center</property>
+ <property name="default_width">512</property>
+ <property name="default_height">348</property>
+ <property name="has_resize_grip">False</property>
+ <child>
+ <object class="GtkNotebook" id="nb_greeter">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkDrawingArea" id="drawingarea1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkAspectFrame" id="aspectframe3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkEntry" id="edt_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="edt_username">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">20</property>
+ <property name="xalign">0</property>
+ <property name="ypad">10</property>
+ <property name="label" translatable="yes">Username</property>
+ <property name="justify">center</property>
+ <attributes>
+ <attribute name="weight" value="ultraheavy"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">20</property>
+ <property name="xalign">0</property>
+ <property name="ypad">10</property>
+ <property name="label" translatable="yes">Password</property>
+ <attributes>
+ <attribute name="weight" value="ultraheavy"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="btn_login">
+ <property name="label" translatable="yes">Login</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lbl_info">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">end</property>
+ <property name="label" translatable="yes">VirtualBox Guest Additions</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="lblLogin">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Login</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAspectFrame" id="aspectframe2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">VirtualBox Guest Additions Auto-Logon</property>
+ <property name="justify">center</property>
+ <attributes>
+ <attribute name="weight" value="ultraheavy"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="lblInformation">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Information</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
+
diff --git a/src/VBox/Additions/linux/sharedfolders/.scm-settings b/src/VBox/Additions/linux/sharedfolders/.scm-settings
new file mode 100644
index 00000000..51bb9cee
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/.scm-settings
@@ -0,0 +1,39 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for linux shared folders module.
+#
+
+#
+# Copyright (C) 2010-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+/*.c|/*.h: --no-convert-tabs
+/Makefile.module: --treat-as Makefile
+
+# MIT licence to make it easier to re-import code from the in-kernel version.
+/dirops.c: --license-mit
+/lnkops.c: --license-mit
+/regops.c: --license-mit
+/utils.c: --license-mit
+/vbsfmount.h: --license-mit
+/vfsmod.c: --license-mit
+/vfsmod.h: --license-mit
diff --git a/src/VBox/Additions/linux/sharedfolders/Makefile.kmk b/src/VBox/Additions/linux/sharedfolders/Makefile.kmk
new file mode 100644
index 00000000..0b7787d0
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/Makefile.kmk
@@ -0,0 +1,57 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the vboxsf (linux shared folders module).
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+#
+# Populate FILES_VBOXSF_NOBIN
+#
+INSTALLS += vboxsf-src
+include $(PATH_SUB_CURRENT)/files_vboxsf
+vboxsf-src_INST = $(INST_ADDITIONS)src/vboxsf/
+vboxsf-src_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXSF_NOBIN))
+vboxsf-src_EXEC_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXSF_BIN))
+
+# Build test for the Guest Additions kernel modules (kmk check).
+$(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,vboxsf-src,vboxguest-src,)
+
+#
+# The mount util.
+#
+PROGRAMS += mount.vboxsf
+mount.vboxsf_TEMPLATE = NewVBoxGuestR3Exe
+mount.vboxsf_DEFS = _GNU_SOURCE
+mount.vboxsf_SOURCES = \
+ mount.vboxsf.c \
+ vbsfmount.c
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/linux/sharedfolders/Makefile.module b/src/VBox/Additions/linux/sharedfolders/Makefile.module
new file mode 100644
index 00000000..d6aca2a1
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/Makefile.module
@@ -0,0 +1,119 @@
+# $Id: Makefile.module $
+## @file
+# VBox Linux Shared Folders VFS Module Makefile.
+#
+# (For 2.6.x this file must be 'Makefile'!)
+#
+
+#
+# Copyright (C) 2006-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Linux kbuild sets this to our source directory if we are called from there
+obj ?= $(CURDIR)
+include $(obj)/Makefile-header.gmk
+VBOXSF_DIR = $(VBOX_MODULE_SRC_DIR)
+
+# Allow building directly from the subdirectory without assuming the toplevel
+# makefile has done the copying. Not the default use case, but can be handy.
+ifndef KBUILD_EXTRA_SYMBOLS
+KBUILD_EXTRA_SYMBOLS=$(abspath $(VBOXSF_DIR)/../vboxguest/Module.symvers)
+endif
+
+VBOXMOD_NAME = vboxsf
+VBOXMOD_OBJS = \
+ vfsmod.o \
+ dirops.o \
+ lnkops.o \
+ regops.o \
+ utils.o \
+ VBoxGuestR0LibGenericRequest.o \
+ VBoxGuestR0LibHGCM.o \
+ VBoxGuestR0LibIdc.o \
+ VBoxGuestR0LibIdc-unix.o \
+ VBoxGuestR0LibInit.o \
+ VBoxGuestR0LibPhysHeap.o \
+ VBoxGuestR0LibSharedFolders.o
+ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86)
+VBOXMOD_OBJS += \
+ divdi3.o \
+ moddi3.o \
+ udivdi3.o \
+ udivmoddi4.o \
+ umoddi3.o \
+ qdivrem.o
+endif
+VBOXMOD_INCL = \
+ $(VBOXSF_DIR) \
+ $(VBOXSF_DIR)include \
+ $(VBOXSF_DIR)r0drv/linux
+VBOXMOD_DEFS = \
+ RT_OS_LINUX \
+ IN_RING0 \
+ IN_RT_R0 \
+ IN_SUP_R0 \
+ VBOX \
+ VBOX_WITH_HGCM \
+ IN_MODULE \
+ IN_GUEST \
+ IN_GUEST_R0 \
+ RT_NO_EXPORT_SYMBOL
+ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64)
+VBOXMOD_DEFS += VBOX_WITH_64_BITS_GUESTS
+endif
+ifneq ($(filter %uek.x86_64,$(KERN_VER)),)
+VBOXMOD_DEFS += VBOX_UEK
+endif
+VBOXMOD_CFLAGS := $(call VBOX_GCC_CHECK_CC,-Wno-declaration-after-statement,-Wno-declaration-after-statement,,)
+VBOXMOD_CFLAGS += $(call VBOX_GCC_CHECK_CC,-fno-pie,-fno-pie,,)
+ifneq ($(KERN_VERSION),24)
+VBOXMOD_CFLAGS += -include $(VBOXSF_DIR)/include/VBox/VBoxGuestMangling.h
+## @todo r-bird: What's with -fshort-wchar here?? We either need that or we dont, right? It should be 2.6+ only.
+VBOXMOD_CFLAGS += -fshort-wchar
+endif
+ifdef VBOX_NO_OMIT_FRAME_POINTER
+VBOXMOD_CFLAGS += -fno-omit-frame-pointer
+endif
+
+ifneq ($(KERN_VERSION),24)
+# special hack for Fedora Core 6 2.6.18 (fc6), rhel5 2.6.18 (el5),
+# ClarkConnect 4.3 (cc4) and ClarkConnect 5 (v5)
+ ifeq ($(KERNELRELEASE),)
+VBOXMOD_CFLAGS += $(foreach inc,$(KERN_INCL),\
+ $(if $(wildcard $(inc)/linux/utsrelease.h),\
+ $(if $(shell grep '"2.6.18.*fc6.*"' $(inc)/linux/utsrelease.h; \
+ grep '"2.6.18.*el5.*"' $(inc)/linux/utsrelease.h; \
+ grep '"2.6.18.*v5.*"' $(inc)/linux/utsrelease.h; \
+ grep '"2.6.18.*cc4.*"' $(inc)/linux/utsrelease.h),\
+ -DKERNEL_FC6,),))
+ else
+VBOXMOD_CFLAGS += $(if $(shell echo "$(KERNELRELEASE)"|grep '2.6.18.*fc6.*';\
+ echo "$(KERNELRELEASE)"|grep '2.6.18.*el5.*';\
+ echo "$(KERNELRELEASE)"|grep '2.6.18.*v5.*';\
+ echo "$(KERNELRELEASE)"|grep '2.6.18.*cc4.*'),\
+ -DKERNEL_FC6,)
+ endif
+endif
+
+VBOXMOD_CLEAN = . linux r0drv r0drv/linux
+
+include $(obj)/Makefile-footer.gmk
+
diff --git a/src/VBox/Additions/linux/sharedfolders/dirops.c b/src/VBox/Additions/linux/sharedfolders/dirops.c
new file mode 100644
index 00000000..c521e6a3
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/dirops.c
@@ -0,0 +1,1417 @@
+/* $Id: dirops.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, directory inode and file operations.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+#include <iprt/err.h>
+
+#if RTLNX_VER_MAX(4,7,0)
+# define d_in_lookup(a_pDirEntry) (d_unhashed(a_pDirEntry))
+#endif
+
+
+
+/**
+ * Open a directory (implements file_operations::open).
+ *
+ * @returns 0 on success, negative errno otherwise.
+ * @param inode inode
+ * @param file file
+ */
+static int vbsf_dir_open(struct inode *inode, struct file *file)
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct dentry *dentry = VBSF_GET_F_DENTRY(file);
+ struct vbsf_dir_info *sf_d;
+ int rc;
+
+ SFLOGFLOW(("vbsf_dir_open: inode=%p file=%p %s\n", inode, file, sf_i && sf_i->path ? sf_i->path->String.ach : NULL));
+ AssertReturn(pSuperInfo, -EINVAL);
+ AssertReturn(sf_i, -EINVAL);
+ AssertReturn(!file->private_data, 0);
+
+ /*
+ * Allocate and initialize our directory info structure.
+ * We delay buffer allocation until vbsf_getdent is actually used.
+ */
+ sf_d = kmalloc(sizeof(*sf_d), GFP_KERNEL);
+ if (sf_d) {
+ VBOXSFCREATEREQ *pReq;
+ RT_ZERO(*sf_d);
+ sf_d->u32Magic = VBSF_DIR_INFO_MAGIC;
+ sema_init(&sf_d->Lock, 1);
+
+ /*
+ * Try open the directory.
+ */
+ pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFCREATEREQ, StrPath.String) + sf_i->path->u16Size);
+ if (pReq) {
+ memcpy(&pReq->StrPath, sf_i->path, SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ RT_ZERO(pReq->CreateParms);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_DIRECTORY
+ | SHFL_CF_ACT_OPEN_IF_EXISTS
+ | SHFL_CF_ACT_FAIL_IF_NEW
+ | SHFL_CF_ACCESS_READ;
+
+ LogFunc(("calling VbglR0SfHostReqCreate on folder %s, flags %#x\n",
+ sf_i->path->String.utf8, pReq->CreateParms.CreateFlags));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ Assert(pReq->CreateParms.Handle != SHFL_HANDLE_NIL);
+
+ /*
+ * Update the inode info with fresh stats and increase the TTL for the
+ * dentry cache chain that got us here.
+ */
+ vbsf_update_inode(inode, sf_i, &pReq->CreateParms.Info, pSuperInfo,
+ true /*fLocked*/ /** @todo inode locking */, 0 /*fSetAttrs*/);
+ vbsf_dentry_chain_increase_ttl(dentry);
+
+ sf_d->Handle.hHost = pReq->CreateParms.Handle;
+ sf_d->Handle.cRefs = 1;
+ sf_d->Handle.fFlags = VBSF_HANDLE_F_READ | VBSF_HANDLE_F_DIR | VBSF_HANDLE_F_MAGIC;
+ vbsf_handle_append(sf_i, &sf_d->Handle);
+
+ file->private_data = sf_d;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_dir_open(%p,%p): returns 0; hHost=%#llx\n", inode, file, sf_d->Handle.hHost));
+ return 0;
+
+ }
+ Assert(pReq->CreateParms.Handle == SHFL_HANDLE_NIL);
+
+ /*
+ * Directory does not exist, so we probably got some invalid
+ * dir cache and inode info.
+ */
+ /** @todo do more to invalidate dentry and inode here. */
+ vbsf_dentry_invalidate_ttl(dentry);
+ sf_i->force_restat = true;
+ rc = -ENOENT;
+ } else
+ rc = -EPERM;
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ LogRelMaxFunc(64, ("failed to allocate %zu bytes for '%s'\n",
+ RT_UOFFSETOF(VBOXSFCREATEREQ, StrPath.String) + sf_i->path->u16Size, sf_i->path->String.ach));
+ rc = -ENOMEM;
+ }
+ sf_d->u32Magic = VBSF_DIR_INFO_MAGIC_DEAD;
+ kfree(sf_d);
+ } else
+ rc = -ENOMEM;
+ SFLOGFLOW(("vbsf_dir_open(%p,%p): returns %d\n", inode, file, rc));
+ return rc;
+}
+
+
+/**
+ * This is called when reference count of [file] goes to zero. Notify
+ * the host that it can free whatever is associated with this directory
+ * and deallocate our own internal buffers
+ *
+ * @param inode inode
+ * @param file file
+ * returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_dir_release(struct inode *inode, struct file *file)
+{
+ struct vbsf_dir_info *sf_d = (struct vbsf_dir_info *)file->private_data;
+
+ SFLOGFLOW(("vbsf_dir_release(%p,%p): sf_d=%p hHost=%#llx\n", inode, file, sf_d, sf_d ? sf_d->Handle.hHost : SHFL_HANDLE_NIL));
+
+ if (sf_d) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+
+ /* Invalidate the non-handle part. */
+ sf_d->u32Magic = VBSF_DIR_INFO_MAGIC_DEAD;
+ sf_d->cEntriesLeft = 0;
+ sf_d->cbValid = 0;
+ sf_d->pEntry = NULL;
+ sf_d->fNoMoreFiles = false;
+ if (sf_d->pBuf) {
+ kfree(sf_d->pBuf);
+ sf_d->pBuf = NULL;
+ }
+
+ /* Closes the handle and frees the structure when the last reference is released. */
+ vbsf_handle_release(&sf_d->Handle, pSuperInfo, "vbsf_dir_release");
+ }
+
+ return 0;
+}
+
+
+/**
+ * Translate RTFMODE into DT_xxx (in conjunction to rtDirType()).
+ * returns d_type
+ * @param fMode file mode
+ */
+DECLINLINE(int) vbsf_get_d_type(RTFMODE fMode)
+{
+ switch (fMode & RTFS_TYPE_MASK) {
+ case RTFS_TYPE_FIFO: return DT_FIFO;
+ case RTFS_TYPE_DEV_CHAR: return DT_CHR;
+ case RTFS_TYPE_DIRECTORY: return DT_DIR;
+ case RTFS_TYPE_DEV_BLOCK: return DT_BLK;
+ case RTFS_TYPE_FILE: return DT_REG;
+ case RTFS_TYPE_SYMLINK: return DT_LNK;
+ case RTFS_TYPE_SOCKET: return DT_SOCK;
+ case RTFS_TYPE_WHITEOUT: return DT_WHT;
+ }
+ return DT_UNKNOWN;
+}
+
+
+/**
+ * Refills the buffer with more entries.
+ *
+ * @returns 0 on success, negative errno on error,
+ */
+static int vbsf_dir_read_more(struct vbsf_dir_info *sf_d, struct vbsf_super_info *pSuperInfo, bool fRestart)
+{
+ int rc;
+ VBOXSFLISTDIRREQ *pReq;
+
+ /*
+ * Don't call the host again if we've reached the end of the
+ * directory entries already.
+ */
+ if (sf_d->fNoMoreFiles) {
+ if (!fRestart) {
+ SFLOGFLOW(("vbsf_dir_read_more: no more files\n"));
+ return 0;
+ }
+ sf_d->fNoMoreFiles = false;
+ }
+
+ /*
+ * Make sure we've got some kind of buffers.
+ */
+ if (sf_d->pBuf) {
+ /* Likely, except for the first time. */
+ } else {
+ sf_d->pBuf = (PSHFLDIRINFO)kmalloc(pSuperInfo->cbDirBuf, GFP_KERNEL);
+ if (sf_d->pBuf)
+ sf_d->cbBuf = pSuperInfo->cbDirBuf;
+ else {
+ sf_d->pBuf = (PSHFLDIRINFO)kmalloc(_4K, GFP_KERNEL);
+ if (!sf_d->pBuf) {
+ LogRelMax(10, ("vbsf_dir_read_more: Failed to allocate buffer!\n"));
+ return -ENOMEM;
+ }
+ sf_d->cbBuf = _4K;
+ }
+ }
+
+ /*
+ * Allocate a request buffer.
+ */
+ pReq = (VBOXSFLISTDIRREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ rc = VbglR0SfHostReqListDirContig2x(pSuperInfo->map.root, pReq, sf_d->Handle.hHost, NULL, NIL_RTGCPHYS64,
+ fRestart ? SHFL_LIST_RESTART : SHFL_LIST_NONE,
+ sf_d->pBuf, virt_to_phys(sf_d->pBuf), sf_d->cbBuf);
+ if (RT_SUCCESS(rc)) {
+ sf_d->pEntry = sf_d->pBuf;
+ sf_d->cbValid = pReq->Parms.cb32Buffer.u.value32;
+ sf_d->cEntriesLeft = pReq->Parms.c32Entries.u.value32;
+ sf_d->fNoMoreFiles = pReq->Parms.f32More.u.value32 == 0;
+ } else {
+ sf_d->pEntry = sf_d->pBuf;
+ sf_d->cbValid = 0;
+ sf_d->cEntriesLeft = 0;
+ if (rc == VERR_NO_MORE_FILES) {
+ sf_d->fNoMoreFiles = true;
+ rc = 0;
+ } else {
+ /* In theory we could end up here with a buffer overflow, but
+ with a 4KB minimum buffer size that's very unlikely with the
+ typical filename length of today's file systems (2019). */
+ LogRelMax(16, ("vbsf_dir_read_more: VbglR0SfHostReqListDirContig2x -> %Rrc\n", rc));
+ rc = -EPROTO;
+ }
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ SFLOGFLOW(("vbsf_dir_read_more: returns %d; cbValid=%#x cEntriesLeft=%#x fNoMoreFiles=%d\n",
+ rc, sf_d->cbValid, sf_d->cEntriesLeft, sf_d->fNoMoreFiles));
+ return rc;
+}
+
+
+/**
+ * Helper function for when we need to convert the name, avoids wasting stack in
+ * the UTF-8 code path.
+ */
+DECL_NO_INLINE(static, bool) vbsf_dir_emit_nls(
+# if RTLNX_VER_MIN(3,11,0)
+ struct dir_context *ctx,
+# else
+ void *opaque, filldir_t filldir, loff_t offPos,
+# endif
+ const char *pszSrcName, uint16_t cchSrcName, ino_t d_ino, int d_type,
+ struct vbsf_super_info *pSuperInfo)
+{
+ char szDstName[NAME_MAX];
+ int rc = vbsf_nlscpy(pSuperInfo, szDstName, sizeof(szDstName), pszSrcName, cchSrcName);
+ if (rc == 0) {
+#if RTLNX_VER_MIN(3,11,0)
+ return dir_emit(ctx, szDstName, strlen(szDstName), d_ino, d_type);
+#else
+ return filldir(opaque, szDstName, strlen(szDstName), offPos, d_ino, d_type) == 0;
+#endif
+ }
+
+ /* Assuming this is a buffer overflow issue, just silently skip it. */
+ SFLOGFLOW(("vbsf_dir_emit_nls: vbsf_nlscopy failed with %d for '%s'\n", rc, pszSrcName));
+ return true;
+}
+
+
+/**
+ * This is called when vfs wants to populate internal buffers with
+ * directory [dir]s contents. [opaque] is an argument to the
+ * [filldir]. [filldir] magically modifies it's argument - [opaque]
+ * and takes following additional arguments (which i in turn get from
+ * the host via vbsf_getdent):
+ *
+ * name : name of the entry (i must also supply it's length huh?)
+ * type : type of the entry (FILE | DIR | etc) (i ellect to use DT_UNKNOWN)
+ * pos : position/index of the entry
+ * ino : inode number of the entry (i fake those)
+ *
+ * [dir] contains:
+ * f_pos : cursor into the directory listing
+ * private_data : mean of communication with the host side
+ *
+ * Extract elements from the directory listing (incrementing f_pos
+ * along the way) and feed them to [filldir] until:
+ *
+ * a. there are no more entries (i.e. vbsf_getdent set done to 1)
+ * b. failure to compute fake inode number
+ * c. filldir returns an error (see comment on that)
+ */
+#if RTLNX_VER_MIN(3,11,0)
+static int vbsf_dir_iterate(struct file *dir, struct dir_context *ctx)
+#else
+static int vbsf_dir_read(struct file *dir, void *opaque, filldir_t filldir)
+#endif
+{
+#if RTLNX_VER_MIN(3,11,0)
+ loff_t offPos = ctx->pos;
+#else
+ loff_t offPos = dir->f_pos;
+#endif
+ struct vbsf_dir_info *sf_d = (struct vbsf_dir_info *)dir->private_data;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(VBSF_GET_F_DENTRY(dir)->d_sb);
+ int rc;
+
+ /*
+ * Lock the directory info structures.
+ */
+ if (RT_LIKELY(down_interruptible(&sf_d->Lock) == 0)) {
+ /* likely */
+ } else
+ return -ERESTARTSYS;
+
+ /*
+ * Any seek performed in the mean time?
+ */
+ if (offPos == sf_d->offPos) {
+ /* likely */
+ } else {
+ /* Restart the search if iPos is lower than the current buffer position. */
+ loff_t offCurEntry = sf_d->offPos;
+ if (offPos < offCurEntry) {
+ rc = vbsf_dir_read_more(sf_d, pSuperInfo, true /*fRestart*/);
+ if (rc == 0)
+ offCurEntry = 0;
+ else {
+ up(&sf_d->Lock);
+ return rc;
+ }
+ }
+
+ /* Skip ahead to offPos. */
+ while (offCurEntry < offPos) {
+ uint32_t cEntriesLeft = sf_d->cEntriesLeft;
+ if ((uint64_t)(offPos - offCurEntry) >= cEntriesLeft) {
+ /* Skip the current buffer and read the next: */
+ offCurEntry += cEntriesLeft;
+ sf_d->offPos = offCurEntry;
+ sf_d->cEntriesLeft = 0;
+ rc = vbsf_dir_read_more(sf_d, pSuperInfo, false /*fRestart*/);
+ if (rc != 0 || sf_d->cEntriesLeft == 0) {
+ up(&sf_d->Lock);
+ return rc;
+ }
+ } else {
+ do
+ {
+ PSHFLDIRINFO pEntry = sf_d->pEntry;
+ pEntry = (PSHFLDIRINFO)&pEntry->name.String.utf8[pEntry->name.u16Length];
+ AssertLogRelBreakStmt( cEntriesLeft == 1
+ || (uintptr_t)pEntry - (uintptr_t)sf_d->pBuf
+ <= sf_d->cbValid - RT_UOFFSETOF(SHFLDIRINFO, name.String),
+ sf_d->cEntriesLeft = 0);
+ sf_d->cEntriesLeft = --cEntriesLeft;
+ sf_d->offPos = ++offCurEntry;
+ } while (offPos < sf_d->offPos);
+ }
+ }
+ }
+
+ /*
+ * Handle '.' and '..' specially so we get the inode numbers right.
+ * We'll skip any '.' or '..' returned by the host (included in pos,
+ * however, to simplify the above skipping code).
+ */
+ if (offPos < 2) {
+#if RTLNX_VER_MIN(3,11,0)
+ if (offPos == 0) {
+ if (dir_emit_dot(dir, ctx))
+ dir->f_pos = ctx->pos = sf_d->offPos = offPos = 1;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+ if (offPos == 1) {
+ if (dir_emit_dotdot(dir, ctx))
+ dir->f_pos = ctx->pos = sf_d->offPos = offPos = 2;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+#else
+ if (offPos == 0) {
+ rc = filldir(opaque, ".", 1, 0, VBSF_GET_F_DENTRY(dir)->d_inode->i_ino, DT_DIR);
+ if (!rc)
+ dir->f_pos = sf_d->offPos = offPos = 1;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+ if (offPos == 1) {
+# if RTLNX_VER_MIN(2,5,5)
+ rc = filldir(opaque, "..", 2, 1, parent_ino(VBSF_GET_F_DENTRY(dir)), DT_DIR);
+# else
+ rc = filldir(opaque, "..", 2, 1, VBSF_GET_F_DENTRY(dir)->d_parent->d_inode->i_ino, DT_DIR);
+# endif
+ if (!rc)
+ dir->f_pos = sf_d->offPos = offPos = 2;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+#endif
+ }
+
+ /*
+ * Produce stuff.
+ */
+ Assert(offPos == sf_d->offPos);
+ for (;;) {
+ PSHFLDIRINFO pBuf;
+ PSHFLDIRINFO pEntry;
+
+ /*
+ * Do we need to read more?
+ */
+ uint32_t cbValid = sf_d->cbValid;
+ uint32_t cEntriesLeft = sf_d->cEntriesLeft;
+ if (!cEntriesLeft) {
+ rc = vbsf_dir_read_more(sf_d, pSuperInfo, false /*fRestart*/);
+ if (rc == 0) {
+ cEntriesLeft = sf_d->cEntriesLeft;
+ if (!cEntriesLeft) {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ cbValid = sf_d->cbValid;
+ } else {
+ up(&sf_d->Lock);
+ return rc;
+ }
+ }
+
+ /*
+ * Feed entries to the caller.
+ */
+ pBuf = sf_d->pBuf;
+ pEntry = sf_d->pEntry;
+ do {
+ /*
+ * Validate the entry in case the host is messing with us.
+ * We're ASSUMING the host gives us a zero terminated string (UTF-8) here.
+ */
+ uintptr_t const offEntryInBuf = (uintptr_t)pEntry - (uintptr_t)pBuf;
+ uint16_t cbSrcName;
+ uint16_t cchSrcName;
+ AssertLogRelMsgBreak(offEntryInBuf + RT_UOFFSETOF(SHFLDIRINFO, name.String) <= cbValid,
+ ("%#llx + %#x vs %#x\n", offEntryInBuf, RT_UOFFSETOF(SHFLDIRINFO, name.String), cbValid));
+ cbSrcName = pEntry->name.u16Size;
+ cchSrcName = pEntry->name.u16Length;
+ AssertLogRelBreak(offEntryInBuf + RT_UOFFSETOF(SHFLDIRINFO, name.String) + cbSrcName <= cbValid);
+ AssertLogRelBreak(cchSrcName < cbSrcName);
+ AssertLogRelBreak(pEntry->name.String.ach[cchSrcName] == '\0');
+
+ /*
+ * Filter out '.' and '..' entires.
+ */
+ if ( cchSrcName > 2
+ || pEntry->name.String.ach[0] != '.'
+ || ( cchSrcName == 2
+ && pEntry->name.String.ach[1] != '.')) {
+ int const d_type = vbsf_get_d_type(pEntry->Info.Attr.fMode);
+ ino_t const d_ino = (ino_t)offPos + 0xbeef; /* very fake */
+ bool fContinue;
+ if (pSuperInfo->fNlsIsUtf8) {
+#if RTLNX_VER_MIN(3,11,0)
+ fContinue = dir_emit(ctx, pEntry->name.String.ach, cchSrcName, d_ino, d_type);
+#else
+ fContinue = filldir(opaque, pEntry->name.String.ach, cchSrcName, offPos, d_ino, d_type) == 0;
+#endif
+ } else {
+#if RTLNX_VER_MIN(3,11,0)
+ fContinue = vbsf_dir_emit_nls(ctx, pEntry->name.String.ach, cchSrcName, d_ino, d_type, pSuperInfo);
+#else
+ fContinue = vbsf_dir_emit_nls(opaque, filldir, offPos, pEntry->name.String.ach, cchSrcName,
+ d_ino, d_type, pSuperInfo);
+#endif
+ }
+ if (fContinue) {
+ /* likely */
+ } else {
+ sf_d->cEntriesLeft = cEntriesLeft;
+ sf_d->pEntry = pEntry;
+ sf_d->offPos = offPos;
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+
+ /*
+ * Advance to the next entry.
+ */
+ pEntry = (PSHFLDIRINFO)((uintptr_t)pEntry + RT_UOFFSETOF(SHFLDIRINFO, name.String) + cbSrcName);
+ offPos += 1;
+ dir->f_pos = offPos;
+#if RTLNX_VER_MIN(3,11,0)
+ ctx->pos = offPos;
+#endif
+ cEntriesLeft -= 1;
+ } while (cEntriesLeft > 0);
+
+ /* Done with all available entries. */
+ sf_d->offPos = offPos + cEntriesLeft;
+ sf_d->pEntry = pBuf;
+ sf_d->cEntriesLeft = 0;
+ }
+}
+
+
+/**
+ * Directory file operations.
+ */
+struct file_operations vbsf_dir_fops = {
+ .open = vbsf_dir_open,
+#if RTLNX_VER_MIN(4,7,0)
+ .iterate_shared = vbsf_dir_iterate,
+#elif RTLNX_VER_MIN(3,11,0)
+ .iterate = vbsf_dir_iterate,
+#else
+ .readdir = vbsf_dir_read,
+#endif
+ .release = vbsf_dir_release,
+ .read = generic_read_dir,
+#if RTLNX_VER_MIN(2,6,37)
+ .llseek = generic_file_llseek
+#endif
+};
+
+
+
+/*********************************************************************************************************************************
+* Directory Inode Operations *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for vbsf_inode_lookup(), vbsf_create_worker() and
+ * vbsf_inode_instantiate().
+ */
+static struct inode *vbsf_create_inode(struct inode *parent, struct dentry *dentry, PSHFLSTRING path,
+ PSHFLFSOBJINFO pObjInfo, struct vbsf_super_info *pSuperInfo, bool fInstantiate)
+{
+ /*
+ * Allocate memory for our additional inode info and create an inode.
+ */
+ struct vbsf_inode_info *sf_new_i = (struct vbsf_inode_info *)kmalloc(sizeof(*sf_new_i), GFP_KERNEL);
+ if (sf_new_i) {
+ ino_t iNodeNo = iunique(parent->i_sb, 16);
+#if RTLNX_VER_MIN(2,4,25)
+ struct inode *pInode = iget_locked(parent->i_sb, iNodeNo);
+#else
+ struct inode *pInode = iget(parent->i_sb, iNodeNo);
+#endif
+ if (pInode) {
+ /*
+ * Initialize the two structures.
+ */
+#ifdef VBOX_STRICT
+ sf_new_i->u32Magic = SF_INODE_INFO_MAGIC;
+#endif
+ sf_new_i->path = path;
+ sf_new_i->force_restat = false;
+ sf_new_i->ts_up_to_date = jiffies;
+ RTListInit(&sf_new_i->HandleList);
+ sf_new_i->handle = SHFL_HANDLE_NIL;
+
+ VBSF_SET_INODE_INFO(pInode, sf_new_i);
+ vbsf_init_inode(pInode, sf_new_i, pObjInfo, pSuperInfo);
+
+ /*
+ * Before we unlock the new inode, we may need to call d_instantiate.
+ */
+ if (fInstantiate)
+ d_instantiate(dentry, pInode);
+#if RTLNX_VER_MIN(2,4,25)
+ unlock_new_inode(pInode);
+#endif
+ return pInode;
+
+ }
+ LogFunc(("iget failed\n"));
+ kfree(sf_new_i);
+ } else
+ LogRelFunc(("could not allocate memory for new inode info\n"));
+ return NULL;
+}
+
+
+/** Helper for vbsf_create_worker() and vbsf_inode_lookup() that wraps
+ * d_add() and setting d_op. */
+DECLINLINE(void) vbsf_d_add_inode(struct dentry *dentry, struct inode *pNewInode)
+{
+#if RTLNX_VER_MIN(2,6,38)
+ Assert(dentry->d_op == &vbsf_dentry_ops); /* (taken from the superblock) */
+#else
+ dentry->d_op = &vbsf_dentry_ops;
+#endif
+ d_add(dentry, pNewInode);
+}
+
+
+/**
+ * This is called when vfs failed to locate dentry in the cache. The
+ * job of this function is to allocate inode and link it to dentry.
+ * [dentry] contains the name to be looked in the [parent] directory.
+ * Failure to locate the name is not a "hard" error, in this case NULL
+ * inode is added to [dentry] and vfs should proceed trying to create
+ * the entry via other means. NULL(or "positive" pointer) ought to be
+ * returned in case of success and "negative" pointer on error
+ */
+static struct dentry *vbsf_inode_lookup(struct inode *parent, struct dentry *dentry
+#if RTLNX_VER_MIN(3,6,0)
+ , unsigned int flags
+#elif RTLNX_VER_MIN(2,6,0)
+ , struct nameidata *nd
+#endif
+ )
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(parent);
+ SHFLSTRING *path;
+ struct dentry *dret;
+ int rc;
+
+#if RTLNX_VER_MIN(3,6,0)
+ SFLOGFLOW(("vbsf_inode_lookup: parent=%p dentry=%p flags=%#x\n", parent, dentry, flags));
+#elif RTLNX_VER_MIN(2,6,0)
+ SFLOGFLOW(("vbsf_inode_lookup: parent=%p dentry=%p nd=%p{.flags=%#x}\n", parent, dentry, nd, nd ? nd->flags : 0));
+#else
+ SFLOGFLOW(("vbsf_inode_lookup: parent=%p dentry=%p\n", parent, dentry));
+#endif
+
+ Assert(pSuperInfo);
+ Assert(sf_i && sf_i->u32Magic == SF_INODE_INFO_MAGIC);
+
+ /*
+ * Build the path. We'll associate the path with dret's inode on success.
+ */
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_i, dentry, &path, __func__);
+ if (rc == 0) {
+ /*
+ * Do a lookup on the host side.
+ */
+ VBOXSFCREATEREQ *pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + path->u16Size);
+ if (pReq) {
+ struct inode *pInode = NULL;
+
+ RT_ZERO(*pReq);
+ memcpy(&pReq->StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ SFLOG2(("vbsf_inode_lookup: Calling VbglR0SfHostReqCreate on %s\n", path->String.utf8));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ /*
+ * Create an inode for the result. Since this also confirms
+ * the existence of all parent dentries, we increase their TTL.
+ */
+ pInode = vbsf_create_inode(parent, dentry, path, &pReq->CreateParms.Info, pSuperInfo, false /*fInstantiate*/);
+ if (rc == 0) {
+ path = NULL; /* given to the inode */
+ dret = dentry;
+ } else
+ dret = (struct dentry *)ERR_PTR(-ENOMEM);
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ } else if ( pReq->CreateParms.Result == SHFL_FILE_NOT_FOUND
+ || pReq->CreateParms.Result == SHFL_PATH_NOT_FOUND /*this probably should happen*/) {
+ dret = dentry;
+ } else {
+ AssertMsgFailed(("%d\n", pReq->CreateParms.Result));
+ dret = (struct dentry *)ERR_PTR(-EPROTO);
+ }
+ } else if (rc == VERR_INVALID_NAME) {
+ SFLOGFLOW(("vbsf_inode_lookup: VERR_INVALID_NAME\n"));
+ dret = dentry; /* this can happen for names like 'foo*' on a Windows host */
+ } else if (rc == VERR_FILENAME_TOO_LONG) {
+ SFLOG(("vbsf_inode_lookup: VbglR0SfHostReqCreate failed on %s: VERR_FILENAME_TOO_LONG\n", path->String.utf8));
+ dret = (struct dentry *)ERR_PTR(-ENAMETOOLONG);
+ } else {
+ SFLOG(("vbsf_inode_lookup: VbglR0SfHostReqCreate failed on %s: %Rrc\n", path->String.utf8, rc));
+ dret = (struct dentry *)ERR_PTR(-EPROTO);
+ }
+ VbglR0PhysHeapFree(pReq);
+
+ /*
+ * When dret is set to dentry we got something to insert,
+ * though it may be negative (pInode == NULL).
+ */
+ if (dret == dentry) {
+ vbsf_dentry_set_update_jiffies(dentry, jiffies);
+ vbsf_d_add_inode(dentry, pInode);
+ dret = NULL;
+ }
+ } else {
+ SFLOGFLOW(("vbsf_inode_lookup: -ENOMEM (phys heap)\n"));
+ dret = (struct dentry *)ERR_PTR(-ENOMEM);
+ }
+ if (path)
+ kfree(path);
+ } else {
+ SFLOG(("vbsf_inode_lookup: vbsf_path_from_dentry failed: %d\n", rc));
+ dret = (struct dentry *)ERR_PTR(rc);
+ }
+ return dret;
+}
+
+
+/**
+ * This should allocate memory for vbsf_inode_info, compute a unique inode
+ * number, get an inode from vfs, initialize inode info, instantiate
+ * dentry.
+ *
+ * @param parent inode entry of the directory
+ * @param dentry directory cache entry
+ * @param path path name. Consumed on success.
+ * @param info file information
+ * @param handle handle
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_inode_instantiate(struct inode *parent, struct dentry *dentry, PSHFLSTRING path,
+ PSHFLFSOBJINFO info, SHFLHANDLE handle)
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ struct inode *pInode = vbsf_create_inode(parent, dentry, path, info, pSuperInfo, true /*fInstantiate*/);
+ if (pInode) {
+ /* Store this handle if we leave the handle open. */
+ struct vbsf_inode_info *sf_new_i = VBSF_GET_INODE_INFO(pInode);
+ sf_new_i->handle = handle;
+ return 0;
+ }
+ return -ENOMEM;
+}
+
+
+/**
+ * Create a new regular file / directory.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param mode file mode
+ * @param fCreateFlags SHFL_CF_XXX.
+ * @param fStashHandle Whether the resulting handle should be stashed in
+ * the inode for a subsequent open call.
+ * @param fDoLookup Whether we're doing a lookup and need to d_add the
+ * inode we create to dentry.
+ * @param phHostFile Where to return the handle to the create file/dir.
+ * @param pfCreated Where to indicate whether the file/dir was created
+ * or not. Optional.
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_create_worker(struct inode *parent, struct dentry *dentry, umode_t mode, uint32_t fCreateFlags,
+ bool fStashHandle, bool fDoLookup, SHFLHANDLE *phHostFile, bool *pfCreated)
+
+{
+#ifdef SFLOG_ENABLED
+ const char * const pszPrefix = S_ISDIR(mode) ? "vbsf_create_worker/dir:" : "vbsf_create_worker/file:";
+#endif
+ struct vbsf_inode_info *sf_parent_i = VBSF_GET_INODE_INFO(parent);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ PSHFLSTRING path;
+ int rc;
+
+ if (pfCreated)
+ *pfCreated = false;
+ AssertReturn(sf_parent_i, -EINVAL);
+ AssertReturn(pSuperInfo, -EINVAL);
+
+ /*
+ * Build a path. We'll donate this to the inode on success.
+ */
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_parent_i, dentry, &path, __func__);
+ if (rc == 0) {
+ /*
+ * Allocate, initialize and issue the SHFL_CREATE request.
+ */
+ /** @todo combine with vbsf_path_from_dentry? */
+ union CreateAuxReq
+ {
+ VBOXSFCREATEREQ Create;
+ VBOXSFCLOSEREQ Close;
+ } *pReq = (union CreateAuxReq *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFCREATEREQ, StrPath.String) + path->u16Size);
+ if (pReq) {
+ memcpy(&pReq->Create.StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ RT_ZERO(pReq->Create.CreateParms);
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->Create.CreateParms.CreateFlags = fCreateFlags;
+ pReq->Create.CreateParms.Info.Attr.fMode = (S_ISDIR(mode) ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE)
+ | sf_access_permissions_to_vbox(mode);
+ pReq->Create.CreateParms.Info.Attr.enmAdditional = SHFLFSOBJATTRADD_NOTHING;
+
+ SFLOGFLOW(("%s calling VbglR0SfHostReqCreate(%s, %#x)\n", pszPrefix, path->String.ach, pReq->Create.CreateParms.CreateFlags));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, &pReq->Create);
+ if (RT_SUCCESS(rc)) {
+ SFLOGFLOW(("%s VbglR0SfHostReqCreate returned %Rrc Result=%d Handle=%#llx\n",
+ pszPrefix, rc, pReq->Create.CreateParms.Result, pReq->Create.CreateParms.Handle));
+
+ /*
+ * Work the dentry cache and inode restatting.
+ */
+ if ( pReq->Create.CreateParms.Result == SHFL_FILE_CREATED
+ || pReq->Create.CreateParms.Result == SHFL_FILE_REPLACED) {
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ sf_parent_i->force_restat = 1;
+ } else if ( pReq->Create.CreateParms.Result == SHFL_FILE_EXISTS
+ || pReq->Create.CreateParms.Result == SHFL_FILE_NOT_FOUND)
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+
+ /*
+ * If we got a handle back, we're good. Create an inode for it and return.
+ */
+ if (pReq->Create.CreateParms.Handle != SHFL_HANDLE_NIL) {
+ struct inode *pNewInode = vbsf_create_inode(parent, dentry, path, &pReq->Create.CreateParms.Info, pSuperInfo,
+ !fDoLookup /*fInstantiate*/);
+ if (pNewInode) {
+ struct vbsf_inode_info *sf_new_i = VBSF_GET_INODE_INFO(pNewInode);
+ if (phHostFile) {
+ *phHostFile = pReq->Create.CreateParms.Handle;
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ } else if (fStashHandle) {
+ sf_new_i->handle = pReq->Create.CreateParms.Handle;
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ }
+ if (pfCreated)
+ *pfCreated = pReq->Create.CreateParms.Result == SHFL_FILE_CREATED;
+ if (fDoLookup)
+ vbsf_d_add_inode(dentry, pNewInode);
+ path = NULL;
+ } else {
+ SFLOGFLOW(("%s vbsf_create_inode failed: -ENOMEM (path %s)\n", pszPrefix, rc, path->String.ach));
+ rc = -ENOMEM;
+ }
+ } else if (pReq->Create.CreateParms.Result == SHFL_FILE_EXISTS) {
+ /*
+ * For atomic_open (at least), we should create an inode and
+ * convert the dentry from a negative to a positive one.
+ */
+ SFLOGFLOW(("%s SHFL_FILE_EXISTS for %s\n", pszPrefix, sf_parent_i->path->String.ach));
+ if (fDoLookup) {
+ struct inode *pNewInode = vbsf_create_inode(parent, dentry, path, &pReq->Create.CreateParms.Info,
+ pSuperInfo, false /*fInstantiate*/);
+ if (pNewInode)
+ vbsf_d_add_inode(dentry, pNewInode);
+ path = NULL;
+ }
+ rc = -EEXIST;
+ } else if (pReq->Create.CreateParms.Result == SHFL_FILE_NOT_FOUND) {
+ SFLOGFLOW(("%s SHFL_FILE_NOT_FOUND for %s\n", pszPrefix, sf_parent_i->path->String.ach));
+ rc = -ENOENT;
+ } else if (pReq->Create.CreateParms.Result == SHFL_PATH_NOT_FOUND) {
+ SFLOGFLOW(("%s SHFL_PATH_NOT_FOUND for %s\n", pszPrefix, sf_parent_i->path->String.ach));
+ rc = -ENOENT;
+ } else {
+ AssertMsgFailed(("result=%d creating '%s'\n", pReq->Create.CreateParms.Result, sf_parent_i->path->String.ach));
+ rc = -EPERM;
+ }
+ } else {
+ int const vrc = rc;
+ rc = -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("%s SHFL_FN_CREATE(%s) failed vrc=%Rrc rc=%d\n", pszPrefix, path->String.ach, vrc, rc));
+ }
+
+ /* Cleanups. */
+ if (pReq->Create.CreateParms.Handle != SHFL_HANDLE_NIL) {
+ AssertCompile(RTASSERT_OFFSET_OF(VBOXSFCREATEREQ, CreateParms.Handle) > sizeof(VBOXSFCLOSEREQ)); /* no aliasing issues */
+ int rc2 = VbglR0SfHostReqClose(pSuperInfo->map.root, &pReq->Close, pReq->Create.CreateParms.Handle);
+ if (RT_FAILURE(rc2))
+ SFLOGFLOW(("%s VbglR0SfHostReqCloseSimple failed rc=%Rrc\n", pszPrefix, rc2));
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ if (path)
+ kfree(path);
+ }
+ return rc;
+}
+
+
+#if RTLNX_VER_MIN(3,16,0)
+/**
+ * More atomic way of handling creation.
+ *
+ * Older kernels would first to a lookup that created the file, followed by
+ * an open call. We've got this horrid vbsf_inode_info::handle member because
+ * of that approach. The call combines the lookup and open.
+ */
+static int vbsf_inode_atomic_open(struct inode *pDirInode, struct dentry *dentry, struct file *file, unsigned fOpen,
+ umode_t fMode
+# if RTLNX_VER_MAX(4,19,0)
+ , int *opened
+# endif
+ )
+{
+ SFLOGFLOW(("vbsf_inode_atomic_open: pDirInode=%p dentry=%p file=%p fOpen=%#x, fMode=%#x\n", pDirInode, dentry, file, fOpen, fMode));
+ int rc;
+
+ /* Code assumes negative dentry. */
+ Assert(dentry->d_inode == NULL);
+
+ /** @todo see if we can do this for non-create calls too, as it may save us a
+ * host call to revalidate the dentry. (Can't see anyone else doing
+ * this, so playing it safe for now.) */
+ if (fOpen & O_CREAT) {
+ /*
+ * Prepare our file info structure.
+ */
+ struct vbsf_reg_info *sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL);
+ if (sf_r) {
+ bool fCreated = false;
+ uint32_t fCreateFlags;
+
+ RTListInit(&sf_r->Handle.Entry);
+ sf_r->Handle.cRefs = 1;
+ sf_r->Handle.fFlags = !(fOpen & O_DIRECTORY)
+ ? VBSF_HANDLE_F_FILE | VBSF_HANDLE_F_MAGIC
+ : VBSF_HANDLE_F_DIR | VBSF_HANDLE_F_MAGIC;
+ sf_r->Handle.hHost = SHFL_HANDLE_NIL;
+
+ /*
+ * Try create it.
+ */
+ /* vbsf_create_worker uses the type from fMode, so match it up to O_DIRECTORY. */
+ AssertMsg(!(fMode & S_IFMT) || (fMode & S_IFMT) == (fOpen & O_DIRECTORY ? S_IFDIR : S_IFREG), ("0%o\n", fMode));
+ if (!(fOpen & O_DIRECTORY))
+ fMode = (fMode & ~S_IFMT) | S_IFREG;
+ else
+ fMode = (fMode & ~S_IFMT) | S_IFDIR;
+
+ fCreateFlags = vbsf_linux_oflags_to_vbox(fOpen, &sf_r->Handle.fFlags, __FUNCTION__);
+
+ rc = vbsf_create_worker(pDirInode, dentry, fMode, fCreateFlags, false /*fStashHandle*/, true /*fDoLookup*/,
+ &sf_r->Handle.hHost, &fCreated);
+ if (rc == 0) {
+ struct inode *inode = dentry->d_inode;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+
+ /*
+ * Set FMODE_CREATED according to the action taken by SHFL_CREATE
+ * and call finish_open() to do the remaining open() work.
+ */
+# if RTLNX_VER_MIN(4,19,0)
+ if (fCreated)
+ file->f_mode |= FMODE_CREATED;
+ rc = finish_open(file, dentry, generic_file_open);
+# else
+ if (fCreated)
+ *opened |= FILE_CREATED;
+ rc = finish_open(file, dentry, generic_file_open, opened);
+# endif
+ if (rc == 0) {
+ /*
+ * Now that the file is fully opened, associate sf_r with it
+ * and link the handle to the inode.
+ */
+ vbsf_handle_append(sf_i, &sf_r->Handle);
+ file->private_data = sf_r;
+ SFLOGFLOW(("vbsf_inode_atomic_open: create succeeded; hHost=%#llx path='%s'\n",
+ rc, sf_r->Handle.hHost, sf_i->path->String.ach));
+ sf_r = NULL; /* don't free it */
+ } else {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pDirInode->i_sb);
+ SFLOGFLOW(("vbsf_inode_atomic_open: finish_open failed: %d (path='%s'\n", rc, sf_i->path->String.ach));
+ VbglR0SfHostReqCloseSimple(pSuperInfo->map.root, sf_r->Handle.hHost);
+ sf_r->Handle.hHost = SHFL_HANDLE_NIL;
+ }
+ } else
+ SFLOGFLOW(("vbsf_inode_atomic_open: vbsf_create_worker failed: %d\n", rc));
+ if (sf_r)
+ kfree(sf_r);
+ } else {
+ LogRelMaxFunc(64, ("could not allocate reg info\n"));
+ rc = -ENOMEM;
+ }
+ }
+ /*
+ * Not creating anything.
+ * Do we need to do a lookup or should we just fail?
+ */
+ else if (d_in_lookup(dentry)) {
+ struct dentry *pResult = vbsf_inode_lookup(pDirInode, dentry, 0 /*fFlags*/);
+ if (!IS_ERR(pResult))
+ rc = finish_no_open(file, pResult);
+ else
+ rc = PTR_ERR(pResult);
+ SFLOGFLOW(("vbsf_inode_atomic_open: open -> %d (%p)\n", rc, pResult));
+ } else {
+ SFLOGFLOW(("vbsf_inode_atomic_open: open -> -ENOENT\n"));
+ rc = -ENOENT;
+ }
+ return rc;
+}
+#endif /* 3.6.0 */
+
+
+/**
+ * Create a new regular file.
+ *
+ * @param ns The name space.
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param mode file mode
+ * @param excl Possible O_EXCL...
+ * @returns 0 on success, Linux error code otherwise
+ */
+#if RTLNX_VER_MIN(5,12,0) || defined(DOXYGEN_RUNNING)
+static int vbsf_inode_create(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, umode_t mode, bool excl)
+#elif RTLNX_VER_MIN(3,6,0)
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, umode_t mode, bool excl)
+#elif RTLNX_VER_MIN(3,3,0)
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, umode_t mode, struct nameidata *nd)
+#elif RTLNX_VER_MIN(2,5,75)
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, int mode, struct nameidata *nd)
+#else
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, int mode)
+#endif
+{
+ uint32_t fCreateFlags = SHFL_CF_ACT_CREATE_IF_NEW
+ | SHFL_CF_ACT_FAIL_IF_EXISTS
+ | SHFL_CF_ACCESS_READWRITE;
+#if RTLNX_VER_RANGE(2,5,75, 3,6,0)
+ /* Clear the RD flag if write-only access requested. Otherwise assume we
+ need write access to create stuff. */
+ if (!(nd->intent.open.flags & 1) ) {
+ fCreateFlags &= SHFL_CF_ACCESS_READWRITE;
+ fCreateFlags |= SHFL_CF_ACCESS_WRITE;
+ }
+ /* (file since 2.6.15) */
+#endif
+ TRACE();
+ AssertMsg(!(mode & S_IFMT) || (mode & S_IFMT) == S_IFREG, ("0%o\n", mode));
+ return vbsf_create_worker(parent, dentry, (mode & ~S_IFMT) | S_IFREG, fCreateFlags,
+ true /*fStashHandle*/, false /*fDoLookup*/, NULL /*phHandle*/, NULL /*fCreated*/);
+}
+
+
+/**
+ * Create a new directory.
+ *
+ * @param ns The name space.
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param mode file mode
+ * @returns 0 on success, Linux error code otherwise
+ */
+#if RTLNX_VER_MIN(5,12,0) || defined(DOXYGEN_RUNNING)
+static int vbsf_inode_mkdir(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, umode_t mode)
+#elif RTLNX_VER_MIN(3,3,0)
+static int vbsf_inode_mkdir(struct inode *parent, struct dentry *dentry, umode_t mode)
+#else
+static int vbsf_inode_mkdir(struct inode *parent, struct dentry *dentry, int mode)
+#endif
+{
+ TRACE();
+ AssertMsg(!(mode & S_IFMT) || (mode & S_IFMT) == S_IFDIR, ("0%o\n", mode));
+ return vbsf_create_worker(parent, dentry, (mode & ~S_IFMT) | S_IFDIR,
+ SHFL_CF_ACT_CREATE_IF_NEW
+ | SHFL_CF_ACT_FAIL_IF_EXISTS
+ | SHFL_CF_ACCESS_READWRITE
+ | SHFL_CF_DIRECTORY,
+ false /*fStashHandle*/, false /*fDoLookup*/, NULL /*phHandle*/, NULL /*fCreated*/);
+}
+
+
+/**
+ * Remove a regular file / directory.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param fDirectory true if directory, false otherwise
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_unlink_worker(struct inode *parent, struct dentry *dentry, int fDirectory)
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ struct vbsf_inode_info *sf_parent_i = VBSF_GET_INODE_INFO(parent);
+ SHFLSTRING *path;
+ int rc;
+
+ TRACE();
+
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_parent_i, dentry, &path, __func__);
+ if (!rc) {
+ VBOXSFREMOVEREQ *pReq = (VBOXSFREMOVEREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFREMOVEREQ, StrPath.String)
+ + path->u16Size);
+ if (pReq) {
+ memcpy(&pReq->StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ uint32_t fFlags = fDirectory ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE;
+ if (dentry->d_inode && ((dentry->d_inode->i_mode & S_IFLNK) == S_IFLNK))
+ fFlags |= SHFL_REMOVE_SYMLINK;
+
+ rc = VbglR0SfHostReqRemove(pSuperInfo->map.root, pReq, fFlags);
+
+ if (dentry->d_inode) {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(dentry->d_inode);
+ sf_i->force_restat = true;
+ }
+
+ if (RT_SUCCESS(rc)) {
+ sf_parent_i->force_restat = true; /* directory access/change time changed */
+ rc = 0;
+ } else if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) {
+ /* Probably deleted on the host while the guest had it cached, so don't complain: */
+ LogFunc(("(%d): VbglR0SfRemove(%s) failed rc=%Rrc; calling d_drop on %p\n",
+ fDirectory, path->String.ach, rc, dentry));
+ sf_parent_i->force_restat = true;
+ d_drop(dentry);
+ rc = 0;
+ } else {
+ LogFunc(("(%d): VbglR0SfRemove(%s) failed rc=%Rrc\n", fDirectory, path->String.ach, rc));
+ rc = -RTErrConvertToErrno(rc);
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ kfree(path);
+ }
+ return rc;
+}
+
+
+/**
+ * Remove a regular file.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_inode_unlink(struct inode *parent, struct dentry *dentry)
+{
+ TRACE();
+ return vbsf_unlink_worker(parent, dentry, false /*fDirectory*/);
+}
+
+
+/**
+ * Remove a directory.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_inode_rmdir(struct inode *parent, struct dentry *dentry)
+{
+ TRACE();
+ return vbsf_unlink_worker(parent, dentry, true /*fDirectory*/);
+}
+
+
+/**
+ * Rename a regular file / directory.
+ *
+ * @param ns The name space.
+ * @param old_parent inode of the old parent directory
+ * @param old_dentry old directory cache entry
+ * @param new_parent inode of the new parent directory
+ * @param new_dentry new directory cache entry
+ * @param flags flags
+ * @returns 0 on success, Linux error code otherwise
+ */
+#if RTLNX_VER_MIN(5,12,0) || defined(DOXYGEN_RUNNING)
+static int vbsf_inode_rename(struct user_namespace *ns,
+ struct inode *old_parent, struct dentry *old_dentry,
+ struct inode *new_parent, struct dentry *new_dentry, unsigned flags)
+#else
+static int vbsf_inode_rename(struct inode *old_parent, struct dentry *old_dentry,
+ struct inode *new_parent, struct dentry *new_dentry, unsigned flags)
+#endif
+{
+ /*
+ * Deal with flags.
+ */
+ int rc;
+ uint32_t fRename = (old_dentry->d_inode->i_mode & S_IFDIR ? SHFL_RENAME_DIR : SHFL_RENAME_FILE)
+ | SHFL_RENAME_REPLACE_IF_EXISTS;
+#if RTLNX_VER_MIN(3,15,0)
+ if (!(flags & ~RENAME_NOREPLACE)) {
+ if (flags & RENAME_NOREPLACE)
+ fRename &= ~SHFL_RENAME_REPLACE_IF_EXISTS;
+#endif
+ /*
+ * Check that they are on the same mount.
+ */
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(old_parent->i_sb);
+ if (pSuperInfo == VBSF_GET_SUPER_INFO(new_parent->i_sb)) {
+ /*
+ * Build the new path.
+ */
+ struct vbsf_inode_info *sf_new_parent_i = VBSF_GET_INODE_INFO(new_parent);
+ PSHFLSTRING pNewPath;
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_new_parent_i, new_dentry, &pNewPath, __func__);
+ if (rc == 0) {
+ /*
+ * Create and issue the rename request.
+ */
+ VBOXSFRENAMEWITHSRCBUFREQ *pReq;
+ pReq = (VBOXSFRENAMEWITHSRCBUFREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFRENAMEWITHSRCBUFREQ, StrDstPath.String)
+ + pNewPath->u16Size);
+ if (pReq) {
+ struct vbsf_inode_info *sf_file_i = VBSF_GET_INODE_INFO(old_dentry->d_inode);
+ PSHFLSTRING pOldPath = sf_file_i->path;
+
+ memcpy(&pReq->StrDstPath, pNewPath, SHFLSTRING_HEADER_SIZE + pNewPath->u16Size);
+ rc = VbglR0SfHostReqRenameWithSrcContig(pSuperInfo->map.root, pReq, pOldPath, virt_to_phys(pOldPath), fRename);
+ VbglR0PhysHeapFree(pReq);
+ if (RT_SUCCESS(rc)) {
+ /*
+ * On success we replace the path in the inode and trigger
+ * restatting of both parent directories.
+ */
+ struct vbsf_inode_info *sf_old_parent_i = VBSF_GET_INODE_INFO(old_parent);
+ SFLOGFLOW(("vbsf_inode_rename: %s -> %s (%#x)\n", pOldPath->String.ach, pNewPath->String.ach, fRename));
+
+ sf_file_i->path = pNewPath;
+ kfree(pOldPath);
+ pNewPath = NULL;
+
+ sf_new_parent_i->force_restat = 1;
+ sf_old_parent_i->force_restat = 1;
+
+ vbsf_dentry_chain_increase_parent_ttl(old_dentry);
+ vbsf_dentry_chain_increase_parent_ttl(new_dentry);
+
+ rc = 0;
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: VbglR0SfHostReqRenameWithSrcContig(%s,%s,%#x) failed -> %d\n",
+ pOldPath->String.ach, pNewPath->String.ach, fRename, rc));
+ if (rc == VERR_IS_A_DIRECTORY || rc == VERR_IS_A_FILE)
+ vbsf_dentry_invalidate_ttl(old_dentry);
+ rc = -RTErrConvertToErrno(rc);
+ }
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: failed to allocate request (%#x bytes)\n",
+ RT_UOFFSETOF(VBOXSFRENAMEWITHSRCBUFREQ, StrDstPath.String) + pNewPath->u16Size));
+ rc = -ENOMEM;
+ }
+ if (pNewPath)
+ kfree(pNewPath);
+ } else
+ SFLOGFLOW(("vbsf_inode_rename: vbsf_path_from_dentry failed: %d\n", rc));
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: rename with different roots (%#x vs %#x)\n",
+ pSuperInfo->map.root, VBSF_GET_SUPER_INFO(new_parent->i_sb)->map.root));
+ rc = -EXDEV;
+ }
+#if RTLNX_VER_MIN(3,15,0)
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: Unsupported flags: %#x\n", flags));
+ rc = -EINVAL;
+ }
+#else
+ RT_NOREF(flags);
+#endif
+ return rc;
+}
+
+
+#if RTLNX_VER_MAX(4,9,0)
+/**
+ * The traditional rename interface without any flags.
+ */
+static int vbsf_inode_rename_no_flags(struct inode *old_parent, struct dentry *old_dentry,
+ struct inode *new_parent, struct dentry *new_dentry)
+{
+ return vbsf_inode_rename(old_parent, old_dentry, new_parent, new_dentry, 0);
+}
+#endif
+
+
+/**
+ * Create a symbolic link.
+ */
+#if RTLNX_VER_MIN(5,12,0)
+static int vbsf_inode_symlink(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, const char *target)
+#else
+static int vbsf_inode_symlink(struct inode *parent, struct dentry *dentry, const char *target)
+#endif
+{
+ /*
+ * Turn the target into a string (contiguous physcial memory).
+ */
+ /** @todo we can save a kmalloc here if we switch to embedding the target rather
+ * than the symlink path into the request. Will require more NLS helpers. */
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ PSHFLSTRING pTarget = NULL;
+ int rc = vbsf_nls_to_shflstring(pSuperInfo, target, &pTarget);
+ if (rc == 0) {
+ /*
+ * Create a full path for the symlink name.
+ */
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(parent);
+ PSHFLSTRING pPath = NULL;
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_i, dentry, &pPath, __func__);
+ if (rc == 0) {
+ /*
+ * Create the request and issue it.
+ */
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFCREATESYMLINKREQ, StrSymlinkPath.String) + pPath->u16Size;
+ VBOXSFCREATESYMLINKREQ *pReq = (VBOXSFCREATESYMLINKREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ RT_ZERO(*pReq);
+ memcpy(&pReq->StrSymlinkPath, pPath, SHFLSTRING_HEADER_SIZE + pPath->u16Size);
+
+ rc = VbglR0SfHostReqCreateSymlinkContig(pSuperInfo->map.root, pTarget, virt_to_phys(pTarget), pReq);
+ if (RT_SUCCESS(rc)) {
+ sf_i->force_restat = 1;
+
+ /*
+ * Instantiate a new inode for the symlink.
+ */
+ rc = vbsf_inode_instantiate(parent, dentry, pPath, &pReq->ObjInfo, SHFL_HANDLE_NIL);
+ if (rc == 0) {
+ SFLOGFLOW(("vbsf_inode_symlink: Successfully created '%s' -> '%s'\n", pPath->String.ach, pTarget->String.ach));
+ pPath = NULL; /* consumed by inode */
+ vbsf_dentry_chain_increase_ttl(dentry);
+ } else {
+ SFLOGFLOW(("vbsf_inode_symlink: Failed to create inode for '%s': %d\n", pPath->String.ach, rc));
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ vbsf_dentry_invalidate_ttl(dentry);
+ }
+ } else {
+ int const vrc = rc;
+ if (vrc == VERR_WRITE_PROTECT)
+ rc = -EPERM; /* EPERM: Symlink creation not supported according to the linux manpage as of 2017-09-15.
+ "VBoxInternal2/SharedFoldersEnableSymlinksCreate/<share>" is not 1. */
+ else
+ rc = -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_inode_symlink: VbglR0SfHostReqCreateSymlinkContig failed for '%s' -> '%s': %Rrc (-> %d)\n",
+ pPath->String.ach, pTarget->String.ach, vrc, rc));
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ SFLOGFLOW(("vbsf_inode_symlink: failed to allocate %u phys heap for the request!\n", cbReq));
+ rc = -ENOMEM;
+ }
+ if (pPath)
+ kfree(pPath);
+ }
+ kfree(pTarget);
+ }
+ return rc;
+}
+
+
+/**
+ * Directory inode operations.
+ */
+struct inode_operations vbsf_dir_iops = {
+ .lookup = vbsf_inode_lookup,
+#if RTLNX_VER_MIN(3,16,0)
+ .atomic_open = vbsf_inode_atomic_open,
+#endif
+ .create = vbsf_inode_create,
+ .symlink = vbsf_inode_symlink,
+ .mkdir = vbsf_inode_mkdir,
+ .rmdir = vbsf_inode_rmdir,
+ .unlink = vbsf_inode_unlink,
+#if RTLNX_VER_MIN(4,9,0)
+ .rename = vbsf_inode_rename,
+#else
+# if RTLNX_VER_MAX(3,17,0)
+ .rename = vbsf_inode_rename_no_flags,
+# endif
+# if RTLNX_VER_MIN(3,15,0)
+ .rename2 = vbsf_inode_rename,
+# endif
+#endif
+#if RTLNX_VER_MIN(2,5,18)
+ .getattr = vbsf_inode_getattr,
+#else
+ .revalidate = vbsf_inode_revalidate,
+#endif
+ .setattr = vbsf_inode_setattr,
+};
+
diff --git a/src/VBox/Additions/linux/sharedfolders/files_vboxsf b/src/VBox/Additions/linux/sharedfolders/files_vboxsf
new file mode 100755
index 00000000..d896212a
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/files_vboxsf
@@ -0,0 +1,107 @@
+#!/bin/sh
+# $Id: files_vboxsf $
+## @file
+# Shared file between Makefile.kmk and export_modules.sh.
+#
+
+#
+# Copyright (C) 2007-2022 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>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+FILES_VBOXSF_NOBIN=" \
+ ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \
+ ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \
+ ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \
+ ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \
+ ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \
+ ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \
+ ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \
+ ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \
+ ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \
+ ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \
+ ${PATH_ROOT}/include/iprt/fs.h=>include/iprt/fs.h \
+ ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \
+ ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \
+ ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \
+ ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \
+ ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \
+ ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \
+ ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \
+ ${PATH_ROOT}/include/iprt/path.h=>include/iprt/path.h \
+ ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \
+ ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \
+ ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \
+ ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \
+ ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \
+ ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \
+ ${PATH_ROOT}/include/iprt/uint64.h=>include/iprt/uint64.h \
+ ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \
+ ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \
+ ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \
+ ${PATH_ROOT}/include/iprt/linux/version.h=>include/iprt/linux/version.h \
+ ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \
+ ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \
+ ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \
+ ${PATH_ROOT}/include/VBox/ostypes.h=>include/VBox/ostypes.h \
+ ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \
+ ${PATH_ROOT}/include/VBox/shflsvc.h=>include/VBox/shflsvc.h \
+ ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuest.h=>include/VBox/VBoxGuest.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestCoreTypes.h=>include/VBox/VBoxGuestCoreTypes.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestLib.h=>include/VBox/VBoxGuestLib.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestLibSharedFolders.h=>include/VBox/VBoxGuestLibSharedFolders.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestLibSharedFoldersInline.h=>include/VBox/VBoxGuestLibSharedFoldersInline.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestMangling.h=>include/VBox/VBoxGuestMangling.h \
+ ${PATH_ROOT}/include/VBox/VMMDev.h=>include/VBox/VMMDev.h \
+ ${PATH_ROOT}/include/VBox/VMMDevCoreTypes.h=>include/VBox/VMMDevCoreTypes.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h=>VBoxGuestR0LibInternal.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp=>VBoxGuestR0LibGenericRequest.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp=>VBoxGuestR0LibHGCM.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp=>VBoxGuestR0LibIdc.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp=>VBoxGuestR0LibIdc-unix.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp=>VBoxGuestR0LibInit.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp=>VBoxGuestR0LibPhysHeap.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c=>VBoxGuestR0LibSharedFolders.c \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divdi3.c=>divdi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/moddi3.c=>moddi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/qdivrem.c=>qdivrem.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/quad.h=>quad.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivdi3.c=>udivdi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivmoddi4.c=>udivmoddi4.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/umoddi3.c=>umoddi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/Makefile.module=>Makefile \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/dirops.c=>dirops.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/lnkops.c=>lnkops.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/regops.c=>regops.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/utils.c=>utils.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/vbsfmount.h=>vbsfmount.h \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/vfsmod.c=>vfsmod.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/vfsmod.h=>vfsmod.h \
+ ${PATH_OUT}/version-generated.h=>version-generated.h \
+ ${PATH_OUT}/revision-generated.h=>revision-generated.h \
+ ${PATH_OUT}/product-generated.h=>product-generated.h \
+"
+
+FILES_VBOXSF_BIN=" \
+"
diff --git a/src/VBox/Additions/linux/sharedfolders/lnkops.c b/src/VBox/Additions/linux/sharedfolders/lnkops.c
new file mode 100644
index 00000000..366d990b
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/lnkops.c
@@ -0,0 +1,305 @@
+/* $Id: lnkops.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, operations for symbolic links.
+ */
+
+/*
+ * Copyright (C) 2010-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+
+
+/**
+ * Converts error codes as best we can.
+ */
+DECLINLINE(int) vbsf_convert_symlink_error(int vrc)
+{
+ if ( vrc == VERR_IS_A_DIRECTORY
+ || vrc == VERR_IS_A_FIFO
+ || vrc == VERR_IS_A_FILE
+ || vrc == VERR_IS_A_BLOCK_DEVICE
+ || vrc == VERR_IS_A_CHAR_DEVICE
+ || vrc == VERR_IS_A_SOCKET
+ || vrc == VERR_NOT_SYMLINK)
+ return -EINVAL;
+ if (vrc == VERR_PATH_NOT_FOUND)
+ return -ENOTDIR;
+ if (vrc == VERR_FILE_NOT_FOUND)
+ return -ENOENT;
+ return -EPROTO;
+}
+
+
+/**
+ * Does the NLS conversion of the symlink target.
+ */
+static int vbsf_symlink_nls_convert_slow(struct vbsf_super_info *pSuperInfo, char *pszTarget, size_t cbTargetBuf)
+{
+ int rc;
+ size_t const cchUtf8 = RTStrNLen(pszTarget, cbTargetBuf);
+ if (cchUtf8 < cbTargetBuf) {
+ /*
+ * If the target is short and there is a lot of space left in the target
+ * buffer (typically PAGE_SIZE in size), we move the input to the end
+ * instead of allocating a temporary buffer for it. This works because
+ * there shouldn't be anything that is more than 8x worse than UTF-8
+ * when it comes to efficiency.
+ */
+ char *pszFree = NULL;
+ char *pszUtf8;
+ if (cchUtf8 - 1 <= cbTargetBuf / 8) {
+ pszUtf8 = &pszTarget[cbTargetBuf - cchUtf8 - 1];
+ cbTargetBuf -= cchUtf8 - 1;
+ } else {
+ pszFree = pszUtf8 = kmalloc(cchUtf8 + 1, GFP_KERNEL);
+ if (RT_UNLIKELY(!pszUtf8)) {
+ LogRelMax(50, ("vbsf_symlink_nls_convert_slow: failed to allocate %u bytes\n", cchUtf8 + 1));
+ return -ENOMEM;
+ }
+ }
+ memcpy(pszUtf8, pszTarget, cchUtf8);
+ pszUtf8[cchUtf8] = '\0';
+
+ rc = vbsf_nlscpy(pSuperInfo, pszTarget, cbTargetBuf, pszUtf8, cchUtf8);
+ if (pszFree)
+ kfree(pszFree);
+ } else {
+ SFLOGFLOW(("vbsf_symlink_nls_convert_slow: Impossible! Unterminated target!\n"));
+ rc = -ENAMETOOLONG;
+ }
+ return rc;
+}
+
+
+/**
+ * Does NLS conversion if needed.
+ */
+DECLINLINE(int) vbsf_symlink_nls_convert(struct vbsf_super_info *pSuperInfo, char *pszTarget, size_t cbTargetBuf)
+{
+ if (pSuperInfo->fNlsIsUtf8)
+ return 0;
+ return vbsf_symlink_nls_convert_slow(pSuperInfo, pszTarget, cbTargetBuf);
+}
+
+#if RTLNX_VER_MIN(4,5,0)
+
+/**
+ * Get symbolic link.
+ */
+static const char *vbsf_get_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done)
+{
+ char *pszTarget;
+ if (dentry) {
+ pszTarget = (char *)kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (pszTarget) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ int rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length,
+ pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1));
+ if (RT_SUCCESS(rc)) {
+ pszTarget[PAGE_SIZE - 1] = '\0';
+ SFLOGFLOW(("vbsf_get_link: %s -> %s\n", sf_i->path->String.ach, pszTarget));
+ rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE);
+ if (rc == 0) {
+ vbsf_dentry_chain_increase_ttl(dentry);
+ set_delayed_call(done, kfree_link, pszTarget);
+ return pszTarget;
+ }
+ } else {
+ SFLOGFLOW(("vbsf_get_link: VbglR0SfHostReqReadLinkContigSimple failed on '%s': %Rrc\n",
+ sf_i->path->String.ach, rc));
+ }
+ kfree(pszTarget);
+ pszTarget = ERR_PTR(vbsf_convert_symlink_error(rc));
+ } else
+ pszTarget = ERR_PTR(-ENOMEM);
+ } else
+ pszTarget = ERR_PTR(-ECHILD);
+ return pszTarget;
+}
+
+#else /* < 4.5 */
+
+# if RTLNX_VER_MAX(2,6,8)
+/**
+ * Reads the link into the given buffer.
+ */
+static int vbsf_readlink(struct dentry *dentry, char *buffer, int len)
+{
+ int rc;
+ char *pszTarget = (char *)get_zeroed_page(GFP_KERNEL);
+ if (pszTarget) {
+ struct inode *inode = dentry->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length,
+ pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1));
+ if (RT_SUCCESS(rc)) {
+ pszTarget[PAGE_SIZE - 1] = '\0';
+ SFLOGFLOW(("vbsf_readlink: %s -> %*s\n", sf_i->path->String.ach, pszTarget));
+ rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE);
+ if (rc == 0) {
+ vbsf_dentry_chain_increase_ttl(dentry);
+ rc = vfs_readlink(dentry, buffer, len, pszTarget);
+ }
+ } else {
+ SFLOGFLOW(("vbsf_readlink: VbglR0SfHostReqReadLinkContigSimple failed on '%s': %Rrc\n", sf_i->path->String.ach, rc));
+ rc = vbsf_convert_symlink_error(rc);
+ }
+ free_page((unsigned long)pszTarget);
+ } else
+ rc = -ENOMEM;
+ return rc;
+}
+# endif /* < 2.6.8 */
+
+/**
+ * Follow link in dentry.
+ */
+# if RTLNX_VER_MIN(4,2,0)
+static const char *vbsf_follow_link(struct dentry *dentry, void **cookie)
+# elif RTLNX_VER_MIN(2,6,13)
+static void *vbsf_follow_link(struct dentry *dentry, struct nameidata *nd)
+# else
+static int vbsf_follow_link(struct dentry *dentry, struct nameidata *nd)
+# endif
+{
+ int rc;
+ char *pszTarget = (char *)get_zeroed_page(GFP_KERNEL);
+ if (pszTarget) {
+ struct inode *inode = dentry->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+
+ rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length,
+ pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1));
+ if (RT_SUCCESS(rc)) {
+ pszTarget[PAGE_SIZE - 1] = '\0';
+ SFLOGFLOW(("vbsf_follow_link: %s -> %s\n", sf_i->path->String.ach, pszTarget));
+ rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE);
+ if (rc == 0) {
+ /*
+ * Succeeded. For 2.6.8 and later the page gets associated
+ * with the caller-cookie or nameidata structure and freed
+ * later by vbsf_put_link(). On earlier kernels we have to
+ * call vfs_follow_link() which will try continue the walking
+ * using the buffer we pass it here.
+ */
+ vbsf_dentry_chain_increase_ttl(dentry);
+# if RTLNX_VER_MIN(4,2,0)
+ *cookie = pszTarget;
+ return pszTarget;
+# elif RTLNX_VER_MIN(2,6,8)
+ nd_set_link(nd, pszTarget);
+# if RTLNX_VER_MIN(2,6,13)
+ return NULL;
+# else
+ return 0;
+# endif
+# else /* < 2.6.8 */
+ rc = vfs_follow_link(nd, pszTarget);
+ free_page((unsigned long)pszTarget);
+ return rc;
+# endif
+ }
+
+ /*
+ * Failed.
+ */
+ } else {
+ LogFunc(("VbglR0SfReadLink failed, caller=%s, rc=%Rrc\n", __func__, rc));
+ rc = vbsf_convert_symlink_error(rc);
+ }
+ free_page((unsigned long)pszTarget);
+ } else {
+ rc = -ENOMEM;
+ }
+# if RTLNX_VER_MIN(4,2,0)
+ *cookie = ERR_PTR(rc);
+ return (const char *)ERR_PTR(rc);
+# elif RTLNX_VER_MIN(2,6,8)
+ nd_set_link(nd, (char *)ERR_PTR(rc));
+# if RTLNX_VER_MIN(2,6,13)
+ return NULL;
+# else
+ return 0;
+# endif
+# else /* < 2.6.8 */
+ return rc;
+# endif /* < 2.6.8 */
+}
+
+# if RTLNX_VER_MIN(2,6,8)
+/**
+ * For freeing target link buffer allocated by vbsf_follow_link.
+ *
+ * For kernels before 2.6.8 memory isn't being kept around.
+ */
+# if RTLNX_VER_MIN(4,2,0)
+static void vbsf_put_link(struct inode *inode, void *cookie)
+# elif RTLNX_VER_MIN(2,6,13)
+static void vbsf_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
+# else
+static void vbsf_put_link(struct dentry *dentry, struct nameidata *nd)
+# endif
+{
+# if RTLNX_VER_MIN(2,6,13)
+ char *page = cookie;
+# else
+ char *page = nd_get_link(nd);
+# endif
+ SFLOGFLOW(("vbsf_put_link: page=%p\n", page));
+ if (!IS_ERR(page))
+ free_page((unsigned long)page);
+}
+# endif /* >= 2.6.8 */
+
+#endif /* < 4.5.0 */
+
+/**
+ * Symlink inode operations.
+ */
+struct inode_operations vbsf_lnk_iops = {
+#if RTLNX_VER_MAX(4,10,0)
+# if RTLNX_VER_MIN(2,6,8)
+ .readlink = generic_readlink,
+# else
+ .readlink = vbsf_readlink,
+# endif
+#endif
+#if RTLNX_VER_MIN(4,5,0)
+ .get_link = vbsf_get_link
+#else
+ .follow_link = vbsf_follow_link,
+# if RTLNX_VER_MIN(2,6,8)
+ .put_link = vbsf_put_link,
+# endif
+#endif
+};
+
diff --git a/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c b/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c
new file mode 100644
index 00000000..3bd12094
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c
@@ -0,0 +1,702 @@
+/* $Id: mount.vboxsf.c $ */
+/** @file
+ * VirtualBox Guest Additions for Linux - mount(8) helper.
+ *
+ * Parses options provided by mount (or user directly)
+ * Packs them into struct vbsfmount and passes to mount(2)
+ * Optionally adds entries to mtab
+ */
+
+/*
+ * Copyright (C) 2006-2022 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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+/* #define DEBUG */
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <mntent.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <mntent.h>
+#include <limits.h>
+#include <iconv.h>
+#include <sys/utsname.h>
+#include <linux/version.h>
+
+#include "vbsfmount.h"
+
+#include <iprt/assertcompile.h>
+#include <iprt/param.h> /* PAGE_SIZE (used by MAX_MNTOPT_STR) */
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define PANIC_ATTR __attribute ((noreturn, __format__ (__printf__, 1, 2)))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct vbsf_mount_opts
+{
+ unsigned long fFlags; /**< MS_XXX */
+
+ /** @name Preformatted option=value or empty if not specified.
+ * Helps eliminate duplicate options as well as simplifying concatting.
+ * @{ */
+ char szTTL[32];
+ char szMsDirCacheTTL[32];
+ char szMsInodeTTL[32];
+ char szMaxIoPages[32];
+ char szDirBuf[32];
+ char szCacheMode[32];
+ char szUid[32];
+ char szGid[32];
+ char szDMode[32];
+ char szFMode[32];
+ char szDMask[32];
+ char szFMask[32];
+ char szIoCharset[32];
+ /** @} */
+
+ bool fSloppy;
+ char *pszConvertCp;
+};
+
+
+static void PANIC_ATTR
+panic(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+static void PANIC_ATTR
+panic_err(const char *fmt, ...)
+{
+ va_list ap;
+ int errno_code = errno;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", strerror(errno_code));
+ exit(EXIT_FAILURE);
+}
+
+static int
+safe_atoi(const char *s, size_t size, int base)
+{
+ char *endptr;
+ long long int val = strtoll(s, &endptr, base);
+
+ if ( val < INT_MIN
+ || ( val > INT_MAX
+ && (base != 8 || val != UINT_MAX) ) /* hack for printf("%o", -1) - 037777777777 */
+ || endptr < s + size)
+ {
+ errno = ERANGE;
+ panic_err("could not convert %.*s to integer, result = %lld (%d)",
+ (int)size, s, val, (int)val);
+ }
+ return (int)val;
+}
+
+static unsigned
+safe_atoiu(const char *s, size_t size, int base)
+{
+ char *endptr;
+ long long int val = strtoll(s, &endptr, base);
+
+ if ( val < 0
+ || val > UINT_MAX
+ || endptr < s + size)
+ {
+ errno = ERANGE;
+ panic_err("could not convert %.*s to unsigned integer, result = %lld (%#llx)",
+ (int)size, s, val, val);
+ }
+ return (unsigned)val;
+}
+
+static void
+process_mount_opts(const char *s, struct vbsf_mount_opts *opts)
+{
+ const char *next = s;
+ size_t len;
+ typedef enum handler_opt
+ {
+ HO_RW,
+ HO_RO,
+ HO_UID,
+ HO_GID,
+ HO_TTL,
+ HO_DENTRY_TTL,
+ HO_INODE_TTL,
+ HO_MAX_IO_PAGES,
+ HO_DIR_BUF,
+ HO_CACHE,
+ HO_DMODE,
+ HO_FMODE,
+ HO_UMASK,
+ HO_DMASK,
+ HO_FMASK,
+ HO_IOCHARSET,
+ HO_NLS,
+ HO_CONVERTCP,
+ HO_NOEXEC,
+ HO_EXEC,
+ HO_NODEV,
+ HO_DEV,
+ HO_NOSUID,
+ HO_SUID,
+ HO_REMOUNT,
+ HO_NOAUTO,
+ HO_NIGNORE
+ } handler_opt;
+ struct
+ {
+ const char *name;
+ handler_opt opt;
+ int has_arg;
+ const char *desc;
+ } handlers[] =
+ {
+ {"rw", HO_RW, 0, "mount read write (default)"},
+ {"ro", HO_RO, 0, "mount read only"},
+ {"uid", HO_UID, 1, "default file owner user id"},
+ {"gid", HO_GID, 1, "default file owner group id"},
+ {"ttl", HO_TTL, 1, "time to live for dentries & inode info"},
+ {"dcachettl", HO_DENTRY_TTL, 1, "time to live for dentries"},
+ {"inodettl", HO_INODE_TTL, 1, "time to live for inode info"},
+ {"maxiopages", HO_MAX_IO_PAGES, 1, "max buffer size for I/O with host"},
+ {"dirbuf", HO_DIR_BUF, 1, "directory buffer size (0 for default)"},
+ {"cache", HO_CACHE, 1, "cache mode: none, strict (default), read, readwrite"},
+ {"iocharset", HO_IOCHARSET, 1, "i/o charset (default utf8)"},
+ {"nls", HO_NLS, 1, "i/o charset (default utf8)"},
+ {"convertcp", HO_CONVERTCP, 1, "convert share name from given charset to utf8"},
+ {"dmode", HO_DMODE, 1, "mode of all directories"},
+ {"fmode", HO_FMODE, 1, "mode of all regular files"},
+ {"umask", HO_UMASK, 1, "umask of directories and regular files"},
+ {"dmask", HO_DMASK, 1, "umask of directories"},
+ {"fmask", HO_FMASK, 1, "umask of regular files"},
+ {"noexec", HO_NOEXEC, 0, NULL}, /* don't document these options directly here */
+ {"exec", HO_EXEC, 0, NULL}, /* as they are well known and described in the */
+ {"nodev", HO_NODEV, 0, NULL}, /* usual manpages */
+ {"dev", HO_DEV, 0, NULL},
+ {"nosuid", HO_NOSUID, 0, NULL},
+ {"suid", HO_SUID, 0, NULL},
+ {"remount", HO_REMOUNT, 0, NULL},
+ {"noauto", HO_NOAUTO, 0, NULL},
+ {"_netdev", HO_NIGNORE, 0, NULL},
+ {"relatime", HO_NIGNORE, 0, NULL},
+ {NULL, 0, 0, NULL}
+ }, *handler;
+
+ while (next)
+ {
+ const char *val;
+ size_t key_len, val_len;
+
+ s = next;
+ next = strchr(s, ',');
+ if (!next)
+ {
+ len = strlen(s);
+ }
+ else
+ {
+ len = next - s;
+ next += 1;
+ if (!*next)
+ next = 0;
+ }
+
+ val = NULL;
+ val_len = 0;
+ for (key_len = 0; key_len < len; ++key_len)
+ {
+ if (s[key_len] == '=')
+ {
+ if (key_len + 1 < len)
+ {
+ val = s + key_len + 1;
+ val_len = len - key_len - 1;
+ }
+ break;
+ }
+ }
+
+ for (handler = handlers; handler->name; ++handler)
+ {
+ size_t j;
+ for (j = 0; j < key_len && handler->name[j] == s[j]; ++j)
+ ;
+
+ if (j == key_len && !handler->name[j])
+ {
+ if (handler->has_arg)
+ {
+ if (!(val && *val))
+ {
+ panic("%.*s requires an argument (i.e. %.*s=<arg>)\n",
+ (int)len, s, (int)len, s);
+ }
+ }
+
+ switch (handler->opt)
+ {
+ case HO_RW:
+ opts->fFlags &= ~MS_RDONLY;
+ break;
+ case HO_RO:
+ opts->fFlags |= MS_RDONLY;
+ break;
+ case HO_NOEXEC:
+ opts->fFlags |= MS_NOEXEC;
+ break;
+ case HO_EXEC:
+ opts->fFlags &= ~MS_NOEXEC;
+ break;
+ case HO_NODEV:
+ opts->fFlags |= MS_NODEV;
+ break;
+ case HO_DEV:
+ opts->fFlags &= ~MS_NODEV;
+ break;
+ case HO_NOSUID:
+ opts->fFlags |= MS_NOSUID;
+ break;
+ case HO_SUID:
+ opts->fFlags &= ~MS_NOSUID;
+ break;
+ case HO_REMOUNT:
+ opts->fFlags |= MS_REMOUNT;
+ break;
+ case HO_TTL:
+ snprintf(opts->szTTL, sizeof(opts->szTTL),
+ "ttl=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_DENTRY_TTL:
+ snprintf(opts->szMsDirCacheTTL, sizeof(opts->szMsDirCacheTTL),
+ "dcachettl=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_INODE_TTL:
+ snprintf(opts->szMsInodeTTL, sizeof(opts->szMsInodeTTL),
+ "inodettl=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_MAX_IO_PAGES:
+ snprintf(opts->szMaxIoPages, sizeof(opts->szMaxIoPages),
+ "maxiopages=%d", safe_atoiu(val, val_len, 10));
+ break;
+ case HO_DIR_BUF:
+ snprintf(opts->szDirBuf, sizeof(opts->szDirBuf),
+ "dirbuf=%d", safe_atoiu(val, val_len, 10));
+ break;
+ case HO_CACHE:
+#define IS_EQUAL(a_sz) (val_len == sizeof(a_sz) - 1U && strncmp(val, a_sz, sizeof(a_sz) - 1U) == 0)
+ if (IS_EQUAL("default"))
+ strcpy(opts->szCacheMode, "cache=default");
+ else if (IS_EQUAL("none"))
+ strcpy(opts->szCacheMode, "cache=none");
+ else if (IS_EQUAL("strict"))
+ strcpy(opts->szCacheMode, "cache=strict");
+ else if (IS_EQUAL("read"))
+ strcpy(opts->szCacheMode, "cache=read");
+ else if (IS_EQUAL("readwrite"))
+ strcpy(opts->szCacheMode, "cache=readwrite");
+ else
+ panic("invalid cache mode '%.*s'\n"
+ "Valid cache modes are: default, none, strict, read, readwrite\n",
+ (int)val_len, val);
+ break;
+ case HO_UID:
+ /** @todo convert string to id. */
+ snprintf(opts->szUid, sizeof(opts->szUid),
+ "uid=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_GID:
+ /** @todo convert string to id. */
+ snprintf(opts->szGid, sizeof(opts->szGid),
+ "gid=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_DMODE:
+ snprintf(opts->szDMode, sizeof(opts->szDMode),
+ "dmode=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_FMODE:
+ snprintf(opts->szFMode, sizeof(opts->szFMode),
+ "fmode=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_UMASK:
+ {
+ int fMask = safe_atoi(val, val_len, 8);
+ snprintf(opts->szDMask, sizeof(opts->szDMask), "dmask=0%o", fMask);
+ snprintf(opts->szFMask, sizeof(opts->szFMask), "fmask=0%o", fMask);
+ break;
+ }
+ case HO_DMASK:
+ snprintf(opts->szDMask, sizeof(opts->szDMask),
+ "dmask=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_FMASK:
+ snprintf(opts->szFMask, sizeof(opts->szFMask),
+ "fmask=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_IOCHARSET:
+ case HO_NLS:
+ if (val_len >= MAX_NLS_NAME)
+ panic("the character set name for I/O is too long: %*.*s\n", (int)val_len, (int)val_len, val);
+ snprintf(opts->szIoCharset, sizeof(opts->szIoCharset),
+ "%s=%*.*s", handler->opt == HO_IOCHARSET ? "iocharset" : "nls", (int)val_len, (int)val_len, val);
+ break;
+ case HO_CONVERTCP:
+ opts->pszConvertCp = malloc(val_len + 1);
+ if (!opts->pszConvertCp)
+ panic_err("could not allocate memory");
+ memcpy(opts->pszConvertCp, val, val_len);
+ opts->pszConvertCp[val_len] = '\0';
+ break;
+ case HO_NOAUTO:
+ case HO_NIGNORE:
+ break;
+ }
+ break;
+ }
+ continue;
+ }
+
+ if ( !handler->name
+ && !opts->fSloppy)
+ {
+ fprintf(stderr, "unknown mount option `%.*s'\n", (int)len, s);
+ fprintf(stderr, "valid options:\n");
+
+ for (handler = handlers; handler->name; ++handler)
+ {
+ if (handler->desc)
+ fprintf(stderr, " %-10s%s %s\n", handler->name,
+ handler->has_arg ? "=<arg>" : "", handler->desc);
+ }
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+/** Appends @a pszOptVal to pszOpts if not empty. */
+static size_t append_option(char *pszOpts, size_t cbOpts, size_t offOpts, const char *pszOptVal)
+{
+ if (*pszOptVal != '\0')
+ {
+ size_t cchOptVal = strlen(pszOptVal);
+ if (offOpts + (offOpts > 0) + cchOptVal < cbOpts)
+ {
+ if (offOpts)
+ pszOpts[offOpts++] = ',';
+ memcpy(&pszOpts[offOpts], pszOptVal, cchOptVal);
+ offOpts += cchOptVal;
+ pszOpts[offOpts] = '\0';
+ }
+ else
+ panic("Too many options!");
+ }
+ return offOpts;
+}
+
+static void
+convertcp(char *in_codeset, char *pszSharedFolder, char *pszDst)
+{
+ char *i = pszSharedFolder;
+ char *o = pszDst;
+ size_t ib = strlen(pszSharedFolder);
+ size_t ob = MAX_HOST_NAME - 1;
+ iconv_t cd;
+
+ cd = iconv_open("UTF-8", in_codeset);
+ if (cd == (iconv_t)-1)
+ {
+ panic_err("could not convert share name, iconv_open `%s' failed",
+ in_codeset);
+ }
+
+ while (ib)
+ {
+ size_t c = iconv(cd, &i, &ib, &o, &ob);
+ if (c == (size_t)-1)
+ {
+ panic_err("could not convert share name(%s) at %d",
+ pszSharedFolder, (int)(strlen(pszSharedFolder) - ib));
+ }
+ }
+ *o = 0;
+}
+
+
+/**
+ * Print out a usage message and exit.
+ *
+ * @returns 1
+ * @param argv0 The name of the application
+ */
+static int usage(char *argv0)
+{
+ printf("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"
+ " -n do not create an mtab entry\n"
+ " -s sloppy parsing, ignore unrecognized mount options\n"
+ " -o OPTION[,OPTION...] use the mount options specified\n"
+ "\n", argv0);
+ printf("Available mount options are:\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");
+ printf(" ttl=MILLIESECSONDS set the \"time to live\" for both the directory cache\n"
+ " and inode info. -1 for kernel default, 0 disables it.\n"
+ " dcachettl=MILLIES set the \"time to live\" for the directory cache,\n"
+ " overriding the 'ttl' option. Ignored if negative.\n"
+ " inodettl=MILLIES set the \"time to live\" for the inode information,\n"
+ " overriding the 'ttl' option. Ignored if negative.\n");
+ printf(" maxiopages=PAGES set the max host I/O buffers size in pages. Uses\n"
+ " default if zero.\n"
+ " dirbuf=BYTES set the directory enumeration buffer size in bytes.\n"
+ " Uses default size if zero.\n");
+ printf(" cache=MODE set the caching mode for the mount. Allowed values:\n"
+ " default: use the kernel default (strict)\n"
+ " none: no caching; may experience guest side\n"
+ " coherence issues between mmap and read.\n");
+ printf(" strict: no caching, except for writably mapped\n"
+ " files (for guest side coherence)\n"
+ " read: read via the page cache; host changes\n"
+ " may be completely ignored\n");
+ printf(" readwrite: read and write via the page cache; host\n"
+ " changes may be completely ignored and\n"
+ " guest changes takes a while to reach the host\n");
+ printf(" dmode=MODE override the mode of all directories to (octal) MODE\n"
+ " fmode=MODE override the mode of all regular files to (octal) MODE\n"
+ " umask=UMASK set the umask to (octal) UMASK\n");
+ printf(" dmask=UMASK set the umask applied to directories only\n"
+ " fmask=UMASK set the umask applied to regular files only\n"
+ " iocharset CHARSET use the character set CHARSET for I/O operations\n"
+ " (default set is utf8)\n"
+ " convertcp CHARSET convert the folder name from CHARSET to utf8\n"
+ "\n");
+ printf("Less common used options:\n"
+ " noexec,exec,nodev,dev,nosuid,suid\n");
+ return EXIT_FAILURE;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int err;
+ int saved_errno;
+ int nomtab = 0;
+ char *pszSharedFolder;
+ char *pszMountPoint;
+ struct utsname uts;
+ int major, minor, patch;
+ size_t offOpts;
+ static const char s_szSfNameOpt[] = "sf_name=";
+ char szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1 + MAX_HOST_NAME];
+ char szOpts[MAX_MNTOPT_STR];
+ struct vbsf_mount_opts opts =
+ {
+ MS_NODEV,
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ false, /*fSloppy*/
+ NULL,
+ };
+
+ AssertCompile(sizeof(uid_t) == sizeof(int));
+ AssertCompile(sizeof(gid_t) == sizeof(int));
+
+ if (getuid())
+ panic("Only root can mount shared folders from the host.\n");
+
+ if (!argv[0])
+ argv[0] = "mount.vboxsf";
+
+ /*
+ * Parse options.
+ */
+ while ((c = getopt(argc, argv, "rwsno:h")) != -1)
+ {
+ switch (c)
+ {
+ default:
+ fprintf(stderr, "unknown option `%c:%#x'\n", c, c);
+ RT_FALL_THRU();
+ case '?':
+ case 'h':
+ return usage(argv[0]);
+
+ case 'r':
+ opts.fFlags |= MS_RDONLY;
+ break;
+
+ case 'w':
+ opts.fFlags &= ~MS_RDONLY;
+ break;
+
+ case 's':
+ opts.fSloppy = true;
+ break;
+
+ case 'o':
+ process_mount_opts(optarg, &opts);
+ break;
+
+ case 'n':
+ nomtab = 1;
+ break;
+ }
+ }
+
+ if (argc - optind < 2)
+ return usage(argv[0]);
+
+ pszSharedFolder = argv[optind];
+ pszMountPoint = argv[optind + 1];
+ if (opts.pszConvertCp)
+ {
+ convertcp(opts.pszConvertCp, pszSharedFolder, &szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1]);
+ pszSharedFolder = &szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1];
+ }
+
+ /*
+ * Concat option strings.
+ */
+ offOpts = 0;
+ szOpts[0] = '\0';
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szTTL);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMsDirCacheTTL);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMsInodeTTL);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMaxIoPages);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDirBuf);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szCacheMode);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szUid);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szGid);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDMode);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szFMode);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDMask);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szFMask);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szIoCharset);
+
+ /* For pre-2.6 kernels we have to supply the shared folder name as a
+ string option because the kernel hides the device name from us. */
+ RT_ZERO(uts);
+ if ( uname(&uts) == -1
+ || sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
+ major = minor = patch = 5;
+
+ if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(2,6,0))
+ {
+ memcpy(szSharedFolderIconved, s_szSfNameOpt, sizeof(s_szSfNameOpt) - 1);
+ if (!opts.pszConvertCp)
+ {
+ if (strlen(pszSharedFolder) >= MAX_HOST_NAME)
+ panic("%s: shared folder name is too long (max %d)", argv[0], (int)MAX_HOST_NAME - 1);
+ strcpy(&szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1], pszSharedFolder);
+ }
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, szSharedFolderIconved);
+ }
+
+ /*
+ * Do the actual mounting.
+ */
+ err = mount(pszSharedFolder, pszMountPoint, "vboxsf", opts.fFlags, szOpts);
+ saved_errno = errno;
+
+ if (err)
+ {
+ if (saved_errno == ENXIO)
+ panic("%s: shared folder '%s' was not found (check VM settings / spelling)\n", argv[0], pszSharedFolder);
+ else
+ panic_err("%s: mounting failed with the error", argv[0]);
+ }
+
+ if (!nomtab)
+ {
+ err = vbsfmount_complete(pszSharedFolder, pszMountPoint, opts.fFlags, szOpts);
+ switch (err)
+ {
+ case 0: /* Success. */
+ break;
+
+ case 1:
+ panic_err("%s: Could not update mount table (out of memory).", argv[0]);
+ break;
+
+ case 2:
+ panic_err("%s: Could not open mount table for update.", argv[0]);
+ break;
+
+ case 3:
+ /* panic_err("%s: Could not add an entry to the mount table.", argv[0]); */
+ break;
+
+ default:
+ panic_err("%s: Unknown error while completing mount operation: %d", argv[0], err);
+ break;
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
diff --git a/src/VBox/Additions/linux/sharedfolders/regops.c b/src/VBox/Additions/linux/sharedfolders/regops.c
new file mode 100644
index 00000000..d88e76c0
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/regops.c
@@ -0,0 +1,3881 @@
+/* $Id: regops.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, regular file inode and file operations.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+#include <linux/uio.h>
+#if RTLNX_VER_MIN(2,5,32)
+# include <linux/aio.h> /* struct kiocb before 4.1 */
+#endif
+#if RTLNX_VER_MIN(2,5,12)
+# include <linux/buffer_head.h>
+#endif
+#if RTLNX_VER_RANGE(2,5,12, 2,6,31)
+# include <linux/writeback.h>
+#endif
+#if RTLNX_VER_RANGE(2,6,23, 3,16,0)
+# include <linux/splice.h>
+#endif
+#if RTLNX_VER_RANGE(2,6,17, 2,6,23)
+# include <linux/pipe_fs_i.h>
+#endif
+#if RTLNX_VER_MIN(2,4,10)
+# include <linux/swap.h> /* for mark_page_accessed */
+#endif
+#include <iprt/err.h>
+
+#if RTLNX_VER_MAX(2,6,18)
+# define SEEK_END 2
+#endif
+
+#if RTLNX_VER_MAX(3,16,0)
+# define iter_is_iovec(a_pIter) ( !((a_pIter)->type & ITER_KVEC) )
+#elif RTLNX_VER_MAX(3,19,0)
+# define iter_is_iovec(a_pIter) ( !((a_pIter)->type & (ITER_KVEC | ITER_BVEC)) )
+#endif
+
+#if RTLNX_VER_MAX(4,17,0)
+# define vm_fault_t int
+#endif
+
+#if RTLNX_VER_MAX(2,5,20)
+# define pgoff_t unsigned long
+#endif
+
+#if RTLNX_VER_MAX(2,5,12)
+# define PageUptodate(a_pPage) Page_Uptodate(a_pPage)
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def VBSF_GET_ITER_TYPE
+ * Accessor for getting iov iter type member which changed name in 5.14. */
+#if RTLNX_VER_MIN(5,14,0)
+# define VBSF_GET_ITER_TYPE(a_pIter) ((a_pIter)->iter_type)
+#else
+# define VBSF_GET_ITER_TYPE(a_pIter) ((a_pIter)->type)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#if RTLNX_VER_MAX(3,16,0)
+struct vbsf_iov_iter {
+ unsigned int type;
+ unsigned int v_write : 1;
+ size_t iov_offset;
+ size_t nr_segs;
+ struct iovec const *iov;
+# ifdef VBOX_STRICT
+ struct iovec const *iov_org;
+ size_t nr_segs_org;
+# endif
+};
+# ifdef VBOX_STRICT
+# define VBSF_IOV_ITER_INITIALIZER(a_cSegs, a_pIov, a_fWrite) \
+ { vbsf_iov_iter_detect_type(a_pIov, a_cSegs), a_fWrite, 0, a_cSegs, a_pIov, a_pIov, a_cSegs }
+# else
+# define VBSF_IOV_ITER_INITIALIZER(a_cSegs, a_pIov, a_fWrite) \
+ { vbsf_iov_iter_detect_type(a_pIov, a_cSegs), a_fWrite, 0, a_cSegs, a_pIov }
+# endif
+# define ITER_KVEC 1
+# define iov_iter vbsf_iov_iter
+#endif
+
+#if RTLNX_VER_MIN(2,6,19)
+/** Used by vbsf_iter_lock_pages() to keep the first page of the next segment. */
+struct vbsf_iter_stash {
+ struct page *pPage;
+ size_t off;
+ size_t cb;
+# if RTLNX_VER_MAX(4,11,0)
+ size_t offFromEnd;
+ struct iov_iter Copy;
+# endif
+};
+#endif /* >= 3.16.0 */
+/** Initializer for struct vbsf_iter_stash. */
+#if RTLNX_VER_MIN(4,11,0)
+# define VBSF_ITER_STASH_INITIALIZER { NULL, 0 }
+#else
+# define VBSF_ITER_STASH_INITIALIZER { NULL, 0, ~(size_t)0 }
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLINLINE(void) vbsf_put_page(struct page *pPage);
+static void vbsf_unlock_user_pages(struct page **papPages, size_t cPages, bool fSetDirty, bool fLockPgHack);
+static void vbsf_reg_write_sync_page_cache(struct address_space *mapping, loff_t offFile, uint32_t cbRange,
+ uint8_t const *pbSrcBuf, struct page **papSrcPages,
+ uint32_t offSrcPage, size_t cSrcPages);
+
+
+/*********************************************************************************************************************************
+* Provide more recent uio.h functionality to older kernels. *
+*********************************************************************************************************************************/
+#if RTLNX_VER_RANGE(2,6,19, 3,16,0)
+
+/**
+ * Detects the vector type.
+ */
+static int vbsf_iov_iter_detect_type(struct iovec const *paIov, size_t cSegs)
+{
+ /* Check the first segment with a non-zero length. */
+ while (cSegs-- > 0) {
+ if (paIov->iov_len > 0) {
+ if (access_ok(VERIFY_READ, paIov->iov_base, paIov->iov_len))
+#if RTLNX_VER_MIN(5,10,0)
+ return (uintptr_t)paIov->iov_base >= TASK_SIZE_MAX ? ITER_KVEC : 0;
+#else
+ return (uintptr_t)paIov->iov_base >= USER_DS.seg ? ITER_KVEC : 0;
+#endif
+ AssertMsgFailed(("%p LB %#zx\n", paIov->iov_base, paIov->iov_len));
+ break;
+ }
+ paIov++;
+ }
+ return 0;
+}
+
+
+# undef iov_iter_count
+# define iov_iter_count(a_pIter) vbsf_iov_iter_count(a_pIter)
+static size_t vbsf_iov_iter_count(struct vbsf_iov_iter const *iter)
+{
+ size_t cbRet = 0;
+ size_t cLeft = iter->nr_segs;
+ struct iovec const *iov = iter->iov;
+ while (cLeft-- > 0) {
+ cbRet += iov->iov_len;
+ iov++;
+ }
+ return cbRet - iter->iov_offset;
+}
+
+
+# undef iov_iter_single_seg_count
+# define iov_iter_single_seg_count(a_pIter) vbsf_iov_iter_single_seg_count(a_pIter)
+static size_t vbsf_iov_iter_single_seg_count(struct vbsf_iov_iter const *iter)
+{
+ if (iter->nr_segs > 0)
+ return iter->iov->iov_len - iter->iov_offset;
+ return 0;
+}
+
+
+# undef iov_iter_advance
+# define iov_iter_advance(a_pIter, a_cbSkip) vbsf_iov_iter_advance(a_pIter, a_cbSkip)
+static void vbsf_iov_iter_advance(struct vbsf_iov_iter *iter, size_t cbSkip)
+{
+ SFLOG2(("vbsf_iov_iter_advance: cbSkip=%#zx\n", cbSkip));
+ if (iter->nr_segs > 0) {
+ size_t const cbLeftCur = iter->iov->iov_len - iter->iov_offset;
+ Assert(iter->iov_offset <= iter->iov->iov_len);
+ if (cbLeftCur > cbSkip) {
+ iter->iov_offset += cbSkip;
+ } else {
+ cbSkip -= cbLeftCur;
+ iter->iov_offset = 0;
+ iter->iov++;
+ iter->nr_segs--;
+ while (iter->nr_segs > 0) {
+ size_t const cbSeg = iter->iov->iov_len;
+ if (cbSeg > cbSkip) {
+ iter->iov_offset = cbSkip;
+ break;
+ }
+ cbSkip -= cbSeg;
+ iter->iov++;
+ iter->nr_segs--;
+ }
+ }
+ }
+}
+
+
+# undef iov_iter_get_pages
+# define iov_iter_get_pages(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0) \
+ vbsf_iov_iter_get_pages(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0)
+static ssize_t vbsf_iov_iter_get_pages(struct vbsf_iov_iter *iter, struct page **papPages,
+ size_t cbMax, unsigned cMaxPages, size_t *poffPg0)
+{
+ while (iter->nr_segs > 0) {
+ size_t const cbLeft = iter->iov->iov_len - iter->iov_offset;
+ Assert(iter->iov->iov_len >= iter->iov_offset);
+ if (cbLeft > 0) {
+ uintptr_t uPtrFrom = (uintptr_t)iter->iov->iov_base + iter->iov_offset;
+ size_t offPg0 = *poffPg0 = uPtrFrom & PAGE_OFFSET_MASK;
+ size_t cPagesLeft = RT_ALIGN_Z(offPg0 + cbLeft, PAGE_SIZE) >> PAGE_SHIFT;
+ size_t cPages = RT_MIN(cPagesLeft, cMaxPages);
+ struct task_struct *pTask = current;
+ size_t cPagesLocked;
+
+ down_read(&pTask->mm->mmap_sem);
+ cPagesLocked = get_user_pages(pTask, pTask->mm, uPtrFrom, cPages, iter->v_write, 1 /*force*/, papPages, NULL);
+ up_read(&pTask->mm->mmap_sem);
+ if (cPagesLocked == cPages) {
+ size_t cbRet = (cPages << PAGE_SHIFT) - offPg0;
+ if (cPages == cPagesLeft) {
+ size_t offLastPg = (uPtrFrom + cbLeft) & PAGE_OFFSET_MASK;
+ if (offLastPg)
+ cbRet -= PAGE_SIZE - offLastPg;
+ }
+ Assert(cbRet <= cbLeft);
+ return cbRet;
+ }
+ if (cPagesLocked > 0)
+ vbsf_unlock_user_pages(papPages, cPagesLocked, false /*fSetDirty*/, false /*fLockPgHack*/);
+ return -EFAULT;
+ }
+ iter->iov_offset = 0;
+ iter->iov++;
+ iter->nr_segs--;
+ }
+ AssertFailed();
+ return 0;
+}
+
+
+# undef iov_iter_truncate
+# define iov_iter_truncate(iter, cbNew) vbsf_iov_iter_truncate(iter, cbNew)
+static void vbsf_iov_iter_truncate(struct vbsf_iov_iter *iter, size_t cbNew)
+{
+ /* we have no counter or stuff, so it's a no-op. */
+ RT_NOREF(iter, cbNew);
+}
+
+
+# undef iov_iter_revert
+# define iov_iter_revert(a_pIter, a_cbRewind) vbsf_iov_iter_revert(a_pIter, a_cbRewind)
+void vbsf_iov_iter_revert(struct vbsf_iov_iter *iter, size_t cbRewind)
+{
+ SFLOG2(("vbsf_iov_iter_revert: cbRewind=%#zx\n", cbRewind));
+ if (iter->iov_offset > 0) {
+ if (cbRewind <= iter->iov_offset) {
+ iter->iov_offset -= cbRewind;
+ return;
+ }
+ cbRewind -= iter->iov_offset;
+ iter->iov_offset = 0;
+ }
+
+ while (cbRewind > 0) {
+ struct iovec const *pIov = --iter->iov;
+ size_t const cbSeg = pIov->iov_len;
+ iter->nr_segs++;
+
+ Assert((uintptr_t)pIov >= (uintptr_t)iter->iov_org);
+ Assert(iter->nr_segs <= iter->nr_segs_org);
+
+ if (cbRewind <= cbSeg) {
+ iter->iov_offset = cbSeg - cbRewind;
+ break;
+ }
+ cbRewind -= cbSeg;
+ }
+}
+
+#endif /* 2.6.19 <= linux < 3.16.0 */
+#if RTLNX_VER_RANGE(3,16,0, 3,16,35)
+
+/** This is for implementing cMaxPage on 3.16 which doesn't have it. */
+static ssize_t vbsf_iov_iter_get_pages_3_16(struct iov_iter *iter, struct page **papPages,
+ size_t cbMax, unsigned cMaxPages, size_t *poffPg0)
+{
+ if (!(iter->type & ITER_BVEC)) {
+ size_t const offPg0 = iter->iov_offset & PAGE_OFFSET_MASK;
+ size_t const cbMaxPages = ((size_t)cMaxPages << PAGE_SHIFT) - offPg0;
+ if (cbMax > cbMaxPages)
+ cbMax = cbMaxPages;
+ }
+ /* else: BVEC works a page at a time and shouldn't have much of a problem here. */
+ return iov_iter_get_pages(iter, papPages, cbMax, poffPg0);
+}
+# undef iov_iter_get_pages
+# define iov_iter_get_pages(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0) \
+ vbsf_iov_iter_get_pages_3_16(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0)
+
+#endif /* 3.16.0-3.16.34 */
+#if RTLNX_VER_RANGE(2,6,19, 3,18,0)
+
+static size_t copy_from_iter(uint8_t *pbDst, size_t cbToCopy, struct iov_iter *pSrcIter)
+{
+ size_t const cbTotal = cbToCopy;
+ Assert(iov_iter_count(pSrcIter) >= cbToCopy);
+# if RTLNX_VER_MIN(3,16,0)
+ if (pSrcIter->type & ITER_BVEC) {
+ while (cbToCopy > 0) {
+ size_t const offPage = (uintptr_t)pbDst & PAGE_OFFSET_MASK;
+ size_t const cbThisCopy = RT_MIN(PAGE_SIZE - offPage, cbToCopy);
+ struct page *pPage = rtR0MemObjLinuxVirtToPage(pbDst);
+ size_t cbCopied = copy_page_from_iter(pPage, offPage, cbThisCopy, pSrcIter);
+ AssertStmt(cbCopied <= cbThisCopy, cbCopied = cbThisCopy);
+ pbDst += cbCopied;
+ cbToCopy -= cbCopied;
+ if (cbCopied != cbToCopy)
+ break;
+ }
+ } else
+# endif
+ {
+ while (cbToCopy > 0) {
+ size_t cbThisCopy = iov_iter_single_seg_count(pSrcIter);
+ if (cbThisCopy > 0) {
+ if (cbThisCopy > cbToCopy)
+ cbThisCopy = cbToCopy;
+ if (pSrcIter->type & ITER_KVEC)
+ memcpy(pbDst, (void *)pSrcIter->iov->iov_base + pSrcIter->iov_offset, cbThisCopy);
+ else if (copy_from_user(pbDst, pSrcIter->iov->iov_base + pSrcIter->iov_offset, cbThisCopy) != 0)
+ break;
+ pbDst += cbThisCopy;
+ cbToCopy -= cbThisCopy;
+ }
+ iov_iter_advance(pSrcIter, cbThisCopy);
+ }
+ }
+ return cbTotal - cbToCopy;
+}
+
+
+static size_t copy_to_iter(uint8_t const *pbSrc, size_t cbToCopy, struct iov_iter *pDstIter)
+{
+ size_t const cbTotal = cbToCopy;
+ Assert(iov_iter_count(pDstIter) >= cbToCopy);
+# if RTLNX_VER_MIN(3,16,0)
+ if (pDstIter->type & ITER_BVEC) {
+ while (cbToCopy > 0) {
+ size_t const offPage = (uintptr_t)pbSrc & PAGE_OFFSET_MASK;
+ size_t const cbThisCopy = RT_MIN(PAGE_SIZE - offPage, cbToCopy);
+ struct page *pPage = rtR0MemObjLinuxVirtToPage((void *)pbSrc);
+ size_t cbCopied = copy_page_to_iter(pPage, offPage, cbThisCopy, pDstIter);
+ AssertStmt(cbCopied <= cbThisCopy, cbCopied = cbThisCopy);
+ pbSrc += cbCopied;
+ cbToCopy -= cbCopied;
+ if (cbCopied != cbToCopy)
+ break;
+ }
+ } else
+# endif
+ {
+ while (cbToCopy > 0) {
+ size_t cbThisCopy = iov_iter_single_seg_count(pDstIter);
+ if (cbThisCopy > 0) {
+ if (cbThisCopy > cbToCopy)
+ cbThisCopy = cbToCopy;
+ if (pDstIter->type & ITER_KVEC)
+ memcpy((void *)pDstIter->iov->iov_base + pDstIter->iov_offset, pbSrc, cbThisCopy);
+ else if (copy_to_user(pDstIter->iov->iov_base + pDstIter->iov_offset, pbSrc, cbThisCopy) != 0) {
+ break;
+ }
+ pbSrc += cbThisCopy;
+ cbToCopy -= cbThisCopy;
+ }
+ iov_iter_advance(pDstIter, cbThisCopy);
+ }
+ }
+ return cbTotal - cbToCopy;
+}
+
+#endif /* 3.16.0 <= linux < 3.18.0 */
+
+
+
+/*********************************************************************************************************************************
+* Handle management *
+*********************************************************************************************************************************/
+
+/**
+ * Called when an inode is released to unlink all handles that might impossibly
+ * still be associated with it.
+ *
+ * @param pInodeInfo The inode which handles to drop.
+ */
+void vbsf_handle_drop_chain(struct vbsf_inode_info *pInodeInfo)
+{
+ struct vbsf_handle *pCur, *pNext;
+ unsigned long fSavedFlags;
+ SFLOGFLOW(("vbsf_handle_drop_chain: %p\n", pInodeInfo));
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ RTListForEachSafe(&pInodeInfo->HandleList, pCur, pNext, struct vbsf_handle, Entry) {
+ AssertMsg( (pCur->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST))
+ == (VBSF_HANDLE_F_MAGIC | VBSF_HANDLE_F_ON_LIST), ("%p %#x\n", pCur, pCur->fFlags));
+ pCur->fFlags |= VBSF_HANDLE_F_ON_LIST;
+ RTListNodeRemove(&pCur->Entry);
+ }
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+}
+
+
+/**
+ * Locates a handle that matches all the flags in @a fFlags.
+ *
+ * @returns Pointer to handle on success (retained), use vbsf_handle_release() to
+ * release it. NULL if no suitable handle was found.
+ * @param pInodeInfo The inode info to search.
+ * @param fFlagsSet The flags that must be set.
+ * @param fFlagsClear The flags that must be clear.
+ */
+struct vbsf_handle *vbsf_handle_find(struct vbsf_inode_info *pInodeInfo, uint32_t fFlagsSet, uint32_t fFlagsClear)
+{
+ struct vbsf_handle *pCur;
+ unsigned long fSavedFlags;
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ RTListForEach(&pInodeInfo->HandleList, pCur, struct vbsf_handle, Entry) {
+ AssertMsg( (pCur->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST))
+ == (VBSF_HANDLE_F_MAGIC | VBSF_HANDLE_F_ON_LIST), ("%p %#x\n", pCur, pCur->fFlags));
+ if ((pCur->fFlags & (fFlagsSet | fFlagsClear)) == fFlagsSet) {
+ uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs);
+ if (cRefs > 1) {
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+ SFLOGFLOW(("vbsf_handle_find: returns %p\n", pCur));
+ return pCur;
+ }
+ /* Oops, already being closed (safe as it's only ever increased here). */
+ ASMAtomicDecU32(&pCur->cRefs);
+ }
+ }
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+ SFLOGFLOW(("vbsf_handle_find: returns NULL!\n"));
+ return NULL;
+}
+
+
+/**
+ * Slow worker for vbsf_handle_release() that does the freeing.
+ *
+ * @returns 0 (ref count).
+ * @param pHandle The handle to release.
+ * @param pSuperInfo The info structure for the shared folder associated with
+ * the handle.
+ * @param pszCaller The caller name (for logging failures).
+ */
+uint32_t vbsf_handle_release_slow(struct vbsf_handle *pHandle, struct vbsf_super_info *pSuperInfo, const char *pszCaller)
+{
+ int rc;
+ unsigned long fSavedFlags;
+
+ SFLOGFLOW(("vbsf_handle_release_slow: %p (%s)\n", pHandle, pszCaller));
+
+ /*
+ * Remove from the list.
+ */
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ AssertMsg((pHandle->fFlags & VBSF_HANDLE_F_MAGIC_MASK) == VBSF_HANDLE_F_MAGIC, ("%p %#x\n", pHandle, pHandle->fFlags));
+ Assert(pHandle->pInodeInfo);
+ Assert(pHandle->pInodeInfo && pHandle->pInodeInfo->u32Magic == SF_INODE_INFO_MAGIC);
+
+ if (pHandle->fFlags & VBSF_HANDLE_F_ON_LIST) {
+ pHandle->fFlags &= ~VBSF_HANDLE_F_ON_LIST;
+ RTListNodeRemove(&pHandle->Entry);
+ }
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+
+ /*
+ * Actually destroy it.
+ */
+ rc = VbglR0SfHostReqCloseSimple(pSuperInfo->map.root, pHandle->hHost);
+ if (RT_FAILURE(rc))
+ LogFunc(("Caller %s: VbglR0SfHostReqCloseSimple %#RX64 failed with rc=%Rrc\n", pszCaller, pHandle->hHost, rc));
+ pHandle->hHost = SHFL_HANDLE_NIL;
+ pHandle->fFlags = VBSF_HANDLE_F_MAGIC_DEAD;
+ kfree(pHandle);
+ return 0;
+}
+
+
+/**
+ * Appends a handle to a handle list.
+ *
+ * @param pInodeInfo The inode to add it to.
+ * @param pHandle The handle to add.
+ */
+void vbsf_handle_append(struct vbsf_inode_info *pInodeInfo, struct vbsf_handle *pHandle)
+{
+#ifdef VBOX_STRICT
+ struct vbsf_handle *pCur;
+#endif
+ unsigned long fSavedFlags;
+
+ SFLOGFLOW(("vbsf_handle_append: %p (to %p)\n", pHandle, pInodeInfo));
+ AssertMsg((pHandle->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST)) == VBSF_HANDLE_F_MAGIC,
+ ("%p %#x\n", pHandle, pHandle->fFlags));
+ Assert(pInodeInfo->u32Magic == SF_INODE_INFO_MAGIC);
+
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ AssertMsg((pHandle->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST)) == VBSF_HANDLE_F_MAGIC,
+ ("%p %#x\n", pHandle, pHandle->fFlags));
+#ifdef VBOX_STRICT
+ RTListForEach(&pInodeInfo->HandleList, pCur, struct vbsf_handle, Entry) {
+ Assert(pCur != pHandle);
+ AssertMsg( (pCur->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST))
+ == (VBSF_HANDLE_F_MAGIC | VBSF_HANDLE_F_ON_LIST), ("%p %#x\n", pCur, pCur->fFlags));
+ }
+ pHandle->pInodeInfo = pInodeInfo;
+#endif
+
+ pHandle->fFlags |= VBSF_HANDLE_F_ON_LIST;
+ RTListAppend(&pInodeInfo->HandleList, &pHandle->Entry);
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+}
+
+
+
+/*********************************************************************************************************************************
+* Misc *
+*********************************************************************************************************************************/
+
+#if RTLNX_VER_MAX(2,6,6)
+/** Any writable mappings? */
+DECLINLINE(bool) mapping_writably_mapped(struct address_space const *mapping)
+{
+# if RTLNX_VER_MIN(2,5,6)
+ return !list_empty(&mapping->i_mmap_shared);
+# else
+ return mapping->i_mmap_shared != NULL;
+# endif
+}
+#endif
+
+
+#if RTLNX_VER_MAX(2,5,12)
+/** Missing in 2.4.x, so just stub it for now. */
+DECLINLINE(bool) PageWriteback(struct page const *page)
+{
+ return false;
+}
+#endif
+
+
+/**
+ * Helper for deciding wheter we should do a read via the page cache or not.
+ *
+ * By default we will only use the page cache if there is a writable memory
+ * mapping of the file with a chance that it may have modified any of the pages
+ * already.
+ */
+DECLINLINE(bool) vbsf_should_use_cached_read(struct file *file, struct address_space *mapping, struct vbsf_super_info *pSuperInfo)
+{
+ if ( (file->f_flags & O_DIRECT)
+ || pSuperInfo->enmCacheMode == kVbsfCacheMode_None)
+ return false;
+ if ( pSuperInfo->enmCacheMode == kVbsfCacheMode_Read
+ || pSuperInfo->enmCacheMode == kVbsfCacheMode_ReadWrite)
+ return true;
+ Assert(pSuperInfo->enmCacheMode == kVbsfCacheMode_Strict);
+ return mapping
+ && mapping->nrpages > 0
+ && mapping_writably_mapped(mapping);
+}
+
+
+
+/*********************************************************************************************************************************
+* Pipe / splice stuff mainly for 2.6.17 >= linux < 2.6.31 (where no fallbacks were available) *
+*********************************************************************************************************************************/
+
+#if RTLNX_VER_RANGE(2,6,17, 3,16,0)
+
+# if RTLNX_VER_MAX(2,6,30)
+# define LOCK_PIPE(a_pPipe) do { if ((a_pPipe)->inode) mutex_lock(&(a_pPipe)->inode->i_mutex); } while (0)
+# define UNLOCK_PIPE(a_pPipe) do { if ((a_pPipe)->inode) mutex_unlock(&(a_pPipe)->inode->i_mutex); } while (0)
+# else
+# define LOCK_PIPE(a_pPipe) pipe_lock(a_pPipe)
+# define UNLOCK_PIPE(a_pPipe) pipe_unlock(a_pPipe)
+# endif
+
+
+/** Waits for the pipe buffer status to change. */
+static void vbsf_wait_pipe(struct pipe_inode_info *pPipe)
+{
+ DEFINE_WAIT(WaitStuff);
+# ifdef TASK_NONINTERACTIVE
+ prepare_to_wait(&pPipe->wait, &WaitStuff, TASK_INTERRUPTIBLE | TASK_NONINTERACTIVE);
+# else
+ prepare_to_wait(&pPipe->wait, &WaitStuff, TASK_INTERRUPTIBLE);
+# endif
+ UNLOCK_PIPE(pPipe);
+
+ schedule();
+
+ finish_wait(&pPipe->wait, &WaitStuff);
+ LOCK_PIPE(pPipe);
+}
+
+
+/** Worker for vbsf_feed_pages_to_pipe that wakes up readers. */
+static void vbsf_wake_up_pipe(struct pipe_inode_info *pPipe, bool fReaders)
+{
+ smp_mb();
+ if (waitqueue_active(&pPipe->wait))
+ wake_up_interruptible_sync(&pPipe->wait);
+ if (fReaders)
+ kill_fasync(&pPipe->fasync_readers, SIGIO, POLL_IN);
+ else
+ kill_fasync(&pPipe->fasync_writers, SIGIO, POLL_OUT);
+}
+
+#endif
+#if RTLNX_VER_RANGE(2,6,17, 2,6,31)
+
+/** Verify pipe buffer content (needed for page-cache to ensure idle page). */
+static int vbsf_pipe_buf_confirm(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ /*SFLOG3(("vbsf_pipe_buf_confirm: %p\n", pPipeBuf));*/
+ return 0;
+}
+
+
+/** Maps the buffer page. */
+static void *vbsf_pipe_buf_map(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf, int atomic)
+{
+ void *pvRet;
+ if (!atomic)
+ pvRet = kmap(pPipeBuf->page);
+ else {
+ pPipeBuf->flags |= PIPE_BUF_FLAG_ATOMIC;
+ pvRet = kmap_atomic(pPipeBuf->page, KM_USER0);
+ }
+ /*SFLOG3(("vbsf_pipe_buf_map: %p -> %p\n", pPipeBuf, pvRet));*/
+ return pvRet;
+}
+
+
+/** Unmaps the buffer page. */
+static void vbsf_pipe_buf_unmap(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf, void *pvMapping)
+{
+ /*SFLOG3(("vbsf_pipe_buf_unmap: %p/%p\n", pPipeBuf, pvMapping)); */
+ if (!(pPipeBuf->flags & PIPE_BUF_FLAG_ATOMIC))
+ kunmap(pPipeBuf->page);
+ else {
+ pPipeBuf->flags &= ~PIPE_BUF_FLAG_ATOMIC;
+ kunmap_atomic(pvMapping, KM_USER0);
+ }
+}
+
+
+/** Gets a reference to the page. */
+static void vbsf_pipe_buf_get(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ page_cache_get(pPipeBuf->page);
+ /*SFLOG3(("vbsf_pipe_buf_get: %p (return count=%d)\n", pPipeBuf, page_count(pPipeBuf->page)));*/
+}
+
+
+/** Release the buffer page (counter to vbsf_pipe_buf_get). */
+static void vbsf_pipe_buf_release(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ /*SFLOG3(("vbsf_pipe_buf_release: %p (incoming count=%d)\n", pPipeBuf, page_count(pPipeBuf->page)));*/
+ page_cache_release(pPipeBuf->page);
+}
+
+
+/** Attempt to steal the page.
+ * @returns 0 success, 1 on failure. */
+static int vbsf_pipe_buf_steal(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ if (page_count(pPipeBuf->page) == 1) {
+ lock_page(pPipeBuf->page);
+ SFLOG3(("vbsf_pipe_buf_steal: %p -> 0\n", pPipeBuf));
+ return 0;
+ }
+ SFLOG3(("vbsf_pipe_buf_steal: %p -> 1\n", pPipeBuf));
+ return 1;
+}
+
+
+/**
+ * Pipe buffer operations for used by vbsf_feed_pages_to_pipe.
+ */
+static struct pipe_buf_operations vbsf_pipe_buf_ops = {
+ .can_merge = 0,
+# if RTLNX_VER_MIN(2,6,23)
+ .confirm = vbsf_pipe_buf_confirm,
+# else
+ .pin = vbsf_pipe_buf_confirm,
+# endif
+ .map = vbsf_pipe_buf_map,
+ .unmap = vbsf_pipe_buf_unmap,
+ .get = vbsf_pipe_buf_get,
+ .release = vbsf_pipe_buf_release,
+ .steal = vbsf_pipe_buf_steal,
+};
+
+
+/**
+ * Feeds the pages to the pipe.
+ *
+ * Pages given to the pipe are set to NULL in papPages.
+ */
+static ssize_t vbsf_feed_pages_to_pipe(struct pipe_inode_info *pPipe, struct page **papPages, size_t cPages, uint32_t offPg0,
+ uint32_t cbActual, unsigned fFlags)
+{
+ ssize_t cbRet = 0;
+ size_t iPage = 0;
+ bool fNeedWakeUp = false;
+
+ LOCK_PIPE(pPipe);
+ for (;;) {
+ if ( pPipe->readers > 0
+ && pPipe->nrbufs < PIPE_BUFFERS) {
+ struct pipe_buffer *pPipeBuf = &pPipe->bufs[(pPipe->curbuf + pPipe->nrbufs) % PIPE_BUFFERS];
+ uint32_t const cbThisPage = RT_MIN(cbActual, PAGE_SIZE - offPg0);
+ pPipeBuf->len = cbThisPage;
+ pPipeBuf->offset = offPg0;
+# if RTLNX_VER_MIN(2,6,23)
+ pPipeBuf->private = 0;
+# endif
+ pPipeBuf->ops = &vbsf_pipe_buf_ops;
+ pPipeBuf->flags = fFlags & SPLICE_F_GIFT ? PIPE_BUF_FLAG_GIFT : 0;
+ pPipeBuf->page = papPages[iPage];
+
+ papPages[iPage++] = NULL;
+ pPipe->nrbufs++;
+ fNeedWakeUp |= pPipe->inode != NULL;
+ offPg0 = 0;
+ cbRet += cbThisPage;
+
+ /* done? */
+ cbActual -= cbThisPage;
+ if (!cbActual)
+ break;
+ } else if (pPipe->readers == 0) {
+ SFLOGFLOW(("vbsf_feed_pages_to_pipe: no readers!\n"));
+ send_sig(SIGPIPE, current, 0);
+ if (cbRet == 0)
+ cbRet = -EPIPE;
+ break;
+ } else if (fFlags & SPLICE_F_NONBLOCK) {
+ if (cbRet == 0)
+ cbRet = -EAGAIN;
+ break;
+ } else if (signal_pending(current)) {
+ if (cbRet == 0)
+ cbRet = -ERESTARTSYS;
+ SFLOGFLOW(("vbsf_feed_pages_to_pipe: pending signal! (%zd)\n", cbRet));
+ break;
+ } else {
+ if (fNeedWakeUp) {
+ vbsf_wake_up_pipe(pPipe, true /*fReaders*/);
+ fNeedWakeUp = 0;
+ }
+ pPipe->waiting_writers++;
+ vbsf_wait_pipe(pPipe);
+ pPipe->waiting_writers--;
+ }
+ }
+ UNLOCK_PIPE(pPipe);
+
+ if (fNeedWakeUp)
+ vbsf_wake_up_pipe(pPipe, true /*fReaders*/);
+
+ return cbRet;
+}
+
+
+/**
+ * For splicing from a file to a pipe.
+ */
+static ssize_t vbsf_splice_read(struct file *file, loff_t *poffset, struct pipe_inode_info *pipe, size_t len, unsigned int flags)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ ssize_t cbRet;
+
+ SFLOGFLOW(("vbsf_splice_read: file=%p poffset=%p{%#RX64} pipe=%p len=%#zx flags=%#x\n", file, poffset, *poffset, pipe, len, flags));
+ if (vbsf_should_use_cached_read(file, inode->i_mapping, pSuperInfo)) {
+ cbRet = generic_file_splice_read(file, poffset, pipe, len, flags);
+ } else {
+ /*
+ * Create a read request.
+ */
+ loff_t offFile = *poffset;
+ size_t cPages = RT_MIN(RT_ALIGN_Z((offFile & ~PAGE_CACHE_MASK) + len, PAGE_CACHE_SIZE) >> PAGE_CACHE_SHIFT,
+ PIPE_BUFFERS);
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ,
+ PgLst.aPages[cPages]));
+ if (pReq) {
+ /*
+ * Allocate pages.
+ */
+ struct page *apPages[PIPE_BUFFERS];
+ size_t i;
+ pReq->PgLst.offFirstPage = (uint16_t)offFile & (uint16_t)PAGE_OFFSET_MASK;
+ cbRet = 0;
+ for (i = 0; i < cPages; i++) {
+ struct page *pPage;
+ apPages[i] = pPage = alloc_page(GFP_USER);
+ if (pPage) {
+ pReq->PgLst.aPages[i] = page_to_phys(pPage);
+# ifdef VBOX_STRICT
+ ASMMemFill32(kmap(pPage), PAGE_SIZE, UINT32_C(0xdeadbeef));
+ kunmap(pPage);
+# endif
+ } else {
+ cbRet = -ENOMEM;
+ break;
+ }
+ }
+ if (cbRet == 0) {
+ /*
+ * Do the reading.
+ */
+ uint32_t const cbToRead = RT_MIN((cPages << PAGE_SHIFT) - (offFile & PAGE_OFFSET_MASK), len);
+ struct vbsf_reg_info *sf_r = (struct vbsf_reg_info *)file->private_data;
+ int vrc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbToRead, cPages);
+ if (RT_SUCCESS(vrc)) {
+ /*
+ * Get the number of bytes read, jettison the request
+ * and, in case of EOF, any unnecessary pages.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbActual <= cbToRead, cbActual = cbToRead);
+ SFLOG2(("vbsf_splice_read: read -> %#x bytes @ %#RX64\n", cbActual, offFile));
+
+ VbglR0PhysHeapFree(pReq);
+ pReq = NULL;
+
+ /*
+ * Now, feed it to the pipe thingy.
+ * This will take ownership of the all pages no matter what happens.
+ */
+ cbRet = vbsf_feed_pages_to_pipe(pipe, apPages, cPages, offFile & PAGE_OFFSET_MASK, cbActual, flags);
+ if (cbRet > 0)
+ *poffset = offFile + cbRet;
+ } else {
+ cbRet = -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_splice_read: Read failed: %Rrc -> %zd\n", vrc, cbRet));
+ }
+ i = cPages;
+ }
+
+ while (i-- > 0)
+ if (apPages[i])
+ __free_pages(apPages[i], 0);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ cbRet = -ENOMEM;
+ }
+ }
+ SFLOGFLOW(("vbsf_splice_read: returns %zd (%#zx), *poffset=%#RX64\n", cbRet, cbRet, *poffset));
+ return cbRet;
+}
+
+#endif /* 2.6.17 <= LINUX_VERSION_CODE < 2.6.31 */
+#if RTLNX_VER_RANGE(2,6,17, 3,16,0)
+
+/**
+ * For splicing from a pipe to a file.
+ *
+ * Since we can combine buffers and request allocations, this should be faster
+ * than the default implementation.
+ */
+static ssize_t vbsf_splice_write(struct pipe_inode_info *pPipe, struct file *file, loff_t *poffset, size_t len, unsigned int flags)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ ssize_t cbRet;
+
+ SFLOGFLOW(("vbsf_splice_write: pPipe=%p file=%p poffset=%p{%#RX64} len=%#zx flags=%#x\n", pPipe, file, poffset, *poffset, len, flags));
+ /** @todo later if (false) {
+ cbRet = generic_file_splice_write(pPipe, file, poffset, len, flags);
+ } else */ {
+ /*
+ * Prepare a write request.
+ */
+# ifdef PIPE_BUFFERS
+ uint32_t const cMaxPages = RT_MIN(PIPE_BUFFERS, RT_ALIGN_Z(len, PAGE_SIZE) >> PAGE_SHIFT);
+# else
+ uint32_t const cMaxPages = RT_MIN(RT_MAX(RT_MIN(pPipe->buffers, 256), PIPE_DEF_BUFFERS),
+ RT_ALIGN_Z(len, PAGE_SIZE) >> PAGE_SHIFT);
+# endif
+ VBOXSFWRITEPGLSTREQ *pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ,
+ PgLst.aPages[cMaxPages]));
+ if (pReq) {
+ /*
+ * Feed from the pipe.
+ */
+ struct vbsf_reg_info *sf_r = (struct vbsf_reg_info *)file->private_data;
+ struct address_space *mapping = inode->i_mapping;
+ loff_t offFile = *poffset;
+ bool fNeedWakeUp = false;
+ cbRet = 0;
+
+ LOCK_PIPE(pPipe);
+
+ for (;;) {
+ unsigned cBufs = pPipe->nrbufs;
+ /*SFLOG2(("vbsf_splice_write: nrbufs=%#x curbuf=%#x\n", cBufs, pPipe->curbuf));*/
+ if (cBufs) {
+ /*
+ * There is data available. Write it to the file.
+ */
+ int vrc;
+ struct pipe_buffer *pPipeBuf = &pPipe->bufs[pPipe->curbuf];
+ uint32_t cPagesToWrite = 1;
+ uint32_t cbToWrite = pPipeBuf->len;
+
+ Assert(pPipeBuf->offset < PAGE_SIZE);
+ Assert(pPipeBuf->offset + pPipeBuf->len <= PAGE_SIZE);
+
+ pReq->PgLst.offFirstPage = pPipeBuf->offset & PAGE_OFFSET;
+ pReq->PgLst.aPages[0] = page_to_phys(pPipeBuf->page);
+
+ /* Add any adjacent page buffers: */
+ while ( cPagesToWrite < cBufs
+ && cPagesToWrite < cMaxPages
+ && ((pReq->PgLst.offFirstPage + cbToWrite) & PAGE_OFFSET_MASK) == 0) {
+# ifdef PIPE_BUFFERS
+ struct pipe_buffer *pPipeBuf2 = &pPipe->bufs[(pPipe->curbuf + cPagesToWrite) % PIPE_BUFFERS];
+# else
+ struct pipe_buffer *pPipeBuf2 = &pPipe->bufs[(pPipe->curbuf + cPagesToWrite) % pPipe->buffers];
+# endif
+ Assert(pPipeBuf2->len <= PAGE_SIZE);
+ Assert(pPipeBuf2->offset < PAGE_SIZE);
+ if (pPipeBuf2->offset != 0)
+ break;
+ pReq->PgLst.aPages[cPagesToWrite] = page_to_phys(pPipeBuf2->page);
+ cbToWrite += pPipeBuf2->len;
+ cPagesToWrite += 1;
+ }
+
+ /* Check that we don't have signals pending before we issue the write, as
+ we'll only end up having to cancel the HGCM request 99% of the time: */
+ if (!signal_pending(current)) {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ vrc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile,
+ cbToWrite, cPagesToWrite);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ } else
+ vrc = VERR_INTERRUPTED;
+ if (RT_SUCCESS(vrc)) {
+ /*
+ * Get the number of bytes actually written, update file position
+ * and return value, and advance the pipe buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbActual <= cbToWrite, cbActual = cbToWrite);
+ SFLOG2(("vbsf_splice_write: write -> %#x bytes @ %#RX64\n", cbActual, offFile));
+
+ cbRet += cbActual;
+
+ while (cbActual > 0) {
+ uint32_t cbAdvance = RT_MIN(pPipeBuf->len, cbActual);
+
+ vbsf_reg_write_sync_page_cache(mapping, offFile, cbAdvance, NULL,
+ &pPipeBuf->page, pPipeBuf->offset, 1);
+
+ offFile += cbAdvance;
+ cbActual -= cbAdvance;
+ pPipeBuf->offset += cbAdvance;
+ pPipeBuf->len -= cbAdvance;
+
+ if (!pPipeBuf->len) {
+ struct pipe_buf_operations const *pOps = pPipeBuf->ops;
+ pPipeBuf->ops = NULL;
+ pOps->release(pPipe, pPipeBuf);
+
+# ifdef PIPE_BUFFERS
+ pPipe->curbuf = (pPipe->curbuf + 1) % PIPE_BUFFERS;
+# else
+ pPipe->curbuf = (pPipe->curbuf + 1) % pPipe->buffers;
+# endif
+ pPipe->nrbufs -= 1;
+ pPipeBuf = &pPipe->bufs[pPipe->curbuf];
+
+# if RTLNX_VER_MAX(2,6,30)
+ fNeedWakeUp |= pPipe->inode != NULL;
+# else
+ fNeedWakeUp = true;
+# endif
+ } else {
+ Assert(cbActual == 0);
+ break;
+ }
+ }
+
+ *poffset = offFile;
+ } else {
+ if (cbRet == 0)
+ cbRet = vrc == VERR_INTERRUPTED ? -ERESTARTSYS : -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_splice_write: Write failed: %Rrc -> %zd (cbRet=%#zx)\n",
+ vrc, -RTErrConvertToErrno(vrc), cbRet));
+ break;
+ }
+ } else {
+ /*
+ * Wait for data to become available, if there is chance that'll happen.
+ */
+ /* Quit if there are no writers (think EOF): */
+ if (pPipe->writers == 0) {
+ SFLOGFLOW(("vbsf_splice_write: No buffers. No writers. The show is done!\n"));
+ break;
+ }
+
+ /* Quit if if we've written some and no writers waiting on the lock: */
+ if (cbRet > 0 && pPipe->waiting_writers == 0) {
+ SFLOGFLOW(("vbsf_splice_write: No waiting writers, returning what we've got.\n"));
+ break;
+ }
+
+ /* Quit with EAGAIN if non-blocking: */
+ if (flags & SPLICE_F_NONBLOCK) {
+ if (cbRet == 0)
+ cbRet = -EAGAIN;
+ break;
+ }
+
+ /* Quit if we've got pending signals: */
+ if (signal_pending(current)) {
+ if (cbRet == 0)
+ cbRet = -ERESTARTSYS;
+ SFLOGFLOW(("vbsf_splice_write: pending signal! (%zd)\n", cbRet));
+ break;
+ }
+
+ /* Wake up writers before we start waiting: */
+ if (fNeedWakeUp) {
+ vbsf_wake_up_pipe(pPipe, false /*fReaders*/);
+ fNeedWakeUp = false;
+ }
+ vbsf_wait_pipe(pPipe);
+ }
+ } /* feed loop */
+
+ if (fNeedWakeUp)
+ vbsf_wake_up_pipe(pPipe, false /*fReaders*/);
+
+ UNLOCK_PIPE(pPipe);
+
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ cbRet = -ENOMEM;
+ }
+ }
+ SFLOGFLOW(("vbsf_splice_write: returns %zd (%#zx), *poffset=%#RX64\n", cbRet, cbRet, *poffset));
+ return cbRet;
+}
+
+#endif /* 2.6.17 <= LINUX_VERSION_CODE < 3.16.0 */
+
+#if RTLNX_VER_RANGE(2,5,30, 2,6,23)
+/**
+ * Our own senfile implementation that does not go via the page cache like
+ * generic_file_sendfile() does.
+ */
+static ssize_t vbsf_reg_sendfile(struct file *pFile, loff_t *poffFile, size_t cbToSend, read_actor_t pfnActor,
+# if RTLNX_VER_MIN(2,6,8)
+ void *pvUser
+# else
+ void __user *pvUser
+# endif
+ )
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(pFile)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ ssize_t cbRet;
+ SFLOGFLOW(("vbsf_reg_sendfile: pFile=%p poffFile=%p{%#RX64} cbToSend=%#zx pfnActor=%p pvUser=%p\n",
+ pFile, poffFile, poffFile ? *poffFile : 0, cbToSend, pfnActor, pvUser));
+ Assert(pSuperInfo);
+
+ /*
+ * Return immediately if asked to send nothing.
+ */
+ if (cbToSend == 0)
+ return 0;
+
+ /*
+ * Like for vbsf_reg_read() and vbsf_reg_read_iter(), we allow going via
+ * the page cache in some cases or configs.
+ */
+ if (vbsf_should_use_cached_read(pFile, inode->i_mapping, pSuperInfo)) {
+ cbRet = generic_file_sendfile(pFile, poffFile, cbToSend, pfnActor, pvUser);
+ SFLOGFLOW(("vbsf_reg_sendfile: returns %#zx *poffFile=%#RX64 [generic_file_sendfile]\n", cbRet, poffFile ? *poffFile : UINT64_MAX));
+ } else {
+ /*
+ * Allocate a request and a bunch of pages for reading from the file.
+ */
+ struct page *apPages[16];
+ loff_t offFile = poffFile ? *poffFile : 0;
+ size_t const cPages = cbToSend + ((size_t)offFile & PAGE_OFFSET_MASK) >= RT_ELEMENTS(apPages) * PAGE_SIZE
+ ? RT_ELEMENTS(apPages)
+ : RT_ALIGN_Z(cbToSend + ((size_t)offFile & PAGE_OFFSET_MASK), PAGE_SIZE) >> PAGE_SHIFT;
+ size_t iPage;
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ,
+ PgLst.aPages[cPages]));
+ if (pReq) {
+ Assert(cPages > 0);
+ cbRet = 0;
+ for (iPage = 0; iPage < cPages; iPage++) {
+ struct page *pPage;
+ apPages[iPage] = pPage = alloc_page(GFP_USER);
+ if (pPage) {
+ Assert(page_count(pPage) == 1);
+ pReq->PgLst.aPages[iPage] = page_to_phys(pPage);
+ } else {
+ while (iPage-- > 0)
+ vbsf_put_page(apPages[iPage]);
+ cbRet = -ENOMEM;
+ break;
+ }
+ }
+ if (cbRet == 0) {
+ /*
+ * Do the job.
+ */
+ struct vbsf_reg_info *sf_r = (struct vbsf_reg_info *)pFile->private_data;
+ read_descriptor_t RdDesc;
+ RdDesc.count = cbToSend;
+# if RTLNX_VER_MIN(2,6,8)
+ RdDesc.arg.data = pvUser;
+# else
+ RdDesc.buf = pvUser;
+# endif
+ RdDesc.written = 0;
+ RdDesc.error = 0;
+
+ Assert(sf_r);
+ Assert((sf_r->Handle.fFlags & VBSF_HANDLE_F_MAGIC_MASK) == VBSF_HANDLE_F_MAGIC);
+
+ while (cbToSend > 0) {
+ /*
+ * Read another chunk. For paranoid reasons, we keep data where the page cache
+ * would keep it, i.e. page offset bits corresponds to the file offset bits.
+ */
+ uint32_t const offPg0 = (uint32_t)offFile & (uint32_t)PAGE_OFFSET_MASK;
+ uint32_t const cbToRead = RT_MIN((cPages << PAGE_SHIFT) - offPg0, cbToSend);
+ uint32_t const cPagesToRead = RT_ALIGN_Z(cbToRead + offPg0, PAGE_SIZE) >> PAGE_SHIFT;
+ int vrc;
+ pReq->PgLst.offFirstPage = (uint16_t)offPg0;
+ if (!signal_pending(current))
+ vrc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile,
+ cbToRead, cPagesToRead);
+ else
+ vrc = VERR_INTERRUPTED;
+ if (RT_SUCCESS(vrc)) {
+ /*
+ * Pass what we read to the actor.
+ */
+ uint32_t off = offPg0;
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ bool const fIsEof = cbActual < cbToRead;
+ AssertStmt(cbActual <= cbToRead, cbActual = cbToRead);
+ SFLOG3(("vbsf_reg_sendfile: Read %#x bytes (offPg0=%#x), wanted %#x ...\n", cbActual, offPg0, cbToRead));
+
+ iPage = 0;
+ while (cbActual > 0) {
+ uint32_t const cbPage = RT_MIN(cbActual, PAGE_SIZE - off);
+ int const cbRetActor = pfnActor(&RdDesc, apPages[iPage], off, cbPage);
+ Assert(cbRetActor >= 0); /* Returns zero on failure, with RdDesc.error holding the status code. */
+
+ AssertMsg(iPage < cPages && iPage < cPagesToRead, ("iPage=%#x cPages=%#x cPagesToRead=%#x\n", iPage, cPages, cPagesToRead));
+
+ offFile += cbRetActor;
+ if ((uint32_t)cbRetActor == cbPage && RdDesc.count > 0) {
+ cbActual -= cbPage;
+ cbToSend -= cbPage;
+ iPage++;
+ } else {
+ SFLOG3(("vbsf_reg_sendfile: cbRetActor=%#x (%d) cbPage=%#x RdDesc{count=%#lx error=%d} iPage=%#x/%#x/%#x cbToSend=%#zx\n",
+ cbRetActor, cbRetActor, cbPage, RdDesc.count, RdDesc.error, iPage, cPagesToRead, cPages, cbToSend));
+ vrc = VERR_CALLBACK_RETURN;
+ break;
+ }
+ off = 0;
+ }
+
+ /*
+ * Are we done yet?
+ */
+ if (RT_FAILURE_NP(vrc) || cbToSend == 0 || RdDesc.error != 0 || fIsEof) {
+ break;
+ }
+
+ /*
+ * Replace pages held by the actor.
+ */
+ vrc = VINF_SUCCESS;
+ for (iPage = 0; iPage < cPages; iPage++) {
+ struct page *pPage = apPages[iPage];
+ if (page_count(pPage) != 1) {
+ struct page *pNewPage = alloc_page(GFP_USER);
+ if (pNewPage) {
+ SFLOGFLOW(("vbsf_reg_sendfile: Replacing page #%x: %p -> %p\n", iPage, pPage, pNewPage));
+ vbsf_put_page(pPage);
+ apPages[iPage] = pNewPage;
+ } else {
+ SFLOGFLOW(("vbsf_reg_sendfile: Failed to allocate a replacement page.\n"));
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ }
+ if (RT_FAILURE(vrc))
+ break; /* RdDesc.written should be non-zero, so don't bother with setting error. */
+ } else {
+ RdDesc.error = vrc == VERR_INTERRUPTED ? -ERESTARTSYS : -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_reg_sendfile: Read failed: %Rrc -> %zd (RdDesc.error=%#d)\n",
+ vrc, -RTErrConvertToErrno(vrc), RdDesc.error));
+ break;
+ }
+ }
+
+ /*
+ * Free memory.
+ */
+ for (iPage = 0; iPage < cPages; iPage++)
+ vbsf_put_page(apPages[iPage]);
+
+ /*
+ * Set the return values.
+ */
+ if (RdDesc.written) {
+ cbRet = RdDesc.written;
+ if (poffFile)
+ *poffFile = offFile;
+ } else {
+ cbRet = RdDesc.error;
+ }
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ cbRet = -ENOMEM;
+ }
+ SFLOGFLOW(("vbsf_reg_sendfile: returns %#zx offFile=%#RX64\n", cbRet, offFile));
+ }
+ return cbRet;
+}
+#endif /* 2.5.30 <= LINUX_VERSION_CODE < 2.6.23 */
+
+
+/*********************************************************************************************************************************
+* File operations on regular files *
+*********************************************************************************************************************************/
+
+/** Wrapper around put_page / page_cache_release. */
+DECLINLINE(void) vbsf_put_page(struct page *pPage)
+{
+#if RTLNX_VER_MIN(4,6,0)
+ put_page(pPage);
+#else
+ page_cache_release(pPage);
+#endif
+}
+
+
+/** Wrapper around get_page / page_cache_get. */
+DECLINLINE(void) vbsf_get_page(struct page *pPage)
+{
+#if RTLNX_VER_MIN(4,6,0)
+ get_page(pPage);
+#else
+ page_cache_get(pPage);
+#endif
+}
+
+
+/** Companion to vbsf_lock_user_pages(). */
+static void vbsf_unlock_user_pages(struct page **papPages, size_t cPages, bool fSetDirty, bool fLockPgHack)
+{
+ /* We don't mark kernel pages dirty: */
+ if (fLockPgHack)
+ fSetDirty = false;
+
+ while (cPages-- > 0)
+ {
+ struct page *pPage = papPages[cPages];
+ Assert((ssize_t)cPages >= 0);
+ if (fSetDirty && !PageReserved(pPage))
+ set_page_dirty(pPage);
+ vbsf_put_page(pPage);
+ }
+}
+
+
+/**
+ * Worker for vbsf_lock_user_pages_failed_check_kernel() and
+ * vbsf_iter_lock_pages().
+ */
+static int vbsf_lock_kernel_pages(uint8_t *pbStart, bool fWrite, size_t cPages, struct page **papPages)
+{
+ uintptr_t const uPtrFrom = (uintptr_t)pbStart;
+ uintptr_t const uPtrLast = (uPtrFrom & ~(uintptr_t)PAGE_OFFSET_MASK) + (cPages << PAGE_SHIFT) - 1;
+ uint8_t *pbPage = (uint8_t *)uPtrLast;
+ size_t iPage = cPages;
+
+ /*
+ * Touch the pages first (paranoia^2).
+ */
+ if (fWrite) {
+ uint8_t volatile *pbProbe = (uint8_t volatile *)uPtrFrom;
+ while (iPage-- > 0) {
+ *pbProbe = *pbProbe;
+ pbProbe += PAGE_SIZE;
+ }
+ } else {
+ uint8_t const *pbProbe = (uint8_t const *)uPtrFrom;
+ while (iPage-- > 0) {
+ ASMProbeReadByte(pbProbe);
+ pbProbe += PAGE_SIZE;
+ }
+ }
+
+ /*
+ * Get the pages.
+ * Note! Fixes here probably applies to rtR0MemObjNativeLockKernel as well.
+ */
+ iPage = cPages;
+ if ( uPtrFrom >= (unsigned long)__va(0)
+ && uPtrLast < (unsigned long)high_memory) {
+ /* The physical page mapping area: */
+ while (iPage-- > 0) {
+ struct page *pPage = papPages[iPage] = virt_to_page(pbPage);
+ vbsf_get_page(pPage);
+ pbPage -= PAGE_SIZE;
+ }
+ } else {
+ /* This is vmalloc or some such thing, so go thru page tables: */
+ while (iPage-- > 0) {
+ struct page *pPage = rtR0MemObjLinuxVirtToPage(pbPage);
+ if (pPage) {
+ papPages[iPage] = pPage;
+ vbsf_get_page(pPage);
+ pbPage -= PAGE_SIZE;
+ } else {
+ while (++iPage < cPages) {
+ pPage = papPages[iPage];
+ vbsf_put_page(pPage);
+ }
+ return -EFAULT;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Catches kernel_read() and kernel_write() calls and works around them.
+ *
+ * The file_operations::read and file_operations::write callbacks supposedly
+ * hands us the user buffers to read into and write out of. To allow the kernel
+ * to read and write without allocating buffers in userland, they kernel_read()
+ * and kernel_write() increases the user space address limit before calling us
+ * so that copyin/copyout won't reject it. Our problem is that get_user_pages()
+ * works on the userspace address space structures and will not be fooled by an
+ * increased addr_limit.
+ *
+ * This code tries to detect this situation and fake get_user_lock() for the
+ * kernel buffer.
+ */
+static int vbsf_lock_user_pages_failed_check_kernel(uintptr_t uPtrFrom, size_t cPages, bool fWrite, int rcFailed,
+ struct page **papPages, bool *pfLockPgHack)
+{
+ /*
+ * Check that this is valid user memory that is actually in the kernel range.
+ */
+#if RTLNX_VER_MIN(5,10,0)
+ if ( access_ok((void *)uPtrFrom, cPages << PAGE_SHIFT)
+ && uPtrFrom >= TASK_SIZE_MAX)
+#elif RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,1)
+ if ( access_ok((void *)uPtrFrom, cPages << PAGE_SHIFT)
+ && uPtrFrom >= USER_DS.seg)
+#else
+ if ( access_ok(fWrite ? VERIFY_WRITE : VERIFY_READ, (void *)uPtrFrom, cPages << PAGE_SHIFT)
+ && uPtrFrom >= USER_DS.seg)
+#endif
+ {
+ int rc = vbsf_lock_kernel_pages((uint8_t *)uPtrFrom, fWrite, cPages, papPages);
+ if (rc == 0) {
+ *pfLockPgHack = true;
+ return 0;
+ }
+ }
+
+ return rcFailed;
+}
+
+
+/** Wrapper around get_user_pages. */
+DECLINLINE(int) vbsf_lock_user_pages(uintptr_t uPtrFrom, size_t cPages, bool fWrite, struct page **papPages, bool *pfLockPgHack)
+{
+# if RTLNX_VER_MIN(4,9,0) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,73, 4,4,74) /** @todo Figure out when & what exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,75, 4,4,90) /** @todo Figure out when & what exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,92, 4,5,0) /** @todo Figure out when & what exactly. */)
+ ssize_t cPagesLocked = get_user_pages_unlocked(uPtrFrom, cPages, papPages,
+ fWrite ? FOLL_WRITE | FOLL_FORCE : FOLL_FORCE);
+# elif RTLNX_VER_MIN(4,6,0)
+ ssize_t cPagesLocked = get_user_pages_unlocked(uPtrFrom, cPages, fWrite, 1 /*force*/, papPages);
+# elif RTLNX_VER_RANGE(4,4,168, 4,5,0)
+ ssize_t cPagesLocked = get_user_pages_unlocked(current, current->mm, uPtrFrom, cPages, papPages,
+ fWrite ? FOLL_WRITE | FOLL_FORCE : FOLL_FORCE);
+# elif RTLNX_VER_MIN(4,0,0)
+ ssize_t cPagesLocked = get_user_pages_unlocked(current, current->mm, uPtrFrom, cPages, fWrite, 1 /*force*/, papPages);
+# else
+ struct task_struct *pTask = current;
+ ssize_t cPagesLocked;
+ down_read(&pTask->mm->mmap_sem);
+ cPagesLocked = get_user_pages(pTask, pTask->mm, uPtrFrom, cPages, fWrite, 1 /*force*/, papPages, NULL);
+ up_read(&pTask->mm->mmap_sem);
+# endif
+ *pfLockPgHack = false;
+ if (cPagesLocked == cPages)
+ return 0;
+
+ /*
+ * It failed.
+ */
+ if (cPagesLocked < 0)
+ return vbsf_lock_user_pages_failed_check_kernel(uPtrFrom, cPages, fWrite, (int)cPagesLocked, papPages, pfLockPgHack);
+
+ vbsf_unlock_user_pages(papPages, cPagesLocked, false /*fSetDirty*/, false /*fLockPgHack*/);
+
+ /* We could use uPtrFrom + cPagesLocked to get the correct status here... */
+ return -EFAULT;
+}
+
+#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */
+
+/**
+ * Read function used when accessing files that are memory mapped.
+ *
+ * We read from the page cache here to present the a cohertent picture of the
+ * the file content.
+ */
+static ssize_t vbsf_reg_read_mapped(struct file *file, char /*__user*/ *buf, size_t size, loff_t *off)
+{
+# if RTLNX_VER_MIN(3,16,0)
+ struct iovec iov = { .iov_base = buf, .iov_len = size };
+ struct iov_iter iter;
+ struct kiocb kiocb;
+ ssize_t cbRet;
+
+ init_sync_kiocb(&kiocb, file);
+ kiocb.ki_pos = *off;
+ iov_iter_init(&iter, READ, &iov, 1, size);
+
+ cbRet = generic_file_read_iter(&kiocb, &iter);
+
+ *off = kiocb.ki_pos;
+ return cbRet;
+
+# elif RTLNX_VER_MIN(2,6,19)
+ struct iovec iov = { .iov_base = buf, .iov_len = size };
+ struct kiocb kiocb;
+ ssize_t cbRet;
+
+ init_sync_kiocb(&kiocb, file);
+ kiocb.ki_pos = *off;
+
+ cbRet = generic_file_aio_read(&kiocb, &iov, 1, *off);
+ if (cbRet == -EIOCBQUEUED)
+ cbRet = wait_on_sync_kiocb(&kiocb);
+
+ *off = kiocb.ki_pos;
+ return cbRet;
+
+# else /* 2.6.18 or earlier: */
+ return generic_file_read(file, buf, size, off);
+# endif
+}
+
+
+/**
+ * Fallback case of vbsf_reg_read() that locks the user buffers and let the host
+ * write directly to them.
+ */
+static ssize_t vbsf_reg_read_locking(struct file *file, char /*__user*/ *buf, size_t size, loff_t *off,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r)
+{
+ /*
+ * Lock pages and execute the read, taking care not to pass the host
+ * more than it can handle in one go or more than we care to allocate
+ * page arrays for. The latter limit is set at just short of 32KB due
+ * to how the physical heap works.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFREADPGLSTREQ *pReq;
+ loff_t offFile = *off;
+ ssize_t cbRet = -ENOMEM;
+ size_t cPages = (((uintptr_t)buf & PAGE_OFFSET_MASK) + size + PAGE_OFFSET_MASK) >> PAGE_SHIFT;
+ size_t cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 1), cPages);
+ bool fLockPgHack;
+
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+ cbRet = 0;
+ for (;;) {
+ /*
+ * Figure out how much to process now and lock the user pages.
+ */
+ int rc;
+ size_t cbChunk = (uintptr_t)buf & PAGE_OFFSET_MASK;
+ pReq->PgLst.offFirstPage = (uint16_t)cbChunk;
+ cPages = RT_ALIGN_Z(cbChunk + size, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPages <= cMaxPages)
+ cbChunk = size;
+ else {
+ cPages = cMaxPages;
+ cbChunk = (cMaxPages << PAGE_SHIFT) - cbChunk;
+ }
+
+ rc = vbsf_lock_user_pages((uintptr_t)buf, cPages, true /*fWrite*/, papPages, &fLockPgHack);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbChunk, cPages);
+
+ Assert(cPages <= cMaxPages);
+ vbsf_unlock_user_pages(papPages, cPages, true /*fSetDirty*/, fLockPgHack);
+
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+ cbRet += cbActual;
+ offFile += cbActual;
+ buf = (uint8_t *)buf + cbActual;
+ size -= cbActual;
+
+ /*
+ * Are we done already? If so commit the new file offset.
+ */
+ if (!size || cbActual < cbChunk) {
+ *off = offFile;
+ break;
+ }
+ } else if (rc == VERR_NO_MEMORY && cMaxPages > 4) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully read stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet > 0) {
+ SFLOGFLOW(("vbsf_reg_read: read at %#RX64 -> %Rrc; got cbRet=%#zx already\n", offFile, rc, cbRet));
+ *off = offFile;
+ } else {
+ SFLOGFLOW(("vbsf_reg_read: read at %#RX64 -> %Rrc\n", offFile, rc));
+ cbRet = -EPROTO;
+ }
+ break;
+ }
+ }
+ }
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read: returns %zd (%#zx), *off=%RX64 [lock]\n", cbRet, cbRet, *off));
+ return cbRet;
+}
+
+
+/**
+ * Read from a regular file.
+ *
+ * @param file the file
+ * @param buf the buffer
+ * @param size length of the buffer
+ * @param off offset within the file (in/out).
+ * @returns the number of read bytes on success, Linux error code otherwise
+ */
+static ssize_t vbsf_reg_read(struct file *file, char /*__user*/ *buf, size_t size, loff_t *off)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_reg_info *sf_r = file->private_data;
+ struct address_space *mapping = inode->i_mapping;
+
+ SFLOGFLOW(("vbsf_reg_read: inode=%p file=%p buf=%p size=%#zx off=%#llx\n", inode, file, buf, size, *off));
+
+ if (!S_ISREG(inode->i_mode)) {
+ LogFunc(("read from non regular file %d\n", inode->i_mode));
+ return -EINVAL;
+ }
+
+ /** @todo XXX Check read permission according to inode->i_mode! */
+
+ if (!size)
+ return 0;
+
+ /*
+ * If there is a mapping and O_DIRECT isn't in effect, we must at a
+ * heed dirty pages in the mapping and read from them. For simplicity
+ * though, we just do page cache reading when there are writable
+ * mappings around with any kind of pages loaded.
+ */
+ if (vbsf_should_use_cached_read(file, mapping, pSuperInfo))
+ return vbsf_reg_read_mapped(file, buf, size, off);
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (size <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) + size;
+ VBOXSFREADEMBEDDEDREQ *pReq = (VBOXSFREADEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ if ((PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqReadEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, *off, (uint32_t)size);
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ if (copy_to_user(buf, pReq->abData, cbRet) == 0)
+ *off += cbRet;
+ else
+ cbRet = -EFAULT;
+ } else
+ cbRet = -EPROTO;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read: returns %zd (%#zx), *off=%RX64 [embed]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ }
+
+# if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */
+ /*
+ * For medium sized requests try use a bounce buffer.
+ */
+ if (size <= _64K /** @todo make this configurable? */) {
+ void *pvBounce = kmalloc(size, GFP_KERNEL);
+ if (pvBounce) {
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqReadContig(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, *off,
+ (uint32_t)size, pvBounce, virt_to_phys(pvBounce));
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ if (copy_to_user(buf, pvBounce, cbRet) == 0)
+ *off += cbRet;
+ else
+ cbRet = -EFAULT;
+ } else
+ cbRet = -EPROTO;
+ VbglR0PhysHeapFree(pReq);
+ kfree(pvBounce);
+ SFLOGFLOW(("vbsf_reg_read: returns %zd (%#zx), *off=%RX64 [bounce]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ kfree(pvBounce);
+ }
+ }
+# endif
+
+ return vbsf_reg_read_locking(file, buf, size, off, pSuperInfo, sf_r);
+}
+
+#endif /* < 5.10.0 */
+
+/**
+ * Helper the synchronizes the page cache content with something we just wrote
+ * to the host.
+ */
+static void vbsf_reg_write_sync_page_cache(struct address_space *mapping, loff_t offFile, uint32_t cbRange,
+ uint8_t const *pbSrcBuf, struct page **papSrcPages,
+ uint32_t offSrcPage, size_t cSrcPages)
+{
+ Assert(offSrcPage < PAGE_SIZE);
+ if (mapping && mapping->nrpages > 0) {
+ /*
+ * Work the pages in the write range.
+ */
+ while (cbRange > 0) {
+ /*
+ * Lookup the page at offFile. We're fine if there aren't
+ * any there. We're skip if it's dirty or is being written
+ * back, at least for now.
+ */
+ size_t const offDstPage = offFile & PAGE_OFFSET_MASK;
+ size_t const cbToCopy = RT_MIN(PAGE_SIZE - offDstPage, cbRange);
+ pgoff_t const idxPage = offFile >> PAGE_SHIFT;
+ struct page *pDstPage = find_lock_page(mapping, idxPage);
+ if (pDstPage) {
+ if ( pDstPage->mapping == mapping /* ignore if re-purposed (paranoia) */
+ && pDstPage->index == idxPage
+ && !PageDirty(pDstPage) /* ignore if dirty */
+ && !PageWriteback(pDstPage) /* ignore if being written back */ ) {
+ /*
+ * Map the page and do the copying.
+ */
+ uint8_t *pbDst = (uint8_t *)kmap(pDstPage);
+ if (pbSrcBuf)
+ memcpy(&pbDst[offDstPage], pbSrcBuf, cbToCopy);
+ else {
+ uint32_t const cbSrc0 = PAGE_SIZE - offSrcPage;
+ uint8_t const *pbSrc = (uint8_t const *)kmap(papSrcPages[0]);
+ AssertMsg(cSrcPages >= 1, ("offFile=%#llx cbRange=%#zx cbToCopy=%#zx\n", offFile, cbRange, cbToCopy));
+ memcpy(&pbDst[offDstPage], &pbSrc[offSrcPage], RT_MIN(cbToCopy, cbSrc0));
+ kunmap(papSrcPages[0]);
+ if (cbToCopy > cbSrc0) {
+ AssertMsg(cSrcPages >= 2, ("offFile=%#llx cbRange=%#zx cbToCopy=%#zx\n", offFile, cbRange, cbToCopy));
+ pbSrc = (uint8_t const *)kmap(papSrcPages[1]);
+ memcpy(&pbDst[offDstPage + cbSrc0], pbSrc, cbToCopy - cbSrc0);
+ kunmap(papSrcPages[1]);
+ }
+ }
+ kunmap(pDstPage);
+ flush_dcache_page(pDstPage);
+ if (cbToCopy == PAGE_SIZE)
+ SetPageUptodate(pDstPage);
+# if RTLNX_VER_MIN(2,4,10)
+ mark_page_accessed(pDstPage);
+# endif
+ } else
+ SFLOGFLOW(("vbsf_reg_write_sync_page_cache: Skipping page %p: mapping=%p (vs %p) writeback=%d offset=%#lx (vs%#lx)\n",
+ pDstPage, pDstPage->mapping, mapping, PageWriteback(pDstPage), pDstPage->index, idxPage));
+ unlock_page(pDstPage);
+ vbsf_put_page(pDstPage);
+ }
+
+ /*
+ * Advance.
+ */
+ if (pbSrcBuf)
+ pbSrcBuf += cbToCopy;
+ else
+ {
+ offSrcPage += cbToCopy;
+ Assert(offSrcPage < PAGE_SIZE * 2);
+ if (offSrcPage >= PAGE_SIZE) {
+ offSrcPage &= PAGE_OFFSET_MASK;
+ papSrcPages++;
+# ifdef VBOX_STRICT
+ Assert(cSrcPages > 0);
+ cSrcPages--;
+# endif
+ }
+ }
+ offFile += cbToCopy;
+ cbRange -= cbToCopy;
+ }
+ }
+ RT_NOREF(cSrcPages);
+}
+
+#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */
+
+/**
+ * Fallback case of vbsf_reg_write() that locks the user buffers and let the host
+ * write directly to them.
+ */
+static ssize_t vbsf_reg_write_locking(struct file *file, const char /*__user*/ *buf, size_t size, loff_t *off, loff_t offFile,
+ struct inode *inode, struct vbsf_inode_info *sf_i,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r)
+{
+ /*
+ * Lock pages and execute the write, taking care not to pass the host
+ * more than it can handle in one go or more than we care to allocate
+ * page arrays for. The latter limit is set at just short of 32KB due
+ * to how the physical heap works.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFWRITEPGLSTREQ *pReq;
+ ssize_t cbRet = -ENOMEM;
+ size_t cPages = (((uintptr_t)buf & PAGE_OFFSET_MASK) + size + PAGE_OFFSET_MASK) >> PAGE_SHIFT;
+ size_t cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 1), cPages);
+ bool fLockPgHack;
+
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+ cbRet = 0;
+ for (;;) {
+ /*
+ * Figure out how much to process now and lock the user pages.
+ */
+ int rc;
+ size_t cbChunk = (uintptr_t)buf & PAGE_OFFSET_MASK;
+ pReq->PgLst.offFirstPage = (uint16_t)cbChunk;
+ cPages = RT_ALIGN_Z(cbChunk + size, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPages <= cMaxPages)
+ cbChunk = size;
+ else {
+ cPages = cMaxPages;
+ cbChunk = (cMaxPages << PAGE_SHIFT) - cbChunk;
+ }
+
+ rc = vbsf_lock_user_pages((uintptr_t)buf, cPages, false /*fWrite*/, papPages, &fLockPgHack);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbChunk, cPages);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+
+ vbsf_reg_write_sync_page_cache(inode->i_mapping, offFile, cbActual, NULL /*pbKrnlBuf*/,
+ papPages, (uintptr_t)buf & PAGE_OFFSET_MASK, cPages);
+ Assert(cPages <= cMaxPages);
+ vbsf_unlock_user_pages(papPages, cPages, false /*fSetDirty*/, fLockPgHack);
+
+ cbRet += cbActual;
+ buf = (uint8_t *)buf + cbActual;
+ size -= cbActual;
+
+ offFile += cbActual;
+ if ((file->f_flags & O_APPEND) && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ offFile = pReq->Parms.off64Write.u.value64;
+ if (offFile > i_size_read(inode))
+ i_size_write(inode, offFile);
+
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+
+ /*
+ * Are we done already? If so commit the new file offset.
+ */
+ if (!size || cbActual < cbChunk) {
+ *off = offFile;
+ break;
+ }
+ } else {
+ vbsf_unlock_user_pages(papPages, cPages, false /*fSetDirty*/, fLockPgHack);
+ if (rc == VERR_NO_MEMORY && cMaxPages > 4) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully written stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet > 0) {
+ SFLOGFLOW(("vbsf_reg_write: write at %#RX64 -> %Rrc; got cbRet=%#zx already\n", offFile, rc, cbRet));
+ *off = offFile;
+ } else {
+ SFLOGFLOW(("vbsf_reg_write: write at %#RX64 -> %Rrc\n", offFile, rc));
+ cbRet = -EPROTO;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write: returns %zd (%#zx), *off=%RX64 [lock]\n", cbRet, cbRet, *off));
+ return cbRet;
+}
+
+
+/**
+ * Write to a regular file.
+ *
+ * @param file the file
+ * @param buf the buffer
+ * @param size length of the buffer
+ * @param off offset within the file
+ * @returns the number of written bytes on success, Linux error code otherwise
+ */
+static ssize_t vbsf_reg_write(struct file *file, const char *buf, size_t size, loff_t * off)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_reg_info *sf_r = file->private_data;
+ struct address_space *mapping = inode->i_mapping;
+ loff_t pos;
+
+ SFLOGFLOW(("vbsf_reg_write: inode=%p file=%p buf=%p size=%#zx off=%#llx\n", inode, file, buf, size, *off));
+ Assert(sf_i);
+ Assert(pSuperInfo);
+ Assert(sf_r);
+ AssertReturn(S_ISREG(inode->i_mode), -EINVAL);
+
+ pos = *off;
+ if (file->f_flags & O_APPEND)
+ pos = i_size_read(inode);
+
+ /** @todo XXX Check write permission according to inode->i_mode! */
+
+ if (!size) {
+ if (file->f_flags & O_APPEND) /** @todo check if this is the consensus behavior... */
+ *off = pos;
+ return 0;
+ }
+
+ /** @todo Implement the read-write caching mode. */
+
+ /*
+ * If there are active writable mappings, coordinate with any
+ * pending writes via those.
+ */
+ if ( mapping
+ && mapping->nrpages > 0
+ && mapping_writably_mapped(mapping)) {
+# if RTLNX_VER_MIN(2,6,32)
+ int err = filemap_fdatawait_range(mapping, pos, pos + size - 1);
+ if (err)
+ return err;
+# else
+ /** @todo ... */
+# endif
+ }
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (size <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) + size;
+ VBOXSFWRITEEMBEDDEDREQ *pReq = (VBOXSFWRITEEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if ( pReq
+ && (PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ if (copy_from_user(pReq->abData, buf, size) == 0) {
+ int vrc = VbglR0SfHostReqWriteEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost,
+ pos, (uint32_t)size);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ vbsf_reg_write_sync_page_cache(mapping, pos, (uint32_t)cbRet, pReq->abData,
+ NULL /*papSrcPages*/, 0 /*offSrcPage0*/, 0 /*cSrcPages*/);
+ pos += cbRet;
+ if ((file->f_flags & O_APPEND) && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ pos = pReq->Parms.off64Write.u.value64;
+ *off = pos;
+ if (pos > i_size_read(inode))
+ i_size_write(inode, pos);
+ } else
+ cbRet = -EPROTO;
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+ } else
+ cbRet = -EFAULT;
+
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write: returns %zd (%#zx), *off=%RX64 [embed]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ }
+
+# if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */
+ /*
+ * For medium sized requests try use a bounce buffer.
+ */
+ if (size <= _64K /** @todo make this configurable? */) {
+ void *pvBounce = kmalloc(size, GFP_KERNEL);
+ if (pvBounce) {
+ if (copy_from_user(pvBounce, buf, size) == 0) {
+ VBOXSFWRITEPGLSTREQ *pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqWriteContig(pSuperInfo->map.root, pReq, sf_r->handle, pos,
+ (uint32_t)size, pvBounce, virt_to_phys(pvBounce));
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ vbsf_reg_write_sync_page_cache(mapping, pos, (uint32_t)cbRet, (uint8_t const *)pvBounce,
+ NULL /*papSrcPages*/, 0 /*offSrcPage0*/, 0 /*cSrcPages*/);
+ pos += cbRet;
+ *off = pos;
+ if (pos > i_size_read(inode))
+ i_size_write(inode, pos);
+ } else
+ cbRet = -EPROTO;
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+ VbglR0PhysHeapFree(pReq);
+ kfree(pvBounce);
+ SFLOGFLOW(("vbsf_reg_write: returns %zd (%#zx), *off=%RX64 [bounce]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ kfree(pvBounce);
+ } else {
+ kfree(pvBounce);
+ SFLOGFLOW(("vbsf_reg_write: returns -EFAULT, *off=%RX64 [bounce]\n", *off));
+ return -EFAULT;
+ }
+ }
+ }
+# endif
+
+ return vbsf_reg_write_locking(file, buf, size, off, pos, inode, sf_i, pSuperInfo, sf_r);
+}
+
+#endif /* < 5.10.0 */
+#if RTLNX_VER_MIN(2,6,19)
+/* See kernel 6.0.0 change eba2d3d798295dc43cae8fade102f9d083a2a741. */
+# if RTLNX_VER_MIN(6,0,0)
+# define VBOX_IOV_GET_PAGES iov_iter_get_pages2
+# else
+# define VBOX_IOV_GET_PAGES iov_iter_get_pages
+# endif
+
+/**
+ * Companion to vbsf_iter_lock_pages().
+ */
+DECLINLINE(void) vbsf_iter_unlock_pages(struct iov_iter *iter, struct page **papPages, size_t cPages, bool fSetDirty)
+{
+ /* We don't mark kernel pages dirty (KVECs, BVECs, PIPEs): */
+ if (!iter_is_iovec(iter))
+ fSetDirty = false;
+
+ while (cPages-- > 0)
+ {
+ struct page *pPage = papPages[cPages];
+ if (fSetDirty && !PageReserved(pPage))
+ set_page_dirty(pPage);
+ vbsf_put_page(pPage);
+ }
+}
+
+
+/**
+ * Locks up to @a cMaxPages from the I/O vector iterator, advancing the
+ * iterator.
+ *
+ * @returns 0 on success, negative errno value on failure.
+ * @param iter The iterator to lock pages from.
+ * @param fWrite Whether to write (true) or read (false) lock the pages.
+ * @param pStash Where we stash peek results.
+ * @param cMaxPages The maximum number of pages to get.
+ * @param papPages Where to return the locked pages.
+ * @param pcPages Where to return the number of pages.
+ * @param poffPage0 Where to return the offset into the first page.
+ * @param pcbChunk Where to return the number of bytes covered.
+ */
+static int vbsf_iter_lock_pages(struct iov_iter *iter, bool fWrite, struct vbsf_iter_stash *pStash, size_t cMaxPages,
+ struct page **papPages, size_t *pcPages, size_t *poffPage0, size_t *pcbChunk)
+{
+ size_t cbChunk = 0;
+ size_t cPages = 0;
+ size_t offPage0 = 0;
+ int rc = 0;
+
+ Assert(iov_iter_count(iter) + pStash->cb > 0);
+ if (!(VBSF_GET_ITER_TYPE(iter) & ITER_KVEC)) {
+ /*
+ * Do we have a stashed page?
+ */
+ if (pStash->pPage) {
+ papPages[0] = pStash->pPage;
+ offPage0 = pStash->off;
+ cbChunk = pStash->cb;
+ cPages = 1;
+ pStash->pPage = NULL;
+ pStash->off = 0;
+ pStash->cb = 0;
+ if ( offPage0 + cbChunk < PAGE_SIZE
+ || iov_iter_count(iter) == 0) {
+ *poffPage0 = offPage0;
+ *pcbChunk = cbChunk;
+ *pcPages = cPages;
+ SFLOGFLOW(("vbsf_iter_lock_pages: returns %d - cPages=%#zx offPage0=%#zx cbChunk=%zx (stashed)\n",
+ rc, cPages, offPage0, cbChunk));
+ return 0;
+ }
+ cMaxPages -= 1;
+ SFLOG3(("vbsf_iter_lock_pages: Picked up stashed page: %#zx LB %#zx\n", offPage0, cbChunk));
+ } else {
+# if RTLNX_VER_MAX(4,11,0)
+ /*
+ * Copy out our starting point to assist rewinding.
+ */
+ pStash->offFromEnd = iov_iter_count(iter);
+ pStash->Copy = *iter;
+# endif
+ }
+
+ /*
+ * Get pages segment by segment.
+ */
+ do {
+ /*
+ * Make a special case of the first time thru here, since that's
+ * the most typical scenario.
+ */
+ ssize_t cbSegRet;
+ if (cPages == 0) {
+# if RTLNX_VER_MAX(3,19,0)
+ while (!iov_iter_single_seg_count(iter)) /* Old code didn't skip empty segments which caused EFAULTs. */
+ iov_iter_advance(iter, 0);
+# endif
+ cbSegRet = VBOX_IOV_GET_PAGES(iter, papPages, iov_iter_count(iter), cMaxPages, &offPage0);
+ if (cbSegRet > 0) {
+# if RTLNX_VER_MAX(6,0,0)
+ iov_iter_advance(iter, cbSegRet);
+#endif
+ cbChunk = (size_t)cbSegRet;
+ cPages = RT_ALIGN_Z(offPage0 + cbSegRet, PAGE_SIZE) >> PAGE_SHIFT;
+ cMaxPages -= cPages;
+ SFLOG3(("vbsf_iter_lock_pages: iov_iter_get_pages -> %#zx @ %#zx; %#zx pages [first]\n", cbSegRet, offPage0, cPages));
+ if ( cMaxPages == 0
+ || ((offPage0 + (size_t)cbSegRet) & PAGE_OFFSET_MASK))
+ break;
+ } else {
+ AssertStmt(cbSegRet < 0, cbSegRet = -EFAULT);
+ rc = (int)cbSegRet;
+ break;
+ }
+ } else {
+ /*
+ * Probe first page of new segment to check that we've got a zero offset and
+ * can continue on the current chunk. Stash the page if the offset isn't zero.
+ */
+ size_t offPgProbe;
+ size_t cbSeg = iov_iter_single_seg_count(iter);
+ while (!cbSeg) {
+ iov_iter_advance(iter, 0);
+ cbSeg = iov_iter_single_seg_count(iter);
+ }
+ cbSegRet = VBOX_IOV_GET_PAGES(iter, &papPages[cPages], iov_iter_count(iter), 1, &offPgProbe);
+ if (cbSegRet > 0) {
+# if RTLNX_VER_MAX(6,0,0)
+ iov_iter_advance(iter, cbSegRet); /** @todo maybe not do this if we stash the page? */
+#endif
+ Assert(offPgProbe + cbSegRet <= PAGE_SIZE);
+ if (offPgProbe == 0) {
+ cbChunk += cbSegRet;
+ cPages += 1;
+ cMaxPages -= 1;
+ SFLOG3(("vbsf_iter_lock_pages: iov_iter_get_pages(1) -> %#zx @ %#zx\n", cbSegRet, offPgProbe));
+ if ( cMaxPages == 0
+ || cbSegRet != PAGE_SIZE)
+ break;
+
+ /*
+ * Get the rest of the segment (if anything remaining).
+ */
+ cbSeg -= cbSegRet;
+ if (cbSeg > 0) {
+ cbSegRet = VBOX_IOV_GET_PAGES(iter, &papPages[cPages], iov_iter_count(iter), cMaxPages, &offPgProbe);
+ if (cbSegRet > 0) {
+ size_t const cPgRet = RT_ALIGN_Z((size_t)cbSegRet, PAGE_SIZE) >> PAGE_SHIFT;
+ Assert(offPgProbe == 0);
+# if RTLNX_VER_MAX(6,0,0)
+ iov_iter_advance(iter, cbSegRet);
+# endif
+ SFLOG3(("vbsf_iter_lock_pages: iov_iter_get_pages() -> %#zx; %#zx pages\n", cbSegRet, cPgRet));
+ cPages += cPgRet;
+ cMaxPages -= cPgRet;
+ cbChunk += cbSegRet;
+ if ( cMaxPages == 0
+ || ((size_t)cbSegRet & PAGE_OFFSET_MASK))
+ break;
+ } else {
+ AssertStmt(cbSegRet < 0, cbSegRet = -EFAULT);
+ rc = (int)cbSegRet;
+ break;
+ }
+ }
+ } else {
+ /* The segment didn't start at a page boundrary, so stash it for
+ the next round: */
+ SFLOGFLOW(("vbsf_iter_lock_pages: iov_iter_get_pages(1) -> %#zx @ %#zx; stashed\n", cbSegRet, offPgProbe));
+ Assert(papPages[cPages]);
+ pStash->pPage = papPages[cPages];
+ pStash->off = offPgProbe;
+ pStash->cb = cbSegRet;
+ break;
+ }
+ } else {
+ AssertStmt(cbSegRet < 0, cbSegRet = -EFAULT);
+ rc = (int)cbSegRet;
+ break;
+ }
+ }
+ Assert(cMaxPages > 0);
+ } while (iov_iter_count(iter) > 0);
+
+ } else {
+ /*
+ * The silly iov_iter_get_pages_alloc() function doesn't handle KVECs,
+ * so everyone needs to do that by themselves.
+ *
+ * Note! Fixes here may apply to rtR0MemObjNativeLockKernel()
+ * and vbsf_lock_user_pages_failed_check_kernel() as well.
+ */
+# if RTLNX_VER_MAX(4,11,0)
+ pStash->offFromEnd = iov_iter_count(iter);
+ pStash->Copy = *iter;
+# endif
+ do {
+ uint8_t *pbBuf;
+ size_t offStart;
+ size_t cPgSeg;
+
+ size_t cbSeg = iov_iter_single_seg_count(iter);
+ while (!cbSeg) {
+ iov_iter_advance(iter, 0);
+ cbSeg = iov_iter_single_seg_count(iter);
+ }
+
+# if RTLNX_VER_MIN(3,19,0)
+ pbBuf = iter->kvec->iov_base + iter->iov_offset;
+# else
+ pbBuf = iter->iov->iov_base + iter->iov_offset;
+# endif
+ offStart = (uintptr_t)pbBuf & PAGE_OFFSET_MASK;
+ if (!cPages)
+ offPage0 = offStart;
+ else if (offStart)
+ break;
+
+ cPgSeg = RT_ALIGN_Z(cbSeg, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPgSeg > cMaxPages) {
+ cPgSeg = cMaxPages;
+ cbSeg = (cPgSeg << PAGE_SHIFT) - offStart;
+ }
+
+ rc = vbsf_lock_kernel_pages(pbBuf, fWrite, cPgSeg, &papPages[cPages]);
+ if (rc == 0) {
+ iov_iter_advance(iter, cbSeg);
+ cbChunk += cbSeg;
+ cPages += cPgSeg;
+ cMaxPages -= cPgSeg;
+ if ( cMaxPages == 0
+ || ((offStart + cbSeg) & PAGE_OFFSET_MASK) != 0)
+ break;
+ } else
+ break;
+ } while (iov_iter_count(iter) > 0);
+ }
+
+ /*
+ * Clean up if we failed; set return values.
+ */
+ if (rc == 0) {
+ /* likely */
+ } else {
+ if (cPages > 0)
+ vbsf_iter_unlock_pages(iter, papPages, cPages, false /*fSetDirty*/);
+ offPage0 = cbChunk = cPages = 0;
+ }
+ *poffPage0 = offPage0;
+ *pcbChunk = cbChunk;
+ *pcPages = cPages;
+ SFLOGFLOW(("vbsf_iter_lock_pages: returns %d - cPages=%#zx offPage0=%#zx cbChunk=%zx\n", rc, cPages, offPage0, cbChunk));
+ return rc;
+}
+
+
+/**
+ * Rewinds the I/O vector.
+ */
+static bool vbsf_iter_rewind(struct iov_iter *iter, struct vbsf_iter_stash *pStash, size_t cbToRewind, size_t cbChunk)
+{
+ size_t cbExtra;
+ if (!pStash->pPage) {
+ cbExtra = 0;
+ } else {
+ cbExtra = pStash->cb;
+ vbsf_put_page(pStash->pPage);
+ pStash->pPage = NULL;
+ pStash->cb = 0;
+ pStash->off = 0;
+ }
+
+# if RTLNX_VER_MIN(4,11,0) || RTLNX_VER_MAX(3,16,0)
+ iov_iter_revert(iter, cbToRewind + cbExtra);
+ return true;
+# else
+ /** @todo impl this */
+ return false;
+# endif
+}
+
+
+/**
+ * Cleans up the page locking stash.
+ */
+DECLINLINE(void) vbsf_iter_cleanup_stash(struct iov_iter *iter, struct vbsf_iter_stash *pStash)
+{
+ if (pStash->pPage)
+ vbsf_iter_rewind(iter, pStash, 0, 0);
+}
+
+
+/**
+ * Calculates the longest span of pages we could transfer to the host in a
+ * single request.
+ *
+ * @returns Page count, non-zero.
+ * @param iter The I/O vector iterator to inspect.
+ */
+static size_t vbsf_iter_max_span_of_pages(struct iov_iter *iter)
+{
+ size_t cPages;
+# if RTLNX_VER_MIN(3,16,0)
+ if (iter_is_iovec(iter) || (VBSF_GET_ITER_TYPE(iter) & ITER_KVEC)) {
+# endif
+ const struct iovec *pCurIov = iter->iov;
+ size_t cLeft = iter->nr_segs;
+ size_t cPagesSpan = 0;
+
+ /* iovect and kvec are identical, except for the __user tagging of iov_base. */
+ AssertCompileMembersSameSizeAndOffset(struct iovec, iov_base, struct kvec, iov_base);
+ AssertCompileMembersSameSizeAndOffset(struct iovec, iov_len, struct kvec, iov_len);
+ AssertCompile(sizeof(struct iovec) == sizeof(struct kvec));
+
+ cPages = 1;
+ AssertReturn(cLeft > 0, cPages);
+
+ /* Special case: segment offset. */
+ if (iter->iov_offset > 0) {
+ if (iter->iov_offset < pCurIov->iov_len) {
+ size_t const cbSegLeft = pCurIov->iov_len - iter->iov_offset;
+ size_t const offPage0 = ((uintptr_t)pCurIov->iov_base + iter->iov_offset) & PAGE_OFFSET_MASK;
+ cPages = cPagesSpan = RT_ALIGN_Z(offPage0 + cbSegLeft, PAGE_SIZE) >> PAGE_SHIFT;
+ if ((offPage0 + cbSegLeft) & PAGE_OFFSET_MASK)
+ cPagesSpan = 0;
+ }
+ SFLOGFLOW(("vbsf_iter: seg[0]= %p LB %#zx\n", pCurIov->iov_base, pCurIov->iov_len));
+ pCurIov++;
+ cLeft--;
+ }
+
+ /* Full segments. */
+ while (cLeft-- > 0) {
+ if (pCurIov->iov_len > 0) {
+ size_t const offPage0 = (uintptr_t)pCurIov->iov_base & PAGE_OFFSET_MASK;
+ if (offPage0 == 0) {
+ if (!(pCurIov->iov_len & PAGE_OFFSET_MASK)) {
+ cPagesSpan += pCurIov->iov_len >> PAGE_SHIFT;
+ } else {
+ cPagesSpan += RT_ALIGN_Z(pCurIov->iov_len, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+ cPagesSpan = 0;
+ }
+ } else {
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+ if (!((offPage0 + pCurIov->iov_len) & PAGE_OFFSET_MASK)) {
+ cPagesSpan = pCurIov->iov_len >> PAGE_SHIFT;
+ } else {
+ cPagesSpan += RT_ALIGN_Z(offPage0 + pCurIov->iov_len, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+ cPagesSpan = 0;
+ }
+ }
+ }
+ SFLOGFLOW(("vbsf_iter: seg[%u]= %p LB %#zx\n", iter->nr_segs - cLeft, pCurIov->iov_base, pCurIov->iov_len));
+ pCurIov++;
+ }
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+# if RTLNX_VER_MIN(3,16,0)
+ } else {
+ /* Won't bother with accurate counts for the next two types, just make
+ some rough estimates (does pipes have segments?): */
+ size_t cSegs = VBSF_GET_ITER_TYPE(iter) & ITER_BVEC ? RT_MAX(1, iter->nr_segs) : 1;
+ cPages = (iov_iter_count(iter) + (PAGE_SIZE * 2 - 2) * cSegs) >> PAGE_SHIFT;
+ }
+# endif
+ SFLOGFLOW(("vbsf_iter_max_span_of_pages: returns %#zx\n", cPages));
+ return cPages;
+}
+
+
+/**
+ * Worker for vbsf_reg_read_iter() that deals with larger reads using page
+ * locking.
+ */
+static ssize_t vbsf_reg_read_iter_locking(struct kiocb *kio, struct iov_iter *iter, size_t cbToRead,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r)
+{
+ /*
+ * Estimate how many pages we may possible submit in a single request so
+ * that we can allocate matching request buffer and page array.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFREADPGLSTREQ *pReq;
+ ssize_t cbRet = 0;
+ size_t cMaxPages = vbsf_iter_max_span_of_pages(iter);
+ cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 2), cMaxPages);
+
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+
+ /*
+ * The read loop.
+ */
+ struct vbsf_iter_stash Stash = VBSF_ITER_STASH_INITIALIZER;
+ do {
+ /*
+ * Grab as many pages as we can. This means that if adjacent
+ * segments both starts and ends at a page boundrary, we can
+ * do them both in the same transfer from the host.
+ */
+ size_t cPages = 0;
+ size_t cbChunk = 0;
+ size_t offPage0 = 0;
+ int rc = vbsf_iter_lock_pages(iter, true /*fWrite*/, &Stash, cMaxPages, papPages, &cPages, &offPage0, &cbChunk);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ pReq->PgLst.offFirstPage = (uint16_t)offPage0;
+ AssertStmt(cbChunk <= cbToRead, cbChunk = cbToRead);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, kio->ki_pos, cbChunk, cPages);
+ SFLOGFLOW(("vbsf_reg_read_iter_locking: VbglR0SfHostReqReadPgLst -> %d (cbActual=%#x cbChunk=%#zx of %#zx cPages=%#zx offPage0=%#x\n",
+ rc, pReq->Parms.cb32Read.u.value32, cbChunk, cbToRead, cPages, offPage0));
+
+ vbsf_iter_unlock_pages(iter, papPages, cPages, true /*fSetDirty*/);
+
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+ cbRet += cbActual;
+ kio->ki_pos += cbActual;
+ cbToRead -= cbActual;
+
+ /*
+ * Are we done already?
+ */
+ if (!cbToRead)
+ break;
+ if (cbActual < cbChunk) { /* We ASSUME end-of-file here. */
+ if (vbsf_iter_rewind(iter, &Stash, cbChunk - cbActual, cbActual))
+ iov_iter_truncate(iter, 0);
+ break;
+ }
+ } else {
+ /*
+ * Try rewind the iter structure.
+ */
+ bool const fRewindOkay = vbsf_iter_rewind(iter, &Stash, cbChunk, cbChunk);
+ if (rc == VERR_NO_MEMORY && cMaxPages > 4 && fRewindOkay) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully read stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet <= 0)
+ cbRet = -EPROTO;
+ break;
+ }
+ }
+ } while (cbToRead > 0);
+
+ vbsf_iter_cleanup_stash(iter, &Stash);
+ }
+ else
+ cbRet = -ENOMEM;
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read_iter_locking: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+}
+
+
+/**
+ * Read into I/O vector iterator.
+ *
+ * @returns Number of bytes read on success, negative errno on error.
+ * @param kio The kernel I/O control block (or something like that).
+ * @param iter The I/O vector iterator describing the buffer.
+ */
+# if RTLNX_VER_MIN(3,16,0)
+static ssize_t vbsf_reg_read_iter(struct kiocb *kio, struct iov_iter *iter)
+# else
+static ssize_t vbsf_reg_aio_read(struct kiocb *kio, const struct iovec *iov, unsigned long cSegs, loff_t offFile)
+# endif
+{
+# if RTLNX_VER_MAX(3,16,0)
+ struct vbsf_iov_iter fake_iter = VBSF_IOV_ITER_INITIALIZER(cSegs, iov, 0 /*write*/);
+ struct vbsf_iov_iter *iter = &fake_iter;
+# endif
+ size_t cbToRead = iov_iter_count(iter);
+ struct inode *inode = VBSF_GET_F_DENTRY(kio->ki_filp)->d_inode;
+ struct address_space *mapping = inode->i_mapping;
+
+ struct vbsf_reg_info *sf_r = kio->ki_filp->private_data;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+
+ SFLOGFLOW(("vbsf_reg_read_iter: inode=%p file=%p size=%#zx off=%#llx type=%#x\n",
+ inode, kio->ki_filp, cbToRead, kio->ki_pos, VBSF_GET_ITER_TYPE(iter) ));
+ AssertReturn(S_ISREG(inode->i_mode), -EINVAL);
+
+ /*
+ * Do we have anything at all to do here?
+ */
+ if (!cbToRead)
+ return 0;
+
+ /*
+ * If there is a mapping and O_DIRECT isn't in effect, we must at a
+ * heed dirty pages in the mapping and read from them. For simplicity
+ * though, we just do page cache reading when there are writable
+ * mappings around with any kind of pages loaded.
+ */
+ if (vbsf_should_use_cached_read(kio->ki_filp, mapping, pSuperInfo)) {
+# if RTLNX_VER_MIN(3,16,0)
+ return generic_file_read_iter(kio, iter);
+# else
+ return generic_file_aio_read(kio, iov, cSegs, offFile);
+# endif
+ }
+
+ /*
+ * Now now we reject async I/O requests.
+ */
+ if (!is_sync_kiocb(kio)) {
+ SFLOGFLOW(("vbsf_reg_read_iter: async I/O not yet supported\n")); /** @todo extend FsPerf with AIO tests. */
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (cbToRead <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) + cbToRead;
+ VBOXSFREADEMBEDDEDREQ *pReq = (VBOXSFREADEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ if ((PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqReadEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost,
+ kio->ki_pos, (uint32_t)cbToRead);
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRet <= (ssize_t)cbToRead, cbRet = cbToRead);
+ if (copy_to_iter(pReq->abData, cbRet, iter) == cbRet) {
+ kio->ki_pos += cbRet;
+ if (cbRet < cbToRead)
+ iov_iter_truncate(iter, 0);
+ } else
+ cbRet = -EFAULT;
+ } else
+ cbRet = -EPROTO;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read_iter: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ }
+
+ /*
+ * Otherwise do the page locking thing.
+ */
+ return vbsf_reg_read_iter_locking(kio, iter, cbToRead, pSuperInfo, sf_r);
+}
+
+
+/**
+ * Worker for vbsf_reg_write_iter() that deals with larger writes using page
+ * locking.
+ */
+static ssize_t vbsf_reg_write_iter_locking(struct kiocb *kio, struct iov_iter *iter, size_t cbToWrite, loff_t offFile,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r, struct inode *inode,
+ struct vbsf_inode_info *sf_i, struct address_space *mapping, bool fAppend)
+{
+ /*
+ * Estimate how many pages we may possible submit in a single request so
+ * that we can allocate matching request buffer and page array.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFWRITEPGLSTREQ *pReq;
+ ssize_t cbRet = 0;
+ size_t cMaxPages = vbsf_iter_max_span_of_pages(iter);
+ cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 2), cMaxPages);
+
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+
+ /*
+ * The write loop.
+ */
+ struct vbsf_iter_stash Stash = VBSF_ITER_STASH_INITIALIZER;
+ do {
+ /*
+ * Grab as many pages as we can. This means that if adjacent
+ * segments both starts and ends at a page boundrary, we can
+ * do them both in the same transfer from the host.
+ */
+ size_t cPages = 0;
+ size_t cbChunk = 0;
+ size_t offPage0 = 0;
+ int rc = vbsf_iter_lock_pages(iter, false /*fWrite*/, &Stash, cMaxPages, papPages, &cPages, &offPage0, &cbChunk);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ pReq->PgLst.offFirstPage = (uint16_t)offPage0;
+ AssertStmt(cbChunk <= cbToWrite, cbChunk = cbToWrite);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbChunk, cPages);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ SFLOGFLOW(("vbsf_reg_write_iter_locking: VbglR0SfHostReqWritePgLst -> %d (cbActual=%#x cbChunk=%#zx of %#zx cPages=%#zx offPage0=%#x\n",
+ rc, pReq->Parms.cb32Write.u.value32, cbChunk, cbToWrite, cPages, offPage0));
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+
+ vbsf_reg_write_sync_page_cache(mapping, offFile, cbActual, NULL /*pbSrcBuf*/, papPages, offPage0, cPages);
+ vbsf_iter_unlock_pages(iter, papPages, cPages, false /*fSetDirty*/);
+
+ cbRet += cbActual;
+ cbToWrite -= cbActual;
+
+ offFile += cbActual;
+ if (fAppend && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ offFile = pReq->Parms.off64Write.u.value64;
+ kio->ki_pos = offFile;
+ if (offFile > i_size_read(inode))
+ i_size_write(inode, offFile);
+
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+
+ /*
+ * Are we done already?
+ */
+ if (!cbToWrite)
+ break;
+ if (cbActual < cbChunk) { /* We ASSUME end-of-file here. */
+ if (vbsf_iter_rewind(iter, &Stash, cbChunk - cbActual, cbActual))
+ iov_iter_truncate(iter, 0);
+ break;
+ }
+ } else {
+ /*
+ * Try rewind the iter structure.
+ */
+ bool fRewindOkay;
+ vbsf_iter_unlock_pages(iter, papPages, cPages, false /*fSetDirty*/);
+ fRewindOkay = vbsf_iter_rewind(iter, &Stash, cbChunk, cbChunk);
+ if (rc == VERR_NO_MEMORY && cMaxPages > 4 && fRewindOkay) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully written stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet <= 0)
+ cbRet = -EPROTO;
+ break;
+ }
+ }
+ } while (cbToWrite > 0);
+
+ vbsf_iter_cleanup_stash(iter, &Stash);
+ }
+ else
+ cbRet = -ENOMEM;
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write_iter_locking: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+}
+
+
+/**
+ * Write from I/O vector iterator.
+ *
+ * @returns Number of bytes written on success, negative errno on error.
+ * @param kio The kernel I/O control block (or something like that).
+ * @param iter The I/O vector iterator describing the buffer.
+ */
+# if RTLNX_VER_MIN(3,16,0)
+static ssize_t vbsf_reg_write_iter(struct kiocb *kio, struct iov_iter *iter)
+# else
+static ssize_t vbsf_reg_aio_write(struct kiocb *kio, const struct iovec *iov, unsigned long cSegs, loff_t offFile)
+# endif
+{
+# if RTLNX_VER_MAX(3,16,0)
+ struct vbsf_iov_iter fake_iter = VBSF_IOV_ITER_INITIALIZER(cSegs, iov, 1 /*write*/);
+ struct vbsf_iov_iter *iter = &fake_iter;
+# endif
+ size_t cbToWrite = iov_iter_count(iter);
+ struct inode *inode = VBSF_GET_F_DENTRY(kio->ki_filp)->d_inode;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct address_space *mapping = inode->i_mapping;
+
+ struct vbsf_reg_info *sf_r = kio->ki_filp->private_data;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+# if RTLNX_VER_MIN(3,16,0)
+ loff_t offFile = kio->ki_pos;
+# endif
+# if RTLNX_VER_MIN(4,1,0)
+ bool const fAppend = RT_BOOL(kio->ki_flags & IOCB_APPEND);
+# else
+ bool const fAppend = RT_BOOL(kio->ki_filp->f_flags & O_APPEND);
+# endif
+
+
+ SFLOGFLOW(("vbsf_reg_write_iter: inode=%p file=%p size=%#zx off=%#llx type=%#x\n",
+ inode, kio->ki_filp, cbToWrite, offFile, VBSF_GET_ITER_TYPE(iter) ));
+ AssertReturn(S_ISREG(inode->i_mode), -EINVAL);
+
+ /*
+ * Enforce APPEND flag (more later).
+ */
+ if (fAppend)
+ kio->ki_pos = offFile = i_size_read(inode);
+
+ /*
+ * Do we have anything at all to do here?
+ */
+ if (!cbToWrite)
+ return 0;
+
+ /** @todo Implement the read-write caching mode. */
+
+ /*
+ * Now now we reject async I/O requests.
+ */
+ if (!is_sync_kiocb(kio)) {
+ SFLOGFLOW(("vbsf_reg_write_iter: async I/O not yet supported\n")); /** @todo extend FsPerf with AIO tests. */
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * If there are active writable mappings, coordinate with any
+ * pending writes via those.
+ */
+ if ( mapping
+ && mapping->nrpages > 0
+ && mapping_writably_mapped(mapping)) {
+# if RTLNX_VER_MIN(2,6,32)
+ int err = filemap_fdatawait_range(mapping, offFile, offFile + cbToWrite - 1);
+ if (err)
+ return err;
+# else
+ /** @todo ... */
+# endif
+ }
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (cbToWrite <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) + cbToWrite;
+ VBOXSFWRITEEMBEDDEDREQ *pReq = (VBOXSFWRITEEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ if ((PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ if (copy_from_iter(pReq->abData, cbToWrite, iter) == cbToWrite) {
+ int vrc = VbglR0SfHostReqWriteEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost,
+ offFile, (uint32_t)cbToWrite);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbRet <= (ssize_t)cbToWrite, cbRet = cbToWrite);
+ vbsf_reg_write_sync_page_cache(mapping, offFile, (uint32_t)cbRet, pReq->abData,
+ NULL /*papSrcPages*/, 0 /*offSrcPage0*/, 0 /*cSrcPages*/);
+
+ offFile += cbRet;
+ if (fAppend && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ offFile = pReq->Parms.off64Write.u.value64;
+ kio->ki_pos = offFile;
+ if (offFile > i_size_read(inode))
+ i_size_write(inode, offFile);
+
+# if RTLNX_VER_MIN(4,11,0)
+ if ((size_t)cbRet < cbToWrite)
+ iov_iter_revert(iter, cbToWrite - cbRet);
+# endif
+ } else
+ cbRet = -EPROTO;
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+ } else
+ cbRet = -EFAULT;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write_iter: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ }
+
+ /*
+ * Otherwise do the page locking thing.
+ */
+ return vbsf_reg_write_iter_locking(kio, iter, cbToWrite, offFile, pSuperInfo, sf_r, inode, sf_i, mapping, fAppend);
+}
+
+#endif /* >= 2.6.19 */
+
+/**
+ * Used by vbsf_reg_open() and vbsf_inode_atomic_open() to
+ *
+ * @returns shared folders create flags.
+ * @param fLnxOpen The linux O_XXX flags to convert.
+ * @param pfHandle Pointer to vbsf_handle::fFlags.
+ * @param pszCaller Caller, for logging purposes.
+ */
+uint32_t vbsf_linux_oflags_to_vbox(unsigned fLnxOpen, uint32_t *pfHandle, const char *pszCaller)
+{
+ uint32_t fVBoxFlags = SHFL_CF_ACCESS_DENYNONE;
+
+ /*
+ * Disposition.
+ */
+ if (fLnxOpen & O_CREAT) {
+ Log(("%s: O_CREAT set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_CREATE_IF_NEW;
+ if (fLnxOpen & O_EXCL) {
+ Log(("%s: O_EXCL set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_FAIL_IF_EXISTS;
+ } else if (fLnxOpen & O_TRUNC) {
+ Log(("%s: O_TRUNC set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
+ } else
+ fVBoxFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS;
+ } else {
+ fVBoxFlags |= SHFL_CF_ACT_FAIL_IF_NEW;
+ if (fLnxOpen & O_TRUNC) {
+ Log(("%s: O_TRUNC set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
+ }
+ }
+
+ /*
+ * Access.
+ */
+ switch (fLnxOpen & O_ACCMODE) {
+ case O_RDONLY:
+ fVBoxFlags |= SHFL_CF_ACCESS_READ;
+ *pfHandle |= VBSF_HANDLE_F_READ;
+ break;
+
+ case O_WRONLY:
+ fVBoxFlags |= SHFL_CF_ACCESS_WRITE;
+ *pfHandle |= VBSF_HANDLE_F_WRITE;
+ break;
+
+ case O_RDWR:
+ fVBoxFlags |= SHFL_CF_ACCESS_READWRITE;
+ *pfHandle |= VBSF_HANDLE_F_READ | VBSF_HANDLE_F_WRITE;
+ break;
+
+ default:
+ BUG();
+ }
+
+ if (fLnxOpen & O_APPEND) {
+ Log(("%s: O_APPEND set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACCESS_APPEND;
+ *pfHandle |= VBSF_HANDLE_F_APPEND;
+ }
+
+ /*
+ * Only directories?
+ */
+ if (fLnxOpen & O_DIRECTORY) {
+ Log(("%s: O_DIRECTORY set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_DIRECTORY;
+ }
+
+ return fVBoxFlags;
+}
+
+
+/**
+ * Open a regular file.
+ *
+ * @param inode the inode
+ * @param file the file
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_reg_open(struct inode *inode, struct file *file)
+{
+ int rc, rc_linux = 0;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct dentry *dentry = VBSF_GET_F_DENTRY(file);
+ struct vbsf_reg_info *sf_r;
+ VBOXSFCREATEREQ *pReq;
+
+ SFLOGFLOW(("vbsf_reg_open: inode=%p file=%p flags=%#x %s\n", inode, file, file->f_flags, sf_i ? sf_i->path->String.ach : NULL));
+ Assert(pSuperInfo);
+ Assert(sf_i);
+
+ sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL);
+ if (!sf_r) {
+ LogRelFunc(("could not allocate reg info\n"));
+ return -ENOMEM;
+ }
+
+ RTListInit(&sf_r->Handle.Entry);
+ sf_r->Handle.cRefs = 1;
+ sf_r->Handle.fFlags = VBSF_HANDLE_F_FILE | VBSF_HANDLE_F_MAGIC;
+ sf_r->Handle.hHost = SHFL_HANDLE_NIL;
+
+ /* Already open? */
+ if (sf_i->handle != SHFL_HANDLE_NIL) {
+ /*
+ * This inode was created with vbsf_create_worker(). Check the CreateFlags:
+ * O_CREAT, O_TRUNC: inherent true (file was just created). Not sure
+ * about the access flags (SHFL_CF_ACCESS_*).
+ */
+ sf_i->force_restat = 1;
+ sf_r->Handle.hHost = sf_i->handle;
+ sf_i->handle = SHFL_HANDLE_NIL;
+ file->private_data = sf_r;
+
+ sf_r->Handle.fFlags |= VBSF_HANDLE_F_READ | VBSF_HANDLE_F_WRITE; /** @todo fix */
+ vbsf_handle_append(sf_i, &sf_r->Handle);
+ SFLOGFLOW(("vbsf_reg_open: returns 0 (#1) - sf_i=%p hHost=%#llx\n", sf_i, sf_r->Handle.hHost));
+ return 0;
+ }
+
+ pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + sf_i->path->u16Size);
+ if (!pReq) {
+ kfree(sf_r);
+ LogRelFunc(("Failed to allocate a VBOXSFCREATEREQ buffer!\n"));
+ return -ENOMEM;
+ }
+ memcpy(&pReq->StrPath, sf_i->path, SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ RT_ZERO(pReq->CreateParms);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+
+ /* We check the value of pReq->CreateParms.Handle afterwards to
+ * find out if the call succeeded or failed, as the API does not seem
+ * to cleanly distinguish error and informational messages.
+ *
+ * Furthermore, we must set pReq->CreateParms.Handle to SHFL_HANDLE_NIL
+ * to make the shared folders host service use our fMode parameter */
+
+ /* We ignore O_EXCL, as the Linux kernel seems to call create
+ beforehand itself, so O_EXCL should always fail. */
+ pReq->CreateParms.CreateFlags = vbsf_linux_oflags_to_vbox(file->f_flags & ~O_EXCL, &sf_r->Handle.fFlags, __FUNCTION__);
+ pReq->CreateParms.Info.Attr.fMode = inode->i_mode;
+ LogFunc(("vbsf_reg_open: calling VbglR0SfHostReqCreate, file %s, flags=%#x, %#x\n",
+ sf_i->path->String.utf8, file->f_flags, pReq->CreateParms.CreateFlags));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_FAILURE(rc)) {
+ LogFunc(("VbglR0SfHostReqCreate failed flags=%d,%#x rc=%Rrc\n", file->f_flags, pReq->CreateParms.CreateFlags, rc));
+ kfree(sf_r);
+ VbglR0PhysHeapFree(pReq);
+ return -RTErrConvertToErrno(rc);
+ }
+
+ if (pReq->CreateParms.Handle != SHFL_HANDLE_NIL) {
+ vbsf_dentry_chain_increase_ttl(dentry);
+ vbsf_update_inode(inode, sf_i, &pReq->CreateParms.Info, pSuperInfo, false /*fInodeLocked*/, 0 /*fSetAttrs*/);
+ rc_linux = 0;
+ } else {
+ switch (pReq->CreateParms.Result) {
+ case SHFL_PATH_NOT_FOUND:
+ vbsf_dentry_invalidate_ttl(dentry);
+ rc_linux = -ENOENT;
+ break;
+ case SHFL_FILE_NOT_FOUND:
+ vbsf_dentry_invalidate_ttl(dentry);
+ /** @todo sf_dentry_increase_parent_ttl(file->f_dentry); if we can trust it. */
+ rc_linux = -ENOENT;
+ break;
+ case SHFL_FILE_EXISTS:
+ vbsf_dentry_chain_increase_ttl(dentry);
+ vbsf_update_inode(inode, sf_i, &pReq->CreateParms.Info, pSuperInfo, false /*fInodeLocked*/, 0 /*fSetAttrs*/);
+ rc_linux = -EEXIST;
+ break;
+ default:
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ rc_linux = 0;
+ break;
+ }
+ }
+
+ sf_r->Handle.hHost = pReq->CreateParms.Handle;
+ file->private_data = sf_r;
+ vbsf_handle_append(sf_i, &sf_r->Handle);
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_open: returns 0 (#2) - sf_i=%p hHost=%#llx\n", sf_i, sf_r->Handle.hHost));
+ return rc_linux;
+}
+
+
+/**
+ * Close a regular file.
+ *
+ * @param inode the inode
+ * @param file the file
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_reg_release(struct inode *inode, struct file *file)
+{
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct vbsf_reg_info *sf_r = file->private_data;
+
+ SFLOGFLOW(("vbsf_reg_release: inode=%p file=%p\n", inode, file));
+ if (sf_r) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct address_space *mapping = inode->i_mapping;
+ Assert(pSuperInfo);
+
+ /* If we're closing the last handle for this inode, make sure the flush
+ the mapping or we'll end up in vbsf_writepage without a handle. */
+ if ( mapping
+ && mapping->nrpages > 0
+ /** @todo && last writable handle */ ) {
+#if RTLNX_VER_MIN(2,4,25)
+ if (filemap_fdatawrite(mapping) != -EIO)
+#else
+ if ( filemap_fdatasync(mapping) == 0
+ && fsync_inode_data_buffers(inode) == 0)
+#endif
+ filemap_fdatawait(inode->i_mapping);
+ }
+
+ /* Release sf_r, closing the handle if we're the last user. */
+ file->private_data = NULL;
+ vbsf_handle_release(&sf_r->Handle, pSuperInfo, "vbsf_reg_release");
+
+ sf_i->handle = SHFL_HANDLE_NIL;
+ }
+ return 0;
+}
+
+
+/**
+ * Wrapper around generic/default seek function that ensures that we've got
+ * the up-to-date file size when doing anything relative to EOF.
+ *
+ * The issue is that the host may extend the file while we weren't looking and
+ * if the caller wishes to append data, it may end up overwriting existing data
+ * if we operate with a stale size. So, we always retrieve the file size on EOF
+ * relative seeks.
+ */
+static loff_t vbsf_reg_llseek(struct file *file, loff_t off, int whence)
+{
+ SFLOGFLOW(("vbsf_reg_llseek: file=%p off=%lld whence=%d\n", file, off, whence));
+
+ switch (whence) {
+#ifdef SEEK_HOLE
+ case SEEK_HOLE:
+ case SEEK_DATA:
+#endif
+ case SEEK_END: {
+ struct vbsf_reg_info *sf_r = file->private_data;
+ int rc = vbsf_inode_revalidate_with_handle(VBSF_GET_F_DENTRY(file), sf_r->Handle.hHost,
+ true /*fForce*/, false /*fInodeLocked*/);
+ if (rc == 0)
+ break;
+ return rc;
+ }
+ }
+
+#if RTLNX_VER_MIN(2,4,8)
+ return generic_file_llseek(file, off, whence);
+#else
+ return default_llseek(file, off, whence);
+#endif
+}
+
+
+/**
+ * Flush region of file - chiefly mmap/msync.
+ *
+ * We cannot use the noop_fsync / simple_sync_file here as that means
+ * msync(,,MS_SYNC) will return before the data hits the host, thereby
+ * causing coherency issues with O_DIRECT access to the same file as
+ * well as any host interaction with the file.
+ */
+#if RTLNX_VER_MIN(3,1,0) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_MIN(3,0,101) /** @todo figure when exactly */)
+static int vbsf_reg_fsync(struct file *file, loff_t start, loff_t end, int datasync)
+{
+# if RTLNX_VER_MIN(3,16,0)
+ return __generic_file_fsync(file, start, end, datasync);
+# else
+ return generic_file_fsync(file, start, end, datasync);
+# endif
+}
+#elif RTLNX_VER_MIN(2,6,35)
+static int vbsf_reg_fsync(struct file *file, int datasync)
+{
+ return generic_file_fsync(file, datasync);
+}
+#else /* < 2.6.35 */
+static int vbsf_reg_fsync(struct file *file, struct dentry *dentry, int datasync)
+{
+# if RTLNX_VER_MIN(2,6,31)
+ return simple_fsync(file, dentry, datasync);
+# else
+ int rc;
+ struct inode *inode = dentry->d_inode;
+ AssertReturn(inode, -EINVAL);
+
+ /** @todo What about file_fsync()? (<= 2.5.11) */
+
+# if RTLNX_VER_MIN(2,5,12)
+ rc = sync_mapping_buffers(inode->i_mapping);
+ if ( rc == 0
+ && (inode->i_state & I_DIRTY)
+ && ((inode->i_state & I_DIRTY_DATASYNC) || !datasync)
+ ) {
+ struct writeback_control wbc = {
+ .sync_mode = WB_SYNC_ALL,
+ .nr_to_write = 0
+ };
+ rc = sync_inode(inode, &wbc);
+ }
+# else /* < 2.5.12 */
+ /** @todo
+ * Somethings is buggy here or in the 2.4.21-27.EL kernel I'm testing on.
+ *
+ * In theory we shouldn't need to do anything here, since msync will call
+ * writepage() on each dirty page and we write them out synchronously. So, the
+ * problem is elsewhere... Doesn't happen all the time either. Sigh.
+ */
+ rc = fsync_inode_buffers(inode);
+# if RTLNX_VER_MIN(2,4,10)
+ if (rc == 0 && datasync)
+ rc = fsync_inode_data_buffers(inode);
+# endif
+
+# endif /* < 2.5.12 */
+ return rc;
+# endif
+}
+#endif /* < 2.6.35 */
+
+
+#if RTLNX_VER_MIN(4,5,0)
+/**
+ * Copy a datablock from one file to another on the host side.
+ */
+static ssize_t vbsf_reg_copy_file_range(struct file *pFileSrc, loff_t offSrc, struct file *pFileDst, loff_t offDst,
+ size_t cbRange, unsigned int fFlags)
+{
+ ssize_t cbRet;
+ if (g_uSfLastFunction >= SHFL_FN_COPY_FILE_PART) {
+ struct inode *pInodeSrc = pFileSrc->f_inode;
+ struct vbsf_inode_info *pInodeInfoSrc = VBSF_GET_INODE_INFO(pInodeSrc);
+ struct vbsf_super_info *pSuperInfoSrc = VBSF_GET_SUPER_INFO(pInodeSrc->i_sb);
+ struct vbsf_reg_info *pFileInfoSrc = (struct vbsf_reg_info *)pFileSrc->private_data;
+ struct inode *pInodeDst = pInodeSrc;
+ struct vbsf_inode_info *pInodeInfoDst = VBSF_GET_INODE_INFO(pInodeDst);
+ struct vbsf_super_info *pSuperInfoDst = VBSF_GET_SUPER_INFO(pInodeDst->i_sb);
+ struct vbsf_reg_info *pFileInfoDst = (struct vbsf_reg_info *)pFileDst->private_data;
+ VBOXSFCOPYFILEPARTREQ *pReq;
+
+ /*
+ * Some extra validation.
+ */
+ AssertPtrReturn(pInodeInfoSrc, -EOPNOTSUPP);
+ Assert(pInodeInfoSrc->u32Magic == SF_INODE_INFO_MAGIC);
+ AssertPtrReturn(pInodeInfoDst, -EOPNOTSUPP);
+ Assert(pInodeInfoDst->u32Magic == SF_INODE_INFO_MAGIC);
+
+# if RTLNX_VER_MAX(4,11,0)
+ if (!S_ISREG(pInodeSrc->i_mode) || !S_ISREG(pInodeDst->i_mode))
+ return S_ISDIR(pInodeSrc->i_mode) || S_ISDIR(pInodeDst->i_mode) ? -EISDIR : -EINVAL;
+# endif
+
+ /*
+ * Allocate the request and issue it.
+ */
+ pReq = (VBOXSFCOPYFILEPARTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ int vrc = VbglR0SfHostReqCopyFilePart(pSuperInfoSrc->map.root, pFileInfoSrc->Handle.hHost, offSrc,
+ pSuperInfoDst->map.root, pFileInfoDst->Handle.hHost, offDst,
+ cbRange, 0 /*fFlags*/, pReq);
+ if (RT_SUCCESS(vrc))
+ cbRet = pReq->Parms.cb64ToCopy.u.value64;
+ else if (vrc == VERR_NOT_IMPLEMENTED)
+ cbRet = -EOPNOTSUPP;
+ else
+ cbRet = -RTErrConvertToErrno(vrc);
+
+ VbglR0PhysHeapFree(pReq);
+ } else
+ cbRet = -ENOMEM;
+ } else {
+ cbRet = -EOPNOTSUPP;
+ }
+ SFLOGFLOW(("vbsf_reg_copy_file_range: returns %zd\n", cbRet));
+ return cbRet;
+}
+#endif /* > 4.5 */
+
+
+#ifdef SFLOG_ENABLED
+/*
+ * This is just for logging page faults and such.
+ */
+
+/** Pointer to the ops generic_file_mmap returns the first time it's called. */
+static struct vm_operations_struct const *g_pGenericFileVmOps = NULL;
+/** Merge of g_LoggingVmOpsTemplate and g_pGenericFileVmOps. */
+static struct vm_operations_struct g_LoggingVmOps;
+
+
+/* Generic page fault callback: */
+# if RTLNX_VER_MIN(4,11,0)
+static vm_fault_t vbsf_vmlog_fault(struct vm_fault *vmf)
+{
+ vm_fault_t rc;
+ SFLOGFLOW(("vbsf_vmlog_fault: vmf=%p flags=%#x addr=%p\n", vmf, vmf->flags, vmf->address));
+ rc = g_pGenericFileVmOps->fault(vmf);
+ SFLOGFLOW(("vbsf_vmlog_fault: returns %d\n", rc));
+ return rc;
+}
+# elif RTLNX_VER_MIN(2,6,23)
+static int vbsf_vmlog_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ int rc;
+# if RTLNX_VER_MIN(4,10,0)
+ SFLOGFLOW(("vbsf_vmlog_fault: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->address));
+# else
+ SFLOGFLOW(("vbsf_vmlog_fault: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->virtual_address));
+# endif
+ rc = g_pGenericFileVmOps->fault(vma, vmf);
+ SFLOGFLOW(("vbsf_vmlog_fault: returns %d\n", rc));
+ return rc;
+}
+# endif
+
+
+/* Special/generic page fault handler: */
+# if RTLNX_VER_MIN(2,6,26)
+# elif RTLNX_VER_MIN(2,6,1)
+static struct page *vbsf_vmlog_nopage(struct vm_area_struct *vma, unsigned long address, int *type)
+{
+ struct page *page;
+ SFLOGFLOW(("vbsf_vmlog_nopage: vma=%p address=%p type=%p:{%#x}\n", vma, address, type, type ? *type : 0));
+ page = g_pGenericFileVmOps->nopage(vma, address, type);
+ SFLOGFLOW(("vbsf_vmlog_nopage: returns %p\n", page));
+ return page;
+}
+# else
+static struct page *vbsf_vmlog_nopage(struct vm_area_struct *vma, unsigned long address, int write_access_or_unused)
+{
+ struct page *page;
+ SFLOGFLOW(("vbsf_vmlog_nopage: vma=%p address=%p wau=%d\n", vma, address, write_access_or_unused));
+ page = g_pGenericFileVmOps->nopage(vma, address, write_access_or_unused);
+ SFLOGFLOW(("vbsf_vmlog_nopage: returns %p\n", page));
+ return page;
+}
+# endif /* < 2.6.26 */
+
+
+/* Special page fault callback for making something writable: */
+# if RTLNX_VER_MIN(4,11,0)
+static vm_fault_t vbsf_vmlog_page_mkwrite(struct vm_fault *vmf)
+{
+ vm_fault_t rc;
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vmf=%p flags=%#x addr=%p\n", vmf, vmf->flags, vmf->address));
+ rc = g_pGenericFileVmOps->page_mkwrite(vmf);
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: returns %d\n", rc));
+ return rc;
+}
+# elif RTLNX_VER_MIN(2,6,30)
+static int vbsf_vmlog_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ int rc;
+# if RTLNX_VER_MIN(4,10,0)
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->address));
+# else
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->virtual_address));
+# endif
+ rc = g_pGenericFileVmOps->page_mkwrite(vma, vmf);
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: returns %d\n", rc));
+ return rc;
+}
+# elif RTLNX_VER_MIN(2,6,18)
+static int vbsf_vmlog_page_mkwrite(struct vm_area_struct *vma, struct page *page)
+{
+ int rc;
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vma=%p page=%p\n", vma, page));
+ rc = g_pGenericFileVmOps->page_mkwrite(vma, page);
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: returns %d\n", rc));
+ return rc;
+}
+# endif
+
+
+/* Special page fault callback for mapping pages: */
+# if RTLNX_VER_MIN(5,12,0)
+static vm_fault_t vbsf_vmlog_map_pages(struct vm_fault *vmf, pgoff_t start, pgoff_t end)
+{
+ vm_fault_t rc;
+ SFLOGFLOW(("vbsf_vmlog_map_pages: vmf=%p (flags=%#x addr=%p) start=%p end=%p\n", vmf, vmf->flags, vmf->address, start, end));
+ rc = g_pGenericFileVmOps->map_pages(vmf, start, end);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+ return rc;
+}
+# elif RTLNX_VER_MIN(4,10,0)
+static void vbsf_vmlog_map_pages(struct vm_fault *vmf, pgoff_t start, pgoff_t end)
+{
+ SFLOGFLOW(("vbsf_vmlog_map_pages: vmf=%p (flags=%#x addr=%p) start=%p end=%p\n", vmf, vmf->flags, vmf->address, start, end));
+ g_pGenericFileVmOps->map_pages(vmf, start, end);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+}
+# elif RTLNX_VER_MIN(4,8,0)
+static void vbsf_vmlog_map_pages(struct fault_env *fenv, pgoff_t start, pgoff_t end)
+{
+ SFLOGFLOW(("vbsf_vmlog_map_pages: fenv=%p (flags=%#x addr=%p) start=%p end=%p\n", fenv, fenv->flags, fenv->address, start, end));
+ g_pGenericFileVmOps->map_pages(fenv, start, end);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+}
+# elif RTLNX_VER_MIN(3,15,0)
+static void vbsf_vmlog_map_pages(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ SFLOGFLOW(("vbsf_vmlog_map_pages: vma=%p vmf=%p (flags=%#x addr=%p)\n", vma, vmf, vmf->flags, vmf->virtual_address));
+ g_pGenericFileVmOps->map_pages(vma, vmf);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+}
+# endif
+
+
+/** Overload template. */
+static struct vm_operations_struct const g_LoggingVmOpsTemplate = {
+# if RTLNX_VER_MIN(2,6,23)
+ .fault = vbsf_vmlog_fault,
+# endif
+# if RTLNX_VER_MAX(2,6,26)
+ .nopage = vbsf_vmlog_nopage,
+# endif
+# if RTLNX_VER_MIN(2,6,18)
+ .page_mkwrite = vbsf_vmlog_page_mkwrite,
+# endif
+# if RTLNX_VER_MIN(3,15,0)
+ .map_pages = vbsf_vmlog_map_pages,
+# endif
+};
+
+/** file_operations::mmap wrapper for logging purposes. */
+extern int vbsf_reg_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ int rc;
+ SFLOGFLOW(("vbsf_reg_mmap: file=%p vma=%p\n", file, vma));
+ rc = generic_file_mmap(file, vma);
+ if (rc == 0) {
+ /* Merge the ops and template the first time thru (there's a race here). */
+ if (g_pGenericFileVmOps == NULL) {
+ uintptr_t const *puSrc1 = (uintptr_t *)vma->vm_ops;
+ uintptr_t const *puSrc2 = (uintptr_t *)&g_LoggingVmOpsTemplate;
+ uintptr_t volatile *puDst = (uintptr_t *)&g_LoggingVmOps;
+ size_t cbLeft = sizeof(g_LoggingVmOps) / sizeof(*puDst);
+ while (cbLeft-- > 0) {
+ *puDst = *puSrc2 && *puSrc1 ? *puSrc2 : *puSrc1;
+ puSrc1++;
+ puSrc2++;
+ puDst++;
+ }
+ g_pGenericFileVmOps = vma->vm_ops;
+ vma->vm_ops = &g_LoggingVmOps;
+ } else if (g_pGenericFileVmOps == vma->vm_ops)
+ vma->vm_ops = &g_LoggingVmOps;
+ else
+ SFLOGFLOW(("vbsf_reg_mmap: Warning: vm_ops=%p, expected %p!\n", vma->vm_ops, g_pGenericFileVmOps));
+ }
+ SFLOGFLOW(("vbsf_reg_mmap: returns %d\n", rc));
+ return rc;
+}
+
+#endif /* SFLOG_ENABLED */
+
+
+/**
+ * File operations for regular files.
+ *
+ * Note on splice_read/splice_write/sendfile:
+ * - Splice was introduced in 2.6.17. The generic_file_splice_read/write
+ * methods go thru the page cache, which is undesirable and is why we
+ * need to cook our own versions of the code as long as we cannot track
+ * host-side writes and correctly invalidate the guest page-cache.
+ * - Sendfile reimplemented using splice in 2.6.23.
+ * - The default_file_splice_read/write no-page-cache fallback functions,
+ * were introduced in 2.6.31. The write one work in page units.
+ * - Since linux 3.16 there is iter_file_splice_write that uses iter_write.
+ * - Since linux 4.9 the generic_file_splice_read function started using
+ * read_iter.
+ */
+struct file_operations vbsf_reg_fops = {
+ .open = vbsf_reg_open,
+#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */
+ .read = vbsf_reg_read,
+ .write = vbsf_reg_write,
+#endif
+#if RTLNX_VER_MIN(3,16,0)
+ .read_iter = vbsf_reg_read_iter,
+ .write_iter = vbsf_reg_write_iter,
+#elif RTLNX_VER_MIN(2,6,19)
+ .aio_read = vbsf_reg_aio_read,
+ .aio_write = vbsf_reg_aio_write,
+#endif
+ .release = vbsf_reg_release,
+#ifdef SFLOG_ENABLED
+ .mmap = vbsf_reg_mmap,
+#else
+ .mmap = generic_file_mmap,
+#endif
+#if RTLNX_VER_RANGE(2,6,17, 2,6,31)
+ .splice_read = vbsf_splice_read,
+#endif
+#if RTLNX_VER_MIN(3,16,0)
+ .splice_write = iter_file_splice_write,
+#elif RTLNX_VER_MIN(2,6,17)
+ .splice_write = vbsf_splice_write,
+#endif
+#if RTLNX_VER_RANGE(2,5,30, 2,6,23)
+ .sendfile = vbsf_reg_sendfile,
+#endif
+ .llseek = vbsf_reg_llseek,
+ .fsync = vbsf_reg_fsync,
+#if RTLNX_VER_MIN(4,5,0)
+ .copy_file_range = vbsf_reg_copy_file_range,
+#endif
+};
+
+
+/**
+ * Inodes operations for regular files.
+ */
+struct inode_operations vbsf_reg_iops = {
+#if RTLNX_VER_MIN(2,5,18)
+ .getattr = vbsf_inode_getattr,
+#else
+ .revalidate = vbsf_inode_revalidate,
+#endif
+ .setattr = vbsf_inode_setattr,
+};
+
+
+
+/*********************************************************************************************************************************
+* Address Space Operations on Regular Files (for mmap, sendfile, direct I/O) *
+*********************************************************************************************************************************/
+
+/**
+ * Used to read the content of a page into the page cache.
+ *
+ * Needed for mmap and reads+writes when the file is mmapped in a
+ * shared+writeable fashion.
+ */
+#if RTLNX_VER_MIN(5,19,0)
+static int vbsf_read_folio(struct file *file, struct folio *folio)
+{
+ struct page *page = &folio->page;
+#else
+static int vbsf_readpage(struct file *file, struct page *page)
+{
+#endif
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ int err;
+
+ SFLOGFLOW(("vbsf_readpage: inode=%p file=%p page=%p off=%#llx\n", inode, file, page, (uint64_t)page->index << PAGE_SHIFT));
+ Assert(PageLocked(page));
+
+ if (PageUptodate(page)) {
+ unlock_page(page);
+ return 0;
+ }
+
+ if (!is_bad_inode(inode)) {
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_reg_info *sf_r = file->private_data;
+ uint32_t cbRead;
+ int vrc;
+
+ pReq->PgLst.offFirstPage = 0;
+ pReq->PgLst.aPages[0] = page_to_phys(page);
+ vrc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root,
+ pReq,
+ sf_r->Handle.hHost,
+ (uint64_t)page->index << PAGE_SHIFT,
+ PAGE_SIZE,
+ 1 /*cPages*/);
+
+ cbRead = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRead <= PAGE_SIZE, cbRead = PAGE_SIZE);
+ VbglR0PhysHeapFree(pReq);
+
+ if (RT_SUCCESS(vrc)) {
+ if (cbRead == PAGE_SIZE) {
+ /* likely */
+ } else {
+ uint8_t *pbMapped = (uint8_t *)kmap(page);
+ RT_BZERO(&pbMapped[cbRead], PAGE_SIZE - cbRead);
+ kunmap(page);
+ /** @todo truncate the inode file size? */
+ }
+
+ flush_dcache_page(page);
+ SetPageUptodate(page);
+ unlock_page(page);
+ return 0;
+ }
+ err = -RTErrConvertToErrno(vrc);
+ } else
+ err = -ENOMEM;
+ } else
+ err = -EIO;
+ SetPageError(page);
+ unlock_page(page);
+ return err;
+}
+
+
+/**
+ * Used to write out the content of a dirty page cache page to the host file.
+ *
+ * Needed for mmap and writes when the file is mmapped in a shared+writeable
+ * fashion.
+ */
+#if RTLNX_VER_MIN(2,5,52)
+static int vbsf_writepage(struct page *page, struct writeback_control *wbc)
+#else
+static int vbsf_writepage(struct page *page)
+#endif
+{
+ struct address_space *mapping = page->mapping;
+ struct inode *inode = mapping->host;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct vbsf_handle *pHandle = vbsf_handle_find(sf_i, VBSF_HANDLE_F_WRITE, VBSF_HANDLE_F_APPEND);
+ int err;
+
+ SFLOGFLOW(("vbsf_writepage: inode=%p page=%p off=%#llx pHandle=%p (%#llx)\n",
+ inode, page, (uint64_t)page->index << PAGE_SHIFT, pHandle, pHandle ? pHandle->hHost : 0));
+
+ if (pHandle) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ VBOXSFWRITEPGLSTREQ *pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ uint64_t const cbFile = i_size_read(inode);
+ uint64_t const offInFile = (uint64_t)page->index << PAGE_SHIFT;
+ uint32_t const cbToWrite = page->index != (cbFile >> PAGE_SHIFT) ? PAGE_SIZE
+ : (uint32_t)cbFile & (uint32_t)PAGE_OFFSET_MASK;
+ int vrc;
+
+ pReq->PgLst.offFirstPage = 0;
+ pReq->PgLst.aPages[0] = page_to_phys(page);
+ vrc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root,
+ pReq,
+ pHandle->hHost,
+ offInFile,
+ cbToWrite,
+ 1 /*cPages*/);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ AssertMsgStmt(pReq->Parms.cb32Write.u.value32 == cbToWrite || RT_FAILURE(vrc), /* lazy bird */
+ ("%#x vs %#x\n", pReq->Parms.cb32Write, cbToWrite),
+ vrc = VERR_WRITE_ERROR);
+ VbglR0PhysHeapFree(pReq);
+
+ if (RT_SUCCESS(vrc)) {
+ /* Update the inode if we've extended the file. */
+ /** @todo is this necessary given the cbToWrite calc above? */
+ uint64_t const offEndOfWrite = offInFile + cbToWrite;
+ if ( offEndOfWrite > cbFile
+ && offEndOfWrite > i_size_read(inode))
+ i_size_write(inode, offEndOfWrite);
+
+ /* Update and unlock the page. */
+ if (PageError(page))
+ ClearPageError(page);
+ SetPageUptodate(page);
+ unlock_page(page);
+
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_writepage");
+ return 0;
+ }
+
+ /*
+ * We failed.
+ */
+ err = -EIO;
+ } else
+ err = -ENOMEM;
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_writepage");
+ } else {
+ /** @todo we could re-open the file here and deal with this... */
+ static uint64_t volatile s_cCalls = 0;
+ if (s_cCalls++ < 16)
+ printk("vbsf_writepage: no writable handle for %s..\n", sf_i->path->String.ach);
+ err = -EIO;
+ }
+ SetPageError(page);
+ unlock_page(page);
+ return err;
+}
+
+
+#if RTLNX_VER_MIN(2,6,24)
+/**
+ * Called when writing thru the page cache (which we shouldn't be doing).
+ */
+static inline void vbsf_write_begin_warn(loff_t pos, unsigned len, unsigned flags)
+{
+ /** @todo r=bird: We shouldn't ever get here, should we? Because we don't use
+ * the page cache for any writes AFAIK. We could just as well use
+ * simple_write_begin & simple_write_end here if we think we really
+ * need to have non-NULL function pointers in the table... */
+ static uint64_t volatile s_cCalls = 0;
+ if (s_cCalls++ < 16) {
+ printk("vboxsf: Unexpected call to vbsf_write_begin(pos=%#llx len=%#x flags=%#x)! Please report.\n",
+ (unsigned long long)pos, len, flags);
+ RTLogBackdoorPrintf("vboxsf: Unexpected call to vbsf_write_begin(pos=%#llx len=%#x flags=%#x)! Please report.\n",
+ (unsigned long long)pos, len, flags);
+# ifdef WARN_ON
+ WARN_ON(1);
+# endif
+ }
+}
+
+# if RTLNX_VER_MIN(5,19,0)
+int vbsf_write_begin(struct file *file, struct address_space *mapping, loff_t pos,
+ unsigned len, struct page **pagep, void **fsdata)
+{
+ vbsf_write_begin_warn(pos, len, 0);
+ return simple_write_begin(file, mapping, pos, len, pagep, fsdata);
+}
+# else
+int vbsf_write_begin(struct file *file, struct address_space *mapping, loff_t pos,
+ unsigned len, unsigned flags, struct page **pagep, void **fsdata)
+{
+ vbsf_write_begin_warn(pos, len, flags);
+ return simple_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
+}
+# endif
+
+#endif /* KERNEL_VERSION >= 2.6.24 */
+
+#if RTLNX_VER_MIN(5,14,0)
+/**
+ * Companion to vbsf_write_begin (i.e. shouldn't be called).
+ */
+static int vbsf_write_end(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned int len, unsigned int copied,
+ struct page *page, void *fsdata)
+{
+ static uint64_t volatile s_cCalls = 0;
+ if (s_cCalls++ < 16)
+ {
+ printk("vboxsf: Unexpected call to vbsf_write_end(pos=%#llx len=%#x)! Please report.\n",
+ (unsigned long long)pos, len);
+ RTLogBackdoorPrintf("vboxsf: Unexpected call to vbsf_write_end(pos=%#llx len=%#x)! Please report.\n",
+ (unsigned long long)pos, len);
+# ifdef WARN_ON
+ WARN_ON(1);
+# endif
+ }
+ return -ENOTSUPP;
+}
+#endif /* KERNEL_VERSION >= 5.14.0 */
+
+
+#if RTLNX_VER_MIN(2,4,10)
+
+# ifdef VBOX_UEK
+# undef iov_iter /* HACK ALERT! Don't put anything needing vbsf_iov_iter after this fun! */
+# endif
+
+/**
+ * This is needed to make open accept O_DIRECT as well as dealing with direct
+ * I/O requests if we don't intercept them earlier.
+ */
+# if RTLNX_VER_MIN(4, 7, 0) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,73, 4,4,74) /** @todo Figure out when exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,75, 4,4,90) /** @todo Figure out when exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,92, 4,5,0) /** @todo Figure out when exactly. */)
+static ssize_t vbsf_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
+# elif RTLNX_VER_MIN(4, 1, 0)
+static ssize_t vbsf_direct_IO(struct kiocb *iocb, struct iov_iter *iter, loff_t offset)
+# elif RTLNX_VER_MIN(3, 16, 0) || defined(VBOX_UEK)
+static ssize_t vbsf_direct_IO(int rw, struct kiocb *iocb, struct iov_iter *iter, loff_t offset)
+# elif RTLNX_VER_MIN(2, 6, 6)
+static ssize_t vbsf_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 55)
+static int vbsf_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 41)
+static int vbsf_direct_IO(int rw, struct file *file, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 35)
+static int vbsf_direct_IO(int rw, struct inode *inode, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 26)
+static int vbsf_direct_IO(int rw, struct inode *inode, char *buf, loff_t offset, size_t count)
+# elif LINUX_VERSION_CODE == KERNEL_VERSION(2, 4, 21) && defined(I_NEW) /* RHEL3 Frankenkernel. */
+static int vbsf_direct_IO(int rw, struct file *file, struct kiobuf *buf, unsigned long whatever1, int whatever2)
+# else
+static int vbsf_direct_IO(int rw, struct inode *inode, struct kiobuf *buf, unsigned long whatever1, int whatever2)
+# endif
+{
+ TRACE();
+ return -EINVAL;
+}
+
+#endif
+
+/**
+ * Address space (for the page cache) operations for regular files.
+ *
+ * @todo the FsPerf touch/flush (mmap) test fails on 4.4.0 (ubuntu 16.04 lts).
+ */
+struct address_space_operations vbsf_reg_aops = {
+#if RTLNX_VER_MIN(5,19,0)
+ .read_folio = vbsf_read_folio,
+#else
+ .readpage = vbsf_readpage,
+#endif
+ .writepage = vbsf_writepage,
+ /** @todo Need .writepages if we want msync performance... */
+#if RTLNX_VER_MIN(5,18,0) || RTLNX_RHEL_RANGE(9,2, 9,99)
+ .dirty_folio = filemap_dirty_folio,
+#elif RTLNX_VER_MIN(2,5,12)
+ .set_page_dirty = __set_page_dirty_buffers,
+#endif
+#if RTLNX_VER_MIN(5,14,0)
+ .write_begin = vbsf_write_begin,
+ .write_end = vbsf_write_end,
+#elif RTLNX_VER_MIN(2,6,24)
+ .write_begin = vbsf_write_begin,
+ .write_end = simple_write_end,
+#elif RTLNX_VER_MIN(2,5,45)
+ .prepare_write = simple_prepare_write,
+ .commit_write = simple_commit_write,
+#endif
+#if RTLNX_VER_MIN(2,4,10)
+ .direct_IO = vbsf_direct_IO,
+#endif
+};
diff --git a/src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c b/src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c
new file mode 100644
index 00000000..468fe587
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c
@@ -0,0 +1,126 @@
+/* $Id: tstmmap.c $ */
+/** @file
+ * vboxsf - Simple writable mmap testcase.
+ */
+
+/*
+ * Copyright (C) 2019-2022 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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+
+int main(int argc, char **argv)
+{
+ uint8_t abBuf[4096];
+ int fd;
+ size_t cErrors = 0;
+ size_t cbFile;
+ size_t offFile;
+ uint8_t *pbMapping;
+ const char *pszFile = "tstmmap-file1";
+ if (argc > 1)
+ pszFile = argv[1];
+
+ fd = open(pszFile, O_CREAT | O_TRUNC | O_RDWR, 0660);
+ if (fd < 0)
+ {
+ fprintf(stderr, "error creating file: %s\n", pszFile);
+ return 1;
+ }
+
+ /* write 64 KB to the file: */
+ memset(abBuf, 0xf6, sizeof(abBuf));
+ for (cbFile = 0; cbFile < 0x10000; cbFile += sizeof(abBuf))
+ if (write(fd, abBuf, sizeof(abBuf)) != sizeof(abBuf))
+ {
+ fprintf(stderr, "error writing file: %s\n", pszFile);
+ return 1;
+ }
+ fsync(fd);
+
+ /* Map the file: */
+ pbMapping = (uint8_t *)mmap(NULL, cbFile, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (pbMapping == (void *)-1)
+ {
+ fprintf(stderr, "error mapping file: %s\n", pszFile);
+ return 1;
+ }
+
+ /* Modify the mapping and sync it: */
+ memset(pbMapping, 0xf7, cbFile);
+ if (msync(pbMapping, cbFile, MS_SYNC) != 0)
+ {
+ fprintf(stderr, "error msync'ing file: %s\n", pszFile);
+ return 1;
+ }
+
+ /* Unmap and close it: */
+ if (munmap(pbMapping, cbFile) != 0)
+ fprintf(stderr, "error munmap'ing file: %s\n", pszFile);
+ close(fd);
+
+ /*
+ * Open it again and check the content.
+ */
+ fd = open(pszFile, O_RDWR, 0);
+ if (fd < 0)
+ {
+ fprintf(stderr, "error reopening file: %s\n", pszFile);
+ return 1;
+ }
+
+ while (offFile < cbFile && cErrors < 42)
+ {
+ size_t offBuf;
+ ssize_t cbRead = read(fd, abBuf, sizeof(abBuf));
+ if (cbRead != (ssize_t)sizeof(abBuf))
+ {
+ fprintf(stderr, "error reading file: %zd, off %#zx (%s)\n", cbRead, offFile, pszFile);
+ return 1;
+ }
+
+ for (offBuf = 0; offBuf < sizeof(abBuf); offBuf++)
+ if (abBuf[offBuf] != 0xf7)
+ {
+ fprintf(stderr, "mismatch at %#zx: %#x, expected %#x\n", offFile + offBuf, abBuf[offBuf], 0xf7);
+ cErrors++;
+ if (cErrors > 42)
+ break;
+ }
+
+ offFile += sizeof(abBuf);
+ }
+
+ close(fd);
+
+ return cErrors == 0 ? 0 : 1;
+}
+
diff --git a/src/VBox/Additions/linux/sharedfolders/utils.c b/src/VBox/Additions/linux/sharedfolders/utils.c
new file mode 100644
index 00000000..56618222
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/utils.c
@@ -0,0 +1,1263 @@
+/* $Id: utils.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, utility functions.
+ *
+ * Utility functions (mainly conversion from/to VirtualBox/Linux data structures).
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "vfsmod.h"
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <linux/vfs.h>
+
+
+int vbsf_nlscpy(struct vbsf_super_info *pSuperInfo, char *name, size_t name_bound_len,
+ const unsigned char *utf8_name, size_t utf8_len)
+{
+ Assert(name_bound_len > 1);
+ Assert(RTStrNLen(utf8_name, utf8_len) == utf8_len);
+
+ if (pSuperInfo->nls) {
+ const char *in = utf8_name;
+ size_t in_bound_len = utf8_len;
+ char *out = name;
+ size_t out_bound_len = name_bound_len - 1;
+
+ while (in_bound_len) {
+#if RTLNX_VER_MIN(2,6,31)
+ unicode_t uni;
+ int cbInEnc = utf8_to_utf32(in, in_bound_len, &uni);
+#else
+ linux_wchar_t uni;
+ int cbInEnc = utf8_mbtowc(&uni, in, in_bound_len);
+#endif
+ if (cbInEnc >= 0) {
+ int cbOutEnc = pSuperInfo->nls->uni2char(uni, out, out_bound_len);
+ if (cbOutEnc >= 0) {
+ /*SFLOG3(("vbsf_nlscpy: cbOutEnc=%d cbInEnc=%d uni=%#x in_bound_len=%u\n", cbOutEnc, cbInEnc, uni, in_bound_len));*/
+ out += cbOutEnc;
+ out_bound_len -= cbOutEnc;
+
+ in += cbInEnc;
+ in_bound_len -= cbInEnc;
+ } else {
+ SFLOG(("vbsf_nlscpy: nls->uni2char failed with %d on %#x (pos %u in '%s'), out_bound_len=%u\n",
+ cbOutEnc, uni, in - (const char *)utf8_name, (const char *)utf8_name, (unsigned)out_bound_len));
+ return cbOutEnc;
+ }
+ } else {
+ SFLOG(("vbsf_nlscpy: utf8_to_utf32/utf8_mbtowc failed with %d on %x (pos %u in '%s'), in_bound_len=%u!\n",
+ cbInEnc, *in, in - (const char *)utf8_name, (const char *)utf8_name, (unsigned)in_bound_len));
+ return -EINVAL;
+ }
+ }
+
+ *out = '\0';
+ } else {
+ if (utf8_len + 1 > name_bound_len)
+ return -ENAMETOOLONG;
+
+ memcpy(name, utf8_name, utf8_len + 1);
+ }
+ return 0;
+}
+
+
+/**
+ * Converts the given NLS string to a host one, kmalloc'ing
+ * the output buffer (use kfree on result).
+ */
+int vbsf_nls_to_shflstring(struct vbsf_super_info *pSuperInfo, const char *pszNls, PSHFLSTRING *ppString)
+{
+ int rc;
+ size_t const cchNls = strlen(pszNls);
+ PSHFLSTRING pString = NULL;
+ if (pSuperInfo->nls) {
+ /*
+ * NLS -> UTF-8 w/ SHLF string header.
+ */
+ /* Calc length first: */
+ size_t cchUtf8 = 0;
+ size_t offNls = 0;
+ while (offNls < cchNls) {
+ linux_wchar_t uc; /* Note! We renamed the type due to clashes. */
+ int const cbNlsCodepoint = pSuperInfo->nls->char2uni(&pszNls[offNls], cchNls - offNls, &uc);
+ if (cbNlsCodepoint >= 0) {
+ char achTmp[16];
+#if RTLNX_VER_MIN(2,6,31)
+ int cbUtf8Codepoint = utf32_to_utf8(uc, achTmp, sizeof(achTmp));
+#else
+ int cbUtf8Codepoint = utf8_wctomb(achTmp, uc, sizeof(achTmp));
+#endif
+ if (cbUtf8Codepoint > 0) {
+ cchUtf8 += cbUtf8Codepoint;
+ offNls += cbNlsCodepoint;
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->uni2char(%#x) failed: %d\n", uc, cbUtf8Codepoint));
+ return -EINVAL;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->char2uni(%.*Rhxs) failed: %d\n",
+ RT_MIN(8, cchNls - offNls), &pszNls[offNls], cbNlsCodepoint));
+ return -EINVAL;
+ }
+ }
+ if (cchUtf8 + 1 < _64K) {
+ /* Allocate: */
+ pString = (PSHFLSTRING)kmalloc(SHFLSTRING_HEADER_SIZE + cchUtf8 + 1, GFP_KERNEL);
+ if (pString) {
+ char *pchDst = pString->String.ach;
+ pString->u16Length = (uint16_t)cchUtf8;
+ pString->u16Size = (uint16_t)(cchUtf8 + 1);
+
+ /* Do the conversion (cchUtf8 is counted down): */
+ rc = 0;
+ offNls = 0;
+ while (offNls < cchNls) {
+ linux_wchar_t uc; /* Note! We renamed the type due to clashes. */
+ int const cbNlsCodepoint = pSuperInfo->nls->char2uni(&pszNls[offNls], cchNls - offNls, &uc);
+ if (cbNlsCodepoint >= 0) {
+#if RTLNX_VER_MIN(2,6,31)
+ int cbUtf8Codepoint = utf32_to_utf8(uc, pchDst, cchUtf8);
+#else
+ int cbUtf8Codepoint = utf8_wctomb(pchDst, uc, cchUtf8);
+#endif
+ if (cbUtf8Codepoint > 0) {
+ AssertBreakStmt(cbUtf8Codepoint <= cchUtf8, rc = -EINVAL);
+ cchUtf8 -= cbUtf8Codepoint;
+ pchDst += cbUtf8Codepoint;
+ offNls += cbNlsCodepoint;
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->uni2char(%#x) failed! %d, cchUtf8=%zu\n",
+ uc, cbUtf8Codepoint, cchUtf8));
+ rc = -EINVAL;
+ break;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->char2uni(%.*Rhxs) failed! %d\n",
+ RT_MIN(8, cchNls - offNls), &pszNls[offNls], cbNlsCodepoint));
+ rc = -EINVAL;
+ break;
+ }
+ }
+ if (rc == 0) {
+ /*
+ * Succeeded. Just terminate the string and we're good.
+ */
+ Assert(pchDst - pString->String.ach == pString->u16Length);
+ *pchDst = '\0';
+ } else {
+ kfree(pString);
+ pString = NULL;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: failed to allocate %u bytes\n", SHFLSTRING_HEADER_SIZE + cchUtf8 + 1));
+ rc = -ENOMEM;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: too long: %zu bytes (%zu nls bytes)\n", cchUtf8, cchNls));
+ rc = -ENAMETOOLONG;
+ }
+ } else {
+ /*
+ * UTF-8 -> UTF-8 w/ SHLF string header.
+ */
+ if (cchNls + 1 < _64K) {
+ pString = (PSHFLSTRING)kmalloc(SHFLSTRING_HEADER_SIZE + cchNls + 1, GFP_KERNEL);
+ if (pString) {
+ pString->u16Length = (uint16_t)cchNls;
+ pString->u16Size = (uint16_t)(cchNls + 1);
+ memcpy(pString->String.ach, pszNls, cchNls);
+ pString->String.ach[cchNls] = '\0';
+ rc = 0;
+ } else {
+ Log(("vbsf_nls_to_shflstring: failed to allocate %u bytes\n", SHFLSTRING_HEADER_SIZE + cchNls + 1));
+ rc = -ENOMEM;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: too long: %zu bytes\n", cchNls));
+ rc = -ENAMETOOLONG;
+ }
+ }
+ *ppString = pString;
+ return rc;
+}
+
+
+/**
+ * Convert from VBox to linux time.
+ */
+#if RTLNX_VER_MAX(2,6,0)
+DECLINLINE(void) vbsf_time_to_linux(time_t *pLinuxDst, PCRTTIMESPEC pVBoxSrc)
+{
+ int64_t t = RTTimeSpecGetNano(pVBoxSrc);
+ do_div(t, RT_NS_1SEC);
+ *pLinuxDst = t;
+}
+#else /* >= 2.6.0 */
+# if RTLNX_VER_MAX(4,18,0)
+DECLINLINE(void) vbsf_time_to_linux(struct timespec *pLinuxDst, PCRTTIMESPEC pVBoxSrc)
+# else
+DECLINLINE(void) vbsf_time_to_linux(struct timespec64 *pLinuxDst, PCRTTIMESPEC pVBoxSrc)
+# endif
+{
+ int64_t t = RTTimeSpecGetNano(pVBoxSrc);
+ pLinuxDst->tv_nsec = do_div(t, RT_NS_1SEC);
+ pLinuxDst->tv_sec = t;
+}
+#endif /* >= 2.6.0 */
+
+
+/**
+ * Convert from linux to VBox time.
+ */
+#if RTLNX_VER_MAX(2,6,0)
+DECLINLINE(void) vbsf_time_to_vbox(PRTTIMESPEC pVBoxDst, time_t *pLinuxSrc)
+{
+ RTTimeSpecSetNano(pVBoxDst, RT_NS_1SEC_64 * *pLinuxSrc);
+}
+#else /* >= 2.6.0 */
+# if RTLNX_VER_MAX(4,18,0)
+DECLINLINE(void) vbsf_time_to_vbox(PRTTIMESPEC pVBoxDst, struct timespec const *pLinuxSrc)
+# else
+DECLINLINE(void) vbsf_time_to_vbox(PRTTIMESPEC pVBoxDst, struct timespec64 const *pLinuxSrc)
+# endif
+{
+ RTTimeSpecSetNano(pVBoxDst, pLinuxSrc->tv_nsec + pLinuxSrc->tv_sec * (int64_t)RT_NS_1SEC);
+}
+#endif /* >= 2.6.0 */
+
+
+/**
+ * Converts VBox access permissions to Linux ones (mode & 0777).
+ *
+ * @note Currently identical.
+ * @sa sf_access_permissions_to_vbox
+ */
+DECLINLINE(int) sf_access_permissions_to_linux(uint32_t fAttr)
+{
+ /* Access bits should be the same: */
+ AssertCompile(RTFS_UNIX_IRUSR == S_IRUSR);
+ AssertCompile(RTFS_UNIX_IWUSR == S_IWUSR);
+ AssertCompile(RTFS_UNIX_IXUSR == S_IXUSR);
+ AssertCompile(RTFS_UNIX_IRGRP == S_IRGRP);
+ AssertCompile(RTFS_UNIX_IWGRP == S_IWGRP);
+ AssertCompile(RTFS_UNIX_IXGRP == S_IXGRP);
+ AssertCompile(RTFS_UNIX_IROTH == S_IROTH);
+ AssertCompile(RTFS_UNIX_IWOTH == S_IWOTH);
+ AssertCompile(RTFS_UNIX_IXOTH == S_IXOTH);
+
+ return fAttr & RTFS_UNIX_ALL_ACCESS_PERMS;
+}
+
+
+/**
+ * Produce the Linux mode mask, given VBox, mount options and file type.
+ */
+DECLINLINE(int) sf_file_mode_to_linux(uint32_t fVBoxMode, int fFixedMode, int fClearMask, int fType)
+{
+ int fLnxMode = sf_access_permissions_to_linux(fVBoxMode);
+ if (fFixedMode != ~0)
+ fLnxMode = fFixedMode & 0777;
+ fLnxMode &= ~fClearMask;
+ fLnxMode |= fType;
+ return fLnxMode;
+}
+
+
+/**
+ * Initializes the @a inode attributes based on @a pObjInfo and @a pSuperInfo
+ * options.
+ */
+void vbsf_init_inode(struct inode *inode, struct vbsf_inode_info *sf_i, PSHFLFSOBJINFO pObjInfo,
+ struct vbsf_super_info *pSuperInfo)
+{
+ PCSHFLFSOBJATTR pAttr = &pObjInfo->Attr;
+
+ TRACE();
+
+ sf_i->ts_up_to_date = jiffies;
+ sf_i->force_restat = 0;
+
+ if (RTFS_IS_DIRECTORY(pAttr->fMode)) {
+ inode->i_mode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->dmode, pSuperInfo->dmask, S_IFDIR);
+ inode->i_op = &vbsf_dir_iops;
+ inode->i_fop = &vbsf_dir_fops;
+
+ /* XXX: this probably should be set to the number of entries
+ in the directory plus two (. ..) */
+ set_nlink(inode, 1);
+ }
+ else if (RTFS_IS_SYMLINK(pAttr->fMode)) {
+ /** @todo r=bird: Aren't System V symlinks w/o any mode mask? IIRC there is
+ * no lchmod on Linux. */
+ inode->i_mode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFLNK);
+ inode->i_op = &vbsf_lnk_iops;
+ set_nlink(inode, 1);
+ } else {
+ inode->i_mode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFREG);
+ inode->i_op = &vbsf_reg_iops;
+ inode->i_fop = &vbsf_reg_fops;
+ inode->i_mapping->a_ops = &vbsf_reg_aops;
+#if RTLNX_VER_RANGE(2,5,17, 4,0,0)
+ inode->i_mapping->backing_dev_info = &pSuperInfo->bdi; /* This is needed for mmap. */
+#endif
+ set_nlink(inode, 1);
+ }
+
+#if RTLNX_VER_MIN(3,5,0)
+ inode->i_uid = make_kuid(current_user_ns(), pSuperInfo->uid);
+ inode->i_gid = make_kgid(current_user_ns(), pSuperInfo->gid);
+#else
+ inode->i_uid = pSuperInfo->uid;
+ inode->i_gid = pSuperInfo->gid;
+#endif
+
+ inode->i_size = pObjInfo->cbObject;
+#if RTLNX_VER_MAX(2,6,19) && !defined(KERNEL_FC6)
+ inode->i_blksize = 4096;
+#endif
+#if RTLNX_VER_MIN(2,4,11)
+ inode->i_blkbits = 12;
+#endif
+ /* i_blocks always in units of 512 bytes! */
+ inode->i_blocks = (pObjInfo->cbAllocated + 511) / 512;
+
+ vbsf_time_to_linux(&inode->i_atime, &pObjInfo->AccessTime);
+ vbsf_time_to_linux(&inode->i_ctime, &pObjInfo->ChangeTime);
+ vbsf_time_to_linux(&inode->i_mtime, &pObjInfo->ModificationTime);
+ sf_i->BirthTime = pObjInfo->BirthTime;
+ sf_i->ModificationTime = pObjInfo->ModificationTime;
+ RTTimeSpecSetSeconds(&sf_i->ModificationTimeAtOurLastWrite, 0);
+}
+
+
+/**
+ * Update the inode with new object info from the host.
+ *
+ * Called by sf_inode_revalidate() and sf_inode_revalidate_with_handle().
+ */
+void vbsf_update_inode(struct inode *pInode, struct vbsf_inode_info *pInodeInfo, PSHFLFSOBJINFO pObjInfo,
+ struct vbsf_super_info *pSuperInfo, bool fInodeLocked, unsigned fSetAttrs)
+{
+ PCSHFLFSOBJATTR pAttr = &pObjInfo->Attr;
+ int fMode;
+
+ TRACE();
+
+#if RTLNX_VER_MIN(4,5,0)
+ if (!fInodeLocked)
+ inode_lock(pInode);
+#endif
+
+ /*
+ * Calc new mode mask and update it if it changed.
+ */
+ if (RTFS_IS_DIRECTORY(pAttr->fMode))
+ fMode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->dmode, pSuperInfo->dmask, S_IFDIR);
+ else if (RTFS_IS_SYMLINK(pAttr->fMode))
+ /** @todo r=bird: Aren't System V symlinks w/o any mode mask? IIRC there is
+ * no lchmod on Linux. */
+ fMode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFLNK);
+ else
+ fMode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFREG);
+
+ if (fMode == pInode->i_mode) {
+ /* likely */
+ } else {
+ if ((fMode & S_IFMT) == (pInode->i_mode & S_IFMT))
+ pInode->i_mode = fMode;
+ else {
+ SFLOGFLOW(("vbsf_update_inode: Changed from %o to %o (%s)\n",
+ pInode->i_mode & S_IFMT, fMode & S_IFMT, pInodeInfo->path->String.ach));
+ /** @todo we probably need to be more drastic... */
+ vbsf_init_inode(pInode, pInodeInfo, pObjInfo, pSuperInfo);
+
+#if RTLNX_VER_MIN(4,5,0)
+ if (!fInodeLocked)
+ inode_unlock(pInode);
+#endif
+ return;
+ }
+ }
+
+ /*
+ * Update the sizes.
+ * Note! i_blocks is always in units of 512 bytes!
+ */
+ pInode->i_blocks = (pObjInfo->cbAllocated + 511) / 512;
+ i_size_write(pInode, pObjInfo->cbObject);
+
+ /*
+ * Update the timestamps.
+ */
+ vbsf_time_to_linux(&pInode->i_atime, &pObjInfo->AccessTime);
+ vbsf_time_to_linux(&pInode->i_ctime, &pObjInfo->ChangeTime);
+ vbsf_time_to_linux(&pInode->i_mtime, &pObjInfo->ModificationTime);
+ pInodeInfo->BirthTime = pObjInfo->BirthTime;
+
+ /*
+ * Mark it as up to date.
+ * Best to do this before we start with any expensive map invalidation.
+ */
+ pInodeInfo->ts_up_to_date = jiffies;
+ pInodeInfo->force_restat = 0;
+
+ /*
+ * If the modification time changed, we may have to invalidate the page
+ * cache pages associated with this inode if we suspect the change was
+ * made by the host. How supicious we are depends on the cache mode.
+ *
+ * Note! The invalidate_inode_pages() call is pretty weak. It will _not_
+ * touch pages that are already mapped into an address space, but it
+ * will help if the file isn't currently mmap'ed or if we're in read
+ * or read/write caching mode.
+ */
+ if (!RTTimeSpecIsEqual(&pInodeInfo->ModificationTime, &pObjInfo->ModificationTime)) {
+ if (RTFS_IS_FILE(pAttr->fMode)) {
+ if (!(fSetAttrs & (ATTR_MTIME | ATTR_SIZE))) {
+ bool fInvalidate;
+ if (pSuperInfo->enmCacheMode == kVbsfCacheMode_None) {
+ fInvalidate = true; /* No-caching: always invalidate. */
+ } else {
+ if (RTTimeSpecIsEqual(&pInodeInfo->ModificationTimeAtOurLastWrite, &pInodeInfo->ModificationTime)) {
+ fInvalidate = false; /* Could be our write, so don't invalidate anything */
+ RTTimeSpecSetSeconds(&pInodeInfo->ModificationTimeAtOurLastWrite, 0);
+ } else {
+ /*RTLogBackdoorPrintf("vbsf_update_inode: Invalidating the mapping %s - %RU64 vs %RU64 vs %RU64 - %#x\n",
+ pInodeInfo->path->String.ach,
+ RTTimeSpecGetNano(&pInodeInfo->ModificationTimeAtOurLastWrite),
+ RTTimeSpecGetNano(&pInodeInfo->ModificationTime),
+ RTTimeSpecGetNano(&pObjInfo->ModificationTime), fSetAttrs);*/
+ fInvalidate = true; /* We haven't modified the file recently, so probably a host update. */
+ }
+ }
+ pInodeInfo->ModificationTime = pObjInfo->ModificationTime;
+
+ if (fInvalidate) {
+ struct address_space *mapping = pInode->i_mapping;
+ if (mapping && mapping->nrpages > 0) {
+ SFLOGFLOW(("vbsf_update_inode: Invalidating the mapping %s (%#x)\n", pInodeInfo->path->String.ach, fSetAttrs));
+#if RTLNX_VER_MIN(2,6,34)
+ invalidate_mapping_pages(mapping, 0, ~(pgoff_t)0);
+#elif RTLNX_VER_MIN(2,5,41)
+ invalidate_inode_pages(mapping);
+#else
+ invalidate_inode_pages(pInode);
+#endif
+ }
+ }
+ } else {
+ RTTimeSpecSetSeconds(&pInodeInfo->ModificationTimeAtOurLastWrite, 0);
+ pInodeInfo->ModificationTime = pObjInfo->ModificationTime;
+ }
+ } else
+ pInodeInfo->ModificationTime = pObjInfo->ModificationTime;
+ }
+
+ /*
+ * Done.
+ */
+#if RTLNX_VER_MIN(4,5,0)
+ if (!fInodeLocked)
+ inode_unlock(pInode);
+#endif
+}
+
+
+/** @note Currently only used for the root directory during (re-)mount. */
+int vbsf_stat(const char *caller, struct vbsf_super_info *pSuperInfo, SHFLSTRING *path, PSHFLFSOBJINFO result, int ok_to_fail)
+{
+ int rc;
+ VBOXSFCREATEREQ *pReq;
+ NOREF(caller);
+
+ TRACE();
+
+ pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + path->u16Size);
+ if (pReq) {
+ RT_ZERO(*pReq);
+ memcpy(&pReq->StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ LogFunc(("Calling VbglR0SfHostReqCreate on %s\n", path->String.utf8));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ *result = pReq->CreateParms.Info;
+ rc = 0;
+ } else {
+ if (!ok_to_fail)
+ LogFunc(("VbglR0SfHostReqCreate on %s: file does not exist: %d (caller=%s)\n",
+ path->String.utf8, pReq->CreateParms.Result, caller));
+ rc = -ENOENT;
+ }
+ } else if (rc == VERR_INVALID_NAME) {
+ rc = -ENOENT; /* this can happen for names like 'foo*' on a Windows host */
+ } else {
+ LogFunc(("VbglR0SfHostReqCreate failed on %s: %Rrc (caller=%s)\n", path->String.utf8, rc, caller));
+ rc = -EPROTO;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ else
+ rc = -ENOMEM;
+ return rc;
+}
+
+
+/**
+ * Revalidate an inode, inner worker.
+ *
+ * @sa sf_inode_revalidate()
+ */
+int vbsf_inode_revalidate_worker(struct dentry *dentry, bool fForced, bool fInodeLocked)
+{
+ int rc;
+ struct inode *pInode = dentry ? dentry->d_inode : NULL;
+ if (pInode) {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(pInode);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pInode->i_sb);
+ AssertReturn(sf_i, -EINVAL);
+ AssertReturn(pSuperInfo, -EINVAL);
+
+ /*
+ * Can we get away without any action here?
+ */
+ if ( !fForced
+ && !sf_i->force_restat
+ && jiffies - sf_i->ts_up_to_date < pSuperInfo->cJiffiesInodeTTL)
+ rc = 0;
+ else {
+ /*
+ * No, we have to query the file info from the host.
+ * Try get a handle we can query, any kind of handle will do here.
+ */
+ struct vbsf_handle *pHandle = vbsf_handle_find(sf_i, 0, 0);
+ if (pHandle) {
+ /* Query thru pHandle. */
+ VBOXSFOBJINFOREQ *pReq = (VBOXSFOBJINFOREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ RT_ZERO(*pReq);
+ rc = VbglR0SfHostReqQueryObjInfo(pSuperInfo->map.root, pReq, pHandle->hHost);
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Reset the TTL and copy the info over into the inode structure.
+ */
+ vbsf_update_inode(pInode, sf_i, &pReq->ObjInfo, pSuperInfo, fInodeLocked, 0 /*fSetAttrs*/);
+ } else if (rc == VERR_INVALID_HANDLE) {
+ rc = -ENOENT; /* Restore.*/
+ } else {
+ LogFunc(("VbglR0SfHostReqQueryObjInfo failed on %#RX64: %Rrc\n", pHandle->hHost, rc));
+ rc = -RTErrConvertToErrno(rc);
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_inode_revalidate_worker");
+
+ } else {
+ /* Query via path. */
+ SHFLSTRING *pPath = sf_i->path;
+ VBOXSFCREATEREQ *pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + pPath->u16Size);
+ if (pReq) {
+ RT_ZERO(*pReq);
+ memcpy(&pReq->StrPath, pPath, SHFLSTRING_HEADER_SIZE + pPath->u16Size);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ /*
+ * Reset the TTL and copy the info over into the inode structure.
+ */
+ vbsf_update_inode(pInode, sf_i, &pReq->CreateParms.Info, pSuperInfo, fInodeLocked, 0 /*fSetAttrs*/);
+ rc = 0;
+ } else {
+ rc = -ENOENT;
+ }
+ } else if (rc == VERR_INVALID_NAME) {
+ rc = -ENOENT; /* this can happen for names like 'foo*' on a Windows host */
+ } else {
+ LogFunc(("VbglR0SfHostReqCreate failed on %s: %Rrc\n", pPath->String.ach, rc));
+ rc = -EPROTO;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ else
+ rc = -ENOMEM;
+ }
+ }
+ } else {
+ LogFunc(("no dentry(%p) or inode(%p)\n", dentry, pInode));
+ rc = -EINVAL;
+ }
+ return rc;
+}
+
+
+#if RTLNX_VER_MAX(2,5,18)
+/**
+ * Revalidate an inode for 2.4.
+ *
+ * This is called in the stat(), lstat() and readlink() code paths. In the stat
+ * cases the caller will use the result afterwards to produce the stat data.
+ *
+ * @note 2.4.x has a getattr() inode operation too, but it is not used.
+ */
+int vbsf_inode_revalidate(struct dentry *dentry)
+{
+ /*
+ * We pretend the inode is locked here, as 2.4.x does not have inode level locking.
+ */
+ return vbsf_inode_revalidate_worker(dentry, false /*fForced*/, true /*fInodeLocked*/);
+}
+#endif /* < 2.5.18 */
+
+
+/**
+ * Similar to sf_inode_revalidate, but uses associated host file handle as that
+ * is quite a bit faster.
+ */
+int vbsf_inode_revalidate_with_handle(struct dentry *dentry, SHFLHANDLE hHostFile, bool fForced, bool fInodeLocked)
+{
+ int err;
+ struct inode *pInode = dentry ? dentry->d_inode : NULL;
+ if (!pInode) {
+ LogFunc(("no dentry(%p) or inode(%p)\n", dentry, pInode));
+ err = -EINVAL;
+ } else {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(pInode);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pInode->i_sb);
+ AssertReturn(sf_i, -EINVAL);
+ AssertReturn(pSuperInfo, -EINVAL);
+
+ /*
+ * Can we get away without any action here?
+ */
+ if ( !fForced
+ && !sf_i->force_restat
+ && jiffies - sf_i->ts_up_to_date < pSuperInfo->cJiffiesInodeTTL)
+ err = 0;
+ else {
+ /*
+ * No, we have to query the file info from the host.
+ */
+ VBOXSFOBJINFOREQ *pReq = (VBOXSFOBJINFOREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ RT_ZERO(*pReq);
+ err = VbglR0SfHostReqQueryObjInfo(pSuperInfo->map.root, pReq, hHostFile);
+ if (RT_SUCCESS(err)) {
+ /*
+ * Reset the TTL and copy the info over into the inode structure.
+ */
+ vbsf_update_inode(pInode, sf_i, &pReq->ObjInfo, pSuperInfo, fInodeLocked, 0 /*fSetAttrs*/);
+ } else {
+ LogFunc(("VbglR0SfHostReqQueryObjInfo failed on %#RX64: %Rrc\n", hHostFile, err));
+ err = -RTErrConvertToErrno(err);
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ err = -ENOMEM;
+ }
+ }
+ return err;
+}
+
+
+/* on 2.6 this is a proxy for [sf_inode_revalidate] which (as a side
+ effect) updates inode attributes for [dentry] (given that [dentry]
+ has inode at all) from these new attributes we derive [kstat] via
+ [generic_fillattr] */
+#if RTLNX_VER_MIN(2,5,18)
+
+# if RTLNX_VER_MIN(5,12,0)
+int vbsf_inode_getattr(struct user_namespace *ns, const struct path *path,
+ struct kstat *kstat, u32 request_mask, unsigned int flags)
+# elif RTLNX_VER_MIN(4,11,0)
+int vbsf_inode_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int flags)
+# else
+int vbsf_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *kstat)
+# endif
+{
+ int rc;
+# if RTLNX_VER_MIN(4,11,0)
+ struct dentry *dentry = path->dentry;
+# endif
+
+# if RTLNX_VER_MIN(4,11,0)
+ SFLOGFLOW(("vbsf_inode_getattr: dentry=%p request_mask=%#x flags=%#x\n", dentry, request_mask, flags));
+# else
+ SFLOGFLOW(("vbsf_inode_getattr: dentry=%p\n", dentry));
+# endif
+
+# if RTLNX_VER_MIN(4,11,0)
+ /*
+ * With the introduction of statx() userland can control whether we
+ * update the inode information or not.
+ */
+ switch (flags & AT_STATX_SYNC_TYPE) {
+ default:
+ rc = vbsf_inode_revalidate_worker(dentry, false /*fForced*/, false /*fInodeLocked*/);
+ break;
+
+ case AT_STATX_FORCE_SYNC:
+ rc = vbsf_inode_revalidate_worker(dentry, true /*fForced*/, false /*fInodeLocked*/);
+ break;
+
+ case AT_STATX_DONT_SYNC:
+ rc = 0;
+ break;
+ }
+# else
+ rc = vbsf_inode_revalidate_worker(dentry, false /*fForced*/, false /*fInodeLocked*/);
+# endif
+ if (rc == 0) {
+ /* Do generic filling in of info. */
+# if RTLNX_VER_MIN(5,12,0)
+ generic_fillattr(ns, dentry->d_inode, kstat);
+# else
+ generic_fillattr(dentry->d_inode, kstat);
+# endif
+
+ /* Add birth time. */
+# if RTLNX_VER_MIN(4,11,0)
+ if (dentry->d_inode) {
+ struct vbsf_inode_info *pInodeInfo = VBSF_GET_INODE_INFO(dentry->d_inode);
+ if (pInodeInfo) {
+ vbsf_time_to_linux(&kstat->btime, &pInodeInfo->BirthTime);
+ kstat->result_mask |= STATX_BTIME;
+ }
+ }
+# endif
+
+ /*
+ * FsPerf shows the following numbers for sequential file access against
+ * a tmpfs folder on an AMD 1950X host running debian buster/sid:
+ *
+ * block size = r128600 ----- r128755 -----
+ * reads reads writes
+ * 4096 KB = 2254 MB/s 4953 MB/s 3668 MB/s
+ * 2048 KB = 2368 MB/s 4908 MB/s 3541 MB/s
+ * 1024 KB = 2208 MB/s 4011 MB/s 3291 MB/s
+ * 512 KB = 1908 MB/s 3399 MB/s 2721 MB/s
+ * 256 KB = 1625 MB/s 2679 MB/s 2251 MB/s
+ * 128 KB = 1413 MB/s 1967 MB/s 1684 MB/s
+ * 64 KB = 1152 MB/s 1409 MB/s 1265 MB/s
+ * 32 KB = 726 MB/s 815 MB/s 783 MB/s
+ * 16 KB = 683 MB/s 475 MB/s
+ * 8 KB = 294 MB/s 286 MB/s
+ * 4 KB = 145 MB/s 156 MB/s 149 MB/s
+ *
+ */
+ if (S_ISREG(kstat->mode))
+ kstat->blksize = _1M;
+ else if (S_ISDIR(kstat->mode))
+ /** @todo this may need more tuning after we rewrite the directory handling. */
+ kstat->blksize = _16K;
+ }
+ return rc;
+}
+#endif /* >= 2.5.18 */
+
+
+/**
+ * Modify inode attributes.
+ */
+#if RTLNX_VER_MIN(5,12,0)
+int vbsf_inode_setattr(struct user_namespace *ns, struct dentry *dentry, struct iattr *iattr)
+#else
+int vbsf_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+#endif
+{
+ struct inode *pInode = dentry->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pInode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(pInode);
+ int vrc;
+ int rc;
+
+ SFLOGFLOW(("vbsf_inode_setattr: dentry=%p inode=%p ia_valid=%#x %s\n",
+ dentry, pInode, iattr->ia_valid, sf_i ? sf_i->path->String.ach : NULL));
+ AssertReturn(sf_i, -EINVAL);
+
+ /*
+ * Do minimal attribute permission checks. We set ATTR_FORCE since we cannot
+ * preserve ownership and such and would end up with EPERM here more often than
+ * we would like. For instance it would cause 'cp' to complain about EPERM
+ * from futimes() when asked to preserve times, see ticketref:18569.
+ */
+ iattr->ia_valid |= ATTR_FORCE;
+#if (RTLNX_VER_RANGE(3,16,39, 3,17,0)) || RTLNX_VER_MIN(4,9,0) || (RTLNX_VER_RANGE(4,1,37, 4,2,0)) || RTLNX_UBUNTU_ABI_MIN(4,4,255,208)
+# if RTLNX_VER_MIN(5,12,0)
+ rc = setattr_prepare(ns, dentry, iattr);
+# else
+ rc = setattr_prepare(dentry, iattr);
+# endif
+#else
+ rc = inode_change_ok(pInode, iattr);
+#endif
+ if (rc == 0) {
+ /*
+ * Don't modify MTIME and CTIME for open(O_TRUNC) and ftruncate, those
+ * operations will set those timestamps automatically. Saves a host call.
+ */
+ unsigned fAttrs = iattr->ia_valid;
+#if RTLNX_VER_MIN(2,6,15)
+ fAttrs &= ~ATTR_FILE;
+#endif
+ if ( fAttrs == (ATTR_SIZE | ATTR_MTIME | ATTR_CTIME)
+#if RTLNX_VER_MIN(2,6,24)
+ || (fAttrs & (ATTR_OPEN | ATTR_SIZE)) == (ATTR_OPEN | ATTR_SIZE)
+#endif
+ )
+ fAttrs &= ~(ATTR_MTIME | ATTR_CTIME);
+
+ /*
+ * We only implement a handful of attributes, so ignore any attempts
+ * at setting bits we don't support.
+ */
+ if (fAttrs & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME | ATTR_CTIME | ATTR_SIZE)) {
+ /*
+ * Try find a handle which allows us to modify the attributes, otherwise
+ * open the file/dir/whatever.
+ */
+ union SetAttrReqs
+ {
+ VBOXSFCREATEREQ Create;
+ VBOXSFOBJINFOREQ Info;
+ VBOXSFSETFILESIZEREQ SetSize;
+ VBOXSFCLOSEREQ Close;
+ } *pReq;
+ size_t cbReq;
+ SHFLHANDLE hHostFile;
+ /** @todo ATTR_FILE (2.6.15+) could be helpful here if we like. */
+ struct vbsf_handle *pHandle = fAttrs & ATTR_SIZE
+ ? vbsf_handle_find(sf_i, VBSF_HANDLE_F_WRITE, 0)
+ : vbsf_handle_find(sf_i, 0, 0);
+ if (pHandle) {
+ hHostFile = pHandle->hHost;
+ cbReq = RT_MAX(sizeof(VBOXSFOBJINFOREQ), sizeof(VBOXSFSETFILESIZEREQ));
+ pReq = (union SetAttrReqs *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ /* likely */
+ } else
+ rc = -ENOMEM;
+ } else {
+ hHostFile = SHFL_HANDLE_NIL;
+ cbReq = RT_MAX(sizeof(pReq->Info), sizeof(pReq->Create) + SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ pReq = (union SetAttrReqs *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ RT_ZERO(pReq->Create.CreateParms);
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->Create.CreateParms.CreateFlags = SHFL_CF_ACT_OPEN_IF_EXISTS
+ | SHFL_CF_ACT_FAIL_IF_NEW
+ | SHFL_CF_ACCESS_ATTR_WRITE;
+ if (fAttrs & ATTR_SIZE)
+ pReq->Create.CreateParms.CreateFlags |= SHFL_CF_ACCESS_WRITE;
+ memcpy(&pReq->Create.StrPath, sf_i->path, SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ vrc = VbglR0SfHostReqCreate(pSuperInfo->map.root, &pReq->Create);
+ if (RT_SUCCESS(vrc)) {
+ if (pReq->Create.CreateParms.Result == SHFL_FILE_EXISTS) {
+ hHostFile = pReq->Create.CreateParms.Handle;
+ Assert(hHostFile != SHFL_HANDLE_NIL);
+ vbsf_dentry_chain_increase_ttl(dentry);
+ } else {
+ LogFunc(("file %s does not exist\n", sf_i->path->String.utf8));
+ vbsf_dentry_invalidate_ttl(dentry);
+ sf_i->force_restat = true;
+ rc = -ENOENT;
+ }
+ } else {
+ rc = -RTErrConvertToErrno(vrc);
+ LogFunc(("VbglR0SfCreate(%s) failed vrc=%Rrc rc=%d\n", sf_i->path->String.ach, vrc, rc));
+ }
+ } else
+ rc = -ENOMEM;
+ }
+ if (rc == 0) {
+ /*
+ * Set mode and/or timestamps.
+ */
+ if (fAttrs & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME | ATTR_CTIME)) {
+ /* Fill in the attributes. Start by setting all to zero
+ since the host will ignore zeroed fields. */
+ RT_ZERO(pReq->Info.ObjInfo);
+
+ if (fAttrs & ATTR_MODE) {
+ pReq->Info.ObjInfo.Attr.fMode = sf_access_permissions_to_vbox(iattr->ia_mode);
+ if (iattr->ia_mode & S_IFDIR)
+ pReq->Info.ObjInfo.Attr.fMode |= RTFS_TYPE_DIRECTORY;
+ else if (iattr->ia_mode & S_IFLNK)
+ pReq->Info.ObjInfo.Attr.fMode |= RTFS_TYPE_SYMLINK;
+ else
+ pReq->Info.ObjInfo.Attr.fMode |= RTFS_TYPE_FILE;
+ }
+ if (fAttrs & ATTR_ATIME)
+ vbsf_time_to_vbox(&pReq->Info.ObjInfo.AccessTime, &iattr->ia_atime);
+ if (fAttrs & ATTR_MTIME)
+ vbsf_time_to_vbox(&pReq->Info.ObjInfo.ModificationTime, &iattr->ia_mtime);
+ if (fAttrs & ATTR_CTIME)
+ vbsf_time_to_vbox(&pReq->Info.ObjInfo.ChangeTime, &iattr->ia_ctime);
+
+ /* Make the change. */
+ vrc = VbglR0SfHostReqSetObjInfo(pSuperInfo->map.root, &pReq->Info, hHostFile);
+ if (RT_SUCCESS(vrc)) {
+ vbsf_update_inode(pInode, sf_i, &pReq->Info.ObjInfo, pSuperInfo, true /*fLocked*/, fAttrs);
+ } else {
+ rc = -RTErrConvertToErrno(vrc);
+ LogFunc(("VbglR0SfHostReqSetObjInfo(%s) failed vrc=%Rrc rc=%d\n", sf_i->path->String.ach, vrc, rc));
+ }
+ }
+
+ /*
+ * Change the file size.
+ * Note! Old API is more convenient here as it gives us up to date
+ * inode info back.
+ */
+ if ((fAttrs & ATTR_SIZE) && rc == 0) {
+ /*vrc = VbglR0SfHostReqSetFileSize(pSuperInfo->map.root, &pReq->SetSize, hHostFile, iattr->ia_size);
+ if (RT_SUCCESS(vrc)) {
+ i_size_write(pInode, iattr->ia_size);
+ } else if (vrc == VERR_NOT_IMPLEMENTED)*/ {
+ /* Fallback for pre 6.0 hosts: */
+ RT_ZERO(pReq->Info.ObjInfo);
+ pReq->Info.ObjInfo.cbObject = iattr->ia_size;
+ vrc = VbglR0SfHostReqSetFileSizeOld(pSuperInfo->map.root, &pReq->Info, hHostFile);
+ if (RT_SUCCESS(vrc))
+ vbsf_update_inode(pInode, sf_i, &pReq->Info.ObjInfo, pSuperInfo, true /*fLocked*/, fAttrs);
+ }
+ if (RT_SUCCESS(vrc)) {
+ /** @todo there is potentially more to be done here if there are mappings of
+ * the lovely file. */
+ } else {
+ rc = -RTErrConvertToErrno(vrc);
+ LogFunc(("VbglR0SfHostReqSetFileSize(%s, %#llx) failed vrc=%Rrc rc=%d\n",
+ sf_i->path->String.ach, (unsigned long long)iattr->ia_size, vrc, rc));
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ if (!pHandle) {
+ vrc = VbglR0SfHostReqClose(pSuperInfo->map.root, &pReq->Close, hHostFile);
+ if (RT_FAILURE(vrc))
+ LogFunc(("VbglR0SfHostReqClose(%s [%#llx]) failed vrc=%Rrc\n", sf_i->path->String.utf8, hHostFile, vrc));
+ }
+ }
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ if (pHandle)
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_inode_setattr");
+ } else
+ SFLOGFLOW(("vbsf_inode_setattr: Nothing to do here: %#x (was %#x).\n", fAttrs, iattr->ia_valid));
+ }
+ return rc;
+}
+
+
+static int vbsf_make_path(const char *caller, struct vbsf_inode_info *sf_i,
+ const char *d_name, size_t d_len, SHFLSTRING **result)
+{
+ size_t path_len, shflstring_len;
+ SHFLSTRING *tmp;
+ uint16_t p_len;
+ uint8_t *p_name;
+ int fRoot = 0;
+
+ TRACE();
+ p_len = sf_i->path->u16Length;
+ p_name = sf_i->path->String.utf8;
+
+ if (p_len == 1 && *p_name == '/') {
+ path_len = d_len + 1;
+ fRoot = 1;
+ } else {
+ /* lengths of constituents plus terminating zero plus slash */
+ path_len = p_len + d_len + 2;
+ if (path_len > 0xffff) {
+ LogFunc(("path too long. caller=%s, path_len=%zu\n",
+ caller, path_len));
+ return -ENAMETOOLONG;
+ }
+ }
+
+ shflstring_len = offsetof(SHFLSTRING, String.utf8) + path_len;
+ tmp = kmalloc(shflstring_len, GFP_KERNEL);
+ if (!tmp) {
+ LogRelFunc(("kmalloc failed, caller=%s\n", caller));
+ return -ENOMEM;
+ }
+ tmp->u16Length = path_len - 1;
+ tmp->u16Size = path_len;
+
+ if (fRoot)
+ memcpy(&tmp->String.utf8[0], d_name, d_len + 1);
+ else {
+ memcpy(&tmp->String.utf8[0], p_name, p_len);
+ tmp->String.utf8[p_len] = '/';
+ memcpy(&tmp->String.utf8[p_len + 1], d_name, d_len);
+ tmp->String.utf8[p_len + 1 + d_len] = '\0';
+ }
+
+ *result = tmp;
+ return 0;
+}
+
+
+/**
+ * [dentry] contains string encoded in coding system that corresponds
+ * to [pSuperInfo]->nls, we must convert it to UTF8 here and pass down to
+ * [vbsf_make_path] which will allocate SHFLSTRING and fill it in
+ */
+int vbsf_path_from_dentry(struct vbsf_super_info *pSuperInfo, struct vbsf_inode_info *sf_i, struct dentry *dentry,
+ SHFLSTRING **result, const char *caller)
+{
+ int err;
+ const char *d_name;
+ size_t d_len;
+ const char *name;
+ size_t len = 0;
+
+ TRACE();
+ d_name = dentry->d_name.name;
+ d_len = dentry->d_name.len;
+
+ if (pSuperInfo->nls) {
+ size_t in_len, i, out_bound_len;
+ const char *in;
+ char *out;
+
+ in = d_name;
+ in_len = d_len;
+
+ out_bound_len = PATH_MAX;
+ out = kmalloc(out_bound_len, GFP_KERNEL);
+ name = out;
+
+ for (i = 0; i < d_len; ++i) {
+ /* We renamed the linux kernel wchar_t type to linux_wchar_t in
+ the-linux-kernel.h, as it conflicts with the C++ type of that name. */
+ linux_wchar_t uni;
+ int nb;
+
+ nb = pSuperInfo->nls->char2uni(in, in_len, &uni);
+ if (nb < 0) {
+ LogFunc(("nls->char2uni failed %x %d\n",
+ *in, in_len));
+ err = -EINVAL;
+ goto fail1;
+ }
+ in_len -= nb;
+ in += nb;
+
+#if RTLNX_VER_MIN(2,6,31)
+ nb = utf32_to_utf8(uni, out, out_bound_len);
+#else
+ nb = utf8_wctomb(out, uni, out_bound_len);
+#endif
+ if (nb < 0) {
+ LogFunc(("nls->uni2char failed %x %d\n",
+ uni, out_bound_len));
+ err = -EINVAL;
+ goto fail1;
+ }
+ out_bound_len -= nb;
+ out += nb;
+ len += nb;
+ }
+ if (len >= PATH_MAX - 1) {
+ err = -ENAMETOOLONG;
+ goto fail1;
+ }
+
+ LogFunc(("result(%d) = %.*s\n", len, len, name));
+ *out = 0;
+ } else {
+ name = d_name;
+ len = d_len;
+ }
+
+ err = vbsf_make_path(caller, sf_i, name, len, result);
+ if (name != d_name)
+ kfree(name);
+
+ return err;
+
+ fail1:
+ kfree(name);
+ return err;
+}
+
+
+/**
+ * This is called during name resolution/lookup to check if the @a dentry in the
+ * cache is still valid. The actual validation is job is handled by
+ * vbsf_inode_revalidate_worker().
+ *
+ * @note Caller holds no relevant locks, just a dentry reference.
+ */
+#if RTLNX_VER_MIN(3,6,0)
+static int vbsf_dentry_revalidate(struct dentry *dentry, unsigned flags)
+#elif RTLNX_VER_MIN(2,6,0)
+static int vbsf_dentry_revalidate(struct dentry *dentry, struct nameidata *nd)
+#else
+static int vbsf_dentry_revalidate(struct dentry *dentry, int flags)
+#endif
+{
+#if RTLNX_VER_RANGE(2,6,0, 3,6,0)
+ int const flags = nd ? nd->flags : 0;
+#endif
+
+ int rc;
+
+ Assert(dentry);
+ SFLOGFLOW(("vbsf_dentry_revalidate: %p %#x %s\n", dentry, flags,
+ dentry->d_inode ? VBSF_GET_INODE_INFO(dentry->d_inode)->path->String.ach : "<negative>"));
+
+ /*
+ * See Documentation/filesystems/vfs.txt why we skip LOOKUP_RCU.
+ *
+ * Also recommended: https://lwn.net/Articles/649115/
+ * https://lwn.net/Articles/649729/
+ * https://lwn.net/Articles/650786/
+ *
+ */
+#if RTLNX_VER_MIN(2,6,38)
+ if (flags & LOOKUP_RCU) {
+ rc = -ECHILD;
+ SFLOGFLOW(("vbsf_dentry_revalidate: RCU -> -ECHILD\n"));
+ } else
+#endif
+ {
+ /*
+ * Do we have an inode or not? If not it's probably a negative cache
+ * entry, otherwise most likely a positive one.
+ */
+ struct inode *pInode = dentry->d_inode;
+ if (pInode) {
+ /*
+ * Positive entry.
+ *
+ * Note! We're more aggressive here than other remote file systems,
+ * current (4.19) CIFS will for instance revalidate the inode
+ * and ignore the dentry timestamp for positive entries.
+ */
+ unsigned long const cJiffiesAge = jiffies - vbsf_dentry_get_update_jiffies(dentry);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(dentry->d_sb);
+ if (cJiffiesAge < pSuperInfo->cJiffiesDirCacheTTL) {
+ SFLOGFLOW(("vbsf_dentry_revalidate: age: %lu vs. TTL %lu -> 1\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 1;
+ } else if (!vbsf_inode_revalidate_worker(dentry, true /*fForced*/, false /*fInodeLocked*/)) {
+ vbsf_dentry_set_update_jiffies(dentry, jiffies);
+ SFLOGFLOW(("vbsf_dentry_revalidate: age: %lu vs. TTL %lu -> reval -> 1\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 1;
+ } else {
+ SFLOGFLOW(("vbsf_dentry_revalidate: age: %lu vs. TTL %lu -> reval -> 0\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 0;
+ }
+ } else {
+ /*
+ * Negative entry.
+ *
+ * Invalidate dentries for open and renames here as we'll revalidate
+ * these when taking the actual action (also good for case preservation
+ * if we do case-insensitive mounts against windows + mac hosts at some
+ * later point).
+ */
+#if RTLNX_VER_MIN(2,6,28)
+ if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET))
+#elif RTLNX_VER_MIN(2,5,75)
+ if (flags & LOOKUP_CREATE)
+#else
+ if (0)
+#endif
+ {
+ SFLOGFLOW(("vbsf_dentry_revalidate: negative: create or rename target -> 0\n"));
+ rc = 0;
+ } else {
+ /* Can we skip revalidation based on TTL? */
+ unsigned long const cJiffiesAge = vbsf_dentry_get_update_jiffies(dentry) - jiffies;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(dentry->d_sb);
+ if (cJiffiesAge < pSuperInfo->cJiffiesDirCacheTTL) {
+ SFLOGFLOW(("vbsf_dentry_revalidate: negative: age: %lu vs. TTL %lu -> 1\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 1;
+ } else {
+ /* We could revalidate it here, but we could instead just
+ have the caller kick it out. */
+ /** @todo stat the direntry and see if it exists now. */
+ SFLOGFLOW(("vbsf_dentry_revalidate: negative: age: %lu vs. TTL %lu -> 0\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 0;
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+#ifdef SFLOG_ENABLED
+
+/** For logging purposes only. */
+# if RTLNX_VER_MIN(2,6,38)
+static int vbsf_dentry_delete(const struct dentry *pDirEntry)
+# else
+static int vbsf_dentry_delete(struct dentry *pDirEntry)
+# endif
+{
+ SFLOGFLOW(("vbsf_dentry_delete: %p\n", pDirEntry));
+ return 0;
+}
+
+# if RTLNX_VER_MIN(4,8,0)
+/** For logging purposes only. */
+static int vbsf_dentry_init(struct dentry *pDirEntry)
+{
+ SFLOGFLOW(("vbsf_dentry_init: %p\n", pDirEntry));
+ return 0;
+}
+# endif
+
+#endif /* SFLOG_ENABLED */
+
+/**
+ * Directory entry operations.
+ *
+ * Since 2.6.38 this is used via the super_block::s_d_op member.
+ */
+struct dentry_operations vbsf_dentry_ops = {
+ .d_revalidate = vbsf_dentry_revalidate,
+#ifdef SFLOG_ENABLED
+ .d_delete = vbsf_dentry_delete,
+# if RTLNX_VER_MIN(4,8,0)
+ .d_init = vbsf_dentry_init,
+# endif
+#endif
+};
+
diff --git a/src/VBox/Additions/linux/sharedfolders/vbsfmount.c b/src/VBox/Additions/linux/sharedfolders/vbsfmount.c
new file mode 100644
index 00000000..bc557879
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vbsfmount.c
@@ -0,0 +1,113 @@
+/* $Id: vbsfmount.c $ */
+/** @file
+ * vbsfmount - Commonly used code to mount shared folders on Linux-based
+ * systems. Currently used by mount.vboxsf and VBoxService.
+ */
+
+/*
+ * Copyright (C) 2010-2022 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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+#include <assert.h>
+#include <ctype.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mount.h>
+
+#include "vbsfmount.h"
+
+
+/** @todo Use defines for return values! */
+int vbsfmount_complete(const char *pszSharedFolder, const char *pszMountPoint,
+ unsigned long fFlags, const char *pszOpts)
+{
+ /*
+ * Combine pszOpts and fFlags.
+ */
+ int rc;
+ size_t const cchFlags = (fFlags & MS_NOSUID ? strlen(MNTOPT_NOSUID) + 1 : 0)
+ + (fFlags & MS_RDONLY ? strlen(MNTOPT_RO) : strlen(MNTOPT_RW));
+ size_t const cchOpts = pszOpts ? 1 + strlen(pszOpts) : 0;
+ char *pszBuf = (char *)malloc(cchFlags + cchOpts + 8);
+ if (pszBuf)
+ {
+ char *psz = pszBuf;
+ FILE *pMTab;
+
+ strcpy(psz, fFlags & MS_RDONLY ? MNTOPT_RO : MNTOPT_RW);
+ psz += strlen(psz);
+
+ if (fFlags & MS_NOSUID)
+ {
+ *psz++ = ',';
+ strcpy(psz, MNTOPT_NOSUID);
+ psz += strlen(psz);
+ }
+
+ if (cchOpts)
+ {
+ *psz++ = ',';
+ strcpy(psz, pszOpts);
+ }
+
+ assert(strlen(pszBuf) <= cchFlags + cchOpts);
+
+ /*
+ * Open the mtab and update it:
+ */
+ pMTab = setmntent(MOUNTED, "a+");
+ if (pMTab)
+ {
+ struct mntent Entry;
+ Entry.mnt_fsname = (char*)pszSharedFolder;
+ Entry.mnt_dir = (char *)pszMountPoint;
+ Entry.mnt_type = "vboxsf";
+ Entry.mnt_opts = pszBuf;
+ Entry.mnt_freq = 0;
+ Entry.mnt_passno = 0;
+
+ if (!addmntent(pMTab, &Entry))
+ rc = 0; /* success. */
+ else
+ rc = 3; /* Could not add an entry to the mount table. */
+
+ endmntent(pMTab);
+ }
+ else
+ rc = 2; /* Could not open mount table for update. */
+
+ free(pszBuf);
+ }
+ else
+ rc = 1; /* allocation error */
+ return rc;
+}
+
diff --git a/src/VBox/Additions/linux/sharedfolders/vbsfmount.h b/src/VBox/Additions/linux/sharedfolders/vbsfmount.h
new file mode 100644
index 00000000..3eb19460
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vbsfmount.h
@@ -0,0 +1,142 @@
+/* $Id: vbsfmount.h $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, mount(2) parameter structure.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef GA_INCLUDED_SRC_linux_sharedfolders_vbsfmount_h
+#define GA_INCLUDED_SRC_linux_sharedfolders_vbsfmount_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/* Linux constrains the size of data mount argument to PAGE_SIZE - 1. */
+#define MAX_MNTOPT_STR PAGE_SIZE
+#define MAX_HOST_NAME 256
+#define MAX_NLS_NAME 32
+#define VBSF_DEFAULT_TTL_MS 200
+
+#define VBSF_MOUNT_SIGNATURE_BYTE_0 '\377'
+#define VBSF_MOUNT_SIGNATURE_BYTE_1 '\376'
+#define VBSF_MOUNT_SIGNATURE_BYTE_2 '\375'
+
+/**
+ * VBox Linux Shared Folders VFS caching mode.
+ */
+enum vbsf_cache_mode {
+ /** Use the kernel modules default caching mode (kVbsfCacheMode_Strict). */
+ kVbsfCacheMode_Default = 0,
+ /** No caching, go to the host for everything. This will have some minor
+ * coherency issues for memory mapping with unsynced dirty pages. */
+ kVbsfCacheMode_None,
+ /** No caching, except for files with writable memory mappings.
+ * (Note to future: if we do oplock like stuff, it goes in here.) */
+ kVbsfCacheMode_Strict,
+ /** Use page cache for reads.
+ * This improves guest performance for read intensive jobs, like compiling
+ * building. The flip side is that the guest may not see host modification in a
+ * timely manner and possibly update files with out-of-date cache information,
+ * as there exists no protocol for the host to notify the guest about file
+ * modifications. */
+ kVbsfCacheMode_Read,
+ /** Use page cache for both reads and writes as far as that's possible.
+ * This is good for guest performance, but the price is that the guest possibly
+ * ignoring host changes and the host not seeing guest changes in a timely
+ * manner. */
+ kVbsfCacheMode_ReadWrite,
+ /** End of valid values (exclusive). */
+ kVbsfCacheMode_End,
+ /** Make sure the enum is sizeof(int32_t). */
+ kVbsfCacheMode_32BitHack = 0x7fffffff
+};
+
+/**
+ * VBox Linux Shared Folders VFS mount options.
+ */
+struct vbsf_mount_info_new {
+ /**
+ * The old version of the mount_info struct started with a
+ * char name[MAX_HOST_NAME] field, where name cannot be '\0'.
+ * So the new version of the mount_info struct starts with a
+ * nullchar field which is always 0 so that we can detect and
+ * reject the old structure being passed.
+ */
+ char nullchar;
+ /** Signature */
+ char signature[3];
+ /** Length of the whole structure */
+ int length;
+ /** Share name */
+ char name[MAX_HOST_NAME];
+ /** Name of an I/O charset */
+ char nls_name[MAX_NLS_NAME];
+ /** User ID for all entries, default 0=root */
+ int uid;
+ /** Group ID for all entries, default 0=root */
+ int gid;
+ /** Directory entry and inode time to live in milliseconds.
+ * -1 for kernel default, 0 to disable caching.
+ * @sa vbsf_mount_info_new::msDirCacheTTL, vbsf_mount_info_new::msInodeTTL */
+ int ttl;
+ /** Mode for directories if != -1. */
+ int dmode;
+ /** Mode for regular files if != -1. */
+ int fmode;
+ /** umask applied to directories */
+ int dmask;
+ /** umask applied to regular files */
+ int fmask;
+ /** Mount tag for VBoxService automounter.
+ * @since 6.0.0 */
+ char szTag[32];
+ /** Max pages to read & write at a time.
+ * @since 6.0.6 */
+ uint32_t cMaxIoPages;
+ /** The directory content buffer size. Set to 0 for kernel module default.
+ * Larger value reduces the number of host calls on large directories. */
+ uint32_t cbDirBuf;
+ /** The time to live for directory entries (in milliseconds). @a ttl is used
+ * if negative.
+ * @since 6.0.6 */
+ int32_t msDirCacheTTL;
+ /** The time to live for inode information (in milliseconds). @a ttl is used
+ * if negative.
+ * @since 6.0.6 */
+ int32_t msInodeTTL;
+ /** The cache and coherency mode.
+ * @since 6.0.6 */
+ enum vbsf_cache_mode enmCacheMode;
+};
+#ifdef AssertCompileSize
+AssertCompileSize(struct vbsf_mount_info_new, 2*4 + MAX_HOST_NAME + MAX_NLS_NAME + 7*4 + 32 + 5*4);
+#endif
+
+/** Completes the mount operation by adding the new mount point to mtab if required. */
+int vbsfmount_complete(const char *pszSharedFolder, const char *pszMountPoint,
+ unsigned long fFlags, const char *pszOpts);
+
+#endif /* !GA_INCLUDED_SRC_linux_sharedfolders_vbsfmount_h */
diff --git a/src/VBox/Additions/linux/sharedfolders/vfsmod.c b/src/VBox/Additions/linux/sharedfolders/vfsmod.c
new file mode 100644
index 00000000..ce670f76
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.c
@@ -0,0 +1,1753 @@
+/* $Id: vfsmod.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, module init/term, super block management.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @note Anyone wishing to make changes here might wish to take a look at
+ * https://github.com/torvalds/linux/blob/master/Documentation/filesystems/vfs.txt
+ * which seems to be the closest there is to official documentation on
+ * writing filesystem drivers for Linux.
+ *
+ * See also: http://us1.samba.org/samba/ftp/cifs-cvs/ols2006-fs-tutorial-smf.odp
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+#include "version-generated.h"
+#include "revision-generated.h"
+#include "product-generated.h"
+#if RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,4)
+# include <uapi/linux/mount.h> /* for MS_REMOUNT */
+#elif RTLNX_VER_MAX(3,3,0)
+# include <linux/mount.h>
+#endif
+#include <linux/seq_file.h>
+#include <linux/vfs.h>
+#if RTLNX_VER_RANGE(2,5,62, 5,8,0)
+# include <linux/vermagic.h>
+#endif
+#include <VBox/err.h>
+#include <iprt/path.h>
+#if RTLNX_VER_MIN(5,1,0)
+# include <linux/fs_context.h>
+# include <linux/fs_parser.h>
+#elif RTLNX_VER_MIN(2,6,0)
+# include <linux/parser.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VBSF_DEFAULT_MAX_IO_PAGES RT_MIN(_16K / sizeof(RTGCPHYS64) /* => 8MB buffer */, VMMDEV_MAX_HGCM_DATA_SIZE >> PAGE_SHIFT)
+#define VBSF_DEFAULT_DIR_BUF_SIZE _64K
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+VBGLSFCLIENT g_SfClient;
+uint32_t g_fHostFeatures = 0;
+/** Last valid shared folders function number. */
+uint32_t g_uSfLastFunction = SHFL_FN_SET_FILE_SIZE;
+/** Shared folders features (SHFL_FEATURE_XXX). */
+uint64_t g_fSfFeatures = 0;
+
+/** Protects all the vbsf_inode_info::HandleList lists. */
+spinlock_t g_SfHandleLock;
+
+/** The 'follow_symlinks' module parameter.
+ * @todo Figure out how do this for 2.4.x! */
+static int g_fFollowSymlinks = 0;
+
+/* forward declaration */
+static struct super_operations g_vbsf_super_ops;
+
+
+
+/**
+ * Copies options from the mount info structure into @a pSuperInfo.
+ *
+ * This is used both by vbsf_super_info_alloc_and_map_it() and
+ * vbsf_remount_fs().
+ */
+static void vbsf_super_info_copy_remount_options(struct vbsf_super_info *pSuperInfo, struct vbsf_mount_info_new *info)
+{
+ pSuperInfo->uid = info->uid;
+ pSuperInfo->gid = info->gid;
+
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, szTag)) {
+ /* new fields */
+ pSuperInfo->dmode = info->dmode;
+ pSuperInfo->fmode = info->fmode;
+ pSuperInfo->dmask = info->dmask;
+ pSuperInfo->fmask = info->fmask;
+ } else {
+ pSuperInfo->dmode = ~0;
+ pSuperInfo->fmode = ~0;
+ }
+
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, cMaxIoPages)) {
+ AssertCompile(sizeof(pSuperInfo->szTag) >= sizeof(info->szTag));
+ memcpy(pSuperInfo->szTag, info->szTag, sizeof(info->szTag));
+ pSuperInfo->szTag[sizeof(pSuperInfo->szTag) - 1] = '\0';
+ } else {
+ pSuperInfo->szTag[0] = '\0';
+ }
+
+ /* The max number of pages in an I/O request. This must take into
+ account that the physical heap generally grows in 64 KB chunks,
+ so we should not try push that limit. It also needs to take
+ into account that the host will allocate temporary heap buffers
+ for the I/O bytes we send/receive, so don't push the host heap
+ too hard as we'd have to retry with smaller requests when this
+ happens, which isn't too efficient. */
+ pSuperInfo->cMaxIoPages = VBSF_DEFAULT_MAX_IO_PAGES;
+ if ( (unsigned)info->length >= sizeof(struct vbsf_mount_info_new)
+ && info->cMaxIoPages > 0) {
+ if (info->cMaxIoPages <= VMMDEV_MAX_HGCM_DATA_SIZE >> PAGE_SHIFT)
+ pSuperInfo->cMaxIoPages = RT_MAX(info->cMaxIoPages, 2); /* read_iter/write_iter requires a minimum of 2. */
+ else
+ printk(KERN_WARNING "vboxsf: max I/O page count (%#x) is out of range, using default (%#x) instead.\n",
+ info->cMaxIoPages, pSuperInfo->cMaxIoPages);
+ }
+
+ pSuperInfo->cbDirBuf = VBSF_DEFAULT_DIR_BUF_SIZE;
+ if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, cbDirBuf)
+ && info->cbDirBuf > 0) {
+ if (info->cbDirBuf <= _16M)
+ pSuperInfo->cbDirBuf = RT_ALIGN_32(info->cbDirBuf, PAGE_SIZE);
+ else
+ printk(KERN_WARNING "vboxsf: max directory buffer size (%#x) is out of range, using default (%#x) instead.\n",
+ info->cMaxIoPages, pSuperInfo->cMaxIoPages);
+ }
+
+ /*
+ * TTLs.
+ */
+ pSuperInfo->msTTL = info->ttl;
+ if (info->ttl > 0)
+ pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(info->ttl);
+ else if (info->ttl == 0 || info->ttl != -1)
+ pSuperInfo->cJiffiesDirCacheTTL = pSuperInfo->msTTL = 0;
+ else
+ pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(VBSF_DEFAULT_TTL_MS);
+ pSuperInfo->cJiffiesInodeTTL = pSuperInfo->cJiffiesDirCacheTTL;
+
+ pSuperInfo->msDirCacheTTL = -1;
+ if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, msDirCacheTTL)
+ && info->msDirCacheTTL >= 0) {
+ if (info->msDirCacheTTL > 0) {
+ pSuperInfo->msDirCacheTTL = info->msDirCacheTTL;
+ pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(info->msDirCacheTTL);
+ } else {
+ pSuperInfo->msDirCacheTTL = 0;
+ pSuperInfo->cJiffiesDirCacheTTL = 0;
+ }
+ }
+
+ pSuperInfo->msInodeTTL = -1;
+ if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, msInodeTTL)
+ && info->msInodeTTL >= 0) {
+ if (info->msInodeTTL > 0) {
+ pSuperInfo->msInodeTTL = info->msInodeTTL;
+ pSuperInfo->cJiffiesInodeTTL = msecs_to_jiffies(info->msInodeTTL);
+ } else {
+ pSuperInfo->msInodeTTL = 0;
+ pSuperInfo->cJiffiesInodeTTL = 0;
+ }
+ }
+
+ /*
+ * Caching.
+ */
+ pSuperInfo->enmCacheMode = kVbsfCacheMode_Strict;
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, enmCacheMode)) {
+ switch (info->enmCacheMode) {
+ case kVbsfCacheMode_Default:
+ case kVbsfCacheMode_Strict:
+ break;
+ case kVbsfCacheMode_None:
+ case kVbsfCacheMode_Read:
+ case kVbsfCacheMode_ReadWrite:
+ pSuperInfo->enmCacheMode = info->enmCacheMode;
+ break;
+ default:
+ printk(KERN_WARNING "vboxsf: cache mode (%#x) is out of range, using default instead.\n", info->enmCacheMode);
+ break;
+ }
+ }
+}
+
+/**
+ * Allocate the super info structure and try map the host share.
+ */
+static int vbsf_super_info_alloc_and_map_it(struct vbsf_mount_info_new *info, struct vbsf_super_info **sf_gp)
+{
+ int rc;
+ SHFLSTRING *str_name;
+ size_t name_len, str_len;
+ struct vbsf_super_info *pSuperInfo;
+
+ TRACE();
+ *sf_gp = NULL; /* (old gcc maybe used initialized) */
+
+ name_len = RTStrNLen(info->name, sizeof(info->name));
+ if (name_len >= sizeof(info->name)) {
+ SFLOGRELBOTH(("vboxsf: Specified shared folder name is not zero terminated!\n"));
+ return -EINVAL;
+ }
+ if (RTStrNLen(info->nls_name, sizeof(info->nls_name)) >= sizeof(info->nls_name)) {
+ SFLOGRELBOTH(("vboxsf: Specified nls name is not zero terminated!\n"));
+ return -EINVAL;
+ }
+
+ /*
+ * Allocate memory.
+ */
+ str_len = offsetof(SHFLSTRING, String.utf8) + name_len + 1;
+ str_name = (PSHFLSTRING)kmalloc(str_len, GFP_KERNEL);
+ pSuperInfo = (struct vbsf_super_info *)kmalloc(sizeof(*pSuperInfo), GFP_KERNEL);
+ if (pSuperInfo && str_name) {
+ RT_ZERO(*pSuperInfo);
+
+ str_name->u16Length = name_len;
+ str_name->u16Size = name_len + 1;
+ memcpy(str_name->String.utf8, info->name, name_len + 1);
+
+ /*
+ * Init the NLS support, if needed.
+ */
+ rc = 0;
+#define _IS_UTF8(_str) (strcmp(_str, "utf8") == 0)
+#define _IS_EMPTY(_str) (strcmp(_str, "") == 0)
+
+ /* Check if NLS charset is valid and not points to UTF8 table */
+ pSuperInfo->fNlsIsUtf8 = true;
+ if (info->nls_name[0]) {
+ if (_IS_UTF8(info->nls_name)) {
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8\n"));
+ pSuperInfo->nls = NULL;
+ } else {
+ pSuperInfo->fNlsIsUtf8 = false;
+ pSuperInfo->nls = load_nls(info->nls_name);
+ if (pSuperInfo->nls) {
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=%s -> %p\n", info->nls_name, pSuperInfo->nls));
+ } else {
+ SFLOGRELBOTH(("vboxsf: Failed to load nls '%s'!\n", info->nls_name));
+ rc = -EINVAL;
+ }
+ }
+ } else {
+#ifdef CONFIG_NLS_DEFAULT
+ /* If no NLS charset specified, try to load the default
+ * one if it's not points to UTF8. */
+ if (!_IS_UTF8(CONFIG_NLS_DEFAULT)
+ && !_IS_EMPTY(CONFIG_NLS_DEFAULT)) {
+ pSuperInfo->fNlsIsUtf8 = false;
+ pSuperInfo->nls = load_nls_default();
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: CONFIG_NLS_DEFAULT=%s -> %p\n", CONFIG_NLS_DEFAULT, pSuperInfo->nls));
+ } else {
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8 (default %s)\n", CONFIG_NLS_DEFAULT));
+ pSuperInfo->nls = NULL;
+ }
+#else
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8 (no default)\n"));
+ pSuperInfo->nls = NULL;
+#endif
+ }
+#undef _IS_UTF8
+#undef _IS_EMPTY
+ if (rc == 0) {
+ /*
+ * Try mount it.
+ */
+ rc = VbglR0SfHostReqMapFolderWithContigSimple(str_name, virt_to_phys(str_name), RTPATH_DELIMITER,
+ true /*fCaseSensitive*/, &pSuperInfo->map.root);
+ if (RT_SUCCESS(rc)) {
+ kfree(str_name);
+
+ /* The rest is shared with remount. */
+ vbsf_super_info_copy_remount_options(pSuperInfo, info);
+
+ *sf_gp = pSuperInfo;
+ return 0;
+ }
+
+ /*
+ * bail out:
+ */
+ if (rc == VERR_FILE_NOT_FOUND) {
+ LogRel(("vboxsf: SHFL_FN_MAP_FOLDER failed for '%s': share not found\n", info->name));
+ rc = -ENXIO;
+ } else {
+ LogRel(("vboxsf: SHFL_FN_MAP_FOLDER failed for '%s': %Rrc\n", info->name, rc));
+ rc = -EPROTO;
+ }
+ if (pSuperInfo->nls)
+ unload_nls(pSuperInfo->nls);
+ }
+ } else {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for super info!\n"));
+ rc = -ENOMEM;
+ }
+ if (str_name)
+ kfree(str_name);
+ if (pSuperInfo)
+ kfree(pSuperInfo);
+ return rc;
+}
+
+/* unmap the share and free super info [pSuperInfo] */
+static void vbsf_super_info_free(struct vbsf_super_info *pSuperInfo)
+{
+ int rc;
+
+ TRACE();
+ rc = VbglR0SfHostReqUnmapFolderSimple(pSuperInfo->map.root);
+ if (RT_FAILURE(rc))
+ LogFunc(("VbglR0SfHostReqUnmapFolderSimple failed rc=%Rrc\n", rc));
+
+ if (pSuperInfo->nls)
+ unload_nls(pSuperInfo->nls);
+
+ kfree(pSuperInfo);
+}
+
+
+/**
+ * Initialize backing device related matters.
+ */
+static int vbsf_init_backing_dev(struct super_block *sb, struct vbsf_super_info *pSuperInfo)
+{
+ int rc = 0;
+#if RTLNX_VER_MIN(2,6,0)
+ /* Each new shared folder map gets a new uint64_t identifier,
+ * allocated in sequence. We ASSUME the sequence will not wrap. */
+# if RTLNX_VER_MIN(2,6,26)
+ static uint64_t s_u64Sequence = 0;
+ uint64_t idSeqMine = ASMAtomicIncU64(&s_u64Sequence);
+# endif
+ struct backing_dev_info *bdi;
+
+# if RTLNX_VER_RANGE(4,0,0, 4,2,0)
+ pSuperInfo->bdi_org = sb->s_bdi;
+# endif
+
+# if RTLNX_VER_MIN(4,12,0)
+ rc = super_setup_bdi_name(sb, "vboxsf-%llu", (unsigned long long)idSeqMine);
+ if (!rc)
+ bdi = sb->s_bdi;
+ else
+ return rc;
+# else
+ bdi = &pSuperInfo->bdi;
+# endif
+
+ bdi->ra_pages = 0; /* No readahead */
+
+# if RTLNX_VER_MIN(2,6,12)
+ bdi->capabilities = 0
+# ifdef BDI_CAP_MAP_DIRECT
+ | BDI_CAP_MAP_DIRECT /* MAP_SHARED */
+# endif
+# ifdef BDI_CAP_MAP_COPY
+ | BDI_CAP_MAP_COPY /* MAP_PRIVATE */
+# endif
+# ifdef BDI_CAP_READ_MAP
+ | BDI_CAP_READ_MAP /* can be mapped for reading */
+# endif
+# ifdef BDI_CAP_WRITE_MAP
+ | BDI_CAP_WRITE_MAP /* can be mapped for writing */
+# endif
+# ifdef BDI_CAP_EXEC_MAP
+ | BDI_CAP_EXEC_MAP /* can be mapped for execution */
+# endif
+# ifdef BDI_CAP_STRICTLIMIT
+# if RTLNX_VER_MIN(4,19,0) /* Trouble with 3.16.x/debian8. Process stops after dirty page throttling.
+ * Only tested successfully with 4.19. Maybe skip altogether? */
+ | BDI_CAP_STRICTLIMIT;
+# endif
+# endif
+ ;
+# ifdef BDI_CAP_STRICTLIMIT
+ /* Smalles possible amount of dirty pages: %1 of RAM. We set this to
+ try reduce amount of data that's out of sync with the host side.
+ Besides, writepages isn't implemented, so flushing is extremely slow.
+ Note! Extremely slow linux 3.0.0 msync doesn't seem to be related to this setting. */
+ bdi_set_max_ratio(bdi, 1);
+# endif
+# endif /* >= 2.6.12 */
+
+# if RTLNX_VER_RANGE(2,6,24, 4,12,0)
+ rc = bdi_init(&pSuperInfo->bdi);
+# if RTLNX_VER_MIN(2,6,26)
+ if (!rc)
+ rc = bdi_register(&pSuperInfo->bdi, NULL, "vboxsf-%llu", (unsigned long long)idSeqMine);
+# endif /* >= 2.6.26 */
+# endif /* 4.11.0 > version >= 2.6.24 */
+
+# if RTLNX_VER_RANGE(2,6,34, 4,12,0)
+ if (!rc)
+ sb->s_bdi = bdi;
+# endif
+
+#endif /* >= 2.6.0 */
+ return rc;
+}
+
+
+/**
+ * Undoes what vbsf_init_backing_dev did.
+ */
+static void vbsf_done_backing_dev(struct super_block *sb, struct vbsf_super_info *pSuperInfo)
+{
+#if RTLNX_VER_RANGE(2,6,24, 4,12,0)
+ bdi_destroy(&pSuperInfo->bdi); /* includes bdi_unregister() */
+
+ /* Paranoia: Make sb->s_bdi not point at pSuperInfo->bdi, in case someone
+ trouches it after this point (we may screw up something). */
+# if RTLNX_VER_RANGE(4,0,0, 4,2,0)
+ sb->s_bdi = pSuperInfo->bdi_org; /* (noop_backing_dev_info is not exported) */
+# elif RTLNX_VER_RANGE(2,6,34, 4,10,0)
+ sb->s_bdi = &noop_backing_dev_info;
+# endif
+#endif
+}
+
+
+/**
+ * Creates the root inode and attaches it to the super block.
+ *
+ * @returns 0 on success, negative errno on failure.
+ * @param sb The super block.
+ * @param pSuperInfo Our super block info.
+ */
+static int vbsf_create_root_inode(struct super_block *sb, struct vbsf_super_info *pSuperInfo)
+{
+ SHFLFSOBJINFO fsinfo;
+ int rc;
+
+ /*
+ * Allocate and initialize the memory for our inode info structure.
+ */
+ struct vbsf_inode_info *sf_i = kmalloc(sizeof(*sf_i), GFP_KERNEL);
+ SHFLSTRING *path = kmalloc(sizeof(SHFLSTRING) + 1, GFP_KERNEL);
+ if (sf_i && path) {
+ sf_i->handle = SHFL_HANDLE_NIL;
+ sf_i->force_restat = false;
+ RTListInit(&sf_i->HandleList);
+#ifdef VBOX_STRICT
+ sf_i->u32Magic = SF_INODE_INFO_MAGIC;
+#endif
+ sf_i->path = path;
+
+ path->u16Length = 1;
+ path->u16Size = 2;
+ path->String.utf8[0] = '/';
+ path->String.utf8[1] = 0;
+
+ /*
+ * Stat the root directory (for inode info).
+ */
+ rc = vbsf_stat(__func__, pSuperInfo, sf_i->path, &fsinfo, 0);
+ if (rc == 0) {
+ /*
+ * Create the actual inode structure.
+ * Note! ls -la does display '.' and '..' entries with st_ino == 0, so root is #1.
+ */
+#if RTLNX_VER_MIN(2,4,25)
+ struct inode *iroot = iget_locked(sb, 1);
+#else
+ struct inode *iroot = iget(sb, 1);
+#endif
+ if (iroot) {
+ vbsf_init_inode(iroot, sf_i, &fsinfo, pSuperInfo);
+ VBSF_SET_INODE_INFO(iroot, sf_i);
+
+#if RTLNX_VER_MIN(2,4,25)
+ unlock_new_inode(iroot);
+#endif
+
+ /*
+ * Now make it a root inode.
+ */
+#if RTLNX_VER_MIN(3,4,0)
+ sb->s_root = d_make_root(iroot);
+#else
+ sb->s_root = d_alloc_root(iroot);
+#endif
+ if (sb->s_root) {
+
+ return 0;
+ }
+
+ SFLOGRELBOTH(("vboxsf: d_make_root failed!\n"));
+#if RTLNX_VER_MAX(3,4,0) /* d_make_root calls iput */
+ iput(iroot);
+#endif
+ /* iput() will call vbsf_evict_inode()/vbsf_clear_inode(). */
+ sf_i = NULL;
+ path = NULL;
+
+ rc = -ENOMEM;
+ } else {
+ SFLOGRELBOTH(("vboxsf: failed to allocate root inode!\n"));
+ rc = -ENOMEM;
+ }
+ } else
+ SFLOGRELBOTH(("vboxsf: could not stat root of share: %d\n", rc));
+ } else {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for root inode info!\n"));
+ rc = -ENOMEM;
+ }
+ if (sf_i)
+ kfree(sf_i);
+ if (path)
+ kfree(path);
+ return rc;
+}
+
+
+#if RTLNX_VER_MAX(5,1,0)
+static void vbsf_init_mount_info(struct vbsf_mount_info_new *mount_info,
+ const char *sf_name)
+{
+ mount_info->ttl = mount_info->msDirCacheTTL = mount_info->msInodeTTL = -1;
+ mount_info->dmode = mount_info->fmode = ~0U;
+ mount_info->enmCacheMode = kVbsfCacheMode_Strict;
+ mount_info->length = sizeof(struct vbsf_mount_info_new);
+ if (sf_name) {
+# if RTLNX_VER_MAX(2,5,69)
+ strncpy(mount_info->name, sf_name, sizeof(mount_info->name));
+ mount_info->name[sizeof(mount_info->name)-1] = 0;
+# else
+ strlcpy(mount_info->name, sf_name, sizeof(mount_info->name));
+# endif
+ }
+}
+#endif
+
+#if RTLNX_VER_RANGE(2,6,0, 5,1,0)
+/**
+ * The following section of code uses the Linux match_token() family of
+ * routines to parse string-based mount options.
+ */
+enum {
+ Opt_iocharset, /* nls_name[] */
+ Opt_nls, /* alias for iocharset */
+ Opt_uid,
+ Opt_gid,
+ Opt_ttl,
+ Opt_dmode,
+ Opt_fmode,
+ Opt_dmask,
+ Opt_fmask,
+ Opt_umask,
+ Opt_maxiopages,
+ Opt_dirbuf,
+ Opt_dcachettl,
+ Opt_inodettl,
+ Opt_cachemode, /* enum vbsf_cache_mode */
+ Opt_tag,
+ Opt_err
+};
+
+# if RTLNX_VER_MAX(2,6,28)
+static match_table_t vbsf_tokens = {
+# else
+static const match_table_t vbsf_tokens = {
+# endif
+ { Opt_iocharset, "iocharset=%s" },
+ { Opt_nls, "nls=%s" },
+ { Opt_uid, "uid=%u" },
+ { Opt_gid, "gid=%u" },
+ { Opt_ttl, "ttl=%u" },
+ { Opt_dmode, "dmode=%o" },
+ { Opt_fmode, "fmode=%o" },
+ { Opt_dmask, "dmask=%o" },
+ { Opt_fmask, "fmask=%o" },
+ { Opt_umask, "umask=%o" },
+ { Opt_maxiopages, "maxiopages=%u" },
+ { Opt_dirbuf, "dirbuf=%u" },
+ { Opt_dcachettl, "dcachettl=%u" },
+ { Opt_inodettl, "inodettl=%u" },
+ { Opt_cachemode, "cache=%s" },
+ { Opt_tag, "tag=%s" }, /* private option for automounter */
+ { Opt_err, NULL }
+};
+
+static int vbsf_parse_mount_options(char *options,
+ struct vbsf_mount_info_new *mount_info)
+{
+ substring_t args[MAX_OPT_ARGS];
+ int option;
+ int token;
+ char *p;
+ char *iocharset;
+ char *cachemode;
+ char *tag;
+
+ if (!options)
+ return -EINVAL;
+
+ while ((p = strsep(&options, ",")) != NULL) {
+ if (!*p)
+ continue;
+
+ token = match_token(p, vbsf_tokens, args);
+ switch (token) {
+ case Opt_iocharset:
+ case Opt_nls:
+ iocharset = match_strdup(&args[0]);
+ if (!iocharset) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for iocharset!\n"));
+ return -ENOMEM;
+ }
+ strlcpy(mount_info->nls_name, iocharset,
+ sizeof(mount_info->nls_name));
+ kfree(iocharset);
+ break;
+ case Opt_uid:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->uid = option;
+ break;
+ case Opt_gid:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->gid = option;
+ break;
+ case Opt_ttl:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->ttl = option;
+ break;
+ case Opt_dmode:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->dmode = option;
+ break;
+ case Opt_fmode:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->fmode = option;
+ break;
+ case Opt_dmask:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->dmask = option;
+ break;
+ case Opt_fmask:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->fmask = option;
+ break;
+ case Opt_umask:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->dmask = mount_info->fmask = option;
+ break;
+ case Opt_maxiopages:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->cMaxIoPages = option;
+ break;
+ case Opt_dirbuf:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->cbDirBuf = option;
+ break;
+ case Opt_dcachettl:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->msDirCacheTTL = option;
+ break;
+ case Opt_inodettl:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->msInodeTTL = option;
+ break;
+ case Opt_cachemode: {
+ cachemode = match_strdup(&args[0]);
+ if (!cachemode) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for cachemode!\n"));
+ return -ENOMEM;
+ }
+ if (!strcmp(cachemode, "default") || !strcmp(cachemode, "strict"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Strict;
+ else if (!strcmp(cachemode, "none"))
+ mount_info->enmCacheMode = kVbsfCacheMode_None;
+ else if (!strcmp(cachemode, "read"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Read;
+ else if (!strcmp(cachemode, "readwrite"))
+ mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite;
+ else
+ printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", cachemode);
+ kfree(cachemode);
+ break;
+ }
+ case Opt_tag:
+ tag = match_strdup(&args[0]);
+ if (!tag) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for automount tag!\n"));
+ return -ENOMEM;
+ }
+ strlcpy(mount_info->szTag, tag, sizeof(mount_info->szTag));
+ kfree(tag);
+ break;
+ default:
+ printk(KERN_ERR "unrecognised mount option \"%s\"", p);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+#endif /* 5.1.0 > version >= 2.6.0 */
+
+
+#if RTLNX_VER_MAX(2,6,0)
+/**
+ * Linux kernel versions older than 2.6.0 don't have the match_token() routines
+ * so we parse the string-based mount options manually here.
+ */
+static int vbsf_parse_mount_options(char *options,
+ struct vbsf_mount_info_new *mount_info)
+{
+ char *value;
+ char *option;
+
+ if (!options)
+ return -EINVAL;
+
+# if RTLNX_VER_MIN(2,3,9)
+ while ((option = strsep(&options, ",")) != NULL) {
+# else
+ for (option = strtok(options, ","); option; option = strtok(NULL, ",")) {
+# endif
+ if (!*option)
+ continue;
+
+ value = strchr(option, '=');
+ if (value)
+ *value++ = '\0';
+
+ if (!strcmp(option, "iocharset") || !strcmp(option, "nls")) {
+ if (!value || !*value)
+ return -EINVAL;
+ strncpy(mount_info->nls_name, value, sizeof(mount_info->nls_name));
+ mount_info->nls_name[sizeof(mount_info->nls_name)-1] = 0;
+ } else if (!strcmp(option, "uid")) {
+ mount_info->uid = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "gid")) {
+ mount_info->gid = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "ttl")) {
+ mount_info->ttl = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dmode")) {
+ mount_info->dmode = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "fmode")) {
+ mount_info->fmode = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dmask")) {
+ mount_info->dmask = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "fmask")) {
+ mount_info->fmask = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "umask")) {
+ mount_info->dmask = mount_info->fmask = simple_strtoul(value,
+ &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "maxiopages")) {
+ mount_info->cMaxIoPages = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dirbuf")) {
+ mount_info->cbDirBuf = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dcachettl")) {
+ mount_info->msDirCacheTTL = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "inodettl")) {
+ mount_info->msInodeTTL = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "cache")) {
+ if (!value || !*value)
+ return -EINVAL;
+ if (!strcmp(value, "default") || !strcmp(value, "strict"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Strict;
+ else if (!strcmp(value, "none"))
+ mount_info->enmCacheMode = kVbsfCacheMode_None;
+ else if (!strcmp(value, "read"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Read;
+ else if (!strcmp(value, "readwrite"))
+ mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite;
+ else
+ printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", value);
+ } else if (!strcmp(option, "tag")) {
+ if (!value || !*value)
+ return -EINVAL;
+ strncpy(mount_info->szTag, value, sizeof(mount_info->szTag));
+ mount_info->szTag[sizeof(mount_info->szTag)-1] = 0;
+ } else if (!strcmp(option, "sf_name")) {
+ if (!value || !*value)
+ return -EINVAL;
+ strncpy(mount_info->name, value, sizeof(mount_info->name));
+ mount_info->name[sizeof(mount_info->name)-1] = 0;
+ } else {
+ printk(KERN_ERR "unrecognised mount option \"%s\"", option);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+
+/**
+ * This is called by vbsf_read_super_24(), vbsf_read_super_26(), and
+ * vbsf_get_tree() when vfs mounts the fs and wants to read the super_block.
+ *
+ * Calls vbsf_super_info_alloc_and_map_it() to map the folder and allocate super
+ * information structure.
+ *
+ * Initializes @a sb, initializes root inode and dentry.
+ *
+ * Should respect @a flags.
+ */
+#if RTLNX_VER_MIN(5,1,0)
+static int vbsf_read_super_aux(struct super_block *sb, struct fs_context *fc)
+#else
+static int vbsf_read_super_aux(struct super_block *sb, void *data, int flags)
+#endif
+{
+ int rc;
+ struct vbsf_super_info *pSuperInfo;
+
+ TRACE();
+#if RTLNX_VER_MAX(5,1,0)
+ if (!data) {
+ SFLOGRELBOTH(("vboxsf: No mount data. Is mount.vboxsf installed (typically in /sbin)?\n"));
+ return -EINVAL;
+ }
+
+ if (flags & MS_REMOUNT) {
+ SFLOGRELBOTH(("vboxsf: Remounting is not supported!\n"));
+ return -ENOSYS;
+ }
+#endif
+
+ /*
+ * Create our super info structure and map the shared folder.
+ */
+#if RTLNX_VER_MIN(5,1,0)
+ struct vbsf_mount_info_new *info = fc->fs_private;
+ rc = vbsf_super_info_alloc_and_map_it(info, &pSuperInfo);
+#else
+ rc = vbsf_super_info_alloc_and_map_it((struct vbsf_mount_info_new *)data, &pSuperInfo);
+#endif
+ if (rc == 0) {
+ /*
+ * Initialize the super block structure (must be done before
+ * root inode creation).
+ */
+ sb->s_magic = 0xface;
+ sb->s_blocksize = 1024;
+#if RTLNX_VER_MIN(2,4,3)
+ /* Required for seek/sendfile (see 'loff_t max' in fs/read_write.c / do_sendfile()). */
+# if defined MAX_LFS_FILESIZE
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+# elif BITS_PER_LONG == 32
+ sb->s_maxbytes = (loff_t)ULONG_MAX << PAGE_SHIFT;
+# else
+ sb->s_maxbytes = INT64_MAX;
+# endif
+#endif
+#if RTLNX_VER_MIN(2,6,11)
+ sb->s_time_gran = 1; /* This might be a little optimistic for windows hosts, where it should be 100. */
+#endif
+ sb->s_op = &g_vbsf_super_ops;
+#if RTLNX_VER_MIN(2,6,38)
+ sb->s_d_op = &vbsf_dentry_ops;
+#endif
+
+ /*
+ * Initialize the backing device. This is important for memory mapped
+ * files among other things.
+ */
+ rc = vbsf_init_backing_dev(sb, pSuperInfo);
+ if (rc == 0) {
+ /*
+ * Create the root inode and we're done.
+ */
+ rc = vbsf_create_root_inode(sb, pSuperInfo);
+ if (rc == 0) {
+ VBSF_SET_SUPER_INFO(sb, pSuperInfo);
+ SFLOGFLOW(("vbsf_read_super_aux: returns successfully\n"));
+ return 0;
+ }
+ vbsf_done_backing_dev(sb, pSuperInfo);
+ } else
+ SFLOGRELBOTH(("vboxsf: backing device information initialization failed: %d\n", rc));
+ vbsf_super_info_free(pSuperInfo);
+ }
+ return rc;
+}
+
+
+/**
+ * This is called when vfs is about to destroy the @a inode.
+ *
+ * We must free the inode info structure here.
+ */
+#if RTLNX_VER_MIN(2,6,36)
+static void vbsf_evict_inode(struct inode *inode)
+#else
+static void vbsf_clear_inode(struct inode *inode)
+#endif
+{
+ struct vbsf_inode_info *sf_i;
+
+ TRACE();
+
+ /*
+ * Flush stuff.
+ */
+#if RTLNX_VER_MIN(2,6,36)
+ truncate_inode_pages(&inode->i_data, 0);
+# if RTLNX_VER_MIN(3,5,0)
+ clear_inode(inode);
+# else
+ end_writeback(inode);
+# endif
+#endif
+ /*
+ * Clean up our inode info.
+ */
+ sf_i = VBSF_GET_INODE_INFO(inode);
+ if (sf_i) {
+ VBSF_SET_INODE_INFO(inode, NULL);
+
+ Assert(sf_i->u32Magic == SF_INODE_INFO_MAGIC);
+ BUG_ON(!sf_i->path);
+ kfree(sf_i->path);
+ vbsf_handle_drop_chain(sf_i);
+# ifdef VBOX_STRICT
+ sf_i->u32Magic = SF_INODE_INFO_MAGIC_DEAD;
+# endif
+ kfree(sf_i);
+ }
+}
+
+
+/* this is called by vfs when it wants to populate [inode] with data.
+ the only thing that is known about inode at this point is its index
+ hence we can't do anything here, and let lookup/whatever with the
+ job to properly fill then [inode] */
+#if RTLNX_VER_MAX(2,6,25)
+static void vbsf_read_inode(struct inode *inode)
+{
+}
+#endif
+
+
+/* vfs is done with [sb] (umount called) call [vbsf_super_info_free] to unmap
+ the folder and free [pSuperInfo] */
+static void vbsf_put_super(struct super_block *sb)
+{
+ struct vbsf_super_info *pSuperInfo;
+
+ pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ BUG_ON(!pSuperInfo);
+ vbsf_done_backing_dev(sb, pSuperInfo);
+ vbsf_super_info_free(pSuperInfo);
+}
+
+
+/**
+ * Get file system statistics.
+ */
+#if RTLNX_VER_MIN(2,6,18)
+static int vbsf_statfs(struct dentry *dentry, struct kstatfs *stat)
+#elif RTLNX_VER_MIN(2,5,73)
+static int vbsf_statfs(struct super_block *sb, struct kstatfs *stat)
+#else
+static int vbsf_statfs(struct super_block *sb, struct statfs *stat)
+#endif
+{
+#if RTLNX_VER_MIN(2,6,18)
+ struct super_block *sb = dentry->d_inode->i_sb;
+#endif
+ int rc;
+ VBOXSFVOLINFOREQ *pReq = (VBOXSFVOLINFOREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ SHFLVOLINFO *pVolInfo = &pReq->VolInfo;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ rc = VbglR0SfHostReqQueryVolInfo(pSuperInfo->map.root, pReq, SHFL_HANDLE_ROOT);
+ if (RT_SUCCESS(rc)) {
+ stat->f_type = UINT32_C(0x786f4256); /* 'VBox' little endian */
+ stat->f_bsize = pVolInfo->ulBytesPerAllocationUnit;
+#if RTLNX_VER_MIN(2,5,73)
+ stat->f_frsize = pVolInfo->ulBytesPerAllocationUnit;
+#endif
+ stat->f_blocks = pVolInfo->ullTotalAllocationBytes
+ / pVolInfo->ulBytesPerAllocationUnit;
+ stat->f_bfree = pVolInfo->ullAvailableAllocationBytes
+ / pVolInfo->ulBytesPerAllocationUnit;
+ stat->f_bavail = pVolInfo->ullAvailableAllocationBytes
+ / pVolInfo->ulBytesPerAllocationUnit;
+ stat->f_files = 1000;
+ stat->f_ffree = 1000000; /* don't return 0 here since the guest may think
+ * that it is not possible to create any more files */
+ stat->f_fsid.val[0] = 0;
+ stat->f_fsid.val[1] = 0;
+ stat->f_namelen = 255;
+#if RTLNX_VER_MIN(2,6,36)
+ stat->f_flags = 0; /* not valid */
+#endif
+ RT_ZERO(stat->f_spare);
+ rc = 0;
+ } else
+ rc = -RTErrConvertToErrno(rc);
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ return rc;
+}
+
+#if RTLNX_VER_MIN(5,1,0)
+static int vbsf_remount_fs(struct super_block *sb,
+ struct vbsf_mount_info_new *info)
+#else
+static int vbsf_remount_fs(struct super_block *sb, int *flags, char *data)
+#endif
+{
+#if RTLNX_VER_MIN(2,4,23)
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ struct vbsf_inode_info *sf_i;
+ struct inode *iroot;
+ SHFLFSOBJINFO fsinfo;
+ int err;
+ Assert(pSuperInfo);
+
+# if RTLNX_VER_MIN(5,1,0)
+ vbsf_super_info_copy_remount_options(pSuperInfo, info);
+# else
+ if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ vbsf_super_info_copy_remount_options(pSuperInfo, (struct vbsf_mount_info_new *)data);
+ } else {
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+ vbsf_init_mount_info(&mount_opts, NULL);
+ err = vbsf_parse_mount_options(data, &mount_opts);
+ if (err)
+ return err;
+ vbsf_super_info_copy_remount_options(pSuperInfo, &mount_opts);
+ }
+# endif
+
+ /* '.' and '..' entries are st_ino == 0 so root is #1 */
+ iroot = ilookup(sb, 1);
+ if (!iroot)
+ return -ENOSYS;
+
+ sf_i = VBSF_GET_INODE_INFO(iroot);
+ err = vbsf_stat(__func__, pSuperInfo, sf_i->path, &fsinfo, 0);
+ BUG_ON(err != 0);
+ vbsf_init_inode(iroot, sf_i, &fsinfo, pSuperInfo);
+ iput(iroot);
+ return 0;
+#else /* < 2.4.23 */
+ return -ENOSYS;
+#endif /* < 2.4.23 */
+}
+
+
+/**
+ * Show mount options.
+ *
+ * This is needed by the VBoxService automounter in order for it to pick up
+ * the the 'szTag' option value it sets on its mount.
+ */
+#if RTLNX_VER_MAX(3,3,0)
+static int vbsf_show_options(struct seq_file *m, struct vfsmount *mnt)
+#else
+static int vbsf_show_options(struct seq_file *m, struct dentry *root)
+#endif
+{
+#if RTLNX_VER_MAX(3,3,0)
+ struct super_block *sb = mnt->mnt_sb;
+#else
+ struct super_block *sb = root->d_sb;
+#endif
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ if (pSuperInfo) {
+ /* Performance related options: */
+ if (pSuperInfo->msTTL != -1)
+ seq_printf(m, ",ttl=%d", pSuperInfo->msTTL);
+ if (pSuperInfo->msDirCacheTTL >= 0)
+ seq_printf(m, ",dcachettl=%d", pSuperInfo->msDirCacheTTL);
+ if (pSuperInfo->msInodeTTL >= 0)
+ seq_printf(m, ",inodettl=%d", pSuperInfo->msInodeTTL);
+ if (pSuperInfo->cMaxIoPages != VBSF_DEFAULT_MAX_IO_PAGES)
+ seq_printf(m, ",maxiopages=%u", pSuperInfo->cMaxIoPages);
+ if (pSuperInfo->cbDirBuf != VBSF_DEFAULT_DIR_BUF_SIZE)
+ seq_printf(m, ",dirbuf=%u", pSuperInfo->cbDirBuf);
+ switch (pSuperInfo->enmCacheMode) {
+ default: AssertFailed(); RT_FALL_THRU();
+ case kVbsfCacheMode_Strict:
+ break;
+ case kVbsfCacheMode_None: seq_puts(m, ",cache=none"); break;
+ case kVbsfCacheMode_Read: seq_puts(m, ",cache=read"); break;
+ case kVbsfCacheMode_ReadWrite: seq_puts(m, ",cache=readwrite"); break;
+ }
+
+ /* Attributes and NLS: */
+ seq_printf(m, ",iocharset=%s", pSuperInfo->nls ? pSuperInfo->nls->charset : "utf8");
+ seq_printf(m, ",uid=%u,gid=%u", pSuperInfo->uid, pSuperInfo->gid);
+ if (pSuperInfo->dmode != ~0)
+ seq_printf(m, ",dmode=0%o", pSuperInfo->dmode);
+ if (pSuperInfo->fmode != ~0)
+ seq_printf(m, ",fmode=0%o", pSuperInfo->fmode);
+ if (pSuperInfo->dmask != 0)
+ seq_printf(m, ",dmask=0%o", pSuperInfo->dmask);
+ if (pSuperInfo->fmask != 0)
+ seq_printf(m, ",fmask=0%o", pSuperInfo->fmask);
+
+ /* Misc: */
+ if (pSuperInfo->szTag[0] != '\0') {
+ seq_puts(m, ",tag=");
+ seq_escape(m, pSuperInfo->szTag, " \t\n\\");
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Super block operations.
+ */
+static struct super_operations g_vbsf_super_ops = {
+#if RTLNX_VER_MAX(2,6,36)
+ .clear_inode = vbsf_clear_inode,
+#else
+ .evict_inode = vbsf_evict_inode,
+#endif
+#if RTLNX_VER_MAX(2,6,25)
+ .read_inode = vbsf_read_inode,
+#endif
+ .put_super = vbsf_put_super,
+ .statfs = vbsf_statfs,
+#if RTLNX_VER_MAX(5,1,0)
+ .remount_fs = vbsf_remount_fs,
+#endif
+ .show_options = vbsf_show_options
+};
+
+
+
+/*********************************************************************************************************************************
+* File system type related stuff. *
+*********************************************************************************************************************************/
+
+#if RTLNX_VER_RANGE(2,5,4, 5,1,0)
+
+static int vbsf_read_super_26(struct super_block *sb, void *data, int flags)
+{
+ int err;
+
+ TRACE();
+ err = vbsf_read_super_aux(sb, data, flags);
+ if (err)
+ printk(KERN_DEBUG "vbsf_read_super_aux err=%d\n", err);
+
+ return err;
+}
+
+# if RTLNX_VER_MIN(2,6,39)
+static struct dentry *sf_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
+{
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, dev_name);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return ERR_PTR(rc);
+ return mount_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26);
+ } else {
+ return mount_nodev(fs_type, flags, data, vbsf_read_super_26);
+ }
+}
+# elif RTLNX_VER_MIN(2,6,18)
+static int vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt)
+{
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, dev_name);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return rc;
+ return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26,
+ mnt);
+ } else {
+ return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26, mnt);
+ }
+}
+# else /* 2.6.18 > version >= 2.5.4 */
+static struct super_block *vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
+{
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, dev_name);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return ERR_PTR(rc);
+ return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26);
+ } else {
+ return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26);
+ }
+}
+# endif
+#endif /* 5.1.0 > version >= 2.5.4 */
+
+#if RTLNX_VER_MAX(2,5,4) /* < 2.5.4 */
+
+static struct super_block *vbsf_read_super_24(struct super_block *sb, void *data, int flags)
+{
+ int err;
+
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, NULL);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return ERR_PTR(rc);
+ err = vbsf_read_super_aux(sb, &mount_opts, flags);
+ } else {
+ err = vbsf_read_super_aux(sb, data, flags);
+ }
+ if (err) {
+ printk(KERN_DEBUG "vbsf_read_super_aux err=%d\n", err);
+ return NULL;
+ }
+
+ return sb;
+}
+
+static DECLARE_FSTYPE(g_vboxsf_fs_type, "vboxsf", vbsf_read_super_24, 0);
+
+#endif /* < 2.5.4 */
+
+#if RTLNX_VER_MIN(5,1,0)
+
+/**
+ * The following section of code uses the Linux filesystem mount API (also
+ * known as the "filesystem context API") to parse string-based mount options.
+ * The API is described here:
+ * https://www.kernel.org/doc/Documentation/filesystems/mount_api.txt
+ */
+enum vbsf_cache_modes {
+ VBSF_CACHE_DEFAULT,
+ VBSF_CACHE_NONE,
+ VBSF_CACHE_STRICT,
+ VBSF_CACHE_READ,
+ VBSF_CACHE_RW
+};
+
+static const struct constant_table vbsf_param_cache_mode[] = {
+ { "default", VBSF_CACHE_DEFAULT },
+ { "none", VBSF_CACHE_NONE },
+ { "strict", VBSF_CACHE_STRICT },
+ { "read", VBSF_CACHE_READ },
+ { "readwrite", VBSF_CACHE_RW },
+ {}
+};
+
+enum {
+ Opt_iocharset, /* nls_name[] */
+ Opt_nls, /* alias for iocharset */
+ Opt_uid,
+ Opt_gid,
+ Opt_ttl,
+ Opt_dmode,
+ Opt_fmode,
+ Opt_dmask,
+ Opt_fmask,
+ Opt_umask,
+ Opt_maxiopages,
+ Opt_dirbuf,
+ Opt_dcachettl,
+ Opt_inodettl,
+ Opt_cachemode, /* enum vbsf_cache_mode */
+ Opt_tag
+};
+
+# if RTLNX_VER_MAX(5,6,0)
+static const struct fs_parameter_spec vbsf_fs_specs[] = {
+# else
+static const struct fs_parameter_spec vbsf_fs_parameters[] = {
+# endif
+ fsparam_string("iocharset", Opt_iocharset),
+ fsparam_string("nls", Opt_nls),
+ fsparam_u32 ("uid", Opt_uid),
+ fsparam_u32 ("gid", Opt_gid),
+ fsparam_u32 ("ttl", Opt_ttl),
+ fsparam_u32oct("dmode", Opt_dmode),
+ fsparam_u32oct("fmode", Opt_fmode),
+ fsparam_u32oct("dmask", Opt_dmask),
+ fsparam_u32oct("fmask", Opt_fmask),
+ fsparam_u32oct("umask", Opt_umask),
+ fsparam_u32 ("maxiopages", Opt_maxiopages),
+ fsparam_u32 ("dirbuf", Opt_dirbuf),
+ fsparam_u32 ("dcachettl", Opt_dcachettl),
+ fsparam_u32 ("inodettl", Opt_inodettl),
+# if RTLNX_VER_MAX(5,6,0)
+ fsparam_enum ("cache", Opt_cachemode),
+# else
+ fsparam_enum ("cache", Opt_cachemode, vbsf_param_cache_mode),
+# endif
+ fsparam_string("tag", Opt_tag),
+ {}
+};
+
+# if RTLNX_VER_MAX(5,6,0)
+static const struct fs_parameter_enum vbsf_fs_enums[] = {
+ { Opt_cachemode, "default", VBSF_CACHE_DEFAULT },
+ { Opt_cachemode, "none", VBSF_CACHE_NONE },
+ { Opt_cachemode, "strict", VBSF_CACHE_STRICT },
+ { Opt_cachemode, "read", VBSF_CACHE_READ },
+ { Opt_cachemode, "readwrite", VBSF_CACHE_RW },
+ {}
+};
+
+static const struct fs_parameter_description vbsf_fs_parameters = {
+ .name = "vboxsf",
+ .specs = vbsf_fs_specs,
+ .enums = vbsf_fs_enums
+};
+# endif
+
+/**
+ * Parse the (string-based) mount options passed in as -o foo,bar=123,etc.
+ */
+static int vbsf_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ struct fs_parse_result result;
+ struct vbsf_mount_info_new *info = fc->fs_private;
+ int opt;
+
+# if RTLNX_VER_MAX(5,6,0)
+ opt = fs_parse(fc, &vbsf_fs_parameters, param, &result);
+# else
+ opt = fs_parse(fc, vbsf_fs_parameters, param, &result);
+# endif
+ if (opt < 0)
+ return opt;
+
+ switch (opt) {
+ case Opt_iocharset:
+ case Opt_nls:
+ strlcpy(info->nls_name, param->string, sizeof(info->nls_name));
+ break;
+ case Opt_uid:
+ info->uid = result.uint_32;
+ break;
+ case Opt_gid:
+ info->gid = result.uint_32;
+ break;
+ case Opt_ttl:
+ info->ttl = result.uint_32;
+ break;
+ case Opt_dmode:
+ if (result.uint_32 & ~0777)
+ return invalf(fc, "Invalid dmode specified: '%o'", result.uint_32);
+ info->dmode = result.uint_32;
+ break;
+ case Opt_fmode:
+ if (result.uint_32 & ~0777)
+ return invalf(fc, "Invalid fmode specified: '%o'", result.uint_32);
+ info->fmode = result.uint_32;
+ break;
+ case Opt_dmask:
+ if (result.uint_32 & ~07777)
+ return invalf(fc, "Invalid dmask specified: '%o'", result.uint_32);
+ info->dmask = result.uint_32;
+ break;
+ case Opt_fmask:
+ if (result.uint_32 & ~07777)
+ return invalf(fc, "Invalid fmask specified: '%o'", result.uint_32);
+ info->fmask = result.uint_32;
+ break;
+ case Opt_umask:
+ if (result.uint_32 & ~07777)
+ return invalf(fc, "Invalid umask specified: '%o'", result.uint_32);
+ info->dmask = info->fmask = result.uint_32;
+ break;
+ case Opt_maxiopages:
+ info->cMaxIoPages = result.uint_32;
+ break;
+ case Opt_dirbuf:
+ info->cbDirBuf = result.uint_32;
+ break;
+ case Opt_dcachettl:
+ info->msDirCacheTTL = result.uint_32;
+ break;
+ case Opt_inodettl:
+ info->msInodeTTL = result.uint_32;
+ break;
+ case Opt_cachemode:
+ if (result.uint_32 == VBSF_CACHE_DEFAULT || result.uint_32 == VBSF_CACHE_STRICT)
+ info->enmCacheMode = kVbsfCacheMode_Strict;
+ else if (result.uint_32 == VBSF_CACHE_NONE)
+ info->enmCacheMode = kVbsfCacheMode_None;
+ else if (result.uint_32 == VBSF_CACHE_READ)
+ info->enmCacheMode = kVbsfCacheMode_Read;
+ else if (result.uint_32 == VBSF_CACHE_RW)
+ info->enmCacheMode = kVbsfCacheMode_ReadWrite;
+ else
+ printk(KERN_WARNING "vboxsf: cache mode (%u) is out of range, using default instead.\n", result.uint_32);
+ break;
+ case Opt_tag:
+ strlcpy(info->szTag, param->string, sizeof(info->szTag));
+ break;
+ default:
+ return invalf(fc, "Invalid mount option: '%s'", param->key);
+ }
+
+ return 0;
+}
+
+/**
+ * Parse the mount options provided whether by the mount.vboxsf utility
+ * which supplies the mount information as a page of data or else as a
+ * string in the following format: key[=val][,key[=val]]*.
+ */
+static int vbsf_parse_monolithic(struct fs_context *fc, void *data)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ if (data) {
+ if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ memcpy(info, data, sizeof(struct vbsf_mount_info_new));
+ } else {
+ /* this will call vbsf_parse_param() */
+ return generic_parse_monolithic(fc, data);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Clean up the filesystem-specific part of the filesystem context.
+ */
+static void vbsf_free_ctx(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ if (info) {
+ kfree(info);
+ fc->fs_private = NULL;
+ }
+}
+
+/**
+ * Create the mountable root and superblock which can then be used later for
+ * mounting the shared folder. The superblock is populated by
+ * vbsf_read_super_aux() which also sets up the shared folder mapping and the
+ * related paperwork in preparation for mounting the shared folder.
+ */
+static int vbsf_get_tree(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ if (!fc->source) {
+ SFLOGRELBOTH(("vboxsf: No shared folder specified\n"));
+ return invalf(fc, "vboxsf: No shared folder specified");
+ }
+
+ /* fc->source (the shared folder name) is set after vbsf_init_fs_ctx() */
+ strlcpy(info->name, fc->source, sizeof(info->name));
+
+# if RTLNX_VER_MAX(5,3,0)
+ return vfs_get_super(fc, vfs_get_independent_super, vbsf_read_super_aux);
+# else
+ return get_tree_nodev(fc, vbsf_read_super_aux);
+# endif
+}
+
+/**
+ * Reconfigures the superblock based on the mount information stored in the
+ * filesystem context. Called via '-o remount' (aka mount(2) with MS_REMOUNT)
+ * and is the equivalent of .fs_remount.
+ */
+static int vbsf_reconfigure(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+ struct super_block *sb = fc->root->d_sb;
+
+ return vbsf_remount_fs(sb, info);
+}
+
+static const struct fs_context_operations vbsf_context_ops = {
+ .parse_param = vbsf_parse_param,
+ .parse_monolithic = vbsf_parse_monolithic,
+ .free = vbsf_free_ctx,
+ .get_tree = vbsf_get_tree,
+ .reconfigure = vbsf_reconfigure
+};
+
+/**
+ * Set up the filesystem mount context.
+ */
+static int vbsf_init_fs_context(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for mount options\n"));
+ return -ENOMEM;
+ }
+
+ /* set default values for the mount information structure */
+ info->ttl = info->msDirCacheTTL = info->msInodeTTL = -1;
+ info->dmode = info->fmode = ~0U;
+ info->enmCacheMode = kVbsfCacheMode_Strict;
+ info->length = sizeof(struct vbsf_mount_info_new);
+
+ fc->fs_private = info;
+ fc->ops = &vbsf_context_ops;
+
+ return 0;
+}
+#endif /* >= 5.1.0 */
+
+
+#if RTLNX_VER_MIN(2,5,4)
+/**
+ * File system registration structure.
+ */
+static struct file_system_type g_vboxsf_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "vboxsf",
+# if RTLNX_VER_MIN(5,1,0)
+ .init_fs_context = vbsf_init_fs_context,
+# if RTLNX_VER_MAX(5,6,0)
+ .parameters = &vbsf_fs_parameters,
+# else
+ .parameters = vbsf_fs_parameters,
+# endif
+# elif RTLNX_VER_MIN(2,6,39)
+ .mount = sf_mount,
+# else
+ .get_sb = vbsf_get_sb,
+# endif
+ .kill_sb = kill_anon_super
+};
+#endif /* >= 2.5.4 */
+
+
+/*********************************************************************************************************************************
+* Module stuff *
+*********************************************************************************************************************************/
+
+/**
+ * Called on module initialization.
+ */
+static int __init init(void)
+{
+ int rc;
+ SFLOGFLOW(("vboxsf: init\n"));
+
+ /*
+ * Must be paranoid about the vbsf_mount_info_new size.
+ */
+ AssertCompile(sizeof(struct vbsf_mount_info_new) <= PAGE_SIZE);
+ if (sizeof(struct vbsf_mount_info_new) > PAGE_SIZE) {
+ printk(KERN_ERR
+ "vboxsf: Mount information structure is too large %lu\n"
+ "vboxsf: Must be less than or equal to %lu\n",
+ (unsigned long)sizeof(struct vbsf_mount_info_new),
+ (unsigned long)PAGE_SIZE);
+ return -EINVAL;
+ }
+
+ /*
+ * Initialize stuff.
+ */
+ spin_lock_init(&g_SfHandleLock);
+ rc = VbglR0SfInit();
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Try connect to the shared folder HGCM service.
+ * It is possible it is not there.
+ */
+ rc = VbglR0SfConnect(&g_SfClient);
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Query host HGCM features and afterwards (must be last) shared folder features.
+ */
+ rc = VbglR0QueryHostFeatures(&g_fHostFeatures);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("vboxsf: VbglR0QueryHostFeatures failed: rc=%Rrc (ignored)\n", rc));
+ g_fHostFeatures = 0;
+ }
+ VbglR0SfHostReqQueryFeaturesSimple(&g_fSfFeatures, &g_uSfLastFunction);
+ LogRel(("vboxsf: g_fHostFeatures=%#x g_fSfFeatures=%#RX64 g_uSfLastFunction=%u\n",
+ g_fHostFeatures, g_fSfFeatures, g_uSfLastFunction));
+
+ /*
+ * Tell the shared folder service about our expectations:
+ * - UTF-8 strings (rather than UTF-16)
+ * - Wheter to return or follow (default) symbolic links.
+ */
+ rc = VbglR0SfHostReqSetUtf8Simple();
+ if (RT_SUCCESS(rc)) {
+ if (!g_fFollowSymlinks) {
+ rc = VbglR0SfHostReqSetSymlinksSimple();
+ if (RT_FAILURE(rc))
+ printk(KERN_WARNING "vboxsf: Host unable to enable showing symlinks, rc=%d\n", rc);
+ }
+ /*
+ * Now that we're ready for action, try register the
+ * file system with the kernel.
+ */
+ rc = register_filesystem(&g_vboxsf_fs_type);
+ if (rc == 0) {
+ printk(KERN_INFO "vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n");
+#ifdef VERMAGIC_STRING
+ LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n",
+ VERMAGIC_STRING, LINUX_VERSION_CODE));
+#elif defined(UTS_RELEASE)
+ LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n",
+ UTS_RELEASE, LINUX_VERSION_CODE));
+#else
+ LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (LINUX_VERSION_CODE=%#x)\n", LINUX_VERSION_CODE));
+#endif
+ return 0;
+ }
+
+ /*
+ * Failed. Bail out.
+ */
+ LogRel(("vboxsf: register_filesystem failed: rc=%d\n", rc));
+ } else {
+ LogRel(("vboxsf: VbglR0SfSetUtf8 failed, rc=%Rrc\n", rc));
+ rc = -EPROTO;
+ }
+ VbglR0SfDisconnect(&g_SfClient);
+ } else {
+ LogRel(("vboxsf: VbglR0SfConnect failed, rc=%Rrc\n", rc));
+ rc = rc == VERR_HGCM_SERVICE_NOT_FOUND ? -EHOSTDOWN : -ECONNREFUSED;
+ }
+ VbglR0SfTerm();
+ } else {
+ LogRel(("vboxsf: VbglR0SfInit failed, rc=%Rrc\n", rc));
+ rc = -EPROTO;
+ }
+ return rc;
+}
+
+
+/**
+ * Called on module finalization.
+ */
+static void __exit fini(void)
+{
+ SFLOGFLOW(("vboxsf: fini\n"));
+
+ unregister_filesystem(&g_vboxsf_fs_type);
+ VbglR0SfDisconnect(&g_SfClient);
+ VbglR0SfTerm();
+}
+
+
+/*
+ * Module parameters.
+ */
+#if RTLNX_VER_MIN(2,5,52)
+module_param_named(follow_symlinks, g_fFollowSymlinks, int, 0);
+MODULE_PARM_DESC(follow_symlinks,
+ "Let host resolve symlinks rather than showing them");
+#endif
+
+
+/*
+ * Module declaration related bits.
+ */
+module_init(init);
+module_exit(fini);
+
+MODULE_DESCRIPTION(VBOX_PRODUCT " VFS Module for Host File System Access");
+MODULE_AUTHOR(VBOX_VENDOR);
+MODULE_LICENSE("GPL and additional rights");
+#ifdef MODULE_ALIAS_FS
+MODULE_ALIAS_FS("vboxsf");
+#endif
+#ifdef MODULE_VERSION
+MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV));
+#endif
+
diff --git a/src/VBox/Additions/linux/sharedfolders/vfsmod.h b/src/VBox/Additions/linux/sharedfolders/vfsmod.h
new file mode 100644
index 00000000..f7d221da
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.h
@@ -0,0 +1,478 @@
+/* $Id: vfsmod.h $ */
+/** @file
+ * vboxsf - Linux Shared Folders VFS, internal header.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef GA_INCLUDED_SRC_linux_sharedfolders_vfsmod_h
+#define GA_INCLUDED_SRC_linux_sharedfolders_vfsmod_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#if 0 /* Enables strict checks. */
+# define RT_STRICT
+# define VBOX_STRICT
+#endif
+
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#include "the-linux-kernel.h"
+#include <iprt/list.h>
+#include <iprt/asm.h>
+#include <VBox/log.h>
+
+#if RTLNX_VER_MIN(2,6,0)
+# include <linux/backing-dev.h>
+#endif
+
+#include <VBox/VBoxGuestLibSharedFolders.h>
+#include <VBox/VBoxGuestLibSharedFoldersInline.h>
+#include <iprt/asm.h>
+#include "vbsfmount.h"
+
+
+/*
+ * Logging wrappers.
+ */
+#if 1
+# define TRACE() LogFunc(("tracepoint\n"))
+# define SFLOG(aArgs) Log(aArgs)
+# define SFLOGFLOW(aArgs) LogFlow(aArgs)
+# define SFLOG2(aArgs) Log2(aArgs)
+# define SFLOG3(aArgs) Log3(aArgs)
+# define SFLOGRELBOTH(aArgs) LogRel(aArgs)
+# ifdef LOG_ENABLED
+# define SFLOG_ENABLED 1
+# endif
+#else
+# define TRACE() RTLogBackdoorPrintf("%s: tracepoint\n", __FUNCTION__)
+# define SFLOG(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOGFLOW(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOG2(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOG3(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOG_ENABLED 1
+# define SFLOGRELBOTH(aArgs) do { RTLogBackdoorPrintf aArgs; printk aArgs; } while (0)
+#endif
+
+
+/*
+ * inode compatibility glue.
+ */
+#if RTLNX_VER_MAX(2,6,0)
+
+DECLINLINE(loff_t) i_size_read(struct inode *pInode)
+{
+ AssertCompile(sizeof(loff_t) == sizeof(uint64_t));
+ return ASMAtomicReadU64((uint64_t volatile *)&pInode->i_size);
+}
+
+DECLINLINE(void) i_size_write(struct inode *pInode, loff_t cbNew)
+{
+ AssertCompile(sizeof(pInode->i_size) == sizeof(uint64_t));
+ ASMAtomicWriteU64((uint64_t volatile *)&pInode->i_size, cbNew);
+}
+
+#endif /* < 2.6.0 */
+
+#if RTLNX_VER_MAX(3,2,0) && !RTLNX_RHEL_MIN(6, 10)
+DECLINLINE(void) set_nlink(struct inode *pInode, unsigned int cLinks)
+{
+ pInode->i_nlink = cLinks;
+}
+#endif
+
+
+/* global variables */
+extern VBGLSFCLIENT g_SfClient;
+extern spinlock_t g_SfHandleLock;
+extern uint32_t g_uSfLastFunction;
+extern uint64_t g_fSfFeatures;
+
+extern struct inode_operations vbsf_dir_iops;
+extern struct inode_operations vbsf_lnk_iops;
+extern struct inode_operations vbsf_reg_iops;
+extern struct file_operations vbsf_dir_fops;
+extern struct file_operations vbsf_reg_fops;
+extern struct dentry_operations vbsf_dentry_ops;
+extern struct address_space_operations vbsf_reg_aops;
+
+
+/**
+ * VBox specific per-mount (shared folder) information.
+ */
+struct vbsf_super_info {
+ VBGLSFMAP map;
+ struct nls_table *nls;
+ /** Set if the NLS table is UTF-8. */
+ bool fNlsIsUtf8;
+ int uid;
+ int gid;
+ int dmode;
+ int fmode;
+ int dmask;
+ int fmask;
+ /** Maximum number of pages to allow in an I/O buffer with the host.
+ * This applies to read and write operations. */
+ uint32_t cMaxIoPages;
+ /** The default directory buffer size. */
+ uint32_t cbDirBuf;
+ /** The time to live for directory entries in jiffies, zero if disabled. */
+ uint32_t cJiffiesDirCacheTTL;
+ /** The time to live for inode information in jiffies, zero if disabled. */
+ uint32_t cJiffiesInodeTTL;
+ /** The cache and coherency mode. */
+ enum vbsf_cache_mode enmCacheMode;
+ /** Mount tag for VBoxService automounter. @since 6.0 */
+ char szTag[32];
+#if RTLNX_VER_RANGE(2,6,0, 4,12,0)
+ /** The backing device info structure. */
+ struct backing_dev_info bdi;
+#endif
+ /** The mount option value for /proc/mounts. */
+ int32_t msTTL;
+ /** The time to live for directory entries in milliseconds, for /proc/mounts. */
+ int32_t msDirCacheTTL;
+ /** The time to live for inode information in milliseconds, for /proc/mounts. */
+ int32_t msInodeTTL;
+#if RTLNX_VER_RANGE(4,0,0, 4,2,0)
+ /** 4.0 and 4.1 are missing noop_backing_dev_info export, so take down the
+ * initial value so we can restore it in vbsf_done_backing_dev(). (paranoia) */
+ struct backing_dev_info *bdi_org;
+#endif
+};
+
+/* Following casts are here to prevent assignment of void * to
+ pointers of arbitrary type */
+#if RTLNX_VER_MAX(2,6,0)
+# define VBSF_GET_SUPER_INFO(sb) ((struct vbsf_super_info *)(sb)->u.generic_sbp)
+# define VBSF_SET_SUPER_INFO(sb, a_pSuperInfo) do { (sb)->u.generic_sbp = a_pSuperInfo; } while (0)
+#else
+# define VBSF_GET_SUPER_INFO(sb) ((struct vbsf_super_info *)(sb)->s_fs_info)
+# define VBSF_SET_SUPER_INFO(sb, a_pSuperInfo) do { (sb)->s_fs_info = a_pSuperInfo;} while (0)
+#endif
+
+
+/**
+ * For associating inodes with host handles.
+ *
+ * This is necessary for address_space_operations::vbsf_writepage and allows
+ * optimizing stat, lookups and other operations on open files and directories.
+ */
+struct vbsf_handle {
+ /** List entry (head vbsf_inode_info::HandleList). */
+ RTLISTNODE Entry;
+ /** Host file/whatever handle. */
+ SHFLHANDLE hHost;
+ /** VBSF_HANDLE_F_XXX */
+ uint32_t fFlags;
+ /** Reference counter.
+ * Close the handle and free the structure when it reaches zero. */
+ uint32_t volatile cRefs;
+#ifdef VBOX_STRICT
+ /** For strictness checks. */
+ struct vbsf_inode_info *pInodeInfo;
+#endif
+};
+
+/** @name VBSF_HANDLE_F_XXX - Handle summary flags (vbsf_handle::fFlags).
+ * @{ */
+#define VBSF_HANDLE_F_READ UINT32_C(0x00000001)
+#define VBSF_HANDLE_F_WRITE UINT32_C(0x00000002)
+#define VBSF_HANDLE_F_APPEND UINT32_C(0x00000004)
+#define VBSF_HANDLE_F_FILE UINT32_C(0x00000010)
+#define VBSF_HANDLE_F_DIR UINT32_C(0x00000020)
+#define VBSF_HANDLE_F_ON_LIST UINT32_C(0x00000080)
+#define VBSF_HANDLE_F_MAGIC_MASK UINT32_C(0xffffff00)
+#define VBSF_HANDLE_F_MAGIC UINT32_C(0x75030700) /**< Maurice Ravel (1875-03-07). */
+#define VBSF_HANDLE_F_MAGIC_DEAD UINT32_C(0x19371228)
+/** @} */
+
+
+/**
+ * VBox specific per-inode information.
+ */
+struct vbsf_inode_info {
+ /** Which file */
+ SHFLSTRING *path;
+ /** Some information was changed, update data on next revalidate */
+ bool force_restat;
+ /** The timestamp (jiffies) where the inode info was last updated. */
+ unsigned long ts_up_to_date;
+ /** The birth time. */
+ RTTIMESPEC BirthTime;
+
+ /** @name Host modification detection stats.
+ * @{ */
+ /** The raw modification time, for mapping invalidation purposes. */
+ RTTIMESPEC ModificationTime;
+ /** Copy of ModificationTime from the last time we wrote to the the file. */
+ RTTIMESPEC ModificationTimeAtOurLastWrite;
+ /** @} */
+
+ /** handle valid if a file was created with vbsf_create_worker until it will
+ * be opened with vbsf_reg_open()
+ * @todo r=bird: figure this one out... */
+ SHFLHANDLE handle;
+
+ /** List of open handles (struct vbsf_handle), protected by g_SfHandleLock. */
+ RTLISTANCHOR HandleList;
+#ifdef VBOX_STRICT
+ uint32_t u32Magic;
+# define SF_INODE_INFO_MAGIC UINT32_C(0x18620822) /**< Claude Debussy */
+# define SF_INODE_INFO_MAGIC_DEAD UINT32_C(0x19180325)
+#endif
+};
+
+#if RTLNX_VER_MIN(2,6,19) || defined(KERNEL_FC6)
+/* FC6 kernel 2.6.18, vanilla kernel 2.6.19+ */
+# define VBSF_GET_INODE_INFO(i) ((struct vbsf_inode_info *) (i)->i_private)
+# define VBSF_SET_INODE_INFO(i, sf_i) (i)->i_private = sf_i
+#else
+/* vanilla kernel up to 2.6.18 */
+# define VBSF_GET_INODE_INFO(i) ((struct vbsf_inode_info *) (i)->u.generic_ip)
+# define VBSF_SET_INODE_INFO(i, sf_i) (i)->u.generic_ip = sf_i
+#endif
+
+extern void vbsf_init_inode(struct inode *inode, struct vbsf_inode_info *sf_i, PSHFLFSOBJINFO info,
+ struct vbsf_super_info *pSuperInfo);
+extern void vbsf_update_inode(struct inode *pInode, struct vbsf_inode_info *pInodeInfo, PSHFLFSOBJINFO pObjInfo,
+ struct vbsf_super_info *pSuperInfo, bool fInodeLocked, unsigned fSetAttrs);
+extern int vbsf_inode_revalidate_worker(struct dentry *dentry, bool fForced, bool fInodeLocked);
+extern int vbsf_inode_revalidate_with_handle(struct dentry *dentry, SHFLHANDLE hHostFile, bool fForced, bool fInodeLocked);
+#if RTLNX_VER_MIN(2,5,18)
+# if RTLNX_VER_MIN(5,12,0)
+extern int vbsf_inode_getattr(struct user_namespace *ns, const struct path *path,
+ struct kstat *kstat, u32 request_mask, unsigned int query_flags);
+# elif RTLNX_VER_MIN(4,11,0)
+extern int vbsf_inode_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int query_flags);
+# else
+extern int vbsf_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *kstat);
+# endif
+#else /* < 2.5.44 */
+extern int vbsf_inode_revalidate(struct dentry *dentry);
+#endif /* < 2.5.44 */
+#if RTLNX_VER_MIN(5,12,0)
+extern int vbsf_inode_setattr(struct user_namespace *ns, struct dentry *dentry, struct iattr *iattr);
+#else
+extern int vbsf_inode_setattr(struct dentry *dentry, struct iattr *iattr);
+#endif
+
+
+extern void vbsf_handle_drop_chain(struct vbsf_inode_info *pInodeInfo);
+extern struct vbsf_handle *vbsf_handle_find(struct vbsf_inode_info *pInodeInfo, uint32_t fFlagsSet, uint32_t fFlagsClear);
+extern uint32_t vbsf_handle_release_slow(struct vbsf_handle *pHandle, struct vbsf_super_info *pSuperInfo,
+ const char *pszCaller);
+extern void vbsf_handle_append(struct vbsf_inode_info *pInodeInfo, struct vbsf_handle *pHandle);
+
+/**
+ * Releases a handle.
+ *
+ * @returns New reference count.
+ * @param pHandle The handle to release.
+ * @param pSuperInfo The info structure for the shared folder associated
+ * with the handle.
+ * @param pszCaller The caller name (for logging failures).
+ */
+DECLINLINE(uint32_t) vbsf_handle_release(struct vbsf_handle *pHandle, struct vbsf_super_info *pSuperInfo, const char *pszCaller)
+{
+ uint32_t cRefs;
+
+ Assert((pHandle->fFlags & VBSF_HANDLE_F_MAGIC_MASK) == VBSF_HANDLE_F_MAGIC);
+ Assert(pHandle->pInodeInfo);
+ Assert(pHandle->pInodeInfo && pHandle->pInodeInfo->u32Magic == SF_INODE_INFO_MAGIC);
+
+ cRefs = ASMAtomicDecU32(&pHandle->cRefs);
+ Assert(cRefs < _64M);
+ if (cRefs)
+ return cRefs;
+ return vbsf_handle_release_slow(pHandle, pSuperInfo, pszCaller);
+}
+
+
+/**
+ * VBox specific information for a regular file.
+ */
+struct vbsf_reg_info {
+ /** Handle tracking structure.
+ * @note Must be first! */
+ struct vbsf_handle Handle;
+};
+
+uint32_t vbsf_linux_oflags_to_vbox(unsigned fLnxOpen, uint32_t *pfHandle, const char *pszCaller);
+
+
+/**
+ * VBox specific information for an open directory.
+ */
+struct vbsf_dir_info {
+ /** Handle tracking structure.
+ * @note Must be first! */
+ struct vbsf_handle Handle;
+ /** Semaphore protecting everything below. */
+ struct semaphore Lock;
+ /** A magic number (VBSF_DIR_INFO_MAGIC). */
+ uint32_t u32Magic;
+ /** Size of the buffer for directory entries. */
+ uint32_t cbBuf;
+ /** Buffer for directory entries on the physical heap. */
+ PSHFLDIRINFO pBuf;
+ /** Number of valid bytes in the buffer. */
+ uint32_t cbValid;
+ /** Number of entries left in the buffer. */
+ uint32_t cEntriesLeft;
+ /** The position of the next entry. Incremented by one for each entry. */
+ loff_t offPos;
+ /** The next entry. */
+ PSHFLDIRINFO pEntry;
+ /** Set if there are no more files. */
+ bool fNoMoreFiles;
+};
+
+/** Magic number for vbsf_dir_info::u32Magic (Robert Anson Heinlein). */
+#define VBSF_DIR_INFO_MAGIC UINT32_C(0x19070707)
+/** Value of vbsf_dir_info::u32Magic when freed. */
+#define VBSF_DIR_INFO_MAGIC_DEAD UINT32_C(0x19880508)
+
+
+/**
+ * Sets the update-jiffies value for a dentry.
+ *
+ * This is used together with vbsf_super_info::cJiffiesDirCacheTTL to reduce
+ * re-validation of dentry structures while walking.
+ *
+ * This used to be living in d_time, but since 4.9.0 that seems to have become
+ * unfashionable and d_fsdata is now used to for this purpose. We do this all
+ * the way back, since d_time seems only to have been used by the file system
+ * specific code (at least going back to 2.4.0).
+ */
+DECLINLINE(void) vbsf_dentry_set_update_jiffies(struct dentry *pDirEntry, unsigned long uToSet)
+{
+ /*SFLOG3(("vbsf_dentry_set_update_jiffies: %p: %lx -> %#lx\n", pDirEntry, (unsigned long)pDirEntry->d_fsdata, uToSet));*/
+ pDirEntry->d_fsdata = (void *)uToSet;
+}
+
+/**
+ * Get the update-jiffies value for a dentry.
+ */
+DECLINLINE(unsigned long) vbsf_dentry_get_update_jiffies(struct dentry *pDirEntry)
+{
+ return (unsigned long)pDirEntry->d_fsdata;
+}
+
+/**
+ * Invalidates the update TTL for the given directory entry so that it is
+ * revalidate the next time it is used.
+ * @param pDirEntry The directory entry cache entry to invalidate.
+ */
+DECLINLINE(void) vbsf_dentry_invalidate_ttl(struct dentry *pDirEntry)
+{
+ vbsf_dentry_set_update_jiffies(pDirEntry, jiffies - INT32_MAX / 2);
+}
+
+/**
+ * Increase the time-to-live of @a pDirEntry and all ancestors.
+ * @param pDirEntry The directory entry cache entry which ancestors
+ * we should increase the TTL for.
+ */
+DECLINLINE(void) vbsf_dentry_chain_increase_ttl(struct dentry *pDirEntry)
+{
+#ifdef VBOX_STRICT
+ struct super_block * const pSuper = pDirEntry->d_sb;
+#endif
+ unsigned long const uToSet = jiffies;
+ do {
+ Assert(pDirEntry->d_sb == pSuper);
+ vbsf_dentry_set_update_jiffies(pDirEntry, uToSet);
+ pDirEntry = pDirEntry->d_parent;
+ } while (!IS_ROOT(pDirEntry));
+}
+
+/**
+ * Increase the time-to-live of all ancestors.
+ * @param pDirEntry The directory entry cache entry which ancestors
+ * we should increase the TTL for.
+ */
+DECLINLINE(void) vbsf_dentry_chain_increase_parent_ttl(struct dentry *pDirEntry)
+{
+ Assert(!pDirEntry->d_parent || pDirEntry->d_parent->d_sb == pDirEntry->d_sb);
+ pDirEntry = pDirEntry->d_parent;
+ if (pDirEntry)
+ vbsf_dentry_chain_increase_ttl(pDirEntry);
+}
+
+/** Macro for getting the dentry for a struct file. */
+#if RTLNX_VER_MIN(4,6,0)
+# define VBSF_GET_F_DENTRY(f) file_dentry(f)
+#elif RTLNX_VER_MIN(2,6,20)
+# define VBSF_GET_F_DENTRY(f) (f->f_path.dentry)
+#else
+# define VBSF_GET_F_DENTRY(f) (f->f_dentry)
+#endif
+
+/**
+ * Macro for checking if the 'data' argument passed in via mount(2) was supplied
+ * by the mount.vboxsf command line utility as a page of data containing the
+ * vbsf_mount_info_new structure.
+ */
+#define VBSF_IS_MOUNT_VBOXSF_DATA(data) \
+ (((struct vbsf_mount_info_new *)data)->nullchar == '\0' && \
+ ((struct vbsf_mount_info_new *)data)->signature[0] == VBSF_MOUNT_SIGNATURE_BYTE_0 && \
+ ((struct vbsf_mount_info_new *)data)->signature[1] == VBSF_MOUNT_SIGNATURE_BYTE_1 && \
+ ((struct vbsf_mount_info_new *)data)->signature[2] == VBSF_MOUNT_SIGNATURE_BYTE_2)
+
+extern int vbsf_stat(const char *caller, struct vbsf_super_info *pSuperInfo, SHFLSTRING * path, PSHFLFSOBJINFO result,
+ int ok_to_fail);
+extern int vbsf_path_from_dentry(struct vbsf_super_info *pSuperInfo, struct vbsf_inode_info *sf_i, struct dentry *dentry,
+ SHFLSTRING ** result, const char *caller);
+extern int vbsf_nlscpy(struct vbsf_super_info *pSuperInfo, char *name, size_t name_bound_len,
+ const unsigned char *utf8_name, size_t utf8_len);
+extern int vbsf_nls_to_shflstring(struct vbsf_super_info *pSuperInfo, const char *pszNls, PSHFLSTRING *ppString);
+
+
+/**
+ * Converts Linux access permissions to VBox ones (mode & 0777).
+ *
+ * @note Currently identical.
+ * @sa sf_access_permissions_to_linux
+ */
+DECLINLINE(uint32_t) sf_access_permissions_to_vbox(int fAttr)
+{
+ /* Access bits should be the same: */
+ AssertCompile(RTFS_UNIX_IRUSR == S_IRUSR);
+ AssertCompile(RTFS_UNIX_IWUSR == S_IWUSR);
+ AssertCompile(RTFS_UNIX_IXUSR == S_IXUSR);
+ AssertCompile(RTFS_UNIX_IRGRP == S_IRGRP);
+ AssertCompile(RTFS_UNIX_IWGRP == S_IWGRP);
+ AssertCompile(RTFS_UNIX_IXGRP == S_IXGRP);
+ AssertCompile(RTFS_UNIX_IROTH == S_IROTH);
+ AssertCompile(RTFS_UNIX_IWOTH == S_IWOTH);
+ AssertCompile(RTFS_UNIX_IXOTH == S_IXOTH);
+
+ return fAttr & RTFS_UNIX_ALL_ACCESS_PERMS;
+}
+
+#endif /* !GA_INCLUDED_SRC_linux_sharedfolders_vfsmod_h */
diff --git a/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c b/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c
new file mode 100644
index 00000000..0beadbfe
--- /dev/null
+++ b/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c
@@ -0,0 +1,103 @@
+/** @file
+ *
+ * VirtualBox Timesync using temporary Backdoor
+ */
+
+/*
+ * Copyright (C) 2006-2022 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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <unistd.h>
+#include <asm/io.h>
+#include <sys/time.h>
+#include <time.h>
+
+void usage()
+{
+ printf("TimesyncBackdoor [-interval <seconds>]"
+ " [-daemonize]"
+ "\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int secInterval = 10;
+ int fDaemonize = 0;
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp(argv[i], "-interval") == 0)
+ {
+ if (argc <= i)
+ {
+ usage();
+ return 1;
+ }
+ secInterval = atoi(argv[i + 1]);
+ i++;
+ }
+ else if (strcmp(argv[i], "-daemonize") == 0)
+ {
+ fDaemonize = 1;
+ }
+ else
+ {
+ usage();
+ return 1;
+ }
+ }
+
+ /* get port IO permission */
+ if (iopl(3))
+ {
+ printf("Error: could not set IOPL to 3!\n");
+ return 1;
+ }
+
+ printf("VirtualBox timesync tool. Sync interval: %d seconds.\n", secInterval);
+
+ if (fDaemonize)
+ daemon(1, 0);
+
+ do
+ {
+ unsigned long long time;
+ /* get the high 32bit, this _must_ be done first */
+ outl(0, 0x505);
+ time = (unsigned long long)inl(0x505) << 32;
+ /* get the low 32bit */
+ outl(1, 0x505);
+ time |= inl(0x505);
+
+ /* set the system time */
+ struct timeval tv;
+ tv.tv_sec = time / (unsigned long long)1000;
+ tv.tv_usec = (time % (unsigned long long)1000) * 1000;
+ settimeofday(&tv, NULL);
+
+ /* wait for the next run */
+ sleep(secInterval);
+
+ } while (1);
+
+ return 0;
+}