diff options
Diffstat (limited to '')
57 files changed, 26271 insertions, 0 deletions
diff --git a/src/VBox/Additions/linux/Makefile b/src/VBox/Additions/linux/Makefile new file mode 100644 index 00000000..b28fe98c --- /dev/null +++ b/src/VBox/Additions/linux/Makefile @@ -0,0 +1,136 @@ +# +# Makefile for the VirtualBox Linux Guest Drivers. +# + +# +# Copyright (C) 2009-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..014775ca --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..8e7c51aa --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..82e9e297 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..99f4dab3 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..04092484 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..4401ca44 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..3e4cbc60 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_drv.c @@ -0,0 +1,455 @@ +/* $Id: vbox_drv.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2023 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) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MIN(9,1) || RTLNX_SUSE_MAJ_PREREQ(15,5) +# 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) && !RTLNX_SUSE_MAJ_PREREQ(15,5) + .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 +#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99) + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, +#endif + .gem_prime_import = drm_gem_prime_import, + .gem_prime_import_sg_table = vbox_gem_prime_import_sg_table, +#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99) + .gem_prime_mmap = vbox_gem_prime_mmap, +#endif + +#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..8eb9f83e --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_drv.h @@ -0,0 +1,557 @@ +/* $Id: vbox_drv.h $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2023 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) && !RTLNX_SUSE_MAJ_PREREQ(15,5) +# 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 + +#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) +# include <drm/ttm/ttm_bo.h> +#else +# include <drm/ttm/ttm_bo_api.h> +# include <drm/ttm/ttm_bo_driver.h> +#endif +#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) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,2) || RTLNX_SUSE_MAJ_PREREQ(15,5) +# 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) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) +# 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); +#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99) +int vbox_gem_prime_mmap(struct drm_gem_object *obj, + struct vm_area_struct *area); +#endif + +/* 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) && !RTLNX_SUSE_MAJ_PREREQ(15,5) +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..37cf7ef1 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_fb.c @@ -0,0 +1,541 @@ +/* $Id: vbox_fb.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2023 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) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) +# 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, +#if RTLNX_VER_MIN(6,5,0) || RTLNX_RHEL_RANGE(9,4, 9,99) + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_fillrect = sys_fillrect, + .fb_copyarea = sys_copyarea, + .fb_imageblit = sys_imageblit, + .fb_mmap = NULL, +#else + .fb_fillrect = drm_fb_helper_sys_fillrect, + .fb_copyarea = drm_fb_helper_sys_copyarea, + .fb_imageblit = drm_fb_helper_sys_imageblit, +#endif + .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) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) + 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. + */ +#if RTLNX_VER_MIN(6,6,0) + info->flags = FBINFO_MISC_ALWAYS_SETPAR; +#else + info->flags = FBINFO_DEFAULT | FBINFO_MISC_ALWAYS_SETPAR; +#endif + info->fbops = &vboxfb_ops; + +#if RTLNX_VER_MAX(6,3,0) && !RTLNX_RHEL_RANGE(8,9, 8,99) && !RTLNX_RHEL_RANGE(9,3, 9,99) + /* + * 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); +#endif + +#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 + +#if RTLNX_VER_MIN(6,5,0) + info->screen_buffer = (char *)bo->kmap.virtual; + info->fix.smem_start = page_to_phys(vmalloc_to_page(bo->kmap.virtual)); +#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) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) || RTLNX_SUSE_MAJ_PREREQ(15,5) + info->fix.smem_len = info->screen_size; +# endif + info->fbdefio = &vbox_defio; +# if RTLNX_VER_MIN(5,19,0) + ret = fb_deferred_io_init(info); + if (ret) + { + DRM_ERROR("failed to initialize deferred io: %d\n", ret); + return ret; + } +# endif + 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) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) + 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_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) + drm_fb_helper_prepare(dev, &fbdev->helper, 32, &vbox_fb_helper_funcs); +#elif RTLNX_VER_MIN(3,17,0) || RTLNX_RHEL_MIN(7,2) + drm_fb_helper_prepare(dev, &fbdev->helper, &vbox_fb_helper_funcs); +#else + fbdev->helper.funcs = &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); + +#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) + ret = drm_fb_helper_initial_config(&fbdev->helper); +#else + ret = drm_fb_helper_initial_config(&fbdev->helper, 32); +#endif + 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); + +#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) + fbdev->fix.smem_start = pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->fbdev->helper.dev), 0) + gpu_addr; +#else + fbdev->fix.smem_start = fbdev->apertures->ranges[0].base + gpu_addr; +#endif + 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..de47f89d --- /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-2023 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..594388f8 --- /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-2023 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) || RTLNX_SUSE_MAJ_PREREQ(15,5) + 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) || RTLNX_SUSE_MAJ_PREREQ(15,5) + 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..ba6ee3c1 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_main.c @@ -0,0 +1,704 @@ +/* $Id: vbox_main.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2023 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> + +#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) +# include <drm/drm_modeset_helper.h> +#endif + +#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..bfe13ced --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_mode.c @@ -0,0 +1,949 @@ +/* $Id: vbox_mode.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2023 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(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) +# include <drm/drm_modeset_helper_vtables.h> +# include <drm/drm_modeset_helper.h> +#endif +#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) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,2) || RTLNX_SUSE_MAJ_PREREQ(15,5) +# 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; + + mutex_lock(&vbox->hw_mutex); + 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(6,4,0) + /* Make sure bo is in SYSTEM (main) memory, so we can access it directly. */ + ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_SYSTEM, NULL); + if (ret) + { + DRM_ERROR("cannot pin bo to main memory\n"); + goto out_bo_unpin; + } +#endif + +#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); +#if RTLNX_VER_MIN(6,4,0) +out_bo_unpin: + vbox_bo_unpin(bo); +#endif +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..87cf0a99 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_prime.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017-2023 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"); +} + +#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99) +int vbox_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *area) +{ + WARN_ONCE(1, "not implemented"); + return -ENOSYS; +} +#endif 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..d020aa3f --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_ttm.c @@ -0,0 +1,843 @@ +/* $Id: vbox_ttm.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2023 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(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,3) +# include <drm/ttm/ttm_tt.h> +#endif + +#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_MIN(5,19,0) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_RANGE(9,2, 9,99) || RTLNX_SUSE_MAJ_PREREQ(15,5) + if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined, 0)) { +#elif RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99) + if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined)) { +#elif RTLNX_VER_MIN(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, bo, page_flags)) { +#else + if (ttm_tt_init(tt, bdev, size, page_flags, dummy_read_page)) { +#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) +{ +# if RTLNX_VER_MIN(6,4,0) + if (!bo->resource) + { + if (new_mem->mem_type != TTM_PL_SYSTEM) + { + hop->mem_type = TTM_PL_SYSTEM; + hop->flags = TTM_PL_FLAG_TEMPORARY; + return -EMULTIHOP; + } + ttm_bo_move_null(bo, new_mem); + return 0; + } +# endif + 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(6,5,0) + .vmap = drm_gem_ttm_vmap, + .vunmap = drm_gem_ttm_vunmap, +# endif +# 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) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) || RTLNX_SUSE_MAJ_PREREQ(15,5) + 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..19713671 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..cd5b9eba --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..146cb46b --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..79fa03dd --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..a3c6329a --- /dev/null +++ b/src/VBox/Additions/linux/installer/install.sh.in @@ -0,0 +1,640 @@ +#!/bin/sh +# +# Oracle VM VirtualBox +# VirtualBox Makeself installation starter script +# for Linux Guest Additions + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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 + +# 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" +"$INSTALLATION_DIR"/init/vboxadd setup 2>&1 | tee -a "$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..5685bed4 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..d3ea8ebe --- /dev/null +++ b/src/VBox/Additions/linux/installer/vboxadd-service.sh @@ -0,0 +1,171 @@ +#!/bin/sh +# $Id: vboxadd-service.sh $ +## @file +# Linux Additions Guest Additions service daemon init script. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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" + RETVAL=0 + else + echo " ...not running" + RETVAL=1 + fi + + return $RETVAL +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac 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..fd265d34 --- /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: 155244 $) +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..b6ca1a4f --- /dev/null +++ b/src/VBox/Additions/linux/installer/vboxadd.sh @@ -0,0 +1,1325 @@ +#! /bin/sh +# $Id: vboxadd.sh $ +## @file +# Linux Additions kernel module init script ($Revision: 158810 $) +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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" +VBOXSERVICE_PIDFILE="/var/run/vboxadd-service.sh" +## systemd logs information about service status, otherwise do that ourselves. +QUIET= +test -z "${TARGET_VER}" && TARGET_VER=`uname -r` + +export VBOX_KBUILD_TYPE +export USERNAME + +setup_log() +{ + test -z "${LOG}" || return 0 + # Rotate log files + LOG="/var/log/vboxadd-setup.log" + mv -f "${LOG}.3" "${LOG}.4" 2>/dev/null + mv -f "${LOG}.2" "${LOG}.3" 2>/dev/null + mv -f "${LOG}.1" "${LOG}.2" 2>/dev/null + mv -f "${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 +} + +# When script is running as non-root, it does not have access to log +# files in /var. In this case, lets print error message to stdout and +# exit with bad status. +early_fail() +{ + echo "$1" >&2 + exit 1 +} + +fail() +{ + log "${1}" + echo "${SERVICE}: $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 +user_config=/etc/virtualbox-guest-additions.conf +owner=vboxadd +group=1 + +# Include custom user configuration file. +[ -r "$user_config" ] && . "$user_config" + +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" + +# Path to VBoxService control script. +VBOX_SERVICE_SCRIPT="/sbin/rcvboxadd-service" + +# Attempt to detect VirtualBox Guest Additions version and revision information. +VBOXCONTROL="${INSTALL_DIR}/bin/VBoxControl" +VBOX_VERSION="`"$VBOXCONTROL" --version | cut -d r -f1`" +[ -n "$VBOX_VERSION" ] || VBOX_VERSION='unknown' +VBOX_REVISION="r`"$VBOXCONTROL" --version | cut -d r -f2`" +[ "$VBOX_REVISION" != "r" ] || VBOX_REVISION='unknown' + +# Returns if the vboxguest module is running or not. +# +# Returns true if vboxguest module is running, false if not. +running_vboxguest() +{ + lsmod | grep -q "vboxguest[^_-]" +} + +# Returns if the vboxadd module is running or not. +# +# Returns true if vboxadd module is running, false if not. +running_vboxadd() +{ + lsmod | grep -q "vboxadd[^_-]" +} + +# Returns if the vboxsf module is running or not. +# +# Returns true if vboxsf module is running, false if not. +running_vboxsf() +{ + lsmod | grep -q "vboxsf[^_-]" +} + +# Returns if the vboxvideo module is running or not. +# +# Returns true if vboxvideo module is running, false if not. +running_vboxvideo() +{ + lsmod | grep -q "vboxvideo[^_-]" +} + +# Returns if a specific module is running or not. +# +# Input $1: Module name to check running status for. +# +# Returns true if the module is running, false if not. +running_module() +{ + lsmod | grep -q "$1" +} + +# Returns the version string of a currently running kernel module. +# +# Input $1: Module name to check. +# +# Returns the module version string if found, or none if not found. +running_module_version() +{ + mod="$1" + version_string_path="/sys/module/"$mod"/version" + + [ -n "$mod" ] || return + if [ -r "$version_string_path" ]; then + cat "$version_string_path" + else + echo "unknown" + fi +} + +# Checks if a loaded kernel module version matches to the currently installed Guest Additions version and revision. +# +# Input $1: Module name to check. +# +# Returns "1" if the module matches the installed Guest Additions, or none if not. +check_running_module_version() +{ + mod=$1 + expected="$VBOX_VERSION $VBOX_REVISION" + + [ -n "$mod" ] || return + [ -n "$expected" ] || return + + [ "$expected" = "$(running_module_version "$mod")" ] || return +} + +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 +} + +## Updates 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 +} + +# Removes 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 +} + +# 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 + +# Checks 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() +{ + kern_ver="$1" + opt_name="$2" + + [ -n "$kern_ver" ] || return + [ -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() +{ + kern_ver="$1" + [ -n "$kern_ver" ] || return + + kernel_get_config_opt "$kern_ver" "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" +} + +# Check if kernel configuration requires modules signature. +kernel_requires_module_signature() +{ + kern_ver="$1" + vbox_sys_lockdown_path="/sys/kernel/security/lockdown" + + [ -n "$kern_ver" ] || return + + requires="" + # We consider that if kernel is running in the following configurations, + # it will require modules to be signed. + if [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG")" = "y" ]; then + + # Modules signature verification is hardcoded in kernel config. + [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG_FORCE")" = "y" ] && requires="1" + + # Unsigned modules loading is restricted by "lockdown" feature in runtime. + if [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_LOCK_DOWN_KERNEL")" = "y" \ + -o "$(kernel_get_config_opt "$kern_ver" "CONFIG_SECURITY_LOCKDOWN_LSM")" = "y" \ + -o "$(kernel_get_config_opt "$kern_ver" "CONFIG_SECURITY_LOCKDOWN_LSM_EARLY")" = "y" ]; then + + # Once lockdown level is set to something different from "none" (e.g., "integrity" + # or "confidentiality"), kernel will reject unsigned modules loading. + if [ -r "$vbox_sys_lockdown_path" ]; then + [ -n "$(cat "$vbox_sys_lockdown_path" | grep "\[integrity\]")" ] && requires="1" + [ -n "$(cat "$vbox_sys_lockdown_path" | grep "\[confidentiality\]")" ] && requires="1" + fi + + # This configuration is used by a number of modern Linux distributions and restricts + # unsigned modules loading when Secure Boot mode is enabled. + [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT")" = "y" -a -n "$HAVE_SEC_BOOT" ] && requires="1" + fi + fi + + [ -n "$requires" ] && 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" + + # Sign kernel modules if kernel configuration requires it. + if test "$(kernel_requires_module_signature $KERN_VER)" = "1"; 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 "$KERN_VER") + [ "$(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 "$KERN_VER" "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}" + + 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 a module file as seen by modinfo(8), or none if not found. +# +# Input $1: Module name to get path for. +# +# Returns the module path as a 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 none if not found. +# +# Input $1: Module name to get version for. +# +# Returns the module version as a string. +module_version() +{ + mod="$1" + [ -n "$mod" ] || return + + modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f2 +} + +# Returns the module revision if module is available in the system, or none if not found. +# +# Input $1: Module name to get revision for. +# +# Returns the module revision as a string. +module_revision() +{ + mod="$1" + [ -n "$mod" ] || return + + modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f3 +} + +# Checks if a given kernel module is properly signed or not. +# +# Input $1: Module name to check. +# +# Returns "1" if module is signed and signature can be verified +# with public key provided in DEB_PUB_KEY, or none otherwise. +module_signed() +{ + mod="$1" + [ -n "$mod" ] || return + + # Be nice with distributions which do not provide tools which we + # use in order to verify module signature. This variable needs to + # be explicitly set by administrator. This script will look for it + # in /etc/virtualbox-guest-additions.conf. Make sure that you know + # what you do! + if [ "$VBOX_BYPASS_MODULES_SIGNATURE_CHECK" = "1" ]; then + echo "1" + return + fi + + 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" +} + +# Checks if a given kernel module matches the installed VirtualBox Guest Additions version. +# +# Input $1: Module name to check. +# +# Returns "1" if externally built module is available in the system and its +# version and revision number do match to current VirtualBox installation. +# None 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 kernel configuration (for currently loaded kernel) requires + # module signature, check if module is signed. + if test "$(kernel_requires_module_signature $(uname -r))" = "1"; then + [ "$(module_signed "$mod")" = "1" ] || return + fi + + echo "1" +} + +# Check if required modules are installed in the system and versions match. +# +# Returns "1" on success, none otherwise. +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 + # Only warn user if currently loaded modules version do not match Guest Additions Installation. + check_running_module_version "vboxguest" || info "Running kernel modules will not be replaced until the system is restarted or 'rcvboxadd reload' triggered" + 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. + test "$(setup_complete)" = "1" || setup + + # Warn if Secure Boot setup not yet complete. + if test "$(kernel_requires_module_signature)" = "1" && 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 + check_running_module_version "vboxguest" || { + rm -f $dev || { + fail "Cannot remove $dev" + } + rm -f $userdev || { + fail "Cannot remove $userdev" + } + # Assuming modules were just (re-)built, try to reload everything. + reload + } + case "$no_udev" in 1) + do_vboxguest_non_udev;; + esac + fi # INSTALL_NO_MODULE_BUILDS + + return 0 +} + +stop() +{ + begin "Stopping." + + 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 or consider running 'rcvboxadd reload'." + return 0 +} + +check_root() +{ + # Check if script is running with root privileges and exit if it does not. + [ `id -u` -eq 0 ] || early_fail "root privileges are required" +} + +# Check if process with this PID is running. +check_pid() +{ + pid="$1" + + test -n "$pid" -a -d "/proc/$pid" +} + +# A wrapper for check_running_module_version. +# Go through the list of Guest Additions' modules and +# verify if they are loaded and running version matches +# to current installation version. Skip vboxvideo since +# it is not loaded for old guests. +check_status_kernel() +{ + for mod in vboxguest vboxsf; do + + for attempt in 1 2 3 4 5; do + + # Wait before the next attempt. + [ $? -ne 0 ] && sleep 1 + + running_module "$mod" + if [ $? -eq 0 ]; then + mod_is_running="1" + check_running_module_version "$mod" + [ $? -eq 0 ] && break + else + mod_is_running="" + false + fi + + done + + # In case of error, try to print out proper reason of failure. + if [ $? -ne 0 ]; then + # Was module loaded? + if [ -z "$mod_is_running" ]; then + info "module $mod is not loaded" + else + # If module was loaded it means that it has incorrect version. + info "currently loaded module $mod version ($(running_module_version "$mod")) does not match to VirtualBox Guest Additions installation version ($VBOX_VERSION $VBOX_REVISION)" + fi + + # Set "bad" rc. + false + fi + + done +} + +# Check whether user-land processes are running. +# Currently only check for VBoxService. +check_status_user() +{ + [ -r "$VBOXSERVICE_PIDFILE" ] && check_pid "$(cat $VBOXSERVICE_PIDFILE)" >/dev/null 2>&1 +} + +send_signal_by_pidfile() +{ + sig="$1" + pidfile="$2" + + if [ -f "$pidfile" ]; then + check_pid $(cat "$pidfile") + if [ $? -eq 0 ]; then + kill "$sig" $(cat "$pidfile") >/dev/null 2>&1 + else + # Do not spoil $?. + true + fi + else + # Do not spoil $?. + true + fi +} + +# SIGUSR1 is used in order to notify VBoxClient processes that system +# update is started or kernel modules are going to be reloaded, +# so VBoxClient can release vboxguest.ko resources and then restart itself. +send_signal() +{ + sig="$1" + # Specify whether we sending signal to VBoxClient parent (control) + # process or a child (actual service) process. + process_type="$2" + + pidfile_postfix="" + [ -z "$process_type" ] || pidfile_postfix="-$process_type" + + for user_name in $(getent passwd | cut -d ':' -f 1); do + + # Filter out empty login names (paranoia). + [ -n "$user_name" ] || continue + + user_shell=$(getent passwd "$user_name" | cut -d ':' -f 7) + + # Filter out login names with not specified shells (paranoia). + [ -n "$user_shell" ] || continue + + # Filter out know non-login account names. + case "$user_shell" in + *nologin) skip_user_home="1";; + *sync) skip_user_home="1";; + *shutdown) skip_user_home="1";; + *halt) skip_user_home="1";; + *) skip_user_home="" + esac + [ -z "$skip_user_home" ] || continue; + + user_home=$(getent passwd "$user_name" | cut -d ':' -f 6) + + for pid_file in "$user_home"/.vboxclient-*"$pidfile_postfix".pid; do + + [ -r "$pid_file" ] || continue + + # If process type was not specified, we assume that signal supposed + # to be sent to legacy VBoxClient processes which have different + # pidfile name pattern (it does not contain "control" or "service"). + # Skip those pidfiles who has. + [ -z "$process_type" -a -n "$(echo "$pid_file" | grep "control")" ] && continue + [ -z "$process_type" -a -n "$(echo "$pid_file" | grep "service")" ] && continue + + send_signal_by_pidfile -USR1 "$pid_file" + + done + done +} + +# Helper function which executes a command, prints error message if command fails, +# and preserves command execution status for further processing. +try_load_preserve_rc() +{ + cmd="$1" + msg="$2" + + $cmd >/dev/null 2>&1 + + rc=$? + [ $rc -eq 0 ] || info "$msg" + + return $rc +} + +reload() +{ + begin "reloading kernel modules and services" + + # Stop VBoxService if running. + $VBOX_SERVICE_SCRIPT status >/dev/null 2>&1 + if [ $? -eq 0 ]; then + $VBOX_SERVICE_SCRIPT stop >/dev/null 2>&1 || fail "unable to stop VBoxService" + fi + + # Unmount Shared Folders. + umount -a -t vboxsf >/dev/null 2>&1 || fail "unable to unmount shared folders, mount point(s) might be still in use" + + # Stop VBoxDRMClient. + send_signal_by_pidfile "-USR1" "/var/run/VBoxDRMClient" || fail "unable to stop VBoxDRMClient" + + if [ $? -eq 0 ]; then + # Tell legacy VBoxClient processes to release vboxguest.ko references. + send_signal "-USR1" "" + + # Tell compatible VBoxClient processes to release vboxguest.ko references. + send_signal "-USR1" "service" + + # Try unload. + for attempt in 1 2 3 4 5; do + + # Give VBoxClient processes some time to close reference to vboxguest module. + [ $? -ne 0 ] && sleep 1 + + # Try unload drivers unconditionally (ignore previous command exit code). + # If final goal of unloading vboxguest.ko won't be met, we will fail on + # the next step anyway. + running_vboxsf && modprobe -r vboxsf >/dev/null 2>&1 + running_vboxguest + if [ $? -eq 0 ]; then + modprobe -r vboxguest >/dev/null 2>&1 + [ $? -eq 0 ] && break + else + # Do not spoil $?. + true + fi + done + + # Check if we succeeded with unloading vboxguest after several attempts. + running_vboxguest + if [ $? -eq 0 ]; then + info "cannot reload kernel modules: one or more module(s) is still in use" + false + else + # Do not spoil $?. + true + fi + + # Load drivers (skip vboxvideo since it is not loaded for very old guests). + [ $? -eq 0 ] && try_load_preserve_rc "modprobe vboxguest" "unable to load vboxguest kernel module, see dmesg" + [ $? -eq 0 ] && try_load_preserve_rc "modprobe vboxsf" "unable to load vboxsf kernel module, see dmesg" + + # Start VBoxService and VBoxDRMClient. + [ $? -eq 0 ] && try_load_preserve_rc "$VBOX_SERVICE_SCRIPT start" "unable to start VBoxService" + + # Reload VBoxClient processes. + [ $? -eq 0 ] && try_load_preserve_rc "send_signal -USR1 control" "unable to reload user session services" + + # Check if we just loaded modules of correct version. + [ $? -eq 0 ] && try_load_preserve_rc "check_status_kernel" "kernel modules were not reloaded" + + # Check if user-land processes were restarted as well. + [ $? -eq 0 ] && try_load_preserve_rc "check_status_user" "user-land services were not started" + + if [ $? -eq 0 ]; then + # Take reported version of running Guest Additions from running vboxguest module (as a paranoia check). + info "kernel modules and services $(running_module_version "vboxguest") reloaded" + info "NOTE: you may still consider to re-login if some user session specific services (Shared Clipboard, Drag and Drop, Seamless or Guest Screen Resize) were not restarted automatically" + else + # In case of failure, sent SIGTERM to abandoned control processes to remove leftovers from failed reloading. + send_signal "-TERM" "control" + + fail "kernel modules and services were not reloaded" + fi + else + fail "cannot stop user services" + fi +} + +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) + check_root + 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) + check_root + stop + ;; +restart) + check_root + restart + ;; +# Tries to reload kernel modules and restart user processes. +reload) + check_root + reload + ;; +# 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) + check_root + cleanup && start + ;; +# Builds kernel modules for the specified kernels if they are not already built. +quicksetup) + check_root + 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) + check_root + cleanup + ;; +status) + dmnstatus + ;; +status-kernel) + check_root + check_status_kernel + if [ $? -eq 0 ]; then + info "kernel modules $VBOX_VERSION $VBOX_REVISION are loaded" + else + info "kernel modules $VBOX_VERSION $VBOX_REVISION were not loaded" + false + fi + ;; +status-user) + check_root + check_status_user + if [ $? -eq 0 ]; then + info "user-land services $VBOX_VERSION $VBOX_REVISION are running" + else + info "user-land services $VBOX_VERSION $VBOX_REVISION are not running" + false + fi + ;; +*) + echo "Usage: $0 {start|stop|restart|reload|status|status-kernel|status-user|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..0d59d2e8 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..08e02b00 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..887514df --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk @@ -0,0 +1,116 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for VBox LightDM greeter for providing automated logons. +# +# Note! This isn't compiled any more. Remove? +# + +# +# Copyright (C) 2012-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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 +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + vbox-greeter_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" +else + vbox-greeter_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" +endif +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 Binary files differnew file mode 100644 index 00000000..d74e9c1a --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/banner-dummy.png 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..dfae0972 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk @@ -0,0 +1,61 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for liblighdm-gobject 1.5.0 +# + +# +# Copyright (C) 2013-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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 = VBoxGuestR3Lib +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 +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + VBox-liblightdm-gobject_DEFS = \ + CONFIG_DIR="/etc/lightdm" \ + XSESSIONS_DIR="/usr/share/xsessions" \ + REMOTE_SESSIONS_DIR="/usr/share/lightdm/remote-sessions" +else + VBox-liblightdm-gobject_DEFS = \ + CONFIG_DIR=\"/etc/lightdm\" \ + XSESSIONS_DIR=\"/usr/share/xsessions\" \ + REMOTE_SESSIONS_DIR=\"/usr/share/lightdm/remote-sessions\" +endif +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..b160c9d5 --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * 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..64190d85 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..50992172 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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 = VBoxGuestR3Exe +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..3eeb976c --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..fb8c8d59 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/dirops.c @@ -0,0 +1,1427 @@ +/* $Id: dirops.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, directory inode and file operations. + */ + +/* + * Copyright (C) 2006-2023 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) { + RT_BCOPY_UNFORTIFIED(&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); + RT_BCOPY_UNFORTIFIED(&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) { + RT_BCOPY_UNFORTIFIED(&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 idmap idmap of the mount. + * @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(6,3,0) || defined(DOXYGEN_RUNNING) +static int vbsf_inode_create(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, umode_t mode, bool excl) +#elif RTLNX_VER_MIN(5,12,0) +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 idmap idmap of the mount. + * @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(6,3,0) || defined(DOXYGEN_RUNNING) +static int vbsf_inode_mkdir(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, umode_t mode) +#elif RTLNX_VER_MIN(5,12,0) +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) { + RT_BCOPY_UNFORTIFIED(&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 idmap idmap of the mount. + * @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(6,3,0) || defined(DOXYGEN_RUNNING) +static int vbsf_inode_rename(struct mnt_idmap *idmap, + struct inode *old_parent, struct dentry *old_dentry, + struct inode *new_parent, struct dentry *new_dentry, unsigned flags) +#elif RTLNX_VER_MIN(5,12,0) +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; + + RT_BCOPY_UNFORTIFIED(&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(6,3,0) +static int vbsf_inode_symlink(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, const char *target) +#elif 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); + RT_BCOPY_UNFORTIFIED(&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..8187ad33 --- /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-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# 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..0d37a155 --- /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-2023 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..bcf0c59b --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * 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..ca604090 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/regops.c @@ -0,0 +1,3902 @@ +/* $Id: regops.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, regular file inode and file operations. + */ + +/* + * Copyright (C) 2006-2023 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_MIN(6,4,0) +# define VBOX_ITER_IOV_ADDR(a_iter) iter_iov_addr(a_iter) +#elif RTLNX_VER_MIN(3,19,0) +# define VBOX_ITER_IOV_ADDR(a_iter) (a_iter->kvec->iov_base + a_iter->iov_offset) +#else +# define VBOX_ITER_IOV_ADDR(a_iter) (a_iter->iov->iov_base + a_iter->iov_offset) +#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 + +/** Starting from 6.4.0, iter_iov() macro should be used in order to access to iov field + * of struct iov_iter. */ +#if RTLNX_VER_MIN(6,4,0) || RTLNX_RHEL_RANGE(9,4, 9,99) +# define VBSF_GET_ITER_IOV(_iter) iter_iov(_iter) +#else +# define VBSF_GET_ITER_IOV(_iter) iter->iov +#endif + +/** @def VBOX_IOV_ITER_IS_KVEC + * Test if iov iter type is ITER_KVEC. */ +#if RTLNX_VER_MIN(4,20,0) +# define VBOX_IOV_ITER_IS_KVEC(a_iter) iov_iter_is_kvec(a_iter) +#else +# define VBOX_IOV_ITER_IS_KVEC(a_iter) (VBSF_GET_ITER_TYPE(iter) & ITER_KVEC) +#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 *)VBOX_ITER_IOV_ADDR(pSrcIter), cbThisCopy); + else if (copy_from_user(pbDst, VBOX_ITER_IOV_ADDR(pSrcIter), 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 *)VBOX_ITER_IOV_ADDR(pDstIter), pbSrc, cbThisCopy); + else if (copy_to_user(VBOX_ITER_IOV_ADDR(pDstIter), 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) || RTLNX_RHEL_RANGE(9,4, 9,99) +# 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 (!VBOX_IOV_ITER_IS_KVEC(iter)) + { + /* + * 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); + } + + pbBuf = VBOX_ITER_IOV_ADDR(iter); + 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 = VBSF_GET_ITER_IOV(iter); + 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; + } + RT_BCOPY_UNFORTIFIED(&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)|| RTLNX_RHEL_RANGE(9,3, 9,99) +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) || RTLNX_RHEL_RANGE(9,3, 9,99) +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) || RTLNX_RHEL_RANGE(9,3, 9,99) + .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..9af2b4e4 --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * 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..b75997a8 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/utils.c @@ -0,0 +1,1288 @@ +/* $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-2023 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); + RT_BCOPY_UNFORTIFIED(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; +} + +/** + * Update inode timestamps. + * + * @param pInode Linux inode object. + * @param pObjInfo VBox vboxsf object. + */ +static void vbsf_update_inode_timestamps(struct inode *pInode, PSHFLFSOBJINFO pObjInfo) +{ +#if RTLNX_VER_MIN(6,6,0) + struct timespec64 ts; + vbsf_time_to_linux(&ts, &pObjInfo->ChangeTime); + inode_set_ctime_to_ts(pInode, ts); +#else + 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); +#endif +} + +/** + * 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_update_inode_timestamps(inode, pObjInfo); + + 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_update_inode_timestamps(pInode, pObjInfo); + 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); + RT_BCOPY_UNFORTIFIED(&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); + RT_BCOPY_UNFORTIFIED(&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(6,3,0) +int vbsf_inode_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *kstat, u32 request_mask, unsigned int flags) +# elif 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(6,6,0) + generic_fillattr(idmap, request_mask, dentry->d_inode, kstat); +# elif RTLNX_VER_MIN(6,3,0) + generic_fillattr(idmap, dentry->d_inode, kstat); +# elif 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(6,3,0) +int vbsf_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) +#elif 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(6,3,0) + rc = setattr_prepare(idmap, dentry, iattr); +# elif 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; + RT_BCOPY_UNFORTIFIED(&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) + RT_BCOPY_UNFORTIFIED(&tmp->String.utf8[0], d_name, d_len + 1); + else { + RT_BCOPY_UNFORTIFIED(&tmp->String.utf8[0], p_name, p_len); + tmp->String.utf8[p_len] = '/'; + RT_BCOPY_UNFORTIFIED(&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..72d9210e --- /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-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * 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..663cc8be --- /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-2023 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..18324fe1 --- /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-2023 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)); + RT_BCOPY_UNFORTIFIED(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; + RT_BCOPY_UNFORTIFIED(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)) { + RT_BCOPY_UNFORTIFIED(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..b39db721 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.h @@ -0,0 +1,483 @@ +/* $Id: vfsmod.h $ */ +/** @file + * vboxsf - Linux Shared Folders VFS, internal header. + */ + +/* + * Copyright (C) 2006-2023 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(6,3,0) +extern int vbsf_inode_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *kstat, u32 request_mask, unsigned int query_flags); +# elif 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(6,3,0) +extern int vbsf_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr); +#elif 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..6f8d6eed --- /dev/null +++ b/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c @@ -0,0 +1,103 @@ +/** @file + * + * VirtualBox Timesync using temporary Backdoor + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * 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; +} |