diff options
Diffstat (limited to 'src/VBox/Additions/linux')
56 files changed, 18576 insertions, 0 deletions
diff --git a/src/VBox/Additions/linux/Makefile b/src/VBox/Additions/linux/Makefile new file mode 100644 index 00000000..113ac48e --- /dev/null +++ b/src/VBox/Additions/linux/Makefile @@ -0,0 +1,103 @@ +# +# Makefile for the VirtualBox Linux Guest Drivers. +# + +# +# Copyright (C) 2009-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +ifneq ($(KBUILD_EXTMOD),) + +# Building from kBuild (make -C <kernel_directory> M=`pwd`). +# KBUILD_EXTMOD is set to $(M) in this case. + +obj-m = vboxguest/ vboxsf/ vboxvideo/ + +else # ! KBUILD_EXTMOD + +KBUILD_VERBOSE = + +vboxguest: + @echo "=== Building 'vboxguest' module ===" + @$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest + @if [ -f vboxguest/vboxguest.ko ]; then \ + cp vboxguest/vboxguest.ko .; \ + else \ + cp vboxguest/vboxguest.o .; \ + fi + @echo + +vboxsf: vboxguest + @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) -C vboxsf || exit 1; \ + if [ -f vboxsf/vboxsf.ko ]; then \ + cp vboxsf/vboxsf.ko .; \ + else \ + cp vboxsf/vboxsf.o .; \ + fi; \ + echo; \ + fi + +vboxvideo: + @if [ -d vboxvideo ]; then \ + if [ -f vboxguest/Module.symvers ]; then \ + cp vboxguest/Module.symvers vboxvideo; \ + fi; \ + 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 + +all: vboxguest vboxsf vboxvideo + +install: + @$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest install + @if [ -d vboxsf ]; then \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxsf install; \ + fi + @if [ -d vboxvideo ]; then \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxvideo install; \ + fi + +clean: + @$(MAKE) -C vboxguest clean + @if [ -d vboxsf ]; then \ + $(MAKE) -C vboxsf clean; \ + fi + @if [ -d vboxvideo ]; then \ + $(MAKE) -C vboxvideo clean; \ + fi + rm -f vboxguest.*o vboxsf.*o vboxvideo.*o + +check: + @$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest check + +load: + @/sbin/rmmod vboxvideo || true + @/sbin/rmmod vboxvfs || true + @/sbin/rmmod vboxsf || true + @/sbin/rmmod vboxguest || true + @/sbin/insmod vboxguest.ko + @if [ -f vboxsf.ko ]; then /sbin/insmod vboxsf.ko; fi + @if [ -f vboxvideo.ko ]; then /sbin/insmod vboxvideo.ko; fi + +.PHONY: vboxguest vboxsf vboxvideo all install clean check load + +endif # ! KBUILD_EXTMOD diff --git a/src/VBox/Additions/linux/Makefile.kmk b/src/VBox/Additions/linux/Makefile.kmk new file mode 100644 index 00000000..080ef828 --- /dev/null +++ b/src/VBox/Additions/linux/Makefile.kmk @@ -0,0 +1,450 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for the linux guest additions base directory. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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) \ + $(if $(VBOX_WITH_CROGL),$(VBOX_LNX_ADD_INST_LIB_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) \ + $(if $(VBOX_WITH_CROGL),$(VBOX_LNX_ADD_DBG_LIB_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 \ + VBoxClient + +VBOX_LNX_ADD_STRIP_SBIN += \ + VBoxService \ + $(if $(VBOX_WITH_LIGHTDM_GREETER),vbox-greeter) + +ifdef VBOX_WITH_CROGL +VBOX_LNX_ADD_STRIP_LIB += \ + VBoxOGLarrayspu.so \ + VBoxOGLcrutil.so \ + VBoxOGLerrorspu.so \ + VBoxOGLfeedbackspu.so \ + VBoxOGLpackspu.so \ + VBoxOGLpassthroughspu.so \ + VBoxOGL.so \ + VBoxEGL.so +endif + +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,vboxsf-src,vboxguest-src,) +$(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 $(VBOX_LNX_ADD_INST_STAGE_DIR) $@ \ + "VirtualBox $(VBOX_VERSION_STRING) Guest Additions for Linux" \ + /bin/sh ./install.sh "\$$0 1> /dev/null" + diff --git a/src/VBox/Additions/linux/drm/.scm-settings b/src/VBox/Additions/linux/drm/.scm-settings new file mode 100644 index 00000000..cd4a860a --- /dev/null +++ b/src/VBox/Additions/linux/drm/.scm-settings @@ -0,0 +1,25 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for linux drm driver. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + + +#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..29338a19 --- /dev/null +++ b/src/VBox/Additions/linux/drm/Makefile.kmk @@ -0,0 +1,42 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the vboxvideo DRM module (linux kernel OpenGL module). +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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..cad8263d --- /dev/null +++ b/src/VBox/Additions/linux/drm/Makefile.module.kms @@ -0,0 +1,51 @@ +# $Id: Makefile.module.kms $ +## @file +# VirtualBox Guest Additions Module Makefile. +# +# (For 2.6.x this file must be 'Makefile'!) +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# Linux kbuild sets this to our source directory if we are called from there +obj ?= $(CURDIR) +include $(obj)/Makefile.include.header + +BUILD = + +# We want to build on Linux 3.11 and later and on all EL 7 kernels. +ifneq ($(filter-out 1.% 2.% 3.0.% 3.1.% 3.2.% 3.3.% 3.4.% 3.5.% 3.6.% 3.7.% \ + 3.8.% 3.9.% 3.10.%,$(KERN_VER)),) + BUILD = 1 +endif +ifeq ($(filter-out %.el7.x86_64,$(KERN_VER)),) + BUILD = 1 +endif + +ifneq ($(BUILD),) + +MOD_NAME = vboxvideo +MOD_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 +MOD_INCL = -I$(KBUILD_EXTMOD) -Iinclude/drm + +include $(obj)/Makefile.include.footer + +else # ! wildcard $(KERN_INCL)/drm/drm_rect.h + + all: + install: + clean: + +endif # ! wildcard $(KERN_INCL)/drm/drm_rect.h 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..c981a5d5 --- /dev/null +++ b/src/VBox/Additions/linux/drm/files_vboxvideo_drv @@ -0,0 +1,49 @@ +#!/bin/sh +# $Id: files_vboxvideo_drv $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2011-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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.include.header=>Makefile.include.header \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile.include.footer=>Makefile.include.footer \ + ${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..cb8b5b74 --- /dev/null +++ b/src/VBox/Additions/linux/drm/indent.sed @@ -0,0 +1,274 @@ +# Oracle VM VirtualBox +# VirtualBox to Linux kernel coding style conversion script. + +# +# Copyright (C) 2017-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# 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/\bDECLCALLBACKMEMBER\b(\([^,)]*\), *\([^,)]*\))/\1 (*\2)/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/!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..d6373843 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_drv.c @@ -0,0 +1,341 @@ +/* $Id: vbox_drv.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * 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 <drm/drmP.h> +#include <drm/drm_crtc_helper.h> + +#include "vbox_drv.h" + +#include "version-generated.h" +#include "revision-generated.h" + +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) +{ + return drm_get_pci_dev(pdev, ent, &driver); +} + +static void vbox_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *dev = pci_get_drvdata(pdev); + + drm_put_dev(dev); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0) && !defined(RHEL_74) +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(dev->pdev); + + 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(dev->pdev)) + 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 LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) && !defined(RHEL_74) +/* 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 /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) && !RHEL_74 */ + +static const struct file_operations vbox_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) && !defined(RHEL_74) + .unlocked_ioctl = vbox_ioctl, +#else + .unlocked_ioctl = drm_ioctl, +#endif + .mmap = vbox_mmap, + .poll = drm_poll, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) && !defined(RHEL_70) + .fasync = drm_fasync, +#endif +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .read = drm_read, +}; + +static int 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); + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) && !defined(RHEL_74) +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 = { + .driver_features = + DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | + DRIVER_PRIME, + .dev_priv_size = 0, + + .load = vbox_driver_load, + .unload = vbox_driver_unload, + .lastclose = vbox_driver_lastclose, + .master_set = vbox_master_set, + .master_drop = vbox_master_drop, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) || defined(RHEL_73) +# if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) && !defined(RHEL_75) + .set_busid = drm_pci_set_busid, +# endif +#endif + + .fops = &vbox_fops, + .irq_handler = vbox_irq_handler, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, + + .gem_free_object = vbox_gem_free_object, + .dumb_create = vbox_dumb_create, + .dumb_map_offset = vbox_dumb_mmap_offset, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) && !defined(RHEL_73) + .dumb_destroy = vbox_dumb_destroy, +#else + .dumb_destroy = drm_gem_dumb_destroy, +#endif + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .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_import_sg_table = vbox_gem_prime_import_sg_table, + .gem_prime_vmap = vbox_gem_prime_vmap, + .gem_prime_vunmap = vbox_gem_prime_vunmap, + .gem_prime_mmap = vbox_gem_prime_mmap, +}; + +static int __init vbox_init(void) +{ +#if defined(CONFIG_VGA_CONSOLE) || LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + if (vgacon_text_force() && vbox_modeset == -1) + return -EINVAL; +#endif + + if (vbox_modeset == 0) + return -EINVAL; + + return pci_register_driver(&vbox_pci_driver); +} + +static void __exit vbox_exit(void) +{ + pci_unregister_driver(&vbox_pci_driver); +} + +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..59bbe261 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_drv.h @@ -0,0 +1,425 @@ +/* $Id: vbox_drv.h $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * 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> +#if LINUX_VERSION_CODE < KERNEL_VERSION(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> + +#if defined(RHEL_MAJOR) && defined(RHEL_MINOR) +# if RHEL_MAJOR == 7 && RHEL_MINOR >= 6 +# define RHEL_76 +# endif +# if RHEL_MAJOR == 7 && RHEL_MINOR >= 5 +# define RHEL_75 +# endif +# if RHEL_MAJOR == 7 && RHEL_MINOR >= 4 +# define RHEL_74 +# endif +# if RHEL_MAJOR == 7 && RHEL_MINOR >= 3 +# define RHEL_73 +# endif +# if RHEL_MAJOR == 7 && RHEL_MINOR >= 2 +# define RHEL_72 +# endif +# if RHEL_MAJOR == 7 && RHEL_MINOR >= 1 +# define RHEL_71 +# endif +# if RHEL_MAJOR == 7 && RHEL_MINOR >= 0 +# define RHEL_70 +# endif +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) || defined(RHEL_71) +#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 + +#include <drm/drmP.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) +#include <drm/drm_encoder.h> +#endif +#include <drm/drm_fb_helper.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) || defined(RHEL_72) +#include <drm/drm_gem.h> +#endif + +#include <drm/ttm/ttm_bo_api.h> +#include <drm/ttm/ttm_bo_driver.h> +#include <drm/ttm/ttm_placement.h> +#include <drm/ttm/ttm_memory.h> +#include <drm/ttm/ttm_module.h> + +#include "vboxvideo_guest.h" +#include "vboxvideo_vbe.h" +#include "hgsmi_ch_setup.h" + +#include "product-generated.h" + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) && !defined(RHEL_75) +static inline void drm_gem_object_put_unlocked(struct drm_gem_object *obj) +{ + drm_gem_object_unreference_unlocked(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 + +/** How frequently we refresh if the guest is not providing dirty rectangles. */ +#define VBOX_REFRESH_PERIOD (HZ / 2) + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) && !defined(RHEL_72) +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 { + struct drm_global_reference mem_global_ref; + struct ttm_bo_global_ref bo_global_ref; + struct ttm_bo_device bdev; + 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 + +int vbox_driver_load(struct drm_device *dev, unsigned long flags); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) +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 { + u16 width; + u16 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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) +#define DRM_MODE_FB_CMD drm_mode_fb_cmd +#else +#define DRM_MODE_FB_CMD drm_mode_fb_cmd2 +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0) && !defined(RHEL_71) +#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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) || defined(RHEL_73) + 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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) && !defined(RHEL_72) + 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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) && !defined(RHEL_73) +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); + +int vbox_bo_pin(struct vbox_bo *bo, u32 pl_flag, 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) || defined(RHEL_74) + 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, int domain); +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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) && !defined(RHEL_72) +struct drm_gem_object *vbox_gem_prime_import_sg_table( + struct drm_device *dev, size_t size, struct sg_table *table); +#else +struct drm_gem_object *vbox_gem_prime_import_sg_table( + struct drm_device *dev, struct dma_buf_attachment *attach, + struct sg_table *table); +#endif +void *vbox_gem_prime_vmap(struct drm_gem_object *obj); +void vbox_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr); +int vbox_gem_prime_mmap(struct drm_gem_object *obj, + struct vm_area_struct *area); + +/* vbox_irq.c */ +int vbox_irq_init(struct vbox_private *vbox); +void vbox_irq_fini(struct vbox_private *vbox); +void vbox_report_hotplug(struct vbox_private *vbox); +irqreturn_t vbox_irq_handler(int irq, void *arg); + +/* 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..6c9d3c0a --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_fb.c @@ -0,0 +1,458 @@ +/* $Id: vbox_fb.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * 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 <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> + +#include "vbox_drv.h" +#include <VBoxVideo.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) && !defined(RHEL_74) +/** + * 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 + +#ifdef CONFIG_FB_DEFERRED_IO +# if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) && !defined(RHEL_74) +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 + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) && !defined(RHEL_73) +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 + +static struct fb_ops vboxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_fillrect = drm_fb_helper_sys_fillrect, + .fb_copyarea = drm_fb_helper_sys_copyarea, + .fb_imageblit = drm_fb_helper_sys_imageblit, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, + .fb_debug_enter = drm_fb_helper_debug_enter, + .fb_debug_leave = drm_fb_helper_debug_leave, +}; + +static int vboxfb_create_object(struct vbox_fbdev *fbdev, + struct DRM_MODE_FB_CMD *mode_cmd, + struct drm_gem_object **gobj_p) +{ + struct drm_device *dev = fbdev->helper.dev; + u32 size; + struct drm_gem_object *gobj; +#if LINUX_VERSION_CODE < KERNEL_VERSION(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 LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) && !defined(RHEL_73) +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 LINUX_VERSION_CODE < KERNEL_VERSION(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, TTM_PL_FLAG_VRAM, NULL); + if (ret) { + vbox_bo_unreserve(bo); + return ret; + } + + ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap); + vbox_bo_unreserve(bo); + if (ret) { + DRM_ERROR("failed to kmap fbcon\n"); + return ret; + } + + info = drm_fb_helper_alloc_fbi(helper); + if (IS_ERR(info)) + return -PTR_ERR(info); + + info->par = fbdev; + + fbdev->size = size; + + fb = &fbdev->afb.base; + fbdev->helper.fb = fb; + + strcpy(info->fix.id, "vboxdrmfb"); + + /* + * The last flag forces a mode set on VT switches even if the kernel + * does not think it is needed. + */ + info->flags = FBINFO_DEFAULT | FBINFO_MISC_ALWAYS_SETPAR; + info->fbops = &vboxfb_ops; + + /* + * This seems to be done for safety checking that the framebuffer + * is not registered twice by different drivers. + */ + info->apertures->ranges[0].base = pci_resource_start(dev->pdev, 0); + info->apertures->ranges[0].size = pci_resource_len(dev->pdev, 0); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) + 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 + drm_fb_helper_fill_var(info, &fbdev->helper, sizes->fb_width, + sizes->fb_height); + + info->screen_base = bo->kmap.virtual; + info->screen_size = size; + +#ifdef CONFIG_FB_DEFERRED_IO + info->fbdefio = &vbox_defio; + fb_deferred_io_init(info); +#endif + + info->pixmap.flags = FB_PIXMAP_SYSTEM; + + DRM_DEBUG_KMS("allocated %dx%d\n", fb->width, fb->height); + + return 0; +} + +static struct drm_fb_helper_funcs vbox_fb_helper_funcs = { + .fb_probe = vboxfb_create, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0) && !defined(RHEL_73) +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; + + drm_fb_helper_unregister_fbi(&fbdev->helper); + + 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); + } + drm_gem_object_put_unlocked(afb->obj); + afb->obj = NULL; + } + drm_fb_helper_fini(&fbdev->helper); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(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; + + fbdev = devm_kzalloc(dev->dev, sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) + return -ENOMEM; + + vbox->fbdev = fbdev; + spin_lock_init(&fbdev->dirty_lock); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) && !defined(RHEL_73) + fbdev->helper.funcs = &vbox_fb_helper_funcs; +#else + drm_fb_helper_prepare(dev, &fbdev->helper, &vbox_fb_helper_funcs); +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) + ret = drm_fb_helper_init(dev, &fbdev->helper, vbox->num_crtcs); +#else + ret = + drm_fb_helper_init(dev, &fbdev->helper, vbox->num_crtcs, + vbox->num_crtcs); +#endif + if (ret) + return ret; + + ret = drm_fb_helper_single_add_all_connectors(&fbdev->helper); + if (ret) + goto err_fini; + + /* disable all the possible outputs/crtcs before entering KMS mode */ + drm_helper_disable_unused_functions(dev); + + ret = drm_fb_helper_initial_config(&fbdev->helper, 32); + if (ret) + goto err_fini; + + return 0; + +err_fini: + drm_fb_helper_fini(&fbdev->helper); + return ret; +} + +void vbox_fbdev_set_base(struct vbox_private *vbox, unsigned long gpu_addr) +{ + struct fb_info *fbdev = vbox->fbdev->helper.fbdev; + + fbdev->fix.smem_start = fbdev->apertures->ranges[0].base + gpu_addr; + fbdev->fix.smem_len = vbox->available_vram_size - gpu_addr; +} diff --git a/src/VBox/Additions/linux/drm/vbox_hgsmi.c b/src/VBox/Additions/linux/drm/vbox_hgsmi.c new file mode 100644 index 00000000..a92ee952 --- /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-2019 Oracle Corporation + * + * 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 LINUX_VERSION_CODE < KERNEL_VERSION(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..31abcc21 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_irq.c @@ -0,0 +1,212 @@ +/* $Id: vbox_irq.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * 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" + +#include <drm/drm_crtc_helper.h> +#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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 & 0x8fff; + vbox_conn->mode_hint.height = hints->cy & 0x8fff; + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) || defined(RHEL_71) + return drm_irq_install(vbox->dev, vbox->dev->pdev->irq); +#else + return drm_irq_install(vbox->dev); +#endif +} + +void vbox_irq_fini(struct vbox_private *vbox) +{ + drm_irq_uninstall(vbox->dev); + 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..31029d70 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_main.c @@ -0,0 +1,661 @@ +/* $Id: vbox_main.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * This file is based on ast_main.c + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * Authors: Dave Airlie <airlied@redhat.com>, + * Michael Thayer <michael.thayer@oracle.com, + * Hans de Goede <hdegoede@redhat.com> + */ +#include "vbox_drv.h" +#include <drm/drm_fb_helper.h> +#include <drm/drm_crtc_helper.h> + +#include <VBoxVideoGuest.h> +#include <VBoxVideoVBE.h> + +#include "hgsmi_channels.h" + +static void vbox_user_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct vbox_framebuffer *vbox_fb = to_vbox_framebuffer(fb); + + if (vbox_fb->obj) + drm_gem_object_put_unlocked(vbox_fb->obj); + + 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 *)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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) || defined(RHEL_73) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) || defined(RHEL_73) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) || defined(RHEL_74) + 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: + drm_gem_object_put_unlocked(obj); + return ERR_PTR(ret); +} + +static const struct drm_mode_config_funcs vbox_mode_funcs = { + .fb_create = vbox_user_framebuffer_create, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0) && !defined(RHEL_73) +#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->dev->pdev, 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->dev->pdev, vbox->vbva_buffers); + return ret; +} + +static void vbox_accel_fini(struct vbox_private *vbox) +{ + vbox_disable_accel(vbox); + pci_iounmap(vbox->dev->pdev, 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->dev->pdev, 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->dev->pdev, 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->dev->pdev, vbox->guest_heap); +} + +int vbox_driver_load(struct drm_device *dev, unsigned long flags) +{ + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) +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 LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0) && !defined(RHEL_75) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0) || defined(RHEL_71) + 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) + return -EINVAL; + + ret = vbox_bo_create(dev, size, 0, 0, &vboxbo); + if (ret) { + if (ret != -ERESTARTSYS) + DRM_ERROR("failed to allocate GEM object\n"); + 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); + drm_gem_object_put_unlocked(gobj); + if (ret) + return ret; + + args->handle = handle; + + return 0; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) && !defined(RHEL_73) +int vbox_dumb_destroy(struct drm_file *file, + struct drm_device *dev, u32 handle) +{ + return drm_gem_handle_delete(file, handle); +} +#endif + +static void vbox_bo_unref(struct vbox_bo **bo) +{ + struct ttm_buffer_object *tbo; + + if ((*bo) == NULL) + return; + + tbo = &((*bo)->bo); + ttm_bo_unref(&tbo); + if (!tbo) + *bo = NULL; +} + +void vbox_gem_free_object(struct drm_gem_object *obj) +{ + struct vbox_bo *vbox_bo = gem_to_vbox_bo(obj); + + vbox_bo_unref(&vbox_bo); +} + +static inline u64 vbox_bo_mmap_offset(struct vbox_bo *bo) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0) && !defined(RHEL_70) + return bo->bo.addr_space_offset; +#else + return drm_vma_node_offset_addr(&bo->bo.vma_node); +#endif +} + +int +vbox_dumb_mmap_offset(struct drm_file *file, + struct drm_device *dev, + u32 handle, u64 *offset) +{ + struct drm_gem_object *obj; + int ret; + struct vbox_bo *bo; + + mutex_lock(&dev->struct_mutex); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) || defined(RHEL_74) + 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); + + drm_gem_object_unreference(obj); + ret = 0; + +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..f8cc0bff --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_mode.c @@ -0,0 +1,857 @@ +/* $Id: vbox_mode.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * 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 LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) || defined(RHEL_72) +#include <drm/drm_plane_helper.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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) + bpp = crtc->enabled ? CRTC_FB(crtc)->format->cpp[0] * 8 : 32; + pitch = crtc->enabled ? CRTC_FB(crtc)->pitches[0] : width * bpp / 8; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) || defined(RHEL_75) + 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; + + 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, 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(CRTC_FB(crtc)); + 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, TTM_PL_FLAG_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); + } + } + + 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, 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 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, */ + .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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) && !defined(RHEL_71) +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) +{ + int enc_id = connector->encoder_ids[0]; + + /* pick the encoder ids */ + if (enc_id) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) || \ + (defined(CONFIG_SUSE_VERSION) && \ + LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0)) || \ + defined(RHEL_76) + return drm_encoder_find(connector->dev, NULL, enc_id); +#else + return drm_encoder_find(connector->dev, enc_id); +#endif + + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) || defined(RHEL_73) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) || defined(RHEL_72) + 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; +} + +static int vbox_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +static void vbox_connector_destroy(struct drm_connector *connector) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) && !defined(RHEL_72) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) || defined(RHEL_72) + 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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) && !defined(RHEL_72) + drm_sysfs_connector_add(connector); +#else + drm_connector_register(connector); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) + 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 LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) || defined(RHEL_74) + 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; + + ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &uobj_map); + if (ret) { + vbox->cursor_data_size = 0; + goto out_unreserve_bo; + } + + src = ttm_kmap_obj_virtual(&uobj_map, &src_isiomem); + if (src_isiomem) { + DRM_ERROR("src cursor bo not in main memory\n"); + ret = -EIO; + goto out_unmap_bo; + } + + copy_cursor_image(src, dst, width, height, mask_size); + + flags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE | + VBOX_MOUSE_POINTER_ALPHA; + ret = VBoxHGSMIUpdatePointerShape(vbox->guest_pool, flags, + vbox->cursor_hot_x, vbox->cursor_hot_y, + width, height, dst, data_size); + ret = ret == VINF_SUCCESS ? 0 : ret == VERR_NO_MEMORY ? -ENOMEM : + ret == VERR_NOT_SUPPORTED ? -EBUSY : -EINVAL; + +out_unmap_bo: + ttm_bo_kunmap(&uobj_map); +out_unreserve_bo: + vbox_bo_unreserve(bo); +out_unref_obj: + drm_gem_object_put_unlocked(obj); + + 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..f46335c4 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_prime.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017-2019 Oracle Corporation + * 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 LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) && !defined(RHEL_72) +struct drm_gem_object *vbox_gem_prime_import_sg_table( + struct drm_device *dev, size_t size, struct sg_table *table) +#else +struct drm_gem_object *vbox_gem_prime_import_sg_table( + struct drm_device *dev, struct dma_buf_attachment *attach, + struct sg_table *table) +#endif +{ + WARN_ONCE(1, "not implemented"); + return ERR_PTR(-ENOSYS); +} + +void *vbox_gem_prime_vmap(struct drm_gem_object *obj) +{ + WARN_ONCE(1, "not implemented"); + return ERR_PTR(-ENOSYS); +} + +void vbox_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr) +{ + WARN_ONCE(1, "not implemented"); +} + +int vbox_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *area) +{ + WARN_ONCE(1, "not implemented"); + return -ENOSYS; +} diff --git a/src/VBox/Additions/linux/drm/vbox_ttm.c b/src/VBox/Additions/linux/drm/vbox_ttm.c new file mode 100644 index 00000000..ec615787 --- /dev/null +++ b/src/VBox/Additions/linux/drm/vbox_ttm.c @@ -0,0 +1,550 @@ +/* $Id: vbox_ttm.c $ */ +/** @file + * VirtualBox Additions Linux kernel video driver + */ + +/* + * Copyright (C) 2013-2019 Oracle Corporation + * 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" +#include <ttm/ttm_page_alloc.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) && !defined(RHEL_72) +#define PLACEMENT_FLAGS(placement) (placement) +#else +#define PLACEMENT_FLAGS(placement) ((placement).flags) +#endif + +static inline struct vbox_private *vbox_bdev(struct ttm_bo_device *bd) +{ + return container_of(bd, struct vbox_private, ttm.bdev); +} + +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; + + 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; + 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"); + drm_global_item_unref(&vbox->ttm.mem_global_ref); + 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); +} + +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; +} + +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; +} + +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, TTM_PL_FLAG_SYSTEM); + *pl = vboxbo->placement; +} + +static int vbox_bo_verify_access(struct ttm_buffer_object *bo, + struct file *filp) +{ + return 0; +} + +static int vbox_ttm_io_mem_reserve(struct ttm_bo_device *bdev, + struct ttm_mem_reg *mem) +{ + struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type]; + struct vbox_private *vbox = vbox_bdev(bdev); + + 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; +} + +static void vbox_ttm_io_mem_free(struct ttm_bo_device *bdev, + struct ttm_mem_reg *mem) +{ +} + +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, +}; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)) && !defined(RHEL_76) +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; + + tt->func = &vbox_tt_backend_func; +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0)) && !defined(RHEL_76) + if (ttm_tt_init(tt, bdev, size, page_flags, dummy_read_page)) { +#else + if (ttm_tt_init(tt, bo, page_flags)) { +#endif + kfree(tt); + return NULL; + } + + return tt; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0) +# if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) && !defined(RHEL_76) +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 + +static struct ttm_bo_driver vbox_bo_driver = { + .ttm_tt_create = vbox_ttm_tt_create, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0) + .ttm_tt_populate = vbox_ttm_tt_populate, + .ttm_tt_unpopulate = vbox_ttm_tt_unpopulate, +#endif + .init_mem_type = vbox_bo_init_mem_type, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) || defined(RHEL_74) + .eviction_valuable = ttm_bo_eviction_valuable, +#endif + .evict_flags = vbox_bo_evict_flags, + .verify_access = vbox_bo_verify_access, + .io_mem_reserve = &vbox_ttm_io_mem_reserve, + .io_mem_free = &vbox_ttm_io_mem_free, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) || defined(RHEL_75) +# if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) && !defined(RHEL_76) + .io_mem_pfn = ttm_bo_default_io_mem_pfn, +# endif +#endif +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0)) \ + || defined(RHEL_74) +# ifndef RHEL_75 + .lru_tail = &ttm_bo_default_lru_tail, + .swap_lru_tail = &ttm_bo_default_swap_lru_tail, +# endif +#endif +}; + +int vbox_mm_init(struct vbox_private *vbox) +{ + int ret; + struct drm_device *dev = vbox->dev; + struct ttm_bo_device *bdev = &vbox->ttm.bdev; + + ret = vbox_ttm_global_init(vbox); + if (ret) + return ret; + + ret = ttm_bo_device_init(&vbox->ttm.bdev, + vbox->ttm.bo_global_ref.ref.object, + &vbox_bo_driver, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0) || defined(RHEL_71) + dev->anon_inode->i_mapping, +#endif + DRM_FILE_PAGE_OFFSET, true); + if (ret) { + DRM_ERROR("Error initialising bo driver; %d\n", ret); + goto err_ttm_global_release; + } + + ret = ttm_bo_init_mm(bdev, TTM_PL_VRAM, + vbox->available_vram_size >> PAGE_SHIFT); + 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(dev->pdev, 0), + pci_resource_len(dev->pdev, 0), + DRM_MTRR_WC); +#else + vbox->fb_mtrr = arch_phys_wc_add(pci_resource_start(dev->pdev, 0), + pci_resource_len(dev->pdev, 0)); +#endif + return 0; + +err_device_release: + ttm_bo_device_release(&vbox->ttm.bdev); +err_ttm_global_release: + vbox_ttm_global_release(vbox); + return ret; +} + +void vbox_mm_fini(struct vbox_private *vbox) +{ +#ifdef DRM_MTRR_WC + drm_mtrr_del(vbox->fb_mtrr, + pci_resource_start(vbox->dev->pdev, 0), + pci_resource_len(vbox->dev->pdev, 0), DRM_MTRR_WC); +#else + arch_phys_wc_del(vbox->fb_mtrr); +#endif + ttm_bo_device_release(&vbox->ttm.bdev); + vbox_ttm_global_release(vbox); +} + +void vbox_ttm_placement(struct vbox_bo *bo, int domain) +{ + u32 c = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) && !defined(RHEL_72) + 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 (domain & TTM_PL_FLAG_VRAM) + PLACEMENT_FLAGS(bo->placements[c++]) = + TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_VRAM; + if (domain & TTM_PL_FLAG_SYSTEM) + PLACEMENT_FLAGS(bo->placements[c++]) = + TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM; + if (!c) + PLACEMENT_FLAGS(bo->placements[c++]) = + TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM; + + bo->placement.num_placement = c; + bo->placement.num_busy_placement = c; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) || defined(RHEL_72) + for (i = 0; i < c; ++i) { + bo->placements[i].fpfn = 0; + bo->placements[i].lpfn = 0; + } +#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; + size_t acc_size; + 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; + + vboxbo->bo.bdev = &vbox->ttm.bdev; +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0) && !defined(RHEL_71) + vboxbo->bo.bdev->dev_mapping = dev->dev_mapping; +#endif + + vbox_ttm_placement(vboxbo, TTM_PL_FLAG_VRAM | TTM_PL_FLAG_SYSTEM); + + acc_size = ttm_bo_dma_acc_size(&vbox->ttm.bdev, size, + sizeof(struct vbox_bo)); + + ret = ttm_bo_init(&vbox->ttm.bdev, &vboxbo->bo, size, + ttm_bo_type_device, &vboxbo->placement, +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 17, 0) && !defined(RHEL_76) + align >> PAGE_SHIFT, false, NULL, acc_size, +#else + align >> PAGE_SHIFT, false, acc_size, +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 18, 0) || defined(RHEL_72) + NULL, NULL, vbox_bo_ttm_destroy); +#else + NULL, vbox_bo_ttm_destroy); +#endif + if (ret) + goto err_free_vboxbo; + + *pvboxbo = vboxbo; + + return 0; + +err_free_vboxbo: + kfree(vboxbo); + return ret; +} + +static inline u64 vbox_bo_gpu_offset(struct vbox_bo *bo) +{ + return bo->bo.offset; +} + +int vbox_bo_pin(struct vbox_bo *bo, u32 pl_flag, u64 *gpu_addr) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)) || defined(RHEL_76) + struct ttm_operation_ctx ctx = { false, false }; +#endif + int i, ret; + + if (bo->pin_count) { + bo->pin_count++; + if (gpu_addr) + *gpu_addr = vbox_bo_gpu_offset(bo); + + return 0; + } + + vbox_ttm_placement(bo, pl_flag); + + for (i = 0; i < bo->placement.num_placement; i++) + PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) && !defined(RHEL_76) + 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 (gpu_addr) + *gpu_addr = vbox_bo_gpu_offset(bo); + + return 0; +} + +int vbox_bo_unpin(struct vbox_bo *bo) +{ +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)) || defined(RHEL_76) + 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; + + for (i = 0; i < bo->placement.num_placement; i++) + PLACEMENT_FLAGS(bo->placements[i]) &= ~TTM_PL_FLAG_NO_EVICT; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) && !defined(RHEL_76) + 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; + + return 0; +} + +/* + * 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 (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0)) || defined(RHEL_76) + 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, TTM_PL_FLAG_SYSTEM); + + for (i = 0; i < bo->placement.num_placement; i++) + PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT; + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)) && !defined(RHEL_76) + 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; +} + +int vbox_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct drm_file *file_priv; + struct vbox_private *vbox; + + if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET)) + return -EINVAL; + + file_priv = filp->private_data; + vbox = file_priv->minor->dev->dev_private; + + return ttm_bo_mmap(filp, vma, &vbox->ttm.bdev); +} diff --git a/src/VBox/Additions/linux/export_modules.sh b/src/VBox/Additions/linux/export_modules.sh new file mode 100755 index 00000000..85f756a2 --- /dev/null +++ b/src/VBox/Additions/linux/export_modules.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# $Id$ +## @file +# Create a tar archive containing the sources of the Linux guest kernel modules. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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_REV=`sed -e 's/^ *VBOX_SVN_REV_FALLBACK *:= \+\$(patsubst *%:,, *\$Rev: *\([0-9]\+\) *\$ *) */\1/;t;d' $PATH_ROOT/Config.kmk` +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..6eecc008 --- /dev/null +++ b/src/VBox/Additions/linux/installer/.scm-settings @@ -0,0 +1,20 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for linux guest additions installer. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + + +/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..5469daf6 --- /dev/null +++ b/src/VBox/Additions/linux/installer/autorun.sh @@ -0,0 +1,190 @@ +#!/bin/sh +# $Id: autorun.sh $ +## @file +# VirtualBox Guest Additions installation script for *nix guests +# + +# +# Copyright (C) 2009-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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 + ;; + esac + fi + done + + # else: unknown failure + echo "Linux guest additions installer not found -- try to start them 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..694605f6 --- /dev/null +++ b/src/VBox/Additions/linux/installer/deffiles @@ -0,0 +1,84 @@ +# $Id: deffiles $ +## @file +# VirtualBox linux Guest Additions default files list +# + +# +# Copyright (C) 2009-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# 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/VBoxClient \ + /usr/bin/VBoxControl \ + /usr/sbin/vbox-greeter \ + /usr/sbin/vboxadd-timesync \ + /usr/sbin/vboxadd-service \ + /usr/sbin/VBoxService \ + /sbin/mount.vboxsf \ + /usr/lib/VBoxOGLarrayspu.so \ + /usr/lib64/VBoxOGLarrayspu.so \ + /usr/lib/VBoxOGLcrutil.so \ + /usr/lib64/VBoxOGLcrutil.so \ + /usr/lib/VBoxOGLerrorspu.so \ + /usr/lib64/VBoxOGLerrorspu.so \ + /usr/lib/VBoxOGLfeedbackspu.so \ + /usr/lib64/VBoxOGLfeedbackspu.so \ + /usr/lib/VBoxOGLpackspu.so \ + /usr/lib64/VBoxOGLpackspu.so \ + /usr/lib/VBoxOGLpassthroughspu.so \ + /usr/lib64/VBoxOGLpassthroughspu.so \ + /usr/lib/VBoxOGL.so \ + /usr/lib64/VBoxOGL.so \ + /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..aa76eab3 --- /dev/null +++ b/src/VBox/Additions/linux/installer/install.sh.in @@ -0,0 +1,632 @@ +#!/bin/sh +# +# Oracle VM VirtualBox +# VirtualBox Makeself installation starter script +# for Linux Guest Additions + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# 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_TYPE="_BUILDTYPE_" +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" + +## @todo r=andy: Explain options like "force" and "no_setup" -- not self-explanatory +# to the user. +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. + +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" +DO_SETUP="true" +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" + DO_SETUP="" + 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) # Keep "no_setup" for backwards compatibility. + DO_SETUP="" + ;; + + --no-cleanup|no_cleanup) # Keep "no_cleanup" for backwards compatibility. + # Do not do cleanup of old modules when removing them. For + # testing purposes only. + DO_SETUP="" + 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" && + eval `grep ^INSTALL_DIR= "$CONFIG_DIR/$CONFIG"` 2>/dev/null && + eval `grep ^UNINSTALLER= "$CONFIG_DIR/$CONFIG"` 2>/dev/null +if test -n "$INSTALL_DIR" -a -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 +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_TYPE='$BUILD_TYPE' +USERNAME='$USERNAME' +EOF + +# Give the modules the chance to write their stuff +# to the installation config as well. +if [ -n "${INSTALLATION_MODULES_LIST}" ]; then + info "Saving modules configuration ..." + for CUR_MODULE in ${INSTALLATION_MODULES_LIST} + do + echo "`mod_${CUR_MODULE}_config_save`" >> "$CONFIG_DIR/$CONFIG" + done +fi + +"$INSTALLATION_DIR/init/vboxadd" setup 1>&2 2>> "${LOGFILE}" + +# Install, set up and start init scripts +install_init_script "$INSTALLATION_DIR"/init/vboxadd vboxadd 2>> "$LOGFILE" +install_init_script "$INSTALLATION_DIR"/init/vboxadd-service vboxadd-service \ + 2>> "$LOGFILE" +finish_init_script_install +addrunlevel vboxadd 2>> "$LOGFILE" +addrunlevel vboxadd-service 2>> "$LOGFILE" +start_init_script vboxadd 2>> "$LOGFILE" +start_init_script vboxadd-service 2>> "$LOGFILE" + +cp $ROUTINES $INSTALLATION_DIR +echo $INSTALLATION_DIR/$ROUTINES >> "$CONFIG_DIR/$CONFIG_FILES" +cat > $INSTALLATION_DIR/$UNINSTALL << EOF +#!/bin/sh +# Auto-generated uninstallation file + +PATH=\$PATH:/bin:/sbin:/usr/sbin +LOGFILE="/var/log/vboxadd-uninstall.log" + +# Read routines.sh +if ! test -r "$INSTALLATION_DIR/$ROUTINES"; then + echo 1>&2 "Required file $ROUTINES not found. Aborting..." + return 1 +fi +. "$INSTALLATION_DIR/$ROUTINES" + +# We need to be run as root +check_root + +create_log "\$LOGFILE" + +echo 1>&2 "Removing installed version $INSTALLATION_VER of $PACKAGE_NAME..." + +NO_CLEANUP="" +if test "\$1" = "no_cleanup"; then + shift + NO_CLEANUP="no_cleanup" +fi + +test -r "$CONFIG_DIR/$CONFIG_FILES" || abort "Required file $CONFIG_FILES not found. Aborting..." + +# Stop and clean up all services +if test -r "$INSTALLATION_DIR"/init/vboxadd-service; then + stop_init_script vboxadd-service 2>> "\$LOGFILE" + delrunlevel vboxadd-service 2>> "\$LOGFILE" + remove_init_script vboxadd-service 2>> "\$LOGFILE" +fi +if test -r "$INSTALLATION_DIR"/init/vboxadd; then + stop_init_script vboxadd 2>> "\$LOGFILE" + test -n "\$NO_CLEANUP" || "$INSTALLATION_DIR"/init/vboxadd cleanup 2>> "\$LOGFILE" + delrunlevel vboxadd 2>> "\$LOGFILE" + remove_init_script vboxadd 2>> "\$LOGFILE" +fi +finish_init_script_install + +# Load all modules +# Important: This needs to be done before loading the configuration +# value below to not override values which are set to a default +# value in the modules itself. +for CUR_MODULE in `find "$INSTALLATION_MODULES_DIR" -name "module-*" 2>/dev/null` + do + . "\$CUR_MODULE" + done + +# Load configuration values +test -r "$CONFIG_DIR/$CONFIG" && . "$CONFIG_DIR/$CONFIG" + +# Call uninstallation initialization of all modules +for CUR_MODULE in "$INSTALLATION_MODULES_LIST" + do + if test -z "\$CUR_MODULE"; then + continue + fi + mod_\${CUR_MODULE}_pre_uninstall + if [ $? -ne 0 ]; then + echo 1>&2 "Module \"\$CUR_MODULE\" failed to initialize uninstallation" + # Continue initialization. + fi + done + +# Call uninstallation of all modules +for CUR_MODULE in "$INSTALLATION_MODULES_LIST" + do + if test -z "\$CUR_MODULE"; then + continue + fi + mod_\${CUR_MODULE}_uninstall + if [ $? -ne 0 ]; then + echo 1>&2 "Module \"\$CUR_MODULE\" failed to uninstall" + # Continue uninstallation. + fi + done + +# And remove all files and empty installation directories +# Remove any non-directory entries +cat "$CONFIG_DIR/$CONFIG_FILES" | xargs rm 2>/dev/null +# Remove any empty (of files) directories in the file list +cat "$CONFIG_DIR/$CONFIG_FILES" | + while read file; do + case "\$file" in + */) + test -d "\$file" && + find "\$file" -depth -type d -exec rmdir '{}' ';' 2>/dev/null + ;; + esac + done + +# Remove configuration files +rm "$CONFIG_DIR/$CONFIG_FILES" 2>/dev/null +rm "$CONFIG_DIR/$CONFIG" 2>/dev/null +rmdir "$CONFIG_DIR" 2>/dev/null +exit 0 +EOF +chmod 0755 $INSTALLATION_DIR/$UNINSTALL +echo $INSTALLATION_DIR/$UNINSTALL >> "$CONFIG_DIR/$CONFIG_FILES" +test -n "$REMOVE_INSTALLATION_DIR" && + echo "$INSTALLATION_DIR/" >> "$CONFIG_DIR/$CONFIG_FILES" + +cat > "${PUBLIC_UNINSTALL_HOOK}" << EOF +#!/bin/sh +# This executable provides a well-known way to uninstall VirtualBox Guest +# Additions in order to re-install them from a different source. A common case +# is uninstalling distribution-provide Additions to install the version provided +# by VirtualBox. Distributions should put the right command in here to do the +# removal, e.g. "dnf remove VirtualBox-guest-additions". Leaving kernel modules +# provided by the distribution kernel package in place is acceptable if the +# location does not clash with the VirtualBox-provided module location (misc). +$INSTALLATION_DIR/$UNINSTALL +EOF +chmod 0755 "${PUBLIC_UNINSTALL_HOOK}" +echo "$PUBLIC_UNINSTALL_HOOK" >> "$CONFIG_DIR/$CONFIG_FILES" + +# Test for a problem with old Mesa versions which stopped our 3D libraries +# from working. Known to affect Debian 7.11, probably OL/RHEL 5. +# The problem was that the system Mesa library had an ABI note, which caused +# ldconfig to always prefer it to ours. +if ldconfig -p | grep -q "libGL.so.1.*Linux 2.4"; then + gl_with_abi=`ldconfig -p | grep "libGL.so.1.*Linux 2.4" | sed 's/.*=> //'` + cat >&2 << EOF +This system appears to be running a version of Mesa with a known problem +which will prevent VirtualBox 3D pass-through from working. See + https://bugs.freedesktop.org/show_bug.cgi?id=26663 +The following, run as root should fix this, though you will have to run it +again if the system version of Mesa is updated: +EOF + for i in ${gl_with_abi}; do + echo >&2 " strip -R .note.ABI-tag ${i}" + done + echo >&2 " ldconfig" +fi + +# 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..46f8e333 --- /dev/null +++ b/src/VBox/Additions/linux/installer/module-autologon.sh @@ -0,0 +1,172 @@ +# Oracle VM VirtualBox +# $Id: module-autologon.sh $ +## @file +# VirtualBox Linux Guest Additions installer - autologon module +# + +# +# Copyright (C) 2012-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# @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..5d6c5525 --- /dev/null +++ b/src/VBox/Additions/linux/installer/vboxadd-service.sh @@ -0,0 +1,159 @@ +#!/bin/sh +# $Id: vboxadd-service.sh $ +## @file +# Linux Additions Guest Additions service daemon init script. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# X-Conflicts-With is our own invention, which we use when converting to +# a systemd unit. + +# chkconfig: 345 35 65 +# description: VirtualBox Additions service +# +### BEGIN INIT INFO +# Provides: vboxadd-service +# Required-Start: vboxadd +# Required-Stop: vboxadd +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# X-Conflicts-With: systemd-timesyncd.service +# Description: VirtualBox Additions Service +### END INIT INFO + +PATH=$PATH:/bin:/sbin:/usr/sbin +SCRIPTNAME=vboxadd-service.sh + +PIDFILE="/var/run/${SCRIPTNAME}" + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +begin() +{ + test -n "${2}" && echo "${SCRIPTNAME}: ${1}." + logger -t "${SCRIPTNAME}" "${1}." +} + +succ_msg() +{ + logger -t "${SCRIPTNAME}" "${1}." +} + +fail_msg() +{ + echo "${SCRIPTNAME}: ${1}." >&2 + logger -t "${SCRIPTNAME}" "${1}." +} + +daemon() { + $1 $2 $3 +} + +killproc() { + killall $1 + rm -f $PIDFILE +} + +if which start-stop-daemon >/dev/null 2>&1; then + daemon() { + start-stop-daemon --start --exec $1 -- $2 $3 + } + + killproc() { + start-stop-daemon --stop --retry 2 --exec $@ + } +fi + +binary=/usr/sbin/VBoxService + +testbinary() { + test -x "$binary" || { + echo "Cannot run $binary" + exit 1 + } +} + +vboxaddrunning() { + lsmod | grep -q "vboxguest[^_-]" +} + +start() { + if ! test -f $PIDFILE; then + begin "Starting VirtualBox Guest Addition service" console; + vboxaddrunning || { + echo "VirtualBox Additions module not loaded!" + exit 1 + } + testbinary + daemon $binary --pidfile $PIDFILE > /dev/null + RETVAL=$? + succ_msg "VirtualBox Guest Addition service started" + fi + return $RETVAL +} + +stop() { + if test -f $PIDFILE; then + begin "Stopping VirtualBox Guest Addition service" console; + killproc $binary + RETVAL=$? + if ! pidof VBoxService > /dev/null 2>&1; then + rm -f $PIDFILE + succ_msg "VirtualBox Guest Addition service stopped" + else + fail_msg "VirtualBox Guest Addition service failed to stop" + fi + fi + return $RETVAL +} + +restart() { + stop && start +} + +status() { + echo -n "Checking for VBoxService" + if [ -f $PIDFILE ]; then + echo " ...running" + else + echo " ...not running" + fi +} + +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +status) + status + ;; +setup) + ;; +cleanup) + ;; +*) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 +esac + +exit $RETVAL diff --git a/src/VBox/Additions/linux/installer/vboxadd-x11.sh b/src/VBox/Additions/linux/installer/vboxadd-x11.sh new file mode 100755 index 00000000..6b6787b0 --- /dev/null +++ b/src/VBox/Additions/linux/installer/vboxadd-x11.sh @@ -0,0 +1,613 @@ +#! /bin/sh +# $Id: vboxadd-x11.sh $ +## @file +# Linux Additions X11 setup init script ($Revision: 127855 $) +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + + +# 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 + chcon -h -t lib_t "/var/lib/VBoxGuestAdditions/lib/libGL.so.1" + 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 + ;; + 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 -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..5eae0cb1 --- /dev/null +++ b/src/VBox/Additions/linux/installer/vboxadd.sh @@ -0,0 +1,595 @@ +#! /bin/sh +# $Id: vboxadd.sh $ +## @file +# Linux Additions kernel module init script ($Revision: 128393 $) +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# 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 user and vboxsf 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" +QUICKSETUP= +## systemd logs information about service status, otherwise do that ourselves. +QUIET= +test -z "${TARGET_VER}" && TARGET_VER=`uname -r` +# Marker to ignore a particular kernel version which was already installed. +SKIPFILE_BASE=/var/lib/VBoxGuestAdditions/skip + +setup_log() +{ + test -z "${LOG}" || return 0 + # Rotate log files + LOG="/var/log/vboxadd-setup.log" + mv "${LOG}.3" "${LOG}.4" 2>/dev/null + mv "${LOG}.2" "${LOG}.3" 2>/dev/null + mv "${LOG}.1" "${LOG}.2" 2>/dev/null + mv "${LOG}" "${LOG}.1" 2>/dev/null +} + +if $MODPROBE -c 2>/dev/null | grep -q '^allow_unsupported_modules *0'; then + MODPROBE="$MODPROBE --allow-unsupported-modules" +fi + +# Check architecture +cpu=`uname -m`; +case "$cpu" in + i[3456789]86|x86) + cpu="x86" + ldconfig_arch="(libc6)" + lib_candidates="/usr/lib/i386-linux-gnu /usr/lib /lib" + ;; + x86_64|amd64) + cpu="amd64" + ldconfig_arch="(libc6,x86-64)" + lib_candidates="/usr/lib/x86_64-linux-gnu /usr/lib64 /usr/lib /lib64 /lib" + ;; +esac +for i in $lib_candidates; do + if test -d "$i/VBoxGuestAdditions"; then + lib_path=$i + break + fi +done + +# Preamble for Gentoo +if [ "`which $0`" = "/sbin/rc" ]; then + shift +fi + +begin() +{ + test -n "${QUIET}" || echo "${SERVICE}: ${1}" +} + +info() +{ + if test -z "${QUIET}"; then + echo "${SERVICE}: $1" | fold -s + else + echo "$1" | fold -s + fi +} + +fail() +{ + log "${1}" + echo "$1" >&2 + echo "The log file $LOG may contain further information." >&2 + exit 1 +} + +log() +{ + setup_log + echo "${1}" >> "${LOG}" +} + +module_build_log() +{ + log "Error building the module. Build output follows." + echo "" + echo "${1}" >> "${LOG}" +} + +dev=/dev/vboxguest +userdev=/dev/vboxuser +config=/var/lib/VBoxGuestAdditions/config +owner=vboxadd +group=1 + +if test -r $config; then + . $config +else + fail "Configuration file $config not found" +fi +test -n "$INSTALL_DIR" -a -n "$INSTALL_VER" || + fail "Configuration file $config not complete" + +running_vboxguest() +{ + lsmod | grep -q "vboxguest[^_-]" +} + +running_vboxadd() +{ + lsmod | grep -q "vboxadd[^_-]" +} + +running_vboxsf() +{ + lsmod | grep -q "vboxsf[^_-]" +} + +running_vboxvideo() +{ + lsmod | grep -q "vboxvideo[^_-]" +} + +do_vboxguest_non_udev() +{ + if [ ! -c $dev ]; then + maj=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/devices` + if [ ! -z "$maj" ]; then + min=0 + else + min=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/misc` + if [ ! -z "$min" ]; then + maj=10 + fi + fi + test -n "$maj" || { + rmmod vboxguest 2>/dev/null + fail "Cannot locate the VirtualBox device" + } + + mknod -m 0664 $dev c $maj $min || { + rmmod vboxguest 2>/dev/null + fail "Cannot create device $dev with major $maj and minor $min" + } + fi + chown $owner:$group $dev 2>/dev/null || { + rm -f $dev 2>/dev/null + rm -f $userdev 2>/dev/null + rmmod vboxguest 2>/dev/null + fail "Cannot change owner $owner:$group for device $dev" + } + + if [ ! -c $userdev ]; then + maj=10 + min=`sed -n 's;\([0-9]\+\) vboxuser;\1;p' /proc/misc` + if [ ! -z "$min" ]; then + mknod -m 0666 $userdev c $maj $min || { + rm -f $dev 2>/dev/null + rmmod vboxguest 2>/dev/null + fail "Cannot create device $userdev with major $maj and minor $min" + } + chown $owner:$group $userdev 2>/dev/null || { + rm -f $dev 2>/dev/null + rm -f $userdev 2>/dev/null + rmmod vboxguest 2>/dev/null + fail "Cannot change owner $owner:$group for device $userdev" + } + fi + fi +} + +start() +{ + begin "Starting." + if test -z "${INSTALL_NO_MODULE_BUILDS}"; then + setup --quick + test -d /sys && + ps -A -o comm | grep -q '/*udevd$' 2>/dev/null || + no_udev=1 + running_vboxguest || { + rm -f $dev || { + fail "Cannot remove $dev" + } + rm -f $userdev || { + fail "Cannot remove $userdev" + } + $MODPROBE vboxguest >/dev/null 2>&1 || + fail "modprobe vboxguest failed" + case "$no_udev" in 1) + sleep .5;; + esac + } + case "$no_udev" in 1) + do_vboxguest_non_udev;; + esac + + running_vboxsf || { + $MODPROBE vboxsf > /dev/null 2>&1 || + info "modprobe vboxsf failed" + } + fi # INSTALL_NO_MODULE_BUILDS + + # 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}" + + # Mount all shared folders from /etc/fstab. Normally this is done by some + # other startup script but this requires the vboxdrv kernel module loaded. + # This isn't necessary anymore as the vboxsf module is autoloaded. + # mount -a -t vboxsf + + return 0 +} + +stop() +{ + begin "Stopping." + test -n "${INSTALL_NO_MODULE_BUILDS}" || setup --quick + 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 + fail "Cannot unmount vboxsf folders" + fi + test -n "${INSTALL_NO_MODULE_BUILDS}" || + info "You may need to restart your guest system to finish removing the guest drivers." + return 0 +} + +restart() +{ + stop && start + return 0 +} + +## Update the initramfs. Debian and Ubuntu put the graphics driver in, and +# need the touch(1) command below. Everyone else that I checked just need +# the right module alias file from depmod(1) and only use the initramfs to +# load the root filesystem, not the boot splash. update-initramfs works +# for the first two and dracut for every one else I checked. We are only +# interested in distributions recent enough to use the KMS vboxvideo driver. +update_initramfs() +{ + ## kernel version to update for. + version="${1}" + depmod "${version}" + rm -f "/lib/modules/${version}/initrd/vboxvideo" + test ! -d "/lib/modules/${version}/initrd" || + test ! -f "/lib/modules/${version}/misc/vboxvideo.ko" || + touch "/lib/modules/${version}/initrd/vboxvideo" + + # Systems without systemd-inhibit probably don't need their initramfs + # rebuild here anyway. + type systemd-inhibit >/dev/null 2>&1 || return + if type dracut >/dev/null 2>&1; then + systemd-inhibit --why="Installing VirtualBox Guest Additions" \ + dracut -f --kver "${version}" + elif type update-initramfs >/dev/null 2>&1; then + systemd-inhibit --why="Installing VirtualBox Guest Additions" \ + update-initramfs -u -k "${version}" + fi +} + +# Remove any existing VirtualBox guest kernel modules from the disk, but not +# from the kernel as they may still be in use +cleanup_modules() +{ + # Needed for Ubuntu and Debian, see update_initramfs + rm -f /lib/modules/*/initrd/vboxvideo + for i in /lib/modules/*/misc; do + KERN_VER="${i%/misc}" + KERN_VER="${KERN_VER#/lib/modules/}" + unset do_update + for j in ${OLDMODULES}; do + test -f "${i}/${j}.ko" && do_update=1 && rm -f "${i}/${j}.ko" + done + test -z "$do_update" || update_initramfs "$KERN_VER" + # Remove empty /lib/modules folders which may have been kept around + rmdir -p "${i}" 2>/dev/null || true + unset keep + for j in /lib/modules/"${KERN_VER}"/*; do + name="${j##*/}" + test -d "${name}" || test "${name%%.*}" != modules && keep=1 + done + if test -z "${keep}"; then + rm -rf /lib/modules/"${KERN_VER}" + rm -f /boot/initrd.img-"${KERN_VER}" + fi + done + for i in ${OLDMODULES}; do + # We no longer support DKMS, remove any leftovers. + rm -rf "/var/lib/dkms/${i}"* + done + rm -f /etc/depmod.d/vboxvideo-upstream.conf + rm -f "$SKIPFILE_BASE"-* +} + +# Build and install the VirtualBox guest kernel modules +setup_modules() +{ + KERN_VER="$1" + test -n "$KERN_VER" || return 1 + test ! -f /lib/modules/"$KERN_VER"/misc/vboxguest.ko || return 0 + test ! -f /lib/modules/"$KERN_VER"/misc/vboxguest.o || return 0 + test -d /lib/modules/"$KERN_VER"/build || return 0 + test ! -f "$SKIPFILE_BASE"-"$KERN_VER" || return 0 + export KERN_VER + info "Building the modules for kernel $KERN_VER." + + log "Building the main Guest Additions 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 + 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 + 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 +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" + fi +} + +# setup_script +setup() +{ + export BUILD_TYPE + export USERNAME + + test x"$1" != x--quick || QUICKSETUP=true + test -n "$QUICKSETUP" || cleanup + MODULE_SRC="$INSTALL_DIR/src/vboxguest-$INSTALL_VER" + BUILDINTMP="$MODULE_SRC/build_in_tmp" + test ! -e /etc/selinux/config || + chcon -t bin_t "$BUILDINTMP" + + if test -z "$INSTALL_NO_MODULE_BUILDS"; then + if test -z "$QUICKSETUP"; then + 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>" + for setupi in /lib/modules/*; do + KERN_VER="${setupi##*/}" + # For a full setup, mark kernels we do not want to build. + touch "$SKIPFILE_BASE"-"$KERN_VER" + done + fi + # That is, we mark all but the requested kernel. + rm -f "$SKIPFILE_BASE"-"$TARGET_VER" + test -d /lib/modules/"$TARGET_VER"/build || test -n "$QUICKSETUP" || + info "Kernel headers not found for target kernel $TARGET_VER. \ +Please install them and execute + /sbin/rcvboxadd setup" + for setupi in /lib/modules/*; do + KERN_VER="${setupi##*/}" + setup_modules "$KERN_VER" + done + depmod + fi + create_vbox_user + create_udev_rule + test -n "${INSTALL_NO_MODULE_BUILDS}" || create_module_rebuild_script + test -z "$QUICKSETUP" || return 0 + shared_folder_setup + if running_vboxguest || running_vboxadd; then + info "Running kernel modules will not be replaced until the system is restarted" + fi + 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 + fi + rm /sbin/mount.vboxsf 2>/dev/null + rm /etc/udev/rules.d/60-vboxadd.rules 2>/dev/null +} + +dmnstatus() +{ + if running_vboxguest; then + echo "The VirtualBox Additions are currently running." + else + echo "The VirtualBox Additions are not currently running." + fi +} + +case "$2" in quiet) + QUIET=yes;; +esac +case "$1" in +start) + start + ;; +stop) + stop + ;; +restart) + restart + ;; +setup) + setup + start + ;; +quicksetup) + test -z "$2" || test ! -d /lib/modules/"$2"/build || TARGET_VER="$2" + setup --quick + ;; +cleanup) + cleanup + ;; +status) + dmnstatus + ;; +*) + echo "Usage: $0 {start|stop|restart|status|setup|quicksetup|cleanup} [quiet]" + exit 1 +esac + +exit diff --git a/src/VBox/Additions/linux/lightdm-greeter/.scm-settings b/src/VBox/Additions/linux/lightdm-greeter/.scm-settings new file mode 100644 index 00000000..e425fa24 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/.scm-settings @@ -0,0 +1,20 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for light-dm greeter. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + + +/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..c8373c94 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/Config.kmk @@ -0,0 +1,31 @@ +# $Id: Config.kmk $ +## @file +# kBuild Configuration file for the lightdm-greeter +# + +# +# Copyright (C) 2016-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + + +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..5cc838c6 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk @@ -0,0 +1,104 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for VBox LightDM greeter for providing automated logons. +# + +# +# Copyright (C) 2012-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +ifndef VBOX_LIGHTDM_GREETER_CONFIG_KMK_INCLUDED + include $(PATH_SUB_CURRENT)/Config.kmk +endif + +ifndef VBOX_OSE + include $(PATH_SUB_CURRENT)/liblightdm-gobject-1.5.0/Makefile.kmk +endif + +# Enable building with FLTK UI + PNG support. +VBOX_WITH_FLTK := 1 +VBOX_GREETER_WITH_PNG_SUPPORT := 1 + +# The greeter module. +PROGRAMS += vbox-greeter +vbox-greeter_TEMPLATE = VBOXGUESTR3EXE +vbox-greeter_SDKS = VBoxGlib20WithIo +vbox-greeter_DEFS = LOG_TO_BACKDOOR VBOX_WITH_HGCM +vbox-greeter_DEFS += \ + VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" +vbox-greeter_DEFS += \ + $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \ + $(if $(VBOX_GREETER_WITH_PNG_SUPPORT),VBOX_GREETER_WITH_PNG_SUPPORT,) +ifdef VBOX_WITH_FLTK + vbox-greeter_DEFS += \ + VBOX_WITH_FLTK +else + vbox-greeter_DEFS += \ + GTK_DISABLE_SINGLE_INCLUDES \ + GDK_DISABLE_DEPRECATED +endif +vbox-greeter_CFLAGS := $(if $(VBOX_OSE),%(filter-out -I%,$(shell pkg-config --cflags liblightdm-gobject-1)),) +## @todo r=bird: Why are we cooking our own lightdm-gobject-1 but using system headers? +## That sounds like a very risky business to me. I've added liblightdm-gobject-1.5.0 +## to the INCS, however lightdm.h is missing and will be taken from the system. +vbox-greeter_INCS := \ + /usr/lib/i386-linux-gnu/glib-2.0/include \ + /usr/lib/x86_64-linux-gnu/glib-2.0/include \ + /usr/include/glib-2.0 \ + $(if $(VBOX_OSE),,liblightdm-gobject-1.5.0) \ + /usr/include/lightdm-gobject-1 \ + $(if $(VBOX_OSE),$(patsubst -I%,%,%(filter -I%,$(shell pkg-config --cflags liblightdm-gobject-1))),) +ifndef VBOX_WITH_FLTK + vbox-greeter_INCS += \ + /usr/include/glib-2.0 \ + /usr/include/gtk-3.0 \ + /usr/include/pango-1.0 \ + /usr/include/cairo \ + /usr/include/gdk-pixbuf-2.0 \ + /usr/include/atk-1.0 +endif + +vbox-greeter_SOURCES = \ + vbox-greeter.cpp + +vbox-greeter_LIBS := \ + $(if $(VBOX_OSE),lightdm-gobject-1,$(VBOX_PATH_ADDITIONS_LIB)/VBox-liblightdm-gobject$(VBOX_SUFF_LIB)) \ + glib-2.0 \ + gio-2.0 \ + gobject-2.0 \ + $(VBOX_LIB_IPRT_GUEST_R3_SHARED) \ + $(VBOX_LIB_VBGL_R3_SHARED) \ + $(VBOX_LIB_IPRT_GUEST_R3_SHARED) +ifdef VBOX_WITH_FLTK + vbox-greeter_LIBS += fltk + ifdef VBOX_GREETER_WITH_PNG_SUPPORT + vbox-greeter_LIBS += fltk_images + endif + if $(HOSTNAME) == "3960x.dev" && $(USER) == "bird" # whatever. + vbox-greeter_LIBS += stdc++ + endif +else + vbox-greeter_LIBS += gtk-3 +endif +ifeq ($(KBUILD_TARGET),linux) +vbox-greeter_LIBS += crypt +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..e9950d3b --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk @@ -0,0 +1,44 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for liblighdm-gobject 1.5.0 +# + +# +# Copyright (C) 2013-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +SUB_DEPTH = ../../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# The greeter module. +LIBRARIES += VBox-liblightdm-gobject + +VBox-liblightdm-gobject_TEMPLATE = VBOXGUESTR3NPLIB +VBox-liblightdm-gobject_SDKS = VBoxGlib20WithIo +VBox-liblightdm-gobject_INCS = \ + /usr/include/glib-2.0 \ + /usr/lib/i386-linux-gnu/glib-2.0/include \ + /usr/lib/x86_64-linux-gnu/glib-2.0/include \ + /usr/include/gio-unix-2.0 +VBox-liblightdm-gobject_DEFS = \ + CONFIG_DIR=\"/etc/lightdm\" \ + XSESSIONS_DIR=\"/usr/share/xsessions\" \ + REMOTE_SESSIONS_DIR=\"/usr/share/lightdm/remote-sessions\" +VBox-liblightdm-gobject_SOURCES = \ + greeter.c \ + language.c \ + layout.c \ + power.c \ + session.c \ + system.c \ + user.c + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h new file mode 100644 index 00000000..c06fc08d --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h @@ -0,0 +1,90 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Gettext package */ +#define GETTEXT_PACKAGE "lightdm" + +/* Greeter session */ +#define GREETER_SESSION "default" + +/* User to run greeter as */ +#define GREETER_USER "lightdm" + +/* Define to 1 if you have the `clearenv' function. */ +#define HAVE_CLEARENV 1 + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the <security/pam_appl.h> header file. */ +#define HAVE_SECURITY_PAM_APPL_H 1 + +/* Define to 1 if you have the `setresgid' function. */ +#define HAVE_SETRESGID 1 + +/* Define to 1 if you have the `setresuid' function. */ +#define HAVE_SETRESUID 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Name of package */ +#define PACKAGE "lightdm" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "lightdm" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "lightdm 1.5.0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "lightdm" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.5.0" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* User session */ +#define USER_SESSION "default" + +/* Version number of package */ +#define VERSION "1.5.0" diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c new file mode 100644 index 00000000..3d44b562 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c @@ -0,0 +1,1442 @@ +/* + * Copyright (C) 2010 Robert Ancell. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2 or version 3 of the License. + * See http://www.gnu.org/copyleft/lgpl.html the full text of the license. + */ + +/* + * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice + * other than GPL or LGPL is available it will apply instead, Oracle elects to use only + * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where + * a choice of LGPL license versions is made available with the language indicating + * that LGPLv2 or any later version may be used, or where a choice of which version + * of the LGPL is applied is otherwise unspecified. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <security/pam_appl.h> + +#include "lightdm/greeter.h" + +enum { + PROP_0, + PROP_DEFAULT_SESSION_HINT, + PROP_HIDE_USERS_HINT, + PROP_SHOW_MANUAL_LOGIN_HINT, + PROP_SHOW_REMOTE_LOGIN_HINT, + PROP_LOCK_HINT, + PROP_HAS_GUEST_ACCOUNT_HINT, + PROP_SELECT_USER_HINT, + PROP_SELECT_GUEST_HINT, + PROP_AUTOLOGIN_USER_HINT, + PROP_AUTOLOGIN_GUEST_HINT, + PROP_AUTOLOGIN_TIMEOUT_HINT, + PROP_AUTHENTICATION_USER, + PROP_IN_AUTHENTICATION, + PROP_IS_AUTHENTICATED, +}; + +enum { + SHOW_PROMPT, + SHOW_MESSAGE, + AUTHENTICATION_COMPLETE, + AUTOLOGIN_TIMER_EXPIRED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0 }; + +typedef struct +{ + gboolean connected; + + GIOChannel *to_server_channel, *from_server_channel; + guint8 *read_buffer; + gsize n_read; + + gsize n_responses_waiting; + GList *responses_received; + + GHashTable *hints; + guint autologin_timeout; + + gchar *authentication_user; + gboolean in_authentication; + gboolean is_authenticated; + guint32 authenticate_sequence_number; + gboolean cancelling_authentication; +} LightDMGreeterPrivate; + +G_DEFINE_TYPE (LightDMGreeter, lightdm_greeter, G_TYPE_OBJECT); + +#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_GREETER, LightDMGreeterPrivate) + +#define HEADER_SIZE 8 +#define MAX_MESSAGE_LENGTH 1024 + +/* Messages from the greeter to the server */ +typedef enum +{ + GREETER_MESSAGE_CONNECT = 0, + GREETER_MESSAGE_AUTHENTICATE, + GREETER_MESSAGE_AUTHENTICATE_AS_GUEST, + GREETER_MESSAGE_CONTINUE_AUTHENTICATION, + GREETER_MESSAGE_START_SESSION, + GREETER_MESSAGE_CANCEL_AUTHENTICATION, + GREETER_MESSAGE_SET_LANGUAGE, + GREETER_MESSAGE_AUTHENTICATE_REMOTE +} GreeterMessage; + +/* Messages from the server to the greeter */ +typedef enum +{ + SERVER_MESSAGE_CONNECTED = 0, + SERVER_MESSAGE_PROMPT_AUTHENTICATION, + SERVER_MESSAGE_END_AUTHENTICATION, + SERVER_MESSAGE_SESSION_RESULT +} ServerMessage; + +/** + * lightdm_greeter_new: + * + * Create a new greeter. + * + * Return value: the new #LightDMGreeter + **/ +LightDMGreeter * +lightdm_greeter_new () +{ + return g_object_new (LIGHTDM_TYPE_GREETER, NULL); +} + +static gboolean +timed_login_cb (gpointer data) +{ + LightDMGreeter *greeter = data; + LightDMGreeterPrivate *priv = GET_PRIVATE (greeter); + + priv->autologin_timeout = 0; + g_signal_emit (G_OBJECT (greeter), signals[AUTOLOGIN_TIMER_EXPIRED], 0); + + return FALSE; +} + +static guint32 +int_length () +{ + return 4; +} + +static void +write_int (guint8 *buffer, gint buffer_length, guint32 value, gsize *offset) +{ + if (*offset + 4 >= buffer_length) + return; + buffer[*offset] = value >> 24; + buffer[*offset+1] = (value >> 16) & 0xFF; + buffer[*offset+2] = (value >> 8) & 0xFF; + buffer[*offset+3] = value & 0xFF; + *offset += 4; +} + +static void +write_string (guint8 *buffer, gint buffer_length, const gchar *value, gsize *offset) +{ + gint length = 0; + + if (value) + length = strlen (value); + write_int (buffer, buffer_length, length, offset); + if (*offset + length >= buffer_length) + return; + memcpy (buffer + *offset, value, length); + *offset += length; +} + +static guint32 +read_int (guint8 *message, gsize message_length, gsize *offset) +{ + guint32 value; + guint8 *buffer; + + if (message_length - *offset < int_length ()) + { + g_warning ("Not enough space for int, need %i, got %zi", int_length (), message_length - *offset); + return 0; + } + + buffer = message + *offset; + value = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]; + *offset += int_length (); + + return value; +} + +static gchar * +read_string (guint8 *message, gsize message_length, gsize *offset) +{ + guint32 length; + gchar *value; + + length = read_int (message, message_length, offset); + if (message_length - *offset < length) + { + g_warning ("Not enough space for string, need %u, got %zu", length, message_length - *offset); + return g_strdup (""); + } + + value = g_malloc (sizeof (gchar) * (length + 1)); + memcpy (value, message + *offset, length); + value[length] = '\0'; + *offset += length; + + return value; +} + +static guint32 +string_length (const gchar *value) +{ + if (value) + return int_length () + strlen (value); + else + return int_length (); +} + +static void +write_header (guint8 *buffer, gint buffer_length, guint32 id, guint32 length, gsize *offset) +{ + write_int (buffer, buffer_length, id, offset); + write_int (buffer, buffer_length, length, offset); +} + +static guint32 +get_message_length (guint8 *message, gsize message_length) +{ + gsize offset = 4; + return read_int (message, message_length, &offset); +} + +static void +write_message (LightDMGreeter *greeter, guint8 *message, gsize message_length) +{ + LightDMGreeterPrivate *priv = GET_PRIVATE (greeter); + GIOStatus status; + GError *error = NULL; + guint32 stated_length; + + /* Double check that we're sending well-formed messages. If we say we're + sending more than we do, we end up DOS'ing lightdm as it waits for the + rest. If we say we're sending less than we do, we confuse the heck out + of lightdm, as it starts reading headers from the middle of our + messages. */ + stated_length = HEADER_SIZE + get_message_length (message, message_length); + if (stated_length != message_length) + { + g_warning ("Refusing to write malformed packet to daemon: declared size is %u, but actual size is %zu", stated_length, message_length); + return; + } + + status = g_io_channel_write_chars (priv->to_server_channel, (gchar *) message, message_length, NULL, &error); + if (error) + g_warning ("Error writing to daemon: %s", error->message); + g_clear_error (&error); + if (status == G_IO_STATUS_NORMAL) + g_debug ("Wrote %zi bytes to daemon", message_length); + g_io_channel_flush (priv->to_server_channel, NULL); +} + +static void +handle_connected (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset) +{ + LightDMGreeterPrivate *priv = GET_PRIVATE (greeter); + gchar *version; + GString *hint_string; + int timeout; + + version = read_string (message, message_length, offset); + hint_string = g_string_new (""); + while (*offset < message_length) + { + gchar *name, *value; + + name = read_string (message, message_length, offset); + value = read_string (message, message_length, offset); + g_hash_table_insert (priv->hints, name, value); + g_string_append_printf (hint_string, " %s=%s", name, value); + } + + g_debug ("Connected version=%s%s", version, hint_string->str); + g_free (version); + g_string_free (hint_string, TRUE); + + /* Set timeout for default login */ + timeout = lightdm_greeter_get_autologin_timeout_hint (greeter); + if (timeout) + { + g_debug ("Setting autologin timer for %d seconds", timeout); + priv->autologin_timeout = g_timeout_add (timeout * 1000, timed_login_cb, greeter); + } +} + +static void +handle_prompt_authentication (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset) +{ + LightDMGreeterPrivate *priv = GET_PRIVATE (greeter); + guint32 sequence_number, n_messages, i; + gchar *username; + + sequence_number = read_int (message, message_length, offset); + if (sequence_number != priv->authenticate_sequence_number) + { + g_debug ("Ignoring prompt authentication with invalid sequence number %d", sequence_number); + return; + } + + if (priv->cancelling_authentication) + { + g_debug ("Ignoring prompt authentication as waiting for it to cancel"); + return; + } + + /* Update username */ + username = read_string (message, message_length, offset); + if (strcmp (username, "") == 0) + { + g_free (username); + username = NULL; + } + g_free (priv->authentication_user); + priv->authentication_user = username; + + g_list_free_full (priv->responses_received, g_free); + priv->responses_received = NULL; + priv->n_responses_waiting = 0; + + n_messages = read_int (message, message_length, offset); + g_debug ("Prompt user with %d message(s)", n_messages); + + for (i = 0; i < n_messages; i++) + { + int style; + gchar *text; + + style = read_int (message, message_length, offset); + text = read_string (message, message_length, offset); + + // FIXME: Should stop on prompts? + switch (style) + { + case PAM_PROMPT_ECHO_OFF: + priv->n_responses_waiting++; + g_signal_emit (G_OBJECT (greeter), signals[SHOW_PROMPT], 0, text, LIGHTDM_PROMPT_TYPE_SECRET); + break; + case PAM_PROMPT_ECHO_ON: + priv->n_responses_waiting++; + g_signal_emit (G_OBJECT (greeter), signals[SHOW_PROMPT], 0, text, LIGHTDM_PROMPT_TYPE_QUESTION); + break; + case PAM_ERROR_MSG: + g_signal_emit (G_OBJECT (greeter), signals[SHOW_MESSAGE], 0, text, LIGHTDM_MESSAGE_TYPE_ERROR); + break; + case PAM_TEXT_INFO: + g_signal_emit (G_OBJECT (greeter), signals[SHOW_MESSAGE], 0, text, LIGHTDM_MESSAGE_TYPE_INFO); + break; + } + + g_free (text); + } +} + +static void +handle_end_authentication (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset) +{ + LightDMGreeterPrivate *priv = GET_PRIVATE (greeter); + guint32 sequence_number, return_code; + gchar *username; + + sequence_number = read_int (message, message_length, offset); + + if (sequence_number != priv->authenticate_sequence_number) + { + g_debug ("Ignoring end authentication with invalid sequence number %d", sequence_number); + return; + } + + username = read_string (message, message_length, offset); + return_code = read_int (message, message_length, offset); + + g_debug ("Authentication complete for user %s with return code %d", username, return_code); + + /* Update username */ + if (strcmp (username, "") == 0) + { + g_free (username); + username = NULL; + } + g_free (priv->authentication_user); + priv->authentication_user = username; + + priv->cancelling_authentication = FALSE; + priv->is_authenticated = (return_code == 0); + + priv->in_authentication = FALSE; + g_signal_emit (G_OBJECT (greeter), signals[AUTHENTICATION_COMPLETE], 0); +} + +static guint8 * +read_message (LightDMGreeter *greeter, gsize *length, gboolean block) +{ + LightDMGreeterPrivate *priv = GET_PRIVATE (greeter); + gsize n_to_read, n_read; + guint8 *buffer; + GError *error = NULL; + + /* Read the header, or the whole message if we already have that */ + n_to_read = HEADER_SIZE; + if (priv->n_read >= HEADER_SIZE) + n_to_read += get_message_length (priv->read_buffer, priv->n_read); + + do + { + GIOStatus status; + status = g_io_channel_read_chars (priv->from_server_channel, + (gchar *) priv->read_buffer + priv->n_read, + n_to_read - priv->n_read, + &n_read, + &error); + if (error) + g_warning ("Error reading from server: %s", error->message); + g_clear_error (&error); + if (status != G_IO_STATUS_NORMAL) + break; + + g_debug ("Read %zi bytes from daemon", n_read); + + priv->n_read += n_read; + } while (priv->n_read < n_to_read && block); + + /* Stop if haven't got all the data we want */ + if (priv->n_read != n_to_read) + return FALSE; + + /* If have header, rerun for content */ + if (priv->n_read == HEADER_SIZE) + { + n_to_read = get_message_length (priv->read_buffer, priv->n_read); + if (n_to_read > 0) + { + priv->read_buffer = g_realloc (priv->read_buffer, HEADER_SIZE + n_to_read); + return read_message (greeter, length, block); + } + } + + buffer = priv->read_buffer; + *length = priv->n_read; + + priv->read_buffer = g_malloc (priv->n_read); + priv->n_read = 0; + + return buffer; +} + +static gboolean +from_server_cb (GIOChannel *source, GIOCondition condition, gpointer data) +{ + LightDMGreeter *greeter = data; + guint8 *message; + gsize message_length, offset; + guint32 id; + + message = read_message (greeter, &message_length, FALSE); + if (!message) + return TRUE; + + offset = 0; + id = read_int (message, message_length, &offset); + read_int (message, message_length, &offset); + switch (id) + { + case SERVER_MESSAGE_PROMPT_AUTHENTICATION: + handle_prompt_authentication (greeter, message, message_length, &offset); + break; + case SERVER_MESSAGE_END_AUTHENTICATION: + handle_end_authentication (greeter, message, message_length, &offset); + break; + default: + g_warning ("Unknown message from server: %d", id); + break; + } + g_free (message); + + return TRUE; +} + +/** + * lightdm_greeter_connect_sync: + * @greeter: The greeter to connect + * @error: return location for a #GError, or %NULL + * + * Connects the greeter to the display manager. Will block until connected. + * + * Return value: #TRUE if successfully connected + **/ +gboolean +lightdm_greeter_connect_sync (LightDMGreeter *greeter, GError **error) +{ + LightDMGreeterPrivate *priv; + const gchar *fd; + guint8 message[MAX_MESSAGE_LENGTH]; + guint8 *response; + gsize response_length, offset = 0; + guint32 id; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + + priv = GET_PRIVATE (greeter); + + fd = g_getenv ("LIGHTDM_TO_SERVER_FD"); + if (!fd) + { + g_warning ("No LIGHTDM_TO_SERVER_FD environment variable"); + return FALSE; + } + priv->to_server_channel = g_io_channel_unix_new (atoi (fd)); + g_io_channel_set_encoding (priv->to_server_channel, NULL, NULL); + + fd = g_getenv ("LIGHTDM_FROM_SERVER_FD"); + if (!fd) + { + g_warning ("No LIGHTDM_FROM_SERVER_FD environment variable"); + return FALSE; + } + priv->from_server_channel = g_io_channel_unix_new (atoi (fd)); + g_io_channel_set_encoding (priv->from_server_channel, NULL, NULL); + g_io_add_watch (priv->from_server_channel, G_IO_IN, from_server_cb, greeter); + + g_debug ("Connecting to display manager..."); + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CONNECT, string_length (VERSION), &offset); + write_string (message, MAX_MESSAGE_LENGTH, VERSION, &offset); + write_message (greeter, message, offset); + + response = read_message (greeter, &response_length, TRUE); + if (!response) + return FALSE; + + offset = 0; + id = read_int (response, response_length, &offset); + read_int (response, response_length, &offset); + if (id == SERVER_MESSAGE_CONNECTED) + handle_connected (greeter, response, response_length, &offset); + g_free (response); + if (id != SERVER_MESSAGE_CONNECTED) + { + g_warning ("Expected CONNECTED message, got %d", id); + return FALSE; + } + + priv->connected = TRUE; + + return TRUE; +} + +/** + * lightdm_greeter_get_hint: + * @greeter: A #LightDMGreeter + * @name: The hint name to query. + * + * Get a hint. + * + * Return value: The value for this hint or #NULL if not set. + **/ +const gchar * +lightdm_greeter_get_hint (LightDMGreeter *greeter, const gchar *name) +{ + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL); + return g_hash_table_lookup (GET_PRIVATE (greeter)->hints, name); +} + +/** + * lightdm_greeter_get_default_session_hint: + * @greeter: A #LightDMGreeter + * + * Get the default session to use. + * + * Return value: The session name + **/ +const gchar * +lightdm_greeter_get_default_session_hint (LightDMGreeter *greeter) +{ + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL); + return lightdm_greeter_get_hint (greeter, "default-session"); +} + +/** + * lightdm_greeter_get_hide_users_hint: + * @greeter: A #LightDMGreeter + * + * Check if user accounts should be shown. If this is TRUE then the list of + * accounts should be taken from #LightDMUserList and displayed in the greeter + * for the user to choose from. Note that this list can be empty and it is + * recommended you show a method for the user to enter a username manually. + * + * If this option is shown the greeter should only allow these users to be + * chosen for login unless the manual login hint is set. + * + * Return value: #TRUE if the available users should not be shown. + */ +gboolean +lightdm_greeter_get_hide_users_hint (LightDMGreeter *greeter) +{ + const gchar *value; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "hide-users"); + + return g_strcmp0 (value, "true") == 0; +} + +/** + * lightdm_greeter_get_show_manual_login_hint: + * @greeter: A #LightDMGreeter + * + * Check if a manual login option should be shown. If set the GUI + * should provide a way for a username to be entered manually. + * Without this hint a greeter which is showing a user list can + * limit logins to only those users. + * + * Return value: #TRUE if a manual login option should be shown. + */ +gboolean +lightdm_greeter_get_show_manual_login_hint (LightDMGreeter *greeter) +{ + const gchar *value; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "show-manual-login"); + + return g_strcmp0 (value, "true") == 0; +} + +/** + * lightdm_greeter_get_show_remote_login_hint: + * @greeter: A #LightDMGreeter + * + * Check if a remote login option should be shown. If set the GUI + * should provide a way for a user to log into a remote desktop server. + * + * Return value: #TRUE if a remote login option should be shown. + */ +gboolean +lightdm_greeter_get_show_remote_login_hint (LightDMGreeter *greeter) +{ + const gchar *value; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "show-remote-login"); + + return g_strcmp0 (value, "true") == 0; +} + +/** + * lightdm_greeter_get_lock_hint: + * @greeter: A #LightDMGreeter + * + * Check if the greeter is acting as a lock screen. + * + * Return value: #TRUE if the greeter was triggered by locking the seat. + */ +gboolean +lightdm_greeter_get_lock_hint (LightDMGreeter *greeter) +{ + const gchar *value; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "lock-screen"); + + return g_strcmp0 (value, "true") == 0; +} + +/** + * lightdm_greeter_get_has_guest_account_hint: + * @greeter: A #LightDMGreeter + * + * Check if guest sessions are supported. + * + * Return value: #TRUE if guest sessions are supported. + */ +gboolean +lightdm_greeter_get_has_guest_account_hint (LightDMGreeter *greeter) +{ + const gchar *value; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "has-guest-account"); + + return g_strcmp0 (value, "true") == 0; +} + +/** + * lightdm_greeter_get_select_user_hint: + * @greeter: A #LightDMGreeter + * + * Get the user to select by default. + * + * Return value: A username + */ +const gchar * +lightdm_greeter_get_select_user_hint (LightDMGreeter *greeter) +{ + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL); + return lightdm_greeter_get_hint (greeter, "select-user"); +} + +/** + * lightdm_greeter_get_select_guest_hint: + * @greeter: A #LightDMGreeter + * + * Check if the guest account should be selected by default. + * + * Return value: #TRUE if the guest account should be selected by default. + */ +gboolean +lightdm_greeter_get_select_guest_hint (LightDMGreeter *greeter) +{ + const gchar *value; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "select-guest"); + + return g_strcmp0 (value, "true") == 0; +} + +/** + * lightdm_greeter_get_autologin_user_hint: + * @greeter: A #LightDMGreeter + * + * Get the user account to automatically logg into when the timer expires. + * + * Return value: The user account to automatically log into. + */ +const gchar * +lightdm_greeter_get_autologin_user_hint (LightDMGreeter *greeter) +{ + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL); + return lightdm_greeter_get_hint (greeter, "autologin-user"); +} + +/** + * lightdm_greeter_get_autologin_guest_hint: + * @greeter: A #LightDMGreeter + * + * Check if the guest account should be automatically logged into when the timer expires. + * + * Return value: #TRUE if the guest account should be automatically logged into. + */ +gboolean +lightdm_greeter_get_autologin_guest_hint (LightDMGreeter *greeter) +{ + const gchar *value; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "autologin-guest"); + + return g_strcmp0 (value, "true") == 0; +} + +/** + * lightdm_greeter_get_autologin_timeout_hint: + * @greeter: A #LightDMGreeter + * + * Get the number of seconds to wait before automaitcally logging in. + * + * Return value: The number of seconds to wait before automatically logging in or 0 for no timeout. + */ +gint +lightdm_greeter_get_autologin_timeout_hint (LightDMGreeter *greeter) +{ + const gchar *value; + gint timeout = 0; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + value = lightdm_greeter_get_hint (greeter, "autologin-timeout"); + if (value) + timeout = atoi (value); + if (timeout < 0) + timeout = 0; + + return timeout; +} + +/** + * lightdm_greeter_cancel_autologin: + * @greeter: A #LightDMGreeter + * + * Cancel the automatic login. + */ +void +lightdm_greeter_cancel_autologin (LightDMGreeter *greeter) +{ + LightDMGreeterPrivate *priv; + + g_return_if_fail (LIGHTDM_IS_GREETER (greeter)); + + priv = GET_PRIVATE (greeter); + + if (priv->autologin_timeout) + g_source_remove (priv->autologin_timeout); + priv->autologin_timeout = 0; +} + +/** + * lightdm_greeter_authenticate: + * @greeter: A #LightDMGreeter + * @username: (allow-none): A username or #NULL to prompt for a username. + * + * Starts the authentication procedure for a user. + **/ +void +lightdm_greeter_authenticate (LightDMGreeter *greeter, const gchar *username) +{ + LightDMGreeterPrivate *priv; + guint8 message[MAX_MESSAGE_LENGTH]; + gsize offset = 0; + + g_return_if_fail (LIGHTDM_IS_GREETER (greeter)); + + priv = GET_PRIVATE (greeter); + + g_return_if_fail (priv->connected); + + priv->cancelling_authentication = FALSE; + priv->authenticate_sequence_number++; + priv->in_authentication = TRUE; + priv->is_authenticated = FALSE; + if (username != priv->authentication_user) + { + g_free (priv->authentication_user); + priv->authentication_user = g_strdup (username); + } + + g_debug ("Starting authentication for user %s...", username); + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE, int_length () + string_length (username), &offset); + write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset); + write_string (message, MAX_MESSAGE_LENGTH, username, &offset); + write_message (greeter, message, offset); +} + +/** + * lightdm_greeter_authenticate_as_guest: + * @greeter: A #LightDMGreeter + * + * Starts the authentication procedure for the guest user. + **/ +void +lightdm_greeter_authenticate_as_guest (LightDMGreeter *greeter) +{ + LightDMGreeterPrivate *priv; + guint8 message[MAX_MESSAGE_LENGTH]; + gsize offset = 0; + + g_return_if_fail (LIGHTDM_IS_GREETER (greeter)); + + priv = GET_PRIVATE (greeter); + + g_return_if_fail (priv->connected); + + priv->cancelling_authentication = FALSE; + priv->authenticate_sequence_number++; + priv->in_authentication = TRUE; + priv->is_authenticated = FALSE; + g_free (priv->authentication_user); + priv->authentication_user = NULL; + + g_debug ("Starting authentication for guest account..."); + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE_AS_GUEST, int_length (), &offset); + write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset); + write_message (greeter, message, offset); +} + +/** + * lightdm_greeter_authenticate_autologin: + * @greeter: A #LightDMGreeter + * + * Starts the authentication procedure for the automatic login user. + **/ +void +lightdm_greeter_authenticate_autologin (LightDMGreeter *greeter) +{ + const gchar *user; + + user = lightdm_greeter_get_autologin_user_hint (greeter); + if (lightdm_greeter_get_autologin_guest_hint (greeter)) + lightdm_greeter_authenticate_as_guest (greeter); + else if (user) + lightdm_greeter_authenticate (greeter, user); +} + +/** + * lightdm_greeter_authenticate_remote: + * @greeter: A #LightDMGreeter + * @session: The name of a remote session + * @username: (allow-none): A username of #NULL to prompt for a username. + * + * Start authentication for a remote session type. + **/ +void +lightdm_greeter_authenticate_remote (LightDMGreeter *greeter, const gchar *session, const gchar *username) +{ + LightDMGreeterPrivate *priv; + guint8 message[MAX_MESSAGE_LENGTH]; + gsize offset = 0; + + g_return_if_fail (LIGHTDM_IS_GREETER (greeter)); + + priv = GET_PRIVATE (greeter); + + g_return_if_fail (priv->connected); + + priv->cancelling_authentication = FALSE; + priv->authenticate_sequence_number++; + priv->in_authentication = TRUE; + priv->is_authenticated = FALSE; + g_free (priv->authentication_user); + priv->authentication_user = NULL; + + if (username) + g_debug ("Starting authentication for remote session %s as user %s...", session, username); + else + g_debug ("Starting authentication for remote session %s...", session); + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE_REMOTE, int_length () + string_length (session) + string_length (username), &offset); + write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset); + write_string (message, MAX_MESSAGE_LENGTH, session, &offset); + write_string (message, MAX_MESSAGE_LENGTH, username, &offset); + write_message (greeter, message, offset); +} + +/** + * lightdm_greeter_respond: + * @greeter: A #LightDMGreeter + * @response: Response to a prompt + * + * Provide response to a prompt. May be one in a series. + **/ +void +lightdm_greeter_respond (LightDMGreeter *greeter, const gchar *response) +{ + LightDMGreeterPrivate *priv; + guint8 message[MAX_MESSAGE_LENGTH]; + gsize offset = 0; + + g_return_if_fail (LIGHTDM_IS_GREETER (greeter)); + g_return_if_fail (response != NULL); + + priv = GET_PRIVATE (greeter); + + g_return_if_fail (priv->connected); + g_return_if_fail (priv->n_responses_waiting > 0); + + priv->n_responses_waiting--; + priv->responses_received = g_list_append (priv->responses_received, g_strdup (response)); + + if (priv->n_responses_waiting == 0) + { + guint32 msg_length; + GList *iter; + + g_debug ("Providing response to display manager"); + + msg_length = int_length (); + for (iter = priv->responses_received; iter; iter = iter->next) + { + msg_length += string_length ((gchar *)iter->data); + } + + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CONTINUE_AUTHENTICATION, msg_length, &offset); + write_int (message, MAX_MESSAGE_LENGTH, g_list_length (priv->responses_received), &offset); + for (iter = priv->responses_received; iter; iter = iter->next) + { + write_string (message, MAX_MESSAGE_LENGTH, (gchar *)iter->data, &offset); + } + write_message (greeter, message, offset); + + g_list_free_full (priv->responses_received, g_free); + priv->responses_received = NULL; + } +} + +/** + * lightdm_greeter_cancel_authentication: + * @greeter: A #LightDMGreeter + * + * Cancel the current user authentication. + **/ +void +lightdm_greeter_cancel_authentication (LightDMGreeter *greeter) +{ + LightDMGreeterPrivate *priv; + guint8 message[MAX_MESSAGE_LENGTH]; + gsize offset = 0; + + g_return_if_fail (LIGHTDM_IS_GREETER (greeter)); + + priv = GET_PRIVATE (greeter); + + g_return_if_fail (priv->connected); + + priv->cancelling_authentication = TRUE; + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CANCEL_AUTHENTICATION, 0, &offset); + write_message (greeter, message, offset); +} + +/** + * lightdm_greeter_get_in_authentication: + * @greeter: A #LightDMGreeter + * + * Checks if the greeter is in the process of authenticating. + * + * Return value: #TRUE if the greeter is authenticating a user. + **/ +gboolean +lightdm_greeter_get_in_authentication (LightDMGreeter *greeter) +{ + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + return GET_PRIVATE (greeter)->in_authentication; +} + +/** + * lightdm_greeter_get_is_authenticated: + * @greeter: A #LightDMGreeter + * + * Checks if the greeter has successfully authenticated. + * + * Return value: #TRUE if the greeter is authenticated for login. + **/ +gboolean +lightdm_greeter_get_is_authenticated (LightDMGreeter *greeter) +{ + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + return GET_PRIVATE (greeter)->is_authenticated; +} + +/** + * lightdm_greeter_get_authentication_user: + * @greeter: A #LightDMGreeter + * + * Get the user that is being authenticated. + * + * Return value: The username of the authentication user being authenticated or #NULL if no authentication in progress. + */ +const gchar * +lightdm_greeter_get_authentication_user (LightDMGreeter *greeter) +{ + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL); + return GET_PRIVATE (greeter)->authentication_user; +} + +/** + * lightdm_greeter_set_language: + * @greeter: A #LightDMGreeter + * @language: The language to use for this user. + * + * Set the language for the currently authenticated user. + **/ +void +lightdm_greeter_set_language (LightDMGreeter *greeter, const gchar *language) +{ + LightDMGreeterPrivate *priv; + guint8 message[MAX_MESSAGE_LENGTH]; + gsize offset = 0; + + g_return_if_fail (LIGHTDM_IS_GREETER (greeter)); + + priv = GET_PRIVATE (greeter); + + g_return_if_fail (priv->connected); + + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_SET_LANGUAGE, string_length (language), &offset); + write_string (message, MAX_MESSAGE_LENGTH, language, &offset); + write_message (greeter, message, offset); +} + +/** + * lightdm_greeter_start_session_sync: + * @greeter: A #LightDMGreeter + * @session: (allow-none): The session to log into or #NULL to use the default. + * @error: return location for a #GError, or %NULL + * + * Start a session for the authenticated user. + * + * Return value: TRUE if the session was started. + **/ +gboolean +lightdm_greeter_start_session_sync (LightDMGreeter *greeter, const gchar *session, GError **error) +{ + LightDMGreeterPrivate *priv; + guint8 message[MAX_MESSAGE_LENGTH]; + guint8 *response; + gsize response_length, offset = 0; + guint32 id, return_code = 1; + + g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE); + + priv = GET_PRIVATE (greeter); + + g_return_val_if_fail (priv->connected, FALSE); + g_return_val_if_fail (priv->is_authenticated, FALSE); + + if (session) + g_debug ("Starting session %s", session); + else + g_debug ("Starting default session"); + + write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_START_SESSION, string_length (session), &offset); + write_string (message, MAX_MESSAGE_LENGTH, session, &offset); + write_message (greeter, message, offset); + + response = read_message (greeter, &response_length, TRUE); + if (!response) + return FALSE; + + offset = 0; + id = read_int (response, response_length, &offset); + read_int (response, response_length, &offset); + if (id == SERVER_MESSAGE_SESSION_RESULT) + return_code = read_int (response, response_length, &offset); + else + g_warning ("Expected SESSION_RESULT message, got %d", id); + + g_free (response); + + return return_code == 0; +} + +static void +lightdm_greeter_init (LightDMGreeter *greeter) +{ + LightDMGreeterPrivate *priv = GET_PRIVATE (greeter); + + priv->read_buffer = g_malloc (HEADER_SIZE); + priv->hints = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); +} + +static void +lightdm_greeter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +lightdm_greeter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LightDMGreeter *self; + + self = LIGHTDM_GREETER (object); + + switch (prop_id) { + case PROP_DEFAULT_SESSION_HINT: + g_value_set_string (value, lightdm_greeter_get_default_session_hint (self)); + break; + case PROP_HIDE_USERS_HINT: + g_value_set_boolean (value, lightdm_greeter_get_hide_users_hint (self)); + break; + case PROP_SHOW_MANUAL_LOGIN_HINT: + g_value_set_boolean (value, lightdm_greeter_get_show_manual_login_hint (self)); + break; + case PROP_SHOW_REMOTE_LOGIN_HINT: + g_value_set_boolean (value, lightdm_greeter_get_show_remote_login_hint (self)); + break; + case PROP_LOCK_HINT: + g_value_set_boolean (value, lightdm_greeter_get_lock_hint (self)); + break; + case PROP_HAS_GUEST_ACCOUNT_HINT: + g_value_set_boolean (value, lightdm_greeter_get_has_guest_account_hint (self)); + break; + case PROP_SELECT_USER_HINT: + g_value_set_string (value, lightdm_greeter_get_select_user_hint (self)); + break; + case PROP_SELECT_GUEST_HINT: + g_value_set_boolean (value, lightdm_greeter_get_select_guest_hint (self)); + break; + case PROP_AUTOLOGIN_USER_HINT: + g_value_set_string (value, lightdm_greeter_get_autologin_user_hint (self)); + break; + case PROP_AUTOLOGIN_GUEST_HINT: + g_value_set_boolean (value, lightdm_greeter_get_autologin_guest_hint (self)); + break; + case PROP_AUTOLOGIN_TIMEOUT_HINT: + g_value_set_int (value, lightdm_greeter_get_autologin_timeout_hint (self)); + break; + case PROP_AUTHENTICATION_USER: + g_value_set_string (value, lightdm_greeter_get_authentication_user (self)); + break; + case PROP_IN_AUTHENTICATION: + g_value_set_boolean (value, lightdm_greeter_get_in_authentication (self)); + break; + case PROP_IS_AUTHENTICATED: + g_value_set_boolean (value, lightdm_greeter_get_is_authenticated (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +marshal_VOID__STRING_INT (GClosure *closure, + GValue *return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint G_GNUC_UNUSED, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__STRING_INT) (gpointer data1, + gpointer arg_1, + gint arg_2, + gpointer data2); + register GMarshalFunc_VOID__STRING_INT callback; + register GCClosure *cc = (GCClosure*) closure; + register gpointer data1, data2; + + g_return_if_fail (n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA (closure)) + { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } + else + { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = (GMarshalFunc_VOID__STRING_INT) (marshal_data ? marshal_data : cc->callback); + + callback (data1, + (param_values + 1)->data[0].v_pointer, + (param_values + 2)->data[0].v_int, + data2); +} + +static void +lightdm_greeter_finalize (GObject *object) +{ + LightDMGreeter *self = LIGHTDM_GREETER (object); + LightDMGreeterPrivate *priv = GET_PRIVATE (self); + + if (priv->to_server_channel) + g_io_channel_unref (priv->to_server_channel); + if (priv->from_server_channel) + g_io_channel_unref (priv->from_server_channel); + g_free (priv->authentication_user); + g_hash_table_unref (priv->hints); + + G_OBJECT_CLASS (lightdm_greeter_parent_class)->finalize (object); +} + +static void +lightdm_greeter_class_init (LightDMGreeterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (LightDMGreeterPrivate)); + + object_class->set_property = lightdm_greeter_set_property; + object_class->get_property = lightdm_greeter_get_property; + object_class->finalize = lightdm_greeter_finalize; + + g_object_class_install_property (object_class, + PROP_DEFAULT_SESSION_HINT, + g_param_spec_string ("default-session-hint", + "default-session-hint", + "Default session hint", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_HIDE_USERS_HINT, + g_param_spec_boolean ("hide-users-hint", + "hide-users-hint", + "Hide users hint", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_SHOW_MANUAL_LOGIN_HINT, + g_param_spec_boolean ("show-manual-login-hint", + "show-manual-login-hint", + "Show manual login hint", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_SHOW_REMOTE_LOGIN_HINT, + g_param_spec_boolean ("show-remote-login-hint", + "show-remote-login-hint", + "Show remote login hint", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_LOCK_HINT, + g_param_spec_boolean ("lock-hint", + "lock-hint", + "Lock hint", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_HAS_GUEST_ACCOUNT_HINT, + g_param_spec_boolean ("has-guest-account-hint", + "has-guest-account-hint", + "Has guest account hint", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_SELECT_USER_HINT, + g_param_spec_string ("select-user-hint", + "select-user-hint", + "Select user hint", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_SELECT_GUEST_HINT, + g_param_spec_boolean ("select-guest-hint", + "select-guest-hint", + "Select guest account hint", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_AUTOLOGIN_USER_HINT, + g_param_spec_string ("autologin-user-hint", + "autologin-user-hint", + "Autologin user hint", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_AUTOLOGIN_GUEST_HINT, + g_param_spec_boolean ("autologin-guest-hint", + "autologin-guest-hint", + "Autologin guest account hint", + FALSE, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_AUTOLOGIN_TIMEOUT_HINT, + g_param_spec_int ("autologin-timeout-hint", + "autologin-timeout-hint", + "Autologin timeout hint", + 0, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_AUTHENTICATION_USER, + g_param_spec_string ("authentication-user", + "authentication-user", + "The user being authenticated", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_IN_AUTHENTICATION, + g_param_spec_boolean ("in-authentication", + "in-authentication", + "TRUE if a user is being authenticated", + FALSE, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_IS_AUTHENTICATED, + g_param_spec_boolean ("is-authenticated", + "is-authenticated", + "TRUE if the selected user is authenticated", + FALSE, + G_PARAM_READABLE)); + + /** + * LightDMGreeter::show-prompt: + * @greeter: A #LightDMGreeter + * @text: Prompt text + * @type: Prompt type + * + * The ::show-prompt signal gets emitted when the greeter should show a + * prompt to the user. The given text should be displayed and an input + * field for the user to provide a response. + * + * Call lightdm_greeter_respond() with the resultant input or + * lightdm_greeter_cancel_authentication() to abort the authentication. + **/ + signals[SHOW_PROMPT] = + g_signal_new ("show-prompt", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMGreeterClass, show_prompt), + NULL, NULL, + marshal_VOID__STRING_INT, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT); + + /** + * LightDMGreeter::show-message: + * @greeter: A #LightDMGreeter + * @text: Message text + * @type: Message type + * + * The ::show-message signal gets emitted when the greeter + * should show a message to the user. + **/ + signals[SHOW_MESSAGE] = + g_signal_new ("show-message", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMGreeterClass, show_message), + NULL, NULL, + marshal_VOID__STRING_INT, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT); + + /** + * LightDMGreeter::authentication-complete: + * @greeter: A #LightDMGreeter + * + * The ::authentication-complete signal gets emitted when the greeter + * has completed authentication. + * + * Call lightdm_greeter_get_is_authenticated() to check if the authentication + * was successful. + **/ + signals[AUTHENTICATION_COMPLETE] = + g_signal_new ("authentication-complete", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMGreeterClass, authentication_complete), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + /** + * LightDMGreeter::autologin-timer-expired: + * @greeter: A #LightDMGreeter + * + * The ::timed-login signal gets emitted when the automatic login timer has expired. + * The application should then call lightdm_greeter_login(). + **/ + signals[AUTOLOGIN_TIMER_EXPIRED] = + g_signal_new ("autologin-timer-expired", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMGreeterClass, autologin_timer_expired), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c new file mode 100644 index 00000000..9709619e --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2010 Robert Ancell. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2 or version 3 of the License. + * See http://www.gnu.org/copyleft/lgpl.html the full text of the license. + */ + +/* + * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice + * other than GPL or LGPL is available it will apply instead, Oracle elects to use only + * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where + * a choice of LGPL license versions is made available with the language indicating + * that LGPLv2 or any later version may be used, or where a choice of which version + * of the LGPL is applied is otherwise unspecified. + */ + +#include <string.h> +#include <locale.h> +#include <langinfo.h> +#include <stdio.h> +#include <glib/gi18n.h> + +#include "lightdm/language.h" + +enum { + PROP_0, + PROP_CODE, + PROP_NAME, + PROP_TERRITORY +}; + +typedef struct +{ + gchar *code; + gchar *name; + gchar *territory; +} LightDMLanguagePrivate; + +G_DEFINE_TYPE (LightDMLanguage, lightdm_language, G_TYPE_OBJECT); + +#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_LANGUAGE, LightDMLanguagePrivate) + +static gboolean have_languages = FALSE; +static GList *languages = NULL; + +static void +update_languages (void) +{ + gchar *command = "locale -a"; + gchar *stdout_text = NULL, *stderr_text = NULL; + gint exit_status; + gboolean result; + GError *error = NULL; + + if (have_languages) + return; + + result = g_spawn_command_line_sync (command, &stdout_text, &stderr_text, &exit_status, &error); + if (error) + { + g_warning ("Failed to run '%s': %s", command, error->message); + g_clear_error (&error); + } + else if (exit_status != 0) + g_warning ("Failed to get languages, '%s' returned %d", command, exit_status); + else if (result) + { + gchar **tokens; + int i; + + tokens = g_strsplit_set (stdout_text, "\n\r", -1); + for (i = 0; tokens[i]; i++) + { + LightDMLanguage *language; + gchar *code; + + code = g_strchug (tokens[i]); + if (code[0] == '\0') + continue; + + /* Ignore the non-interesting languages */ + if (strcmp (command, "locale -a") == 0 && !g_strrstr (code, ".utf8")) + continue; + + language = g_object_new (LIGHTDM_TYPE_LANGUAGE, "code", code, NULL); + languages = g_list_append (languages, language); + } + + g_strfreev (tokens); + } + + g_free (stdout_text); + g_free (stderr_text); + + have_languages = TRUE; +} + +static gboolean +is_utf8 (const gchar *code) +{ + return g_strrstr (code, ".utf8") || g_strrstr (code, ".UTF-8"); +} + +/* Get a valid locale name that can be passed to setlocale(), so we always can use nl_langinfo() to get language and country names. */ +static gchar * +get_locale_name (const gchar *code) +{ + gchar *locale = NULL, *language; + char *at; + static gchar **avail_locales; + gint i; + + if (is_utf8 (code)) + return (gchar *) code; + + if ((at = strchr (code, '@'))) + language = g_strndup (code, at - code); + else + language = g_strdup (code); + + if (!avail_locales) + { + gchar *locales; + GError *error = NULL; + + if (g_spawn_command_line_sync ("locale -a", &locales, NULL, NULL, &error)) + { + avail_locales = g_strsplit (g_strchomp (locales), "\n", -1); + g_free (locales); + } + else + { + g_warning ("Failed to run 'locale -a': %s", error->message); + g_clear_error (&error); + } + } + + if (avail_locales) + { + for (i = 0; avail_locales[i]; i++) + { + gchar *loc = avail_locales[i]; + if (!g_strrstr (loc, ".utf8")) + continue; + if (g_str_has_prefix (loc, language)) + { + locale = g_strdup (loc); + break; + } + } + } + + g_free (language); + + return locale; +} + +/** + * lightdm_get_language: + * + * Get the current language. + * + * Return value: (transfer none): The current language or #NULL if no language. + **/ +LightDMLanguage * +lightdm_get_language (void) +{ + const gchar *lang; + GList *link; + + lang = g_getenv ("LANG"); + if (!lang) + return NULL; + + for (link = lightdm_get_languages (); link; link = link->next) + { + LightDMLanguage *language = link->data; + if (lightdm_language_matches (language, lang)) + return language; + } + + return NULL; +} + +/** + * lightdm_get_languages: + * + * Get a list of languages to present to the user. + * + * Return value: (element-type LightDMLanguage) (transfer none): A list of #LightDMLanguage that should be presented to the user. + **/ +GList * +lightdm_get_languages (void) +{ + update_languages (); + return languages; +} + +/** + * lightdm_language_get_code: + * @language: A #LightDMLanguage + * + * Get the code of a language. + * + * Return value: The code of the language + **/ +const gchar * +lightdm_language_get_code (LightDMLanguage *language) +{ + g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL); + return GET_PRIVATE (language)->code; +} + +/** + * lightdm_language_get_name: + * @language: A #LightDMLanguage + * + * Get the name of a language. + * + * Return value: The name of the language + **/ +const gchar * +lightdm_language_get_name (LightDMLanguage *language) +{ + LightDMLanguagePrivate *priv; + + g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL); + + priv = GET_PRIVATE (language); + + if (!priv->name) + { + gchar *locale = get_locale_name (priv->code); + if (locale) + { + gchar *current = setlocale (LC_ALL, NULL); + setlocale (LC_IDENTIFICATION, locale); + setlocale (LC_MESSAGES, ""); + + gchar *language_en = nl_langinfo (_NL_IDENTIFICATION_LANGUAGE); + if (language_en && strlen (language_en) > 0) + priv->name = g_strdup (dgettext ("iso_639_3", language_en)); + + setlocale (LC_ALL, current); + } + if (!priv->name) + { + gchar **tokens = g_strsplit_set (priv->code, "_.@", 2); + priv->name = g_strdup (tokens[0]); + g_strfreev (tokens); + } + } + + return priv->name; +} + +/** + * lightdm_language_get_territory: + * @language: A #LightDMLanguage + * + * Get the territory the language is used in. + * + * Return value: The territory the language is used in. + **/ +const gchar * +lightdm_language_get_territory (LightDMLanguage *language) +{ + LightDMLanguagePrivate *priv; + + g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL); + + priv = GET_PRIVATE (language); + + if (!priv->territory && strchr (priv->code, '_')) + { + gchar *locale = get_locale_name (priv->code); + if (locale) + { + gchar *current = setlocale (LC_ALL, NULL); + setlocale (LC_IDENTIFICATION, locale); + setlocale (LC_MESSAGES, ""); + + gchar *country_en = nl_langinfo (_NL_IDENTIFICATION_TERRITORY); + if (country_en && strlen (country_en) > 0 && g_strcmp0 (country_en, "ISO") != 0) + priv->territory = g_strdup (dgettext ("iso_3166", country_en)); + + setlocale (LC_ALL, current); + } + if (!priv->territory) + { + gchar **tokens = g_strsplit_set (priv->code, "_.@", 3); + priv->territory = g_strdup (tokens[1]); + g_strfreev (tokens); + } + } + + return priv->territory; +} + +/** + * lightdm_language_matches: + * @language: A #LightDMLanguage + * @code: A language code + * + * Check if a language code matches this language. + * + * Return value: #TRUE if the code matches this language. + **/ +gboolean +lightdm_language_matches (LightDMLanguage *language, const gchar *code) +{ + LightDMLanguagePrivate *priv; + + g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), FALSE); + g_return_val_if_fail (code != NULL, FALSE); + + priv = GET_PRIVATE (language); + + /* Handle the fact the UTF-8 is specified both as '.utf8' and '.UTF-8' */ + if (is_utf8 (priv->code) && is_utf8 (code)) + { + /* Match the characters before the '.' */ + int i; + for (i = 0; priv->code[i] && code[i] && priv->code[i] == code[i] && code[i] != '.' ; i++); + return priv->code[i] == '.' && code[i] == '.'; + } + + return g_str_equal (priv->code, code); +} + +static void +lightdm_language_init (LightDMLanguage *language) +{ +} + +static void +lightdm_language_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + LightDMLanguage *self = LIGHTDM_LANGUAGE (object); + LightDMLanguagePrivate *priv = GET_PRIVATE (self); + + switch (prop_id) { + case PROP_CODE: + g_free (priv->name); + priv->code = g_strdup (g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +lightdm_language_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LightDMLanguage *self; + + self = LIGHTDM_LANGUAGE (object); + + switch (prop_id) { + case PROP_CODE: + g_value_set_string (value, lightdm_language_get_code (self)); + break; + case PROP_NAME: + g_value_set_string (value, lightdm_language_get_name (self)); + break; + case PROP_TERRITORY: + g_value_set_string (value, lightdm_language_get_territory (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +lightdm_language_class_init (LightDMLanguageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (LightDMLanguagePrivate)); + + object_class->set_property = lightdm_language_set_property; + object_class->get_property = lightdm_language_get_property; + + g_object_class_install_property (object_class, + PROP_CODE, + g_param_spec_string ("code", + "code", + "Language code", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "name", + "Name of the language", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_TERRITORY, + g_param_spec_string ("territory", + "territory", + "Territory the language is from", + NULL, + G_PARAM_READABLE)); +} diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c new file mode 100644 index 00000000..d5057450 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c @@ -0,0 +1,344 @@ +/* -*- Mode: C; indent-tabs-mode:nil; tab-width:4 -*- + * + * Copyright (C) 2010 Robert Ancell. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2 or version 3 of the License. + * See http://www.gnu.org/copyleft/lgpl.html the full text of the license. + */ + +/* + * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice + * other than GPL or LGPL is available it will apply instead, Oracle elects to use only + * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where + * a choice of LGPL license versions is made available with the language indicating + * that LGPLv2 or any later version may be used, or where a choice of which version + * of the LGPL is applied is otherwise unspecified. + */ + +#include <libxklavier/xklavier.h> + +#include "lightdm/layout.h" + +enum { + PROP_0, + PROP_NAME, + PROP_SHORT_DESCRIPTION, + PROP_DESCRIPTION +}; + +typedef struct +{ + gchar *name; + gchar *short_description; + gchar *description; +} LightDMLayoutPrivate; + +G_DEFINE_TYPE (LightDMLayout, lightdm_layout, G_TYPE_OBJECT); + +#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_LAYOUT, LightDMLayoutPrivate) + +static gboolean have_layouts = FALSE; +static Display *display = NULL; +static XklEngine *xkl_engine = NULL; +static XklConfigRec *xkl_config = NULL; +static GList *layouts = NULL; +static LightDMLayout *default_layout = NULL; + +static gchar * +make_layout_string (const gchar *layout, const gchar *variant) +{ + if (!layout || layout[0] == 0) + return NULL; + else if (!variant || variant[0] == 0) + return g_strdup (layout); + else + return g_strdup_printf ("%s\t%s", layout, variant); +} + +static void +parse_layout_string (const gchar *name, gchar **layout, gchar **variant) +{ + gchar **split; + + *layout = NULL; + *variant = NULL; + + if (!name) + return; + + split = g_strsplit (name, "\t", 2); + if (split[0]) + { + *layout = g_strdup (split[0]); + if (split[1]) + *variant = g_strdup (split[1]); + } + g_strfreev (split); +} + +static void +variant_cb (XklConfigRegistry *config, + const XklConfigItem *item, + gpointer data) +{ + LightDMLayout *layout; + gchar *full_name; + + full_name = make_layout_string (data, item->name); + + layout = g_object_new (LIGHTDM_TYPE_LAYOUT, "name", full_name, "short-description", item->short_description, "description", item->description, NULL); + layouts = g_list_append (layouts, layout); + + g_free (full_name); +} + +static void +layout_cb (XklConfigRegistry *config, + const XklConfigItem *item, + gpointer data) +{ + LightDMLayout *layout; + + layout = g_object_new (LIGHTDM_TYPE_LAYOUT, "name", item->name, "short-description", item->short_description, "description", item->description, NULL); + layouts = g_list_append (layouts, layout); + + xkl_config_registry_foreach_layout_variant (config, item->name, variant_cb, (gpointer) item->name); +} + +/** + * lightdm_get_layouts: + * + * Get a list of keyboard layouts to present to the user. + * + * Return value: (element-type LightDMLayout) (transfer none): A list of #LightDMLayout that should be presented to the user. + **/ +GList * +lightdm_get_layouts (void) +{ + XklConfigRegistry *registry; + + if (have_layouts) + return layouts; + + display = XOpenDisplay (NULL); + xkl_engine = xkl_engine_get_instance (display); + xkl_config = xkl_config_rec_new (); + if (!xkl_config_rec_get_from_server (xkl_config, xkl_engine)) + g_warning ("Failed to get Xkl configuration from server"); + + registry = xkl_config_registry_get_instance (xkl_engine); + xkl_config_registry_load (registry, FALSE); + xkl_config_registry_foreach_layout (registry, layout_cb, NULL); + g_object_unref (registry); + + have_layouts = TRUE; + + return layouts; +} + +/** + * lightdm_set_layout: + * @layout: The layout to use + * + * Set the layout for this session. + **/ +void +lightdm_set_layout (LightDMLayout *dmlayout) +{ + XklConfigRec *config; + gchar *layout, *variant; + + g_return_if_fail (dmlayout != NULL); + + g_debug ("Setting keyboard layout to '%s'", lightdm_layout_get_name (dmlayout)); + + parse_layout_string (lightdm_layout_get_name (dmlayout), &layout, &variant); + + config = xkl_config_rec_new (); + config->layouts = g_malloc (sizeof (gchar *) * 2); + config->variants = g_malloc (sizeof (gchar *) * 2); + config->model = g_strdup (xkl_config->model); + config->layouts[0] = layout; + config->layouts[1] = NULL; + config->variants[0] = variant; + config->variants[1] = NULL; + if (!xkl_config_rec_activate (config, xkl_engine)) + g_warning ("Failed to activate XKL config"); + g_object_unref (config); +} + +/** + * lightdm_get_layout: + * + * Get the current keyboard layout. + * + * Return value: (transfer none): The currently active layout for this user. + **/ +LightDMLayout * +lightdm_get_layout (void) +{ + lightdm_get_layouts (); + + if (layouts && xkl_config && !default_layout) + { + gchar *full_name; + GList *item; + + full_name = make_layout_string (xkl_config->layouts ? xkl_config->layouts[0] : NULL, + xkl_config->variants ? xkl_config->variants[0] : NULL); + + for (item = layouts; item; item = item->next) + { + LightDMLayout *iter_layout = (LightDMLayout *) item->data; + if (g_strcmp0 (lightdm_layout_get_name (iter_layout), full_name) == 0) + { + default_layout = iter_layout; + break; + } + } + + g_free (full_name); + } + + return default_layout; +} + +/** + * lightdm_layout_get_name: + * @layout: A #LightDMLayout + * + * Get the name of a layout. + * + * Return value: The name of the layout + **/ +const gchar * +lightdm_layout_get_name (LightDMLayout *layout) +{ + g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL); + return GET_PRIVATE (layout)->name; +} + +/** + * lightdm_layout_get_short_description: + * @layout: A #LightDMLayout + * + * Get the short description of a layout. + * + * Return value: A short description of the layout + **/ +const gchar * +lightdm_layout_get_short_description (LightDMLayout *layout) +{ + g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL); + return GET_PRIVATE (layout)->short_description; +} + +/** + * lightdm_layout_get_description: + * @layout: A #LightDMLayout + * + * Get the long description of a layout. + * + * Return value: A long description of the layout + **/ +const gchar * +lightdm_layout_get_description (LightDMLayout *layout) +{ + g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL); + return GET_PRIVATE (layout)->description; +} + +static void +lightdm_layout_init (LightDMLayout *layout) +{ +} + +static void +lightdm_layout_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + LightDMLayout *self = LIGHTDM_LAYOUT (object); + LightDMLayoutPrivate *priv = GET_PRIVATE (self); + + switch (prop_id) { + case PROP_NAME: + g_free (priv->name); + priv->name = g_strdup (g_value_get_string (value)); + break; + case PROP_SHORT_DESCRIPTION: + g_free (priv->short_description); + priv->short_description = g_strdup (g_value_get_string (value)); + break; + case PROP_DESCRIPTION: + g_free (priv->description); + priv->description = g_strdup (g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +lightdm_layout_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LightDMLayout *self; + + self = LIGHTDM_LAYOUT (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, lightdm_layout_get_name (self)); + break; + case PROP_SHORT_DESCRIPTION: + g_value_set_string (value, lightdm_layout_get_short_description (self)); + break; + case PROP_DESCRIPTION: + g_value_set_string (value, lightdm_layout_get_description (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +lightdm_layout_class_init (LightDMLayoutClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (LightDMLayoutPrivate)); + + object_class->set_property = lightdm_layout_set_property; + object_class->get_property = lightdm_layout_get_property; + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "name", + "Name of the layout", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_SHORT_DESCRIPTION, + g_param_spec_string ("short-description", + "short-description", + "Short description of the layout", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_DESCRIPTION, + g_param_spec_string ("description", + "description", + "Long description of the layout", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c new file mode 100644 index 00000000..f7830ff2 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2010-2011 Robert Ancell. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2 or version 3 of the License. + * See http://www.gnu.org/copyleft/lgpl.html the full text of the license. + */ + +/* + * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice + * other than GPL or LGPL is available it will apply instead, Oracle elects to use only + * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where + * a choice of LGPL license versions is made available with the language indicating + * that LGPLv2 or any later version may be used, or where a choice of which version + * of the LGPL is applied is otherwise unspecified. + */ + +#include "config.h" + +#include <string.h> +#include <gio/gio.h> + +#include "lightdm/power.h" + +static GDBusProxy *upower_proxy = NULL; +static GDBusProxy *ck_proxy = NULL; + +static gboolean +upower_call_function (const gchar *function, gboolean default_result, GError **error) +{ + GVariant *result; + gboolean function_result = FALSE; + + if (!upower_proxy) + { + upower_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.UPower", + "/org/freedesktop/UPower", + "org.freedesktop.UPower", + NULL, + error); + if (!upower_proxy) + return FALSE; + } + + result = g_dbus_proxy_call_sync (upower_proxy, + function, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + if (!result) + return default_result; + + if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(b)"))) + g_variant_get (result, "(b)", &function_result); + + g_variant_unref (result); + return function_result; +} + +/** + * lightdm_get_can_suspend: + * + * Checks if authorized to do a system suspend. + * + * Return value: #TRUE if can suspend the system + **/ +gboolean +lightdm_get_can_suspend (void) +{ + return upower_call_function ("SuspendAllowed", FALSE, NULL); +} + +/** + * lightdm_suspend: + * @error: return location for a #GError, or %NULL + * + * Triggers a system suspend. + * + * Return value: #TRUE if suspend initiated. + **/ +gboolean +lightdm_suspend (GError **error) +{ + return upower_call_function ("Suspend", TRUE, error); +} + +/** + * lightdm_get_can_hibernate: + * + * Checks if is authorized to do a system hibernate. + * + * Return value: #TRUE if can hibernate the system + **/ +gboolean +lightdm_get_can_hibernate (void) +{ + return upower_call_function ("HibernateAllowed", FALSE, NULL); +} + +/** + * lightdm_hibernate: + * @error: return location for a #GError, or %NULL + * + * Triggers a system hibernate. + * + * Return value: #TRUE if hibernate initiated. + **/ +gboolean +lightdm_hibernate (GError **error) +{ + return upower_call_function ("Hibernate", TRUE, error); +} + +static gboolean +ck_call_function (const gchar *function, gboolean default_result, GError **error) +{ + GVariant *result; + gboolean function_result = FALSE; + + if (!ck_proxy) + { + ck_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.ConsoleKit", + "/org/freedesktop/ConsoleKit/Manager", + "org.freedesktop.ConsoleKit.Manager", + NULL, + error); + if (!ck_proxy) + return FALSE; + } + + result = g_dbus_proxy_call_sync (ck_proxy, + function, + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + error); + + if (!result) + return default_result; + + if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(b)"))) + g_variant_get (result, "(b)", &function_result); + + g_variant_unref (result); + return function_result; +} + +/** + * lightdm_get_can_restart: + * + * Checks if is authorized to do a system restart. + * + * Return value: #TRUE if can restart the system + **/ +gboolean +lightdm_get_can_restart (void) +{ + return ck_call_function ("CanRestart", FALSE, NULL); +} + +/** + * lightdm_restart: + * @error: return location for a #GError, or %NULL + * + * Triggers a system restart. + * + * Return value: #TRUE if restart initiated. + **/ +gboolean +lightdm_restart (GError **error) +{ + return ck_call_function ("Restart", TRUE, error); +} + +/** + * lightdm_get_can_shutdown: + * + * Checks if is authorized to do a system shutdown. + * + * Return value: #TRUE if can shutdown the system + **/ +gboolean +lightdm_get_can_shutdown (void) +{ + return ck_call_function ("CanStop", FALSE, NULL); +} + +/** + * lightdm_shutdown: + * @error: return location for a #GError, or %NULL + * + * Triggers a system shutdown. + * + * Return value: #TRUE if shutdown initiated. + **/ +gboolean +lightdm_shutdown (GError **error) +{ + return ck_call_function ("Stop", TRUE, error); +} diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c new file mode 100644 index 00000000..2df3a1af --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2010 Robert Ancell. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2 or version 3 of the License. + * See http://www.gnu.org/copyleft/lgpl.html the full text of the license. + */ + +/* + * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice + * other than GPL or LGPL is available it will apply instead, Oracle elects to use only + * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where + * a choice of LGPL license versions is made available with the language indicating + * that LGPLv2 or any later version may be used, or where a choice of which version + * of the LGPL is applied is otherwise unspecified. + */ + +#include <string.h> +#include <gio/gdesktopappinfo.h> + +#include "lightdm/session.h" + +enum { + PROP_0, + PROP_KEY, + PROP_NAME, + PROP_COMMENT +}; + +typedef struct +{ + gchar *key; + gchar *name; + gchar *comment; +} LightDMSessionPrivate; + +G_DEFINE_TYPE (LightDMSession, lightdm_session, G_TYPE_OBJECT); + +#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_SESSION, LightDMSessionPrivate) + +static gboolean have_sessions = FALSE; +static GList *local_sessions = NULL; +static GList *remote_sessions = NULL; + +static gint +compare_session (gconstpointer a, gconstpointer b) +{ + LightDMSessionPrivate *priv_a = GET_PRIVATE (a); + LightDMSessionPrivate *priv_b = GET_PRIVATE (b); + return strcmp (priv_a->name, priv_b->name); +} + +static LightDMSession * +load_session (GKeyFile *key_file, const gchar *key) +{ + gchar *domain, *name; + LightDMSession *session; + LightDMSessionPrivate *priv; + gchar *try_exec; + + if (g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) || + g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL)) + return NULL; + +#ifdef G_KEY_FILE_DESKTOP_KEY_GETTEXT_DOMAIN + domain = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_GETTEXT_DOMAIN, NULL); +#else + domain = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL); +#endif + name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, domain, NULL); + if (!name) + { + g_warning ("Ignoring session without name"); + g_free (domain); + return NULL; + } + + try_exec = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, domain, NULL); + if (try_exec) + { + gchar *full_path; + + full_path = g_find_program_in_path (try_exec); + g_free (try_exec); + + if (!full_path) + { + g_free (name); + g_free (domain); + return NULL; + } + g_free (full_path); + } + + session = g_object_new (LIGHTDM_TYPE_SESSION, NULL); + priv = GET_PRIVATE (session); + + g_free (priv->key); + priv->key = g_strdup (key); + + g_free (priv->name); + priv->name = name; + + g_free (priv->comment); + priv->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, domain, NULL); + if (!priv->comment) + priv->comment = g_strdup (""); + + g_free (domain); + + return session; +} + +static GList * +load_sessions (const gchar *sessions_dir) +{ + GDir *directory; + GList *sessions = NULL; + GError *error = NULL; + + directory = g_dir_open (sessions_dir, 0, &error); + if (error) + g_warning ("Failed to open sessions directory: %s", error->message); + g_clear_error (&error); + if (!directory) + return NULL; + + while (TRUE) + { + const gchar *filename; + gchar *path; + GKeyFile *key_file; + gboolean result; + + filename = g_dir_read_name (directory); + if (filename == NULL) + break; + + if (!g_str_has_suffix (filename, ".desktop")) + continue; + + path = g_build_filename (sessions_dir, filename, NULL); + + key_file = g_key_file_new (); + result = g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, &error); + if (error) + g_warning ("Failed to load session file %s: %s:", path, error->message); + g_clear_error (&error); + + if (result) + { + gchar *key; + LightDMSession *session; + + key = g_strndup (filename, strlen (filename) - strlen (".desktop")); + session = load_session (key_file, key); + if (session) + { + g_debug ("Loaded session %s (%s, %s)", path, GET_PRIVATE (session)->name, GET_PRIVATE (session)->comment); + sessions = g_list_insert_sorted (sessions, session, compare_session); + } + else + g_debug ("Ignoring session %s", path); + g_free (key); + } + + g_free (path); + g_key_file_free (key_file); + } + + g_dir_close (directory); + + return sessions; +} + +static void +update_sessions (void) +{ + GKeyFile *config_key_file = NULL; + gchar *config_path = NULL; + gchar *xsessions_dir; + gchar *remote_sessions_dir; + gboolean result; + GError *error = NULL; + + if (have_sessions) + return; + + xsessions_dir = g_strdup (XSESSIONS_DIR); + remote_sessions_dir = g_strdup (REMOTE_SESSIONS_DIR); + + /* Use session directory from configuration */ + /* FIXME: This should be sent in the greeter connection */ + config_path = g_build_filename (CONFIG_DIR, "lightdm.conf", NULL); + config_key_file = g_key_file_new (); + result = g_key_file_load_from_file (config_key_file, config_path, G_KEY_FILE_NONE, &error); + if (error) + g_warning ("Failed to open configuration file: %s", error->message); + g_clear_error (&error); + if (result) + { + gchar *value; + + value = g_key_file_get_string (config_key_file, "LightDM", "xsessions-directory", NULL); + if (value) + { + g_free (xsessions_dir); + xsessions_dir = value; + } + + value = g_key_file_get_string (config_key_file, "LightDM", "remote-sessions-directory", NULL); + if (value) + { + g_free (remote_sessions_dir); + remote_sessions_dir = value; + } + } + g_key_file_free (config_key_file); + g_free (config_path); + + local_sessions = load_sessions (xsessions_dir); + remote_sessions = load_sessions (remote_sessions_dir); + + g_free (xsessions_dir); + g_free (remote_sessions_dir); + + have_sessions = TRUE; +} + +/** + * lightdm_get_sessions: + * + * Get the available sessions. + * + * Return value: (element-type LightDMSession) (transfer none): A list of #LightDMSession + **/ +GList * +lightdm_get_sessions (void) +{ + update_sessions (); + return local_sessions; +} + +/** + * lightdm_get_remote_sessions: + * + * Get the available remote sessions. + * + * Return value: (element-type LightDMSession) (transfer none): A list of #LightDMSession + **/ +GList * +lightdm_get_remote_sessions (void) +{ + update_sessions (); + return remote_sessions; +} + +/** + * lightdm_session_get_key: + * @session: A #LightDMSession + * + * Get the key for a session + * + * Return value: The session key + **/ +const gchar * +lightdm_session_get_key (LightDMSession *session) +{ + g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL); + return GET_PRIVATE (session)->key; +} + +/** + * lightdm_session_get_name: + * @session: A #LightDMSession + * + * Get the name for a session + * + * Return value: The session name + **/ +const gchar * +lightdm_session_get_name (LightDMSession *session) +{ + g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL); + return GET_PRIVATE (session)->name; +} + +/** + * lightdm_session_get_comment: + * @session: A #LightDMSession + * + * Get the comment for a session + * + * Return value: The session comment + **/ +const gchar * +lightdm_session_get_comment (LightDMSession *session) +{ + g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL); + return GET_PRIVATE (session)->comment; +} + +static void +lightdm_session_init (LightDMSession *session) +{ +} + +static void +lightdm_session_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +lightdm_session_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LightDMSession *self; + + self = LIGHTDM_SESSION (object); + + switch (prop_id) { + case PROP_KEY: + g_value_set_string (value, lightdm_session_get_key (self)); + break; + case PROP_NAME: + g_value_set_string (value, lightdm_session_get_name (self)); + break; + case PROP_COMMENT: + g_value_set_string (value, lightdm_session_get_comment (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +lightdm_session_finalize (GObject *object) +{ + LightDMSession *self = LIGHTDM_SESSION (object); + LightDMSessionPrivate *priv = GET_PRIVATE (self); + + g_free (priv->key); + g_free (priv->name); + g_free (priv->comment); +} + +static void +lightdm_session_class_init (LightDMSessionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (LightDMSessionPrivate)); + + object_class->set_property = lightdm_session_set_property; + object_class->get_property = lightdm_session_get_property; + object_class->finalize = lightdm_session_finalize; + + g_object_class_install_property (object_class, + PROP_KEY, + g_param_spec_string ("key", + "key", + "Session key", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "name", + "Session name", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + PROP_COMMENT, + g_param_spec_string ("comment", + "comment", + "Session comment", + NULL, + G_PARAM_READABLE)); +} diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c new file mode 100644 index 00000000..77f8a182 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010-2011 Robert Ancell. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2 or version 3 of the License. + * See http://www.gnu.org/copyleft/lgpl.html the full text of the license. + */ + +/* + * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice + * other than GPL or LGPL is available it will apply instead, Oracle elects to use only + * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where + * a choice of LGPL license versions is made available with the language indicating + * that LGPLv2 or any later version may be used, or where a choice of which version + * of the LGPL is applied is otherwise unspecified. + */ + +#include <sys/utsname.h> + +#include "lightdm/system.h" + +static gchar *hostname = NULL; + +/** + * lightdm_get_hostname: + * + * Return value: The name of the host we are running on. + **/ +const gchar * +lightdm_get_hostname (void) +{ + if (!hostname) + { + struct utsname info; + uname (&info); + hostname = g_strdup (info.nodename); + } + + return hostname; +} diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c new file mode 100644 index 00000000..8df19079 --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c @@ -0,0 +1,1655 @@ +/* -*- Mode: C; indent-tabs-mode:nil; tab-width:4 -*- + * + * Copyright (C) 2010 Robert Ancell. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2 or version 3 of the License. + * See http://www.gnu.org/copyleft/lgpl.html the full text of the license. + */ + +/* + * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice + * other than GPL or LGPL is available it will apply instead, Oracle elects to use only + * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where + * a choice of LGPL license versions is made available with the language indicating + * that LGPLv2 or any later version may be used, or where a choice of which version + * of the LGPL is applied is otherwise unspecified. + */ + +#include "config.h" + +#include <errno.h> +#include <string.h> +#include <sys/utsname.h> +#include <pwd.h> +#include <gio/gio.h> + +#include "lightdm/user.h" + +enum +{ + LIST_PROP_0, + LIST_PROP_NUM_USERS, + LIST_PROP_USERS, +}; + +enum +{ + USER_PROP_0, + USER_PROP_NAME, + USER_PROP_REAL_NAME, + USER_PROP_DISPLAY_NAME, + USER_PROP_HOME_DIRECTORY, + USER_PROP_IMAGE, + USER_PROP_BACKGROUND, + USER_PROP_LANGUAGE, + USER_PROP_LAYOUT, + USER_PROP_LAYOUTS, + USER_PROP_SESSION, + USER_PROP_LOGGED_IN, + USER_PROP_HAS_MESSAGES +}; + +enum +{ + USER_ADDED, + USER_CHANGED, + USER_REMOVED, + LAST_LIST_SIGNAL +}; +static guint list_signals[LAST_LIST_SIGNAL] = { 0 }; + +enum +{ + CHANGED, + LAST_USER_SIGNAL +}; +static guint user_signals[LAST_USER_SIGNAL] = { 0 }; + +typedef struct +{ + /* Connection to AccountsService */ + GDBusProxy *accounts_service_proxy; + GList *user_account_objects; + + /* Connection to DisplayManager */ + GDBusProxy *display_manager_proxy; + + /* File monitor for password file */ + GFileMonitor *passwd_monitor; + + /* TRUE if have scanned users */ + gboolean have_users; + + /* List of users */ + GList *users; + + /* List of sessions */ + GList *sessions; +} LightDMUserListPrivate; + +typedef struct +{ + GDBusProxy *proxy; + LightDMUser *user; +} UserAccountObject; + +typedef struct +{ + LightDMUserList *user_list; + + gchar *name; + gchar *real_name; + gchar *home_directory; + gchar *image; + gchar *background; + gboolean has_messages; + + GKeyFile *dmrc_file; + gchar *language; + gchar **layouts; + gchar *session; +} LightDMUserPrivate; + +typedef struct +{ + GObject parent_instance; + gchar *path; + gchar *username; +} Session; + +typedef struct +{ + GObjectClass parent_class; +} SessionClass; + +G_DEFINE_TYPE (LightDMUserList, lightdm_user_list, G_TYPE_OBJECT); +G_DEFINE_TYPE (LightDMUser, lightdm_user, G_TYPE_OBJECT); +#define SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), session_get_type (), Session)) +GType session_get_type (void); +G_DEFINE_TYPE (Session, session, G_TYPE_OBJECT); + +#define GET_LIST_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER_LIST, LightDMUserListPrivate) +#define GET_USER_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER, LightDMUserPrivate) + +#define PASSWD_FILE "/etc/passwd" +#define USER_CONFIG_FILE "/etc/lightdm/users.conf" + +static LightDMUserList *singleton = NULL; + +/** + * lightdm_user_list_get_instance: + * + * Get the user list. + * + * Return value: (transfer none): the #LightDMUserList + **/ +LightDMUserList * +lightdm_user_list_get_instance (void) +{ + if (!singleton) + singleton = g_object_new (LIGHTDM_TYPE_USER_LIST, NULL); + return singleton; +} + +static LightDMUser * +get_user_by_name (LightDMUserList *user_list, const gchar *username) +{ + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list); + GList *link; + + for (link = priv->users; link; link = link->next) + { + LightDMUser *user = link->data; + if (strcmp (lightdm_user_get_name (user), username) == 0) + return user; + } + + return NULL; +} + +static gint +compare_user (gconstpointer a, gconstpointer b) +{ + LightDMUser *user_a = (LightDMUser *) a, *user_b = (LightDMUser *) b; + return strcmp (lightdm_user_get_display_name (user_a), lightdm_user_get_display_name (user_b)); +} + +static gboolean +update_passwd_user (LightDMUser *user, const gchar *real_name, const gchar *home_directory, const gchar *image) +{ + LightDMUserPrivate *priv = GET_USER_PRIVATE (user); + + if (g_strcmp0 (lightdm_user_get_real_name (user), real_name) == 0 && + g_strcmp0 (lightdm_user_get_home_directory (user), home_directory) == 0 && + g_strcmp0 (lightdm_user_get_image (user), image) == 0) + return FALSE; + + g_free (priv->real_name); + priv->real_name = g_strdup (real_name); + g_free (priv->home_directory); + priv->home_directory = g_strdup (home_directory); + g_free (priv->image); + priv->image = g_strdup (image); + + return TRUE; +} + +static void +user_changed_cb (LightDMUser *user, LightDMUserList *user_list) +{ + g_signal_emit (user_list, list_signals[USER_CHANGED], 0, user); +} + +static void +load_passwd_file (LightDMUserList *user_list, gboolean emit_add_signal) +{ + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list); + GKeyFile *config; + gchar *value; + gint minimum_uid; + gchar **hidden_users, **hidden_shells; + GList *users = NULL, *old_users, *new_users = NULL, *changed_users = NULL, *link; + GError *error = NULL; + + g_debug ("Loading user config from %s", USER_CONFIG_FILE); + + config = g_key_file_new (); + g_key_file_load_from_file (config, USER_CONFIG_FILE, G_KEY_FILE_NONE, &error); + if (error && !g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + g_warning ("Failed to load configuration from %s: %s", USER_CONFIG_FILE, error->message); // FIXME: Don't make warning on no file, just info + g_clear_error (&error); + + if (g_key_file_has_key (config, "UserList", "minimum-uid", NULL)) + minimum_uid = g_key_file_get_integer (config, "UserList", "minimum-uid", NULL); + else + minimum_uid = 500; + + value = g_key_file_get_string (config, "UserList", "hidden-users", NULL); + if (!value) + value = g_strdup ("nobody nobody4 noaccess"); + hidden_users = g_strsplit (value, " ", -1); + g_free (value); + + value = g_key_file_get_string (config, "UserList", "hidden-shells", NULL); + if (!value) + value = g_strdup ("/bin/false /usr/sbin/nologin"); + hidden_shells = g_strsplit (value, " ", -1); + g_free (value); + + g_key_file_free (config); + + setpwent (); + + while (TRUE) + { + struct passwd *entry; + LightDMUser *user; + LightDMUserPrivate *user_priv; + char **tokens; + gchar *real_name, *image; + int i; + + errno = 0; + entry = getpwent (); + if (!entry) + break; + + /* Ignore system users */ + if (entry->pw_uid < minimum_uid) + continue; + + /* Ignore users disabled by shell */ + if (entry->pw_shell) + { + for (i = 0; hidden_shells[i] && strcmp (entry->pw_shell, hidden_shells[i]) != 0; i++); + if (hidden_shells[i]) + continue; + } + + /* Ignore certain users */ + for (i = 0; hidden_users[i] && strcmp (entry->pw_name, hidden_users[i]) != 0; i++); + if (hidden_users[i]) + continue; + + tokens = g_strsplit (entry->pw_gecos, ",", -1); + if (tokens[0] != NULL && tokens[0][0] != '\0') + real_name = g_strdup (tokens[0]); + else + real_name = g_strdup (""); + g_strfreev (tokens); + + image = g_build_filename (entry->pw_dir, ".face", NULL); + if (!g_file_test (image, G_FILE_TEST_EXISTS)) + { + g_free (image); + image = g_build_filename (entry->pw_dir, ".face.icon", NULL); + if (!g_file_test (image, G_FILE_TEST_EXISTS)) + { + g_free (image); + image = NULL; + } + } + + user = g_object_new (LIGHTDM_TYPE_USER, NULL); + user_priv = GET_USER_PRIVATE (user); + user_priv->user_list = user_list; + g_free (user_priv->name); + user_priv->name = g_strdup (entry->pw_name); + g_free (user_priv->real_name); + user_priv->real_name = real_name; + g_free (user_priv->home_directory); + user_priv->home_directory = g_strdup (entry->pw_dir); + g_free (user_priv->image); + user_priv->image = image; + + /* Update existing users if have them */ + for (link = priv->users; link; link = link->next) + { + LightDMUser *info = link->data; + if (strcmp (lightdm_user_get_name (info), lightdm_user_get_name (user)) == 0) + { + if (update_passwd_user (info, lightdm_user_get_real_name (user), lightdm_user_get_home_directory (user), lightdm_user_get_image (user))) + changed_users = g_list_insert_sorted (changed_users, info, compare_user); + g_object_unref (user); + user = info; + break; + } + } + if (!link) + { + /* Only notify once we have loaded the user list */ + if (priv->have_users) + new_users = g_list_insert_sorted (new_users, user, compare_user); + } + users = g_list_insert_sorted (users, user, compare_user); + } + g_strfreev (hidden_users); + g_strfreev (hidden_shells); + + if (errno != 0) + g_warning ("Failed to read password database: %s", strerror (errno)); + + endpwent (); + + /* Use new user list */ + old_users = priv->users; + priv->users = users; + + /* Notify of changes */ + for (link = new_users; link; link = link->next) + { + LightDMUser *info = link->data; + g_debug ("User %s added", lightdm_user_get_name (info)); + g_signal_connect (info, "changed", G_CALLBACK (user_changed_cb), user_list); + if (emit_add_signal) + g_signal_emit (user_list, list_signals[USER_ADDED], 0, info); + } + g_list_free (new_users); + for (link = changed_users; link; link = link->next) + { + LightDMUser *info = link->data; + g_debug ("User %s changed", lightdm_user_get_name (info)); + g_signal_emit (info, user_signals[CHANGED], 0); + } + g_list_free (changed_users); + for (link = old_users; link; link = link->next) + { + GList *new_link; + + /* See if this user is in the current list */ + for (new_link = priv->users; new_link; new_link = new_link->next) + { + if (new_link->data == link->data) + break; + } + + if (!new_link) + { + LightDMUser *info = link->data; + g_debug ("User %s removed", lightdm_user_get_name (info)); + g_signal_emit (user_list, list_signals[USER_REMOVED], 0, info); + g_object_unref (info); + } + } + g_list_free (old_users); +} + +static void +passwd_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, LightDMUserList *user_list) +{ + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) + { + g_debug ("%s changed, reloading user list", g_file_get_path (file)); + load_passwd_file (user_list, TRUE); + } +} + +static gboolean +update_user (UserAccountObject *object) +{ + LightDMUserPrivate *priv = GET_USER_PRIVATE (object->user); + GVariant *result, *value; + GVariantIter *iter; + gchar *name; + GError *error = NULL; + + result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (object->proxy), + "org.freedesktop.Accounts", + g_dbus_proxy_get_object_path (object->proxy), + "org.freedesktop.DBus.Properties", + "GetAll", + g_variant_new ("(s)", "org.freedesktop.Accounts.User"), + G_VARIANT_TYPE ("(a{sv})"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error) + g_warning ("Error updating user %s: %s", g_dbus_proxy_get_object_path (object->proxy), error->message); + g_clear_error (&error); + if (!result) + return FALSE; + + g_variant_get (result, "(a{sv})", &iter); + while (g_variant_iter_loop (iter, "{&sv}", &name, &value)) + { + if (strcmp (name, "UserName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + gchar *user_name; + g_variant_get (value, "&s", &user_name); + g_free (priv->name); + priv->name = g_strdup (user_name); + } + else if (strcmp (name, "RealName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + gchar *real_name; + g_variant_get (value, "&s", &real_name); + g_free (priv->real_name); + priv->real_name = g_strdup (real_name); + } + else if (strcmp (name, "HomeDirectory") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + gchar *home_directory; + g_variant_get (value, "&s", &home_directory); + g_free (priv->home_directory); + priv->home_directory = g_strdup (home_directory); + } + else if (strcmp (name, "IconFile") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + gchar *icon_file; + g_variant_get (value, "&s", &icon_file); + g_free (priv->image); + if (strcmp (icon_file, "") == 0) + priv->image = NULL; + else + priv->image = g_strdup (icon_file); + } + else if (strcmp (name, "BackgroundFile") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + { + gchar *background_file; + g_variant_get (value, "&s", &background_file); + g_free (priv->background); + if (strcmp (background_file, "") == 0) + priv->background = NULL; + else + priv->background = g_strdup (background_file); + } + } + g_variant_iter_free (iter); + + g_variant_unref (result); + + return TRUE; +} + +static void +user_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UserAccountObject *object) +{ + if (strcmp (signal_name, "Changed") == 0) + { + if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("()"))) + { + g_debug ("User %s changed", g_dbus_proxy_get_object_path (object->proxy)); + update_user (object); + g_signal_emit (object->user, user_signals[CHANGED], 0); + } + else + g_warning ("Got org.freedesktop.Accounts.User signal Changed with unknown parameters %s", g_variant_get_type_string (parameters)); + } +} + +static UserAccountObject * +user_account_object_new (LightDMUserList *user_list, const gchar *path) +{ + GDBusProxy *proxy; + UserAccountObject *object; + GError *error = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.Accounts", + path, + "org.freedesktop.Accounts.User", + NULL, + &error); + if (error) + g_warning ("Error getting user %s: %s", path, error->message); + g_clear_error (&error); + if (!proxy) + return NULL; + + object = g_malloc0 (sizeof (UserAccountObject)); + object->user = g_object_new (LIGHTDM_TYPE_USER, NULL); + GET_USER_PRIVATE (object->user)->user_list = user_list; + object->proxy = proxy; + g_signal_connect (proxy, "g-signal", G_CALLBACK (user_signal_cb), object); + + return object; +} + +static void +user_account_object_free (UserAccountObject *object) +{ + if (!object) + return; + g_object_unref (object->user); + g_object_unref (object->proxy); + g_free (object); +} + +static UserAccountObject * +find_user_account_object (LightDMUserList *user_list, const gchar *path) +{ + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list); + GList *link; + + for (link = priv->user_account_objects; link; link = link->next) + { + UserAccountObject *object = link->data; + if (strcmp (g_dbus_proxy_get_object_path (object->proxy), path) == 0) + return object; + } + + return NULL; +} + +static void +user_accounts_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list) +{ + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list); + + if (strcmp (signal_name, "UserAdded") == 0) + { + if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) + { + gchar *path; + UserAccountObject *object; + + g_variant_get (parameters, "(&o)", &path); + + /* Ignore duplicate requests */ + object = find_user_account_object (user_list, path); + if (object) + return; + + object = user_account_object_new (user_list, path); + if (object && update_user (object)) + { + g_debug ("User %s added", path); + priv->user_account_objects = g_list_append (priv->user_account_objects, object); + priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user); + g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list); + g_signal_emit (user_list, list_signals[USER_ADDED], 0, object->user); + } + else + user_account_object_free (object); + } + else + g_warning ("Got UserAccounts signal UserAdded with unknown parameters %s", g_variant_get_type_string (parameters)); + } + else if (strcmp (signal_name, "UserDeleted") == 0) + { + if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) + { + gchar *path; + UserAccountObject *object; + + g_variant_get (parameters, "(&o)", &path); + + object = find_user_account_object (user_list, path); + if (!object) + return; + + g_debug ("User %s deleted", path); + priv->users = g_list_remove (priv->users, object->user); + g_object_unref (object->user); + + g_signal_emit (user_list, list_signals[USER_REMOVED], 0, object->user); + + priv->user_account_objects = g_list_remove (priv->user_account_objects, object); + user_account_object_free (object); + } + else + g_warning ("Got UserAccounts signal UserDeleted with unknown parameters %s", g_variant_get_type_string (parameters)); + } +} + +static Session * +load_session (LightDMUserList *user_list, const gchar *path) +{ + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list); + Session *session = NULL; + GVariant *result, *username; + GError *error = NULL; + + result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy), + "org.freedesktop.DisplayManager", + path, + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", "org.freedesktop.DisplayManager.Session", "UserName"), + G_VARIANT_TYPE ("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error) + g_warning ("Error getting UserName from org.freedesktop.DisplayManager.Session: %s", error->message); + g_clear_error (&error); + if (!result) + return NULL; + + g_variant_get (result, "(v)", &username); + if (g_variant_is_of_type (username, G_VARIANT_TYPE_STRING)) + { + gchar *name; + + g_variant_get (username, "&s", &name); + + g_debug ("Loaded session %s (%s)", path, name); + session = g_object_new (session_get_type (), NULL); + session->username = g_strdup (name); + session->path = g_strdup (path); + priv->sessions = g_list_append (priv->sessions, session); + } + g_variant_unref (username); + g_variant_unref (result); + + return session; +} + +static void +display_manager_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list) +{ + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list); + + if (strcmp (signal_name, "SessionAdded") == 0) + { + if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) + { + gchar *path; + Session *session; + LightDMUser *user = NULL; + + g_variant_get (parameters, "(&o)", &path); + session = load_session (user_list, path); + if (session) + user = get_user_by_name (user_list, session->username); + if (user) + g_signal_emit (user, user_signals[CHANGED], 0); + } + } + else if (strcmp (signal_name, "SessionRemoved") == 0) + { + if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)"))) + { + gchar *path; + GList *link; + + g_variant_get (parameters, "(&o)", &path); + + for (link = priv->sessions; link; link = link->next) + { + Session *session = link->data; + if (strcmp (session->path, path) == 0) + { + LightDMUser *user; + + g_debug ("Session %s removed", path); + priv->sessions = g_list_remove_link (priv->sessions, link); + user = get_user_by_name (user_list, session->username); + if (user) + g_signal_emit (user, user_signals[CHANGED], 0); + g_object_unref (session); + break; + } + } + } + } +} + +static void +update_users (LightDMUserList *user_list) +{ + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list); + GError *error = NULL; + + if (priv->have_users) + return; + priv->have_users = TRUE; + + priv->accounts_service_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.Accounts", + "/org/freedesktop/Accounts", + "org.freedesktop.Accounts", + NULL, + &error); + if (error) + g_warning ("Error contacting org.freedesktop.Accounts: %s", error->message); + g_clear_error (&error); + + /* Check if the service exists */ + if (priv->accounts_service_proxy) + { + gchar *name; + + name = g_dbus_proxy_get_name_owner (priv->accounts_service_proxy); + if (!name) + { + g_debug ("org.freedesktop.Accounts does not exist, falling back to passwd file"); + g_object_unref (priv->accounts_service_proxy); + priv->accounts_service_proxy = NULL; + } + g_free (name); + } + + if (priv->accounts_service_proxy) + { + GVariant *result; + + g_signal_connect (priv->accounts_service_proxy, "g-signal", G_CALLBACK (user_accounts_signal_cb), user_list); + + result = g_dbus_proxy_call_sync (priv->accounts_service_proxy, + "ListCachedUsers", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error) + g_warning ("Error getting user list from org.freedesktop.Accounts: %s", error->message); + g_clear_error (&error); + if (!result) + return; + + if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(ao)"))) + { + GVariantIter *iter; + const gchar *path; + + g_debug ("Loading users from org.freedesktop.Accounts"); + g_variant_get (result, "(ao)", &iter); + while (g_variant_iter_loop (iter, "&o", &path)) + { + UserAccountObject *object; + + g_debug ("Loading user %s", path); + + object = user_account_object_new (user_list, path); + if (object && update_user (object)) + { + priv->user_account_objects = g_list_append (priv->user_account_objects, object); + priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user); + g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list); + } + else + user_account_object_free (object); + } + g_variant_iter_free (iter); + } + else + g_warning ("Unexpected type from ListCachedUsers: %s", g_variant_get_type_string (result)); + + g_variant_unref (result); + } + else + { + GFile *passwd_file; + + load_passwd_file (user_list, FALSE); + + /* Watch for changes to user list */ + + passwd_file = g_file_new_for_path (PASSWD_FILE); + priv->passwd_monitor = g_file_monitor (passwd_file, G_FILE_MONITOR_NONE, NULL, &error); + g_object_unref (passwd_file); + if (error) + g_warning ("Error monitoring %s: %s", PASSWD_FILE, error->message); + else + g_signal_connect (priv->passwd_monitor, "changed", G_CALLBACK (passwd_changed_cb), user_list); + g_clear_error (&error); + } + + priv->display_manager_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.DisplayManager", + "/org/freedesktop/DisplayManager", + "org.freedesktop.DisplayManager", + NULL, + &error); + if (error) + g_warning ("Error contacting org.freedesktop.DisplayManager: %s", error->message); + g_clear_error (&error); + + if (priv->display_manager_proxy) + { + GVariant *result; + + g_signal_connect (priv->display_manager_proxy, "g-signal", G_CALLBACK (display_manager_signal_cb), user_list); + + result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy), + "org.freedesktop.DisplayManager", + "/org/freedesktop/DisplayManager", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", "org.freedesktop.DisplayManager", "Sessions"), + G_VARIANT_TYPE ("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + &error); + if (error) + g_warning ("Error getting session list from org.freedesktop.DisplayManager: %s", error->message); + g_clear_error (&error); + if (!result) + return; + + if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(v)"))) + { + GVariant *value; + GVariantIter *iter; + const gchar *path; + + g_variant_get (result, "(v)", &value); + + g_debug ("Loading sessions from org.freedesktop.DisplayManager"); + g_variant_get (value, "ao", &iter); + while (g_variant_iter_loop (iter, "&o", &path)) + load_session (user_list, path); + g_variant_iter_free (iter); + + g_variant_unref (value); + } + else + g_warning ("Unexpected type from org.freedesktop.DisplayManager.Sessions: %s", g_variant_get_type_string (result)); + + g_variant_unref (result); + } +} + +/** + * lightdm_user_list_get_length: + * @user_list: a #LightDMUserList + * + * Return value: The number of users able to log in + **/ +gint +lightdm_user_list_get_length (LightDMUserList *user_list) +{ + g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), 0); + update_users (user_list); + return g_list_length (GET_LIST_PRIVATE (user_list)->users); +} + +/** + * lightdm_user_list_get_users: + * @user_list: A #LightDMUserList + * + * Get a list of users to present to the user. This list may be a subset of the + * available users and may be empty depending on the server configuration. + * + * Return value: (element-type LightDMUser) (transfer none): A list of #LightDMUser that should be presented to the user. + **/ +GList * +lightdm_user_list_get_users (LightDMUserList *user_list) +{ + g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL); + update_users (user_list); + return GET_LIST_PRIVATE (user_list)->users; +} + +/** + * lightdm_user_list_get_user_by_name: + * @user_list: A #LightDMUserList + * @username: Name of user to get. + * + * Get infomation about a given user or #NULL if this user doesn't exist. + * + * Return value: (transfer none): A #LightDMUser entry for the given user. + **/ +LightDMUser * +lightdm_user_list_get_user_by_name (LightDMUserList *user_list, const gchar *username) +{ + g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL); + g_return_val_if_fail (username != NULL, NULL); + + update_users (user_list); + + return get_user_by_name (user_list, username); +} + +static void +lightdm_user_list_init (LightDMUserList *user_list) +{ +} + +static void +lightdm_user_list_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +lightdm_user_list_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LightDMUserList *self; + + self = LIGHTDM_USER_LIST (object); + + switch (prop_id) + { + case LIST_PROP_NUM_USERS: + g_value_set_int (value, lightdm_user_list_get_length (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +lightdm_user_list_finalize (GObject *object) +{ + LightDMUserList *self = LIGHTDM_USER_LIST (object); + LightDMUserListPrivate *priv = GET_LIST_PRIVATE (self); + + if (priv->accounts_service_proxy) + g_object_unref (priv->accounts_service_proxy); + g_list_free_full (priv->user_account_objects, (GDestroyNotify) user_account_object_free); + if (priv->passwd_monitor) + g_object_unref (priv->passwd_monitor); + g_list_free_full (priv->users, g_object_unref); + g_list_free_full (priv->sessions, g_object_unref); + + G_OBJECT_CLASS (lightdm_user_list_parent_class)->finalize (object); +} + +static void +lightdm_user_list_class_init (LightDMUserListClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (LightDMUserListPrivate)); + + object_class->set_property = lightdm_user_list_set_property; + object_class->get_property = lightdm_user_list_get_property; + object_class->finalize = lightdm_user_list_finalize; + + g_object_class_install_property (object_class, + LIST_PROP_NUM_USERS, + g_param_spec_int ("num-users", + "num-users", + "Number of login users", + 0, G_MAXINT, 0, + G_PARAM_READABLE)); + /** + * LightDMUserList::user-added: + * @user_list: A #LightDMUserList + * @user: The #LightDM user that has been added. + * + * The ::user-added signal gets emitted when a user account is created. + **/ + list_signals[USER_ADDED] = + g_signal_new ("user-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMUserListClass, user_added), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, LIGHTDM_TYPE_USER); + + /** + * LightDMUserList::user-changed: + * @user_list: A #LightDMUserList + * @user: The #LightDM user that has been changed. + * + * The ::user-changed signal gets emitted when a user account is modified. + **/ + list_signals[USER_CHANGED] = + g_signal_new ("user-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMUserListClass, user_changed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, LIGHTDM_TYPE_USER); + + /** + * LightDMUserList::user-removed: + * @user_list: A #LightDMUserList + * @user: The #LightDM user that has been removed. + * + * The ::user-removed signal gets emitted when a user account is removed. + **/ + list_signals[USER_REMOVED] = + g_signal_new ("user-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMUserListClass, user_removed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, LIGHTDM_TYPE_USER); +} + +/** + * lightdm_user_get_name: + * @user: A #LightDMUser + * + * Get the name of a user. + * + * Return value: The name of the given user + **/ +const gchar * +lightdm_user_get_name (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + return GET_USER_PRIVATE (user)->name; +} + +/** + * lightdm_user_get_real_name: + * @user: A #LightDMUser + * + * Get the real name of a user. + * + * Return value: The real name of the given user + **/ +const gchar * +lightdm_user_get_real_name (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + return GET_USER_PRIVATE (user)->real_name; +} + +/** + * lightdm_user_get_display_name: + * @user: A #LightDMUser + * + * Get the display name of a user. + * + * Return value: The display name of the given user + **/ +const gchar * +lightdm_user_get_display_name (LightDMUser *user) +{ + LightDMUserPrivate *priv; + + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + + priv = GET_USER_PRIVATE (user); + if (strcmp (priv->real_name, "")) + return priv->real_name; + else + return priv->name; +} + +/** + * lightdm_user_get_home_directory: + * @user: A #LightDMUser + * + * Get the home directory for a user. + * + * Return value: The users home directory + */ +const gchar * +lightdm_user_get_home_directory (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + return GET_USER_PRIVATE (user)->home_directory; +} + +/** + * lightdm_user_get_image: + * @user: A #LightDMUser + * + * Get the image URI for a user. + * + * Return value: The image URI for the given user or #NULL if no URI + **/ +const gchar * +lightdm_user_get_image (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + return GET_USER_PRIVATE (user)->image; +} + +/** + * lightdm_user_get_background: + * @user: A #LightDMUser + * + * Get the background file path for a user. + * + * Return value: The background file path for the given user or #NULL if no path + **/ +const gchar * +lightdm_user_get_background (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + return GET_USER_PRIVATE (user)->background; +} + +static void +load_dmrc (LightDMUser *user) +{ + LightDMUserPrivate *priv = GET_USER_PRIVATE (user); + gchar *path; + //gboolean have_dmrc; + + if (!priv->dmrc_file) + priv->dmrc_file = g_key_file_new (); + + /* Load from the user directory */ + path = g_build_filename (priv->home_directory, ".dmrc", NULL); + /*have_dmrc = */g_key_file_load_from_file (priv->dmrc_file, path, G_KEY_FILE_KEEP_COMMENTS, NULL); + g_free (path); + + /* If no ~/.dmrc, then load from the cache */ + // FIXME + + // FIXME: Watch for changes + + /* The Language field is actually a locale, strip the codeset off it to get the language */ + if (priv->language) + g_free (priv->language); + priv->language = g_key_file_get_string (priv->dmrc_file, "Desktop", "Language", NULL); + if (priv->language) + { + gchar *codeset = strchr (priv->language, '.'); + if (codeset) + *codeset = '\0'; + } + + if (priv->layouts) + { + g_strfreev (priv->layouts); + priv->layouts = NULL; + } + if (g_key_file_has_key (priv->dmrc_file, "Desktop", "Layout", NULL)) + { + priv->layouts = g_malloc (sizeof (gchar *) * 2); + priv->layouts[0] = g_key_file_get_string (priv->dmrc_file, "Desktop", "Layout", NULL); + priv->layouts[1] = NULL; + } + + if (priv->session) + g_free (priv->session); + priv->session = g_key_file_get_string (priv->dmrc_file, "Desktop", "Session", NULL); +} + +static GVariant * +get_property (GDBusProxy *proxy, const gchar *property) +{ + GVariant *answer; + + if (!proxy) + return NULL; + + answer = g_dbus_proxy_get_cached_property (proxy, property); + + if (!answer) + { + g_warning ("Could not get accounts property %s", property); + return NULL; + } + + return answer; +} + +static gboolean +get_boolean_property (GDBusProxy *proxy, const gchar *property) +{ + GVariant *answer; + gboolean rv; + + answer = get_property (proxy, property); + if (!g_variant_is_of_type (answer, G_VARIANT_TYPE_BOOLEAN)) + { + g_warning ("Unexpected accounts property type for %s: %s", + property, g_variant_get_type_string (answer)); + g_variant_unref (answer); + return FALSE; + } + + rv = g_variant_get_boolean (answer); + g_variant_unref (answer); + + return rv; +} + +static gchar * +get_string_property (GDBusProxy *proxy, const gchar *property) +{ + GVariant *answer; + gchar *rv; + + answer = get_property (proxy, property); + if (!g_variant_is_of_type (answer, G_VARIANT_TYPE_STRING)) + { + g_warning ("Unexpected accounts property type for %s: %s", + property, g_variant_get_type_string (answer)); + g_variant_unref (answer); + return NULL; + } + + rv = g_strdup (g_variant_get_string (answer, NULL)); + if (strcmp (rv, "") == 0) + { + g_free (rv); + rv = NULL; + } + g_variant_unref (answer); + + return rv; +} + +static gchar ** +get_string_array_property (GDBusProxy *proxy, const gchar *property) +{ + GVariant *answer; + gchar **rv; + + if (!proxy) + return NULL; + + answer = g_dbus_proxy_get_cached_property (proxy, property); + + if (!answer) + { + g_warning ("Could not get accounts property %s", property); + return NULL; + } + + if (!g_variant_is_of_type (answer, G_VARIANT_TYPE ("as"))) + { + g_warning ("Unexpected accounts property type for %s: %s", + property, g_variant_get_type_string (answer)); + g_variant_unref (answer); + return NULL; + } + + rv = g_variant_dup_strv (answer, NULL); + + g_variant_unref (answer); + return rv; +} + +static gboolean +load_accounts_service (LightDMUser *user) +{ + LightDMUserPrivate *priv = GET_USER_PRIVATE (user); + LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list); + UserAccountObject *account = NULL; + GList *iter; + gchar **value; + + /* First, find AccountObject proxy */ + for (iter = list_priv->user_account_objects; iter; iter = iter->next) + { + UserAccountObject *a = iter->data; + if (a->user == user) + { + account = a; + break; + } + } + if (!account) + return FALSE; + + /* We have proxy, let's grab some properties */ + if (priv->language) + g_free (priv->language); + priv->language = get_string_property (account->proxy, "Language"); + if (priv->session) + g_free (priv->session); + priv->session = get_string_property (account->proxy, "XSession"); + + value = get_string_array_property (account->proxy, "XKeyboardLayouts"); + if (value) + { + if (value[0]) + { + g_strfreev (priv->layouts); + priv->layouts = value; + } + else + g_strfreev (value); + } + + priv->has_messages = get_boolean_property (account->proxy, "XHasMessages"); + + return TRUE; +} + +/* Loads language/layout/session info for user */ +static void +load_user_values (LightDMUser *user) +{ + LightDMUserPrivate *priv = GET_USER_PRIVATE (user); + + load_dmrc (user); + load_accounts_service (user); // overrides dmrc values + + /* Ensure a few guarantees */ + if (priv->layouts == NULL) + { + priv->layouts = g_malloc (sizeof (gchar *) * 1); + priv->layouts[0] = NULL; + } +} + +/** + * lightdm_user_get_language: + * @user: A #LightDMUser + * + * Get the language for a user. + * + * Return value: The language for the given user or #NULL if using system defaults. + **/ +const gchar * +lightdm_user_get_language (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + load_user_values (user); + return GET_USER_PRIVATE (user)->language; +} + +/** + * lightdm_user_get_layout: + * @user: A #LightDMUser + * + * Get the keyboard layout for a user. + * + * Return value: The keyboard layout for the given user or #NULL if using system defaults. Copy the value if you want to use it long term. + **/ +const gchar * +lightdm_user_get_layout (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + load_user_values (user); + return GET_USER_PRIVATE (user)->layouts[0]; +} + +/** + * lightdm_user_get_layouts: + * @user: A #LightDMUser + * + * Get the configured keyboard layouts for a user. + * + * Return value: (transfer none): A NULL-terminated array of keyboard layouts for the given user. Copy the values if you want to use them long term. + **/ +const gchar * const * +lightdm_user_get_layouts (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + load_user_values (user); + return (const gchar * const *) GET_USER_PRIVATE (user)->layouts; +} + +/** + * lightdm_user_get_session: + * @user: A #LightDMUser + * + * Get the session for a user. + * + * Return value: The session for the given user or #NULL if using system defaults. + **/ +const gchar * +lightdm_user_get_session (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL); + load_user_values (user); + return GET_USER_PRIVATE (user)->session; +} + +/** + * lightdm_user_get_logged_in: + * @user: A #LightDMUser + * + * Check if a user is logged in. + * + * Return value: #TRUE if the user is currently logged in. + **/ +gboolean +lightdm_user_get_logged_in (LightDMUser *user) +{ + LightDMUserPrivate *priv = GET_USER_PRIVATE (user); + LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list); + GList *link; + + g_return_val_if_fail (LIGHTDM_IS_USER (user), FALSE); + + for (link = list_priv->sessions; link; link = link->next) + { + Session *session = link->data; + if (strcmp (session->username, priv->name) == 0) + return TRUE; + } + + return FALSE; +} + +/** + * lightdm_user_get_has_messages: + * @user: A #LightDMUser + * + * Check if a user has waiting messages. + * + * Return value: #TRUE if the user has waiting messages. + **/ +gboolean +lightdm_user_get_has_messages (LightDMUser *user) +{ + g_return_val_if_fail (LIGHTDM_IS_USER (user), FALSE); + load_user_values (user); + return GET_USER_PRIVATE (user)->has_messages; +} + +static void +lightdm_user_init (LightDMUser *user) +{ +} + +static void +lightdm_user_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +lightdm_user_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + LightDMUser *self; + + self = LIGHTDM_USER (object); + + switch (prop_id) + { + case USER_PROP_NAME: + g_value_set_string (value, lightdm_user_get_name (self)); + break; + case USER_PROP_REAL_NAME: + g_value_set_string (value, lightdm_user_get_real_name (self)); + break; + case USER_PROP_DISPLAY_NAME: + g_value_set_string (value, lightdm_user_get_display_name (self)); + break; + case USER_PROP_HOME_DIRECTORY: + g_value_set_string (value, lightdm_user_get_home_directory (self)); + break; + case USER_PROP_IMAGE: + g_value_set_string (value, lightdm_user_get_image (self)); + break; + case USER_PROP_BACKGROUND: + g_value_set_string (value, lightdm_user_get_background (self)); + break; + case USER_PROP_LANGUAGE: + g_value_set_string (value, lightdm_user_get_language (self)); + break; + case USER_PROP_LAYOUT: + g_value_set_string (value, lightdm_user_get_layout (self)); + break; + case USER_PROP_LAYOUTS: + g_value_set_boxed (value, g_strdupv ((gchar **) lightdm_user_get_layouts (self))); + break; + case USER_PROP_SESSION: + g_value_set_string (value, lightdm_user_get_session (self)); + break; + case USER_PROP_LOGGED_IN: + g_value_set_boolean (value, lightdm_user_get_logged_in (self)); + break; + case USER_PROP_HAS_MESSAGES: + g_value_set_boolean (value, lightdm_user_get_has_messages (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +lightdm_user_finalize (GObject *object) +{ + LightDMUser *self = LIGHTDM_USER (object); + LightDMUserPrivate *priv = GET_USER_PRIVATE (self); + + g_free (priv->name); + g_free (priv->real_name); + g_free (priv->home_directory); + g_free (priv->image); + g_free (priv->background); + g_strfreev (priv->layouts); + if (priv->dmrc_file) + g_key_file_free (priv->dmrc_file); +} + +static void +lightdm_user_class_init (LightDMUserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (LightDMUserPrivate)); + + object_class->set_property = lightdm_user_set_property; + object_class->get_property = lightdm_user_get_property; + object_class->finalize = lightdm_user_finalize; + + g_object_class_install_property (object_class, + USER_PROP_NAME, + g_param_spec_string ("name", + "name", + "Username", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + USER_PROP_REAL_NAME, + g_param_spec_string ("real-name", + "real-name", + "Users real name", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + USER_PROP_DISPLAY_NAME, + g_param_spec_string ("display-name", + "display-name", + "Users display name", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + USER_PROP_HOME_DIRECTORY, + g_param_spec_string ("home-directory", + "home-directory", + "Home directory", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + USER_PROP_IMAGE, + g_param_spec_string ("image", + "image", + "Avatar image", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + USER_PROP_BACKGROUND, + g_param_spec_string ("background", + "background", + "User background", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + USER_PROP_LANGUAGE, + g_param_spec_string ("language", + "language", + "Language used by this user", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + USER_PROP_LAYOUT, + g_param_spec_string ("layout", + "layout", + "Keyboard layout used by this user", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + USER_PROP_LAYOUTS, + g_param_spec_boxed ("layouts", + "layouts", + "Keyboard layouts used by this user", + G_TYPE_STRV, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + USER_PROP_SESSION, + g_param_spec_string ("session", + "session", + "Session used by this user", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property (object_class, + USER_PROP_LOGGED_IN, + g_param_spec_boolean ("logged-in", + "logged-in", + "TRUE if the user is currently in a session", + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + USER_PROP_LOGGED_IN, + g_param_spec_boolean ("has-messages", + "has-messages", + "TRUE if the user is has waiting messages", + FALSE, + G_PARAM_READWRITE)); + + /** + * LightDMUser::changed: + * @user: A #LightDMUser + * + * The ::changed signal gets emitted this user account is modified. + **/ + user_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (LightDMUserClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +session_init (Session *session) +{ +} + +static void +session_finalize (GObject *object) +{ + Session *self = SESSION (object); + + g_free (self->path); + g_free (self->username); +} + +static void +session_class_init (SessionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->finalize = session_finalize; +} diff --git a/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp new file mode 100644 index 00000000..a799e0db --- /dev/null +++ b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp @@ -0,0 +1,1520 @@ +/* $Id: vbox-greeter.cpp $ */ +/** @file + * vbox-greeter - an own LightDM greeter module supporting auto-logons + * controlled by the host. + */ + +/* + * Copyright (C) 2012-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* 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); + } + 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, fFlags, "all", + "VBOXGREETER_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, + RTLOGDEST_STDOUT, + vboxGreeterLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime, + 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..14d6e145 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/.scm-settings @@ -0,0 +1,29 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for linux shared folders module. +# + +# +# Copyright (C) 2010-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + + +/*.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..d520f8df --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/Makefile.kmk @@ -0,0 +1,45 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the vboxsf (linux shared folders module). +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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)) + + +# +# The mount util. +# +PROGRAMS += mount.vboxsf +mount.vboxsf_TEMPLATE = NewVBoxGuestR3Exe +mount.vboxsf_DEFS = _GNU_SOURCE +mount.vboxsf_SOURCES = \ + mount.vboxsf.c \ + vbsfmount.c + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/linux/sharedfolders/Makefile.module b/src/VBox/Additions/linux/sharedfolders/Makefile.module new file mode 100644 index 00000000..6c09d5f3 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/Makefile.module @@ -0,0 +1,91 @@ +# $Id: Makefile.module $ +## @file +# VBox Linux Shared Folders VFS Module Makefile. +# +# (For 2.6.x this file must be 'Makefile'!) +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +# Linux kbuild sets this to our source directory if we are called from there +obj ?= $(CURDIR) +include $(obj)/Makefile.include.header + +MOD_NAME = vboxsf +MOD_OBJS = \ + vfsmod.o \ + dirops.o \ + lnkops.o \ + regops.o \ + utils.o \ + VBoxGuestR0LibHGCM.o \ + VBoxGuestR0LibIdc.o \ + VBoxGuestR0LibIdc-unix.o \ + VBoxGuestR0LibSharedFolders.o +ifeq ($(BUILD_TARGET_ARCH),x86) +MOD_OBJS += \ + divdi3.o \ + moddi3.o \ + udivdi3.o \ + udivmoddi4.o \ + umoddi3.o \ + qdivrem.o +endif + +MOD_INCL = \ + $(addprefix -I$(KBUILD_EXTMOD),/ /include /r0drv/linux) \ + $(addprefix -I$(KBUILD_EXTMOD)/vboxsf,/ /include /r0drv/linux) + +ifneq ($(wildcard $(KBUILD_EXTMOD)/vboxsf),) + MANGLING := $(KBUILD_EXTMOD)/vboxsf/include/VBox/VBoxGuestMangling.h +else + MANGLING := $(KBUILD_EXTMOD)/include/VBox/VBoxGuestMangling.h +endif + +MOD_DEFS = -DRT_OS_LINUX -DIN_RING0 -DIN_RT_R0 \ + -DIN_SUP_R0 -DVBOX -DVBOX_WITH_HGCM -DIN_MODULE -DIN_GUEST_R0 +# our module does not export any symbol +MOD_DEFS += -DRT_NO_EXPORT_SYMBOL +ifeq ($(BUILD_TARGET_ARCH),amd64) + MOD_DEFS += -DRT_ARCH_AMD64 -DVBOX_WITH_64_BITS_GUESTS +else + MOD_DEFS += -DRT_ARCH_X86 +endif + +ifeq ($(KERN_VERSION), 24) + MOD_CFLAGS = +else + MOD_CFLAGS = -Wno-declaration-after-statement -fshort-wchar -include $(MANGLING) -fno-pie + +# 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),) + MOD_EXTRA += $(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 + MOD_EXTRA += $(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 + +MOD_CLEAN = . linux r0drv r0drv/linux + +include $(obj)/Makefile.include.footer diff --git a/src/VBox/Additions/linux/sharedfolders/dirops.c b/src/VBox/Additions/linux/sharedfolders/dirops.c new file mode 100644 index 00000000..9b14382f --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/dirops.c @@ -0,0 +1,893 @@ +/* $Id: dirops.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, directory inode and file operations. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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/err.h> + +/** + * Open a directory. Read the complete content into a buffer. + * + * @param inode inode + * @param file file + * @returns 0 on success, Linux error code otherwise + */ +static int sf_dir_open(struct inode *inode, struct file *file) +{ + int rc; + int err; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_dir_info *sf_d; + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + SHFLCREATEPARMS params; + + TRACE(); + BUG_ON(!sf_g); + BUG_ON(!sf_i); + + if (file->private_data) { + LogFunc(("sf_dir_open() called on already opened directory '%s'\n", sf_i->path->String.utf8)); + return 0; + } + + sf_d = sf_dir_info_alloc(); + if (!sf_d) { + LogRelFunc(("could not allocate directory info for '%s'\n", + sf_i->path->String.utf8)); + return -ENOMEM; + } + + RT_ZERO(params); + params.Handle = SHFL_HANDLE_NIL; + params.CreateFlags = 0 + | SHFL_CF_DIRECTORY + | SHFL_CF_ACT_OPEN_IF_EXISTS + | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ; + + LogFunc(("sf_dir_open(): calling VbglR0SfCreate, folder %s, flags %#x\n", sf_i->path->String.utf8, params.CreateFlags)); + rc = VbglR0SfCreate(&client_handle, &sf_g->map, sf_i->path, ¶ms); + if (RT_SUCCESS(rc)) { + if (params.Result == SHFL_FILE_EXISTS) { + err = sf_dir_read_all(sf_g, sf_i, sf_d, params.Handle); + if (!err) + file->private_data = sf_d; + } else + err = -ENOENT; + + rc = VbglR0SfClose(&client_handle, &sf_g->map, params.Handle); + if (RT_FAILURE(rc)) + LogFunc(("sf_dir_open(): VbglR0SfClose(%s) after err=%d failed rc=%Rrc\n", sf_i->path->String.utf8, err, rc)); + } else + err = -EPERM; + + if (err) + sf_dir_info_free(sf_d); + + return err; +} + +/** + * 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 sf_dir_release(struct inode *inode, struct file *file) +{ + TRACE(); + + if (file->private_data) + sf_dir_info_free(file->private_data); + + return 0; +} + +/** + * Translate RTFMODE into DT_xxx (in conjunction to rtDirType()) + * @param fMode file mode + * returns d_type + */ +static int sf_get_d_type(RTFMODE fMode) +{ + int d_type; + switch (fMode & RTFS_TYPE_MASK) { + case RTFS_TYPE_FIFO: + d_type = DT_FIFO; + break; + case RTFS_TYPE_DEV_CHAR: + d_type = DT_CHR; + break; + case RTFS_TYPE_DIRECTORY: + d_type = DT_DIR; + break; + case RTFS_TYPE_DEV_BLOCK: + d_type = DT_BLK; + break; + case RTFS_TYPE_FILE: + d_type = DT_REG; + break; + case RTFS_TYPE_SYMLINK: + d_type = DT_LNK; + break; + case RTFS_TYPE_SOCKET: + d_type = DT_SOCK; + break; + case RTFS_TYPE_WHITEOUT: + d_type = DT_WHT; + break; + default: + d_type = DT_UNKNOWN; + break; + } + return d_type; +} + +/** + * Extract element ([dir]->f_pos) from the directory [dir] into [d_name]. + * + * @returns 0 for success, 1 for end reached, Linux error code otherwise. + */ +static int sf_getdent(struct file *dir, char d_name[NAME_MAX], int *d_type) +{ + loff_t cur; + struct sf_glob_info *sf_g; + struct sf_dir_info *sf_d; + struct sf_inode_info *sf_i; + struct inode *inode; + struct list_head *pos, *list; + + TRACE(); + + inode = GET_F_DENTRY(dir)->d_inode; + sf_i = GET_INODE_INFO(inode); + sf_g = GET_GLOB_INFO(inode->i_sb); + sf_d = dir->private_data; + + BUG_ON(!sf_g); + BUG_ON(!sf_d); + BUG_ON(!sf_i); + + if (sf_i->force_reread) { + int rc; + int err; + SHFLCREATEPARMS params; + + RT_ZERO(params); + params.Handle = SHFL_HANDLE_NIL; + params.CreateFlags = 0 + | SHFL_CF_DIRECTORY + | SHFL_CF_ACT_OPEN_IF_EXISTS + | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ; + + LogFunc(("sf_getdent: calling VbglR0SfCreate, folder %s, flags %#x\n", sf_i->path->String.utf8, params.CreateFlags)); + rc = VbglR0SfCreate(&client_handle, &sf_g->map, sf_i->path, + ¶ms); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfCreate(%s) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + return -EPERM; + } + + if (params.Result != SHFL_FILE_EXISTS) { + LogFunc(("directory %s does not exist\n", + sf_i->path->String.utf8)); + sf_dir_info_free(sf_d); + return -ENOENT; + } + + sf_dir_info_empty(sf_d); + err = sf_dir_read_all(sf_g, sf_i, sf_d, params.Handle); + rc = VbglR0SfClose(&client_handle, &sf_g->map, params.Handle); + if (RT_FAILURE(rc)) + LogFunc(("VbglR0SfClose(%s) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + if (err) + return err; + + sf_i->force_reread = 0; + } + + cur = 0; + list = &sf_d->info_list; + list_for_each(pos, list) { + struct sf_dir_buf *b; + SHFLDIRINFO *info; + loff_t i; + + b = list_entry(pos, struct sf_dir_buf, head); + if (dir->f_pos >= cur + b->cEntries) { + cur += b->cEntries; + continue; + } + + for (i = 0, info = b->buf; i < dir->f_pos - cur; ++i) { + size_t size; + + size = + offsetof(SHFLDIRINFO, + name.String) + info->name.u16Size; + info = (SHFLDIRINFO *) ((uintptr_t) info + size); + } + + *d_type = sf_get_d_type(info->Info.Attr.fMode); + + return sf_nlscpy(sf_g, d_name, NAME_MAX, + info->name.String.utf8, info->name.u16Length); + } + + return 1; +} + +/** + * 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 sf_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. sf_getdent set done to 1) + * b. failure to compute fake inode number + * c. filldir returns an error (see comment on that) + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) +static int sf_dir_iterate(struct file *dir, struct dir_context *ctx) +#else +static int sf_dir_read(struct file *dir, void *opaque, filldir_t filldir) +#endif +{ + TRACE(); + for (;;) { + int err; + ino_t fake_ino; + loff_t sanity; + char d_name[NAME_MAX]; + int d_type = DT_UNKNOWN; + + err = sf_getdent(dir, d_name, &d_type); + switch (err) { + case 1: + return 0; + + case 0: + break; + + case -1: + default: + /* skip erroneous entry and proceed */ + LogFunc(("sf_getdent error %d\n", err)); + dir->f_pos += 1; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + ctx->pos += 1; +#endif + continue; + } + + /* d_name now contains a valid entry name */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + sanity = ctx->pos + 0xbeef; +#else + sanity = dir->f_pos + 0xbeef; +#endif + fake_ino = sanity; + if (sanity - fake_ino) { + LogRelFunc(("can not compute ino\n")); + return -EINVAL; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + if (!dir_emit(ctx, d_name, strlen(d_name), fake_ino, d_type)) { + LogFunc(("dir_emit failed\n")); + return 0; + } +#else + err = + filldir(opaque, d_name, strlen(d_name), dir->f_pos, + fake_ino, d_type); + if (err) { + LogFunc(("filldir returned error %d\n", err)); + /* Rely on the fact that filldir returns error + only when it runs out of space in opaque */ + return 0; + } +#endif + + dir->f_pos += 1; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + ctx->pos += 1; +#endif + } + + BUG(); +} + +struct file_operations sf_dir_fops = { + .open = sf_dir_open, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 11, 0) + .iterate = sf_dir_iterate, +#else + .readdir = sf_dir_read, +#endif + .release = sf_dir_release, + .read = generic_read_dir +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) + , .llseek = generic_file_llseek +#endif +}; + +/* iops */ + +/** + * 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 *sf_lookup(struct inode *parent, struct dentry *dentry +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) + , unsigned int flags +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + , struct nameidata *nd +#endif + ) +{ + int err; + struct sf_inode_info *sf_i, *sf_new_i; + struct sf_glob_info *sf_g; + SHFLSTRING *path; + struct inode *inode; + ino_t ino; + SHFLFSOBJINFO fsinfo; + + TRACE(); + sf_g = GET_GLOB_INFO(parent->i_sb); + sf_i = GET_INODE_INFO(parent); + + BUG_ON(!sf_g); + BUG_ON(!sf_i); + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + goto fail0; + + err = sf_stat(__func__, sf_g, path, &fsinfo, 1); + if (err) { + if (err == -ENOENT) { + /* -ENOENT: add NULL inode to dentry so it later can be + created via call to create/mkdir/open */ + kfree(path); + inode = NULL; + } else + goto fail1; + } else { + sf_new_i = kmalloc(sizeof(*sf_new_i), GFP_KERNEL); + if (!sf_new_i) { + LogRelFunc(("could not allocate memory for new inode info\n")); + err = -ENOMEM; + goto fail1; + } + sf_new_i->handle = SHFL_HANDLE_NIL; + sf_new_i->force_reread = 0; + + ino = iunique(parent->i_sb, 1); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + inode = iget_locked(parent->i_sb, ino); +#else + inode = iget(parent->i_sb, ino); +#endif + if (!inode) { + LogFunc(("iget failed\n")); + err = -ENOMEM; /* XXX: ??? */ + goto fail2; + } + + SET_INODE_INFO(inode, sf_new_i); + sf_init_inode(sf_g, inode, &fsinfo); + sf_new_i->path = path; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + unlock_new_inode(inode); +#endif + } + + sf_i->force_restat = 0; + dentry->d_time = jiffies; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38) + d_set_d_op(dentry, &sf_dentry_ops); +#else + dentry->d_op = &sf_dentry_ops; +#endif + d_add(dentry, inode); + return NULL; + + fail2: + kfree(sf_new_i); + + fail1: + kfree(path); + + fail0: + return ERR_PTR(err); +} + +/** + * This should allocate memory for sf_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 + * @param info file information + * @param handle handle + * @returns 0 on success, Linux error code otherwise + */ +static int sf_instantiate(struct inode *parent, struct dentry *dentry, + SHFLSTRING * path, PSHFLFSOBJINFO info, + SHFLHANDLE handle) +{ + int err; + ino_t ino; + struct inode *inode; + struct sf_inode_info *sf_new_i; + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + + TRACE(); + BUG_ON(!sf_g); + + sf_new_i = kmalloc(sizeof(*sf_new_i), GFP_KERNEL); + if (!sf_new_i) { + LogRelFunc(("could not allocate inode info.\n")); + err = -ENOMEM; + goto fail0; + } + + ino = iunique(parent->i_sb, 1); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + inode = iget_locked(parent->i_sb, ino); +#else + inode = iget(parent->i_sb, ino); +#endif + if (!inode) { + LogFunc(("iget failed\n")); + err = -ENOMEM; + goto fail1; + } + + sf_init_inode(sf_g, inode, info); + sf_new_i->path = path; + SET_INODE_INFO(inode, sf_new_i); + sf_new_i->force_restat = 1; + sf_new_i->force_reread = 0; + + d_instantiate(dentry, inode); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + unlock_new_inode(inode); +#endif + + /* Store this handle if we leave the handle open. */ + sf_new_i->handle = handle; + return 0; + + fail1: + kfree(sf_new_i); + + fail0: + return err; + +} + +/** + * Create a new regular file / directory. + * + * @param parent inode of the directory + * @param dentry directory cache entry + * @param mode file mode + * @param fDirectory true if directory, false otherwise + * @returns 0 on success, Linux error code otherwise + */ +static int sf_create_aux(struct inode *parent, struct dentry *dentry, + umode_t mode, int fDirectory) +{ + int rc, err; + SHFLCREATEPARMS params; + SHFLSTRING *path; + struct sf_inode_info *sf_i = GET_INODE_INFO(parent); + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + + TRACE(); + BUG_ON(!sf_i); + BUG_ON(!sf_g); + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + goto fail0; + + RT_ZERO(params); + params.Handle = SHFL_HANDLE_NIL; + params.CreateFlags = 0 + | SHFL_CF_ACT_CREATE_IF_NEW + | SHFL_CF_ACT_FAIL_IF_EXISTS + | SHFL_CF_ACCESS_READWRITE | (fDirectory ? SHFL_CF_DIRECTORY : 0); + params.Info.Attr.fMode = 0 + | (fDirectory ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE) + | (mode & S_IRWXUGO); + params.Info.Attr.enmAdditional = RTFSOBJATTRADD_NOTHING; + + LogFunc(("sf_create_aux: calling VbglR0SfCreate, folder %s, flags %#x\n", path->String.utf8, params.CreateFlags)); + rc = VbglR0SfCreate(&client_handle, &sf_g->map, path, ¶ms); + if (RT_FAILURE(rc)) { + if (rc == VERR_WRITE_PROTECT) { + err = -EROFS; + goto fail1; + } + err = -EPROTO; + LogFunc(("(%d): VbglR0SfCreate(%s) failed rc=%Rrc\n", + fDirectory, sf_i->path->String.utf8, rc)); + goto fail1; + } + + if (params.Result != SHFL_FILE_CREATED) { + err = -EPERM; + LogFunc(("(%d): could not create file %s result=%d\n", + fDirectory, sf_i->path->String.utf8, params.Result)); + goto fail1; + } + + err = sf_instantiate(parent, dentry, path, ¶ms.Info, + fDirectory ? SHFL_HANDLE_NIL : params.Handle); + if (err) { + LogFunc(("(%d): could not instantiate dentry for %s err=%d\n", + fDirectory, sf_i->path->String.utf8, err)); + goto fail2; + } + + /* + * Don't close this handle right now. We assume that the same file is + * opened with sf_reg_open() and later closed with sf_reg_close(). Save + * the handle in between. Does not apply to directories. True? + */ + if (fDirectory) { + rc = VbglR0SfClose(&client_handle, &sf_g->map, params.Handle); + if (RT_FAILURE(rc)) + LogFunc(("(%d): VbglR0SfClose failed rc=%Rrc\n", + fDirectory, rc)); + } + + sf_i->force_restat = 1; + return 0; + + fail2: + rc = VbglR0SfClose(&client_handle, &sf_g->map, params.Handle); + if (RT_FAILURE(rc)) + LogFunc(("(%d): VbglR0SfClose failed rc=%Rrc\n", fDirectory, + rc)); + + fail1: + kfree(path); + + fail0: + return err; +} + +/** + * Create a new regular file. + * + * @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 LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) || defined(DOXYGEN_RUNNING) +static int sf_create(struct inode *parent, struct dentry *dentry, umode_t mode, + bool excl) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0) +static int sf_create(struct inode *parent, struct dentry *dentry, umode_t mode, + struct nameidata *nd) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +static int sf_create(struct inode *parent, struct dentry *dentry, int mode, + struct nameidata *nd) +#else +static int sf_create(struct inode *parent, struct dentry *dentry, int mode) +#endif +{ + TRACE(); + return sf_create_aux(parent, dentry, mode, 0); +} + +/** + * Create a new directory. + * + * @param parent inode of the directory + * @param dentry directory cache entry + * @param mode file mode + * @returns 0 on success, Linux error code otherwise + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0) +static int sf_mkdir(struct inode *parent, struct dentry *dentry, umode_t mode) +#else +static int sf_mkdir(struct inode *parent, struct dentry *dentry, int mode) +#endif +{ + TRACE(); + return sf_create_aux(parent, dentry, mode, 1); +} + +/** + * 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 sf_unlink_aux(struct inode *parent, struct dentry *dentry, + int fDirectory) +{ + int rc, err; + struct sf_glob_info *sf_g = GET_GLOB_INFO(parent->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(parent); + SHFLSTRING *path; + uint32_t fFlags; + + TRACE(); + BUG_ON(!sf_g); + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + goto fail0; + + 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 = VbglR0SfRemove(&client_handle, &sf_g->map, path, fFlags); + if (RT_FAILURE(rc)) { + LogFunc(("(%d): VbglR0SfRemove(%s) failed rc=%Rrc\n", + fDirectory, path->String.utf8, rc)); + err = -RTErrConvertToErrno(rc); + goto fail1; + } + + /* directory access/change time changed */ + sf_i->force_restat = 1; + /* directory content changed */ + sf_i->force_reread = 1; + + err = 0; + + fail1: + kfree(path); + + fail0: + return err; +} + +/** + * 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 sf_unlink(struct inode *parent, struct dentry *dentry) +{ + TRACE(); + return sf_unlink_aux(parent, dentry, 0); +} + +/** + * Remove a directory. + * + * @param parent inode of the directory + * @param dentry directory cache entry + * @returns 0 on success, Linux error code otherwise + */ +static int sf_rmdir(struct inode *parent, struct dentry *dentry) +{ + TRACE(); + return sf_unlink_aux(parent, dentry, 1); +} + +/** + * Rename a regular file / directory. + * + * @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 + */ +static int sf_rename(struct inode *old_parent, struct dentry *old_dentry, + struct inode *new_parent, struct dentry *new_dentry +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) + , unsigned flags +#endif + ) +{ + int err = 0, rc = VINF_SUCCESS; + struct sf_glob_info *sf_g = GET_GLOB_INFO(old_parent->i_sb); + + TRACE(); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) + if (flags) { + LogFunc(("rename with flags=%x\n", flags)); + return -EINVAL; + } +#endif + + if (sf_g != GET_GLOB_INFO(new_parent->i_sb)) { + LogFunc(("rename with different roots\n")); + err = -EINVAL; + } else { + struct sf_inode_info *sf_old_i = GET_INODE_INFO(old_parent); + struct sf_inode_info *sf_new_i = GET_INODE_INFO(new_parent); + /* As we save the relative path inside the inode structure, we need to change + this if the rename is successful. */ + struct sf_inode_info *sf_file_i = + GET_INODE_INFO(old_dentry->d_inode); + SHFLSTRING *old_path; + SHFLSTRING *new_path; + + BUG_ON(!sf_old_i); + BUG_ON(!sf_new_i); + BUG_ON(!sf_file_i); + + old_path = sf_file_i->path; + err = sf_path_from_dentry(__func__, sf_g, sf_new_i, + new_dentry, &new_path); + if (err) + LogFunc(("failed to create new path\n")); + else { + int fDir = + ((old_dentry->d_inode->i_mode & S_IFDIR) != 0); + + rc = VbglR0SfRename(&client_handle, &sf_g->map, + old_path, new_path, + fDir ? 0 : SHFL_RENAME_FILE | + SHFL_RENAME_REPLACE_IF_EXISTS); + if (RT_SUCCESS(rc)) { + kfree(old_path); + sf_new_i->force_restat = 1; + sf_old_i->force_restat = 1; /* XXX: needed? */ + /* Set the new relative path in the inode. */ + sf_file_i->path = new_path; + } else { + LogFunc(("VbglR0SfRename failed rc=%Rrc\n", + rc)); + err = -RTErrConvertToErrno(rc); + kfree(new_path); + } + } + } + return err; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +static int sf_symlink(struct inode *parent, struct dentry *dentry, + const char *symname) +{ + int err; + int rc; + struct sf_inode_info *sf_i; + struct sf_glob_info *sf_g; + SHFLSTRING *path, *ssymname; + SHFLFSOBJINFO info; + int symname_len = strlen(symname) + 1; + + TRACE(); + sf_g = GET_GLOB_INFO(parent->i_sb); + sf_i = GET_INODE_INFO(parent); + + BUG_ON(!sf_g); + BUG_ON(!sf_i); + + err = sf_path_from_dentry(__func__, sf_g, sf_i, dentry, &path); + if (err) + goto fail0; + + ssymname = + kmalloc(offsetof(SHFLSTRING, String.utf8) + symname_len, + GFP_KERNEL); + if (!ssymname) { + LogRelFunc(("kmalloc failed, caller=sf_symlink\n")); + err = -ENOMEM; + goto fail1; + } + + ssymname->u16Length = symname_len - 1; + ssymname->u16Size = symname_len; + memcpy(ssymname->String.utf8, symname, symname_len); + + rc = VbglR0SfSymlink(&client_handle, &sf_g->map, path, ssymname, &info); + kfree(ssymname); + + if (RT_FAILURE(rc)) { + if (rc == VERR_WRITE_PROTECT) { + err = -EROFS; + goto fail1; + } + LogFunc(("VbglR0SfSymlink(%s) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + err = -EPROTO; + goto fail1; + } + + err = sf_instantiate(parent, dentry, path, &info, SHFL_HANDLE_NIL); + if (err) { + LogFunc(("could not instantiate dentry for %s err=%d\n", + sf_i->path->String.utf8, err)); + goto fail1; + } + + sf_i->force_restat = 1; + return 0; + + fail1: + kfree(path); + fail0: + return err; +} +#endif /* LINUX_VERSION_CODE >= 2.6.0 */ + +struct inode_operations sf_dir_iops = { + .lookup = sf_lookup, + .create = sf_create, + .mkdir = sf_mkdir, + .rmdir = sf_rmdir, + .unlink = sf_unlink, + .rename = sf_rename, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) + .revalidate = sf_inode_revalidate +#else + .getattr = sf_getattr, + .setattr = sf_setattr, + .symlink = sf_symlink +#endif +}; diff --git a/src/VBox/Additions/linux/sharedfolders/files_vboxsf b/src/VBox/Additions/linux/sharedfolders/files_vboxsf new file mode 100755 index 00000000..9660f366 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/files_vboxsf @@ -0,0 +1,89 @@ +#!/bin/sh +# $Id: files_vboxsf $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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/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/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/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/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/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/VBoxGuestR0LibSharedFolders.c=>VBoxGuestR0LibSharedFolders.c \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile.include.header=>Makefile.include.header \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile.include.footer=>Makefile.include.footer \ + ${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..d735b677 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/lnkops.c @@ -0,0 +1,119 @@ +/* $Id: lnkops.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, operations for symbolic links. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * 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" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + +# if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0) +# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) +static const char *sf_follow_link(struct dentry *dentry, void **cookie) +# else +static void *sf_follow_link(struct dentry *dentry, struct nameidata *nd) +# endif +{ + struct inode *inode = dentry->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + int error = -ENOMEM; + char *path = (char *)get_zeroed_page(GFP_KERNEL); + int rc; + + if (path) { + error = 0; + rc = VbglR0SfReadLink(&client_handle, &sf_g->map, sf_i->path, + PATH_MAX, path); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfReadLink failed, caller=%s, rc=%Rrc\n", __func__, rc)); + free_page((unsigned long)path); + error = -EPROTO; + } + } +# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) + return error ? ERR_PTR(error) : (*cookie = path); +# else + nd_set_link(nd, error ? ERR_PTR(error) : path); + return NULL; +# endif +} + +# if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) +static void sf_put_link(struct dentry *dentry, struct nameidata *nd, + void *cookie) +{ + char *page = nd_get_link(nd); + if (!IS_ERR(page)) + free_page((unsigned long)page); +} +# endif + +# else /* LINUX_VERSION_CODE >= 4.5.0 */ +static const char *sf_get_link(struct dentry *dentry, struct inode *inode, + struct delayed_call *done) +{ + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + char *path; + int rc; + + if (!dentry) + return ERR_PTR(-ECHILD); + path = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!path) + return ERR_PTR(-ENOMEM); + rc = VbglR0SfReadLink(&client_handle, &sf_g->map, sf_i->path, PATH_MAX, + path); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfReadLink failed, caller=%s, rc=%Rrc\n", + __func__, rc)); + kfree(path); + return ERR_PTR(-EPROTO); + } + set_delayed_call(done, kfree_link, path); + return path; +} +# endif /* LINUX_VERSION_CODE >= 4.5.0 */ + +struct inode_operations sf_lnk_iops = { +# if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) + .readlink = generic_readlink, +# endif +# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0) + .get_link = sf_get_link +# elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) + .follow_link = sf_follow_link, + .put_link = free_page_put_link, +# else + .follow_link = sf_follow_link, + .put_link = sf_put_link +# endif +}; + +#endif /* LINUX_VERSION_CODE >= 2.6.0 */ 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..7392b047 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c @@ -0,0 +1,538 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +#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 <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 "vbsfmount.h" + +#include <iprt/assert.h> + + +#define PANIC_ATTR __attribute ((noreturn, __format__ (__printf__, 1, 2))) + +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 || endptr < s + size) + { + errno = ERANGE; + panic_err("could not convert %.*s to integer, result = %d", + (int)size, s, (int) val); + } + return (int)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 + { + HORW, + HORO, + HOUID, + HOGID, + HOTTL, + HODMODE, + HOFMODE, + HOUMASK, + HODMASK, + HOFMASK, + HOIOCHARSET, + HOCONVERTCP, + HONOEXEC, + HOEXEC, + HONODEV, + HODEV, + HONOSUID, + HOSUID, + HOREMOUNT, + HONOAUTO, + HONIGNORE + } handler_opt; + struct + { + const char *name; + handler_opt opt; + int has_arg; + const char *desc; + } handlers[] = + { + {"rw", HORW, 0, "mount read write (default)"}, + {"ro", HORO, 0, "mount read only"}, + {"uid", HOUID, 1, "default file owner user id"}, + {"gid", HOGID, 1, "default file owner group id"}, + {"ttl", HOTTL, 1, "time to live for dentry"}, + {"iocharset", HOIOCHARSET, 1, "i/o charset (default utf8)"}, + {"convertcp", HOCONVERTCP, 1, "convert share name from given charset to utf8"}, + {"dmode", HODMODE, 1, "mode of all directories"}, + {"fmode", HOFMODE, 1, "mode of all regular files"}, + {"umask", HOUMASK, 1, "umask of directories and regular files"}, + {"dmask", HODMASK, 1, "umask of directories"}, + {"fmask", HOFMASK, 1, "umask of regular files"}, + {"noexec", HONOEXEC, 0, 0 }, /* don't document these options directly here */ + {"exec", HOEXEC, 0, 0 }, /* as they are well known and described in the */ + {"nodev", HONODEV, 0, 0 }, /* usual manpages */ + {"dev", HODEV, 0, 0 }, + {"nosuid", HONOSUID, 0, 0 }, + {"suid", HOSUID, 0, 0 }, + {"remount", HOREMOUNT, 0, 0 }, + {"noauto", HONOAUTO, 0, 0 }, + {"_netdev", HONIGNORE, 0, 0 }, + {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 HORW: + opts->ronly = 0; + break; + case HORO: + opts->ronly = 1; + break; + case HONOEXEC: + opts->noexec = 1; + break; + case HOEXEC: + opts->noexec = 0; + break; + case HONODEV: + opts->nodev = 1; + break; + case HODEV: + opts->nodev = 0; + break; + case HONOSUID: + opts->nosuid = 1; + break; + case HOSUID: + opts->nosuid = 0; + break; + case HOREMOUNT: + opts->remount = 1; + break; + case HOUID: + /** @todo convert string to id. */ + opts->uid = safe_atoi(val, val_len, 10); + break; + case HOGID: + /** @todo convert string to id. */ + opts->gid = safe_atoi(val, val_len, 10); + break; + case HOTTL: + opts->ttl = safe_atoi(val, val_len, 10); + break; + case HODMODE: + opts->dmode = safe_atoi(val, val_len, 8); + break; + case HOFMODE: + opts->fmode = safe_atoi(val, val_len, 8); + break; + case HOUMASK: + opts->dmask = opts->fmask = safe_atoi(val, val_len, 8); + break; + case HODMASK: + opts->dmask = safe_atoi(val, val_len, 8); + break; + case HOFMASK: + opts->fmask = safe_atoi(val, val_len, 8); + break; + case HOIOCHARSET: + if (val_len + 1 > sizeof(opts->nls_name)) + { + panic("iocharset name too long\n"); + } + memcpy(opts->nls_name, val, val_len); + opts->nls_name[val_len] = 0; + break; + case HOCONVERTCP: + opts->convertcp = malloc(val_len + 1); + if (!opts->convertcp) + { + panic_err("could not allocate memory"); + } + memcpy(opts->convertcp, val, val_len); + opts->convertcp[val_len] = 0; + break; + case HONOAUTO: + case HONIGNORE: + break; + } + break; + } + continue; + } + + if ( !handler->name + && !opts->sloppy) + { + 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); + } + } +} + +static void +convertcp(char *in_codeset, char *host_name, struct vbsf_mount_info_new *info) +{ + char *i = host_name; + char *o = info->name; + size_t ib = strlen(host_name); + size_t ob = sizeof(info->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", + host_name, (int)(strlen (host_name) - 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" + " ttl=TTL set the \"time to live\" to TID for the dentry\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 nomtab = 0; + unsigned long flags = MS_NODEV; + char *host_name; + char *mount_point; + struct vbsf_mount_info_new mntinf; + struct vbsf_mount_opts opts = + { + 0, /* uid */ + 0, /* gid */ + 0, /* ttl */ + ~0U, /* dmode */ + ~0U, /* fmode*/ + 0, /* dmask */ + 0, /* fmask */ + 0, /* ronly */ + 0, /* sloppy */ + 0, /* noexec */ + 0, /* nodev */ + 0, /* nosuid */ + 0, /* remount */ + "\0", /* nls_name */ + NULL, /* convertcp */ + }; + AssertCompile(sizeof(uid_t) == sizeof(int)); + AssertCompile(sizeof(gid_t) == sizeof(int)); + + mntinf.nullchar = '\0'; + mntinf.signature[0] = VBSF_MOUNT_SIGNATURE_BYTE_0; + mntinf.signature[1] = VBSF_MOUNT_SIGNATURE_BYTE_1; + mntinf.signature[2] = VBSF_MOUNT_SIGNATURE_BYTE_2; + mntinf.length = sizeof(mntinf); + mntinf.tag[0] = '\0'; + + if (getuid()) + panic("Only root can mount shared folders from the host.\n"); + + if (!argv[0]) + argv[0] = "mount.vboxsf"; + + 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.ronly = 1; + break; + + case 'w': + opts.ronly = 0; + break; + + case 's': + opts.sloppy = 1; + break; + + case 'o': + process_mount_opts(optarg, &opts); + break; + + case 'n': + nomtab = 1; + break; + } + } + + if (argc - optind < 2) + return usage(argv[0]); + + host_name = argv[optind]; + mount_point = argv[optind + 1]; + + if (opts.convertcp) + convertcp(opts.convertcp, host_name, &mntinf); + else + { + if (strlen(host_name) > MAX_HOST_NAME - 1) + panic("host name is too big\n"); + + strcpy(mntinf.name, host_name); + } + + if (strlen(opts.nls_name) > MAX_NLS_NAME - 1) + panic("%s: the character set name for I/O is too long.\n", argv[0]); + + strcpy(mntinf.nls_name, opts.nls_name); + + if (opts.ronly) + flags |= MS_RDONLY; + if (opts.noexec) + flags |= MS_NOEXEC; + if (opts.nodev) + flags |= MS_NODEV; + if (opts.remount) + flags |= MS_REMOUNT; + + mntinf.uid = opts.uid; + mntinf.gid = opts.gid; + mntinf.ttl = opts.ttl; + mntinf.dmode = opts.dmode; + mntinf.fmode = opts.fmode; + mntinf.dmask = opts.dmask; + mntinf.fmask = opts.fmask; + + /* + * Note: When adding and/or modifying parameters of the vboxsf mounting + * options you also would have to adjust VBoxServiceAutoMount.cpp + * to keep this code here slick without having VbglR3. + */ + err = mount(host_name, mount_point, "vboxsf", flags, &mntinf); + if (err == -1 && errno == EPROTO) + { + /* Sometimes the mount utility messes up the share name. Try to + * un-mangle it again. */ + char szCWD[4096]; + size_t cchCWD; + if (!getcwd(szCWD, sizeof(szCWD))) + panic_err("%s: failed to get the current working directory", argv[0]); + cchCWD = strlen(szCWD); + if (!strncmp(host_name, szCWD, cchCWD)) + { + while (host_name[cchCWD] == '/') + ++cchCWD; + /* We checked before that we have enough space */ + strcpy(mntinf.name, host_name + cchCWD); + } + err = mount(host_name, mount_point, "vboxsf", flags, &mntinf); + } + if (err) + panic_err("%s: mounting failed with the error", argv[0]); + + if (!nomtab) + { + err = vbsfmount_complete(host_name, mount_point, flags, &opts); + switch (err) + { + case 0: /* Success. */ + break; + + case 1: + panic_err("%s: Could not update mount table (failed to create memstream).", 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..646d6062 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/regops.c @@ -0,0 +1,876 @@ +/* $Id: regops.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, regular file inode and file operations. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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. + */ + +/* + * Limitations: only COW memory mapping is supported + */ + +#include "vfsmod.h" + +static void *alloc_bounce_buffer(size_t * tmp_sizep, PRTCCPHYS physp, size_t + xfer_size, const char *caller) +{ + size_t tmp_size; + void *tmp; + + /* try for big first. */ + tmp_size = RT_ALIGN_Z(xfer_size, PAGE_SIZE); + if (tmp_size > 16U * _1K) + tmp_size = 16U * _1K; + tmp = kmalloc(tmp_size, GFP_KERNEL); + if (!tmp) { + /* fall back on a page sized buffer. */ + tmp = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!tmp) { + LogRel(("%s: could not allocate bounce buffer for xfer_size=%zu %s\n", caller, xfer_size)); + return NULL; + } + tmp_size = PAGE_SIZE; + } + + *tmp_sizep = tmp_size; + *physp = virt_to_phys(tmp); + return tmp; +} + +static void free_bounce_buffer(void *tmp) +{ + kfree(tmp); +} + +/* fops */ +static int sf_reg_read_aux(const char *caller, struct sf_glob_info *sf_g, + struct sf_reg_info *sf_r, void *buf, + uint32_t * nread, uint64_t pos) +{ + /** @todo bird: yes, kmap() and kmalloc() input only. Since the buffer is + * contiguous in physical memory (kmalloc or single page), we should + * use a physical address here to speed things up. */ + int rc = VbglR0SfRead(&client_handle, &sf_g->map, sf_r->handle, + pos, nread, buf, false /* already locked? */ ); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfRead failed. caller=%s, rc=%Rrc\n", caller, + rc)); + return -EPROTO; + } + return 0; +} + +static int sf_reg_write_aux(const char *caller, struct sf_glob_info *sf_g, + struct sf_reg_info *sf_r, void *buf, + uint32_t * nwritten, uint64_t pos) +{ + /** @todo bird: yes, kmap() and kmalloc() input only. Since the buffer is + * contiguous in physical memory (kmalloc or single page), we should + * use a physical address here to speed things up. */ + int rc = VbglR0SfWrite(&client_handle, &sf_g->map, sf_r->handle, + pos, nwritten, buf, + false /* already locked? */ ); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfWrite failed. caller=%s, rc=%Rrc\n", + caller, rc)); + return -EPROTO; + } + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) \ + && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) + +void free_pipebuf(struct page *kpage) +{ + kunmap(kpage); + __free_pages(kpage, 0); +} + +void *sf_pipe_buf_map(struct pipe_inode_info *pipe, + struct pipe_buffer *pipe_buf, int atomic) +{ + return 0; +} + +void sf_pipe_buf_get(struct pipe_inode_info *pipe, struct pipe_buffer *pipe_buf) +{ +} + +void sf_pipe_buf_unmap(struct pipe_inode_info *pipe, + struct pipe_buffer *pipe_buf, void *map_data) +{ +} + +int sf_pipe_buf_steal(struct pipe_inode_info *pipe, + struct pipe_buffer *pipe_buf) +{ + return 0; +} + +static void sf_pipe_buf_release(struct pipe_inode_info *pipe, + struct pipe_buffer *pipe_buf) +{ + free_pipebuf(pipe_buf->page); +} + +int sf_pipe_buf_confirm(struct pipe_inode_info *info, + struct pipe_buffer *pipe_buf) +{ + return 0; +} + +static struct pipe_buf_operations sf_pipe_buf_ops = { + .can_merge = 0, + .map = sf_pipe_buf_map, + .unmap = sf_pipe_buf_unmap, + .confirm = sf_pipe_buf_confirm, + .release = sf_pipe_buf_release, + .steal = sf_pipe_buf_steal, + .get = sf_pipe_buf_get, +}; + +#define LOCK_PIPE(pipe) \ + if (pipe->inode) \ + mutex_lock(&pipe->inode->i_mutex); + +#define UNLOCK_PIPE(pipe) \ + if (pipe->inode) \ + mutex_unlock(&pipe->inode->i_mutex); + +ssize_t +sf_splice_read(struct file *in, loff_t * poffset, + struct pipe_inode_info *pipe, size_t len, unsigned int flags) +{ + size_t bytes_remaining = len; + loff_t orig_offset = *poffset; + loff_t offset = orig_offset; + struct inode *inode = GET_F_DENTRY(in)->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = in->private_data; + ssize_t retval; + struct page *kpage = 0; + size_t nsent = 0; + + TRACE(); + if (!S_ISREG(inode->i_mode)) { + LogFunc(("read from non regular file %d\n", inode->i_mode)); + return -EINVAL; + } + if (!len) { + return 0; + } + + LOCK_PIPE(pipe); + + uint32_t req_size = 0; + while (bytes_remaining > 0) { + kpage = alloc_page(GFP_KERNEL); + if (unlikely(kpage == NULL)) { + UNLOCK_PIPE(pipe); + return -ENOMEM; + } + req_size = 0; + uint32_t nread = req_size = + (uint32_t) min(bytes_remaining, (size_t) PAGE_SIZE); + uint32_t chunk = 0; + void *kbuf = kmap(kpage); + while (chunk < req_size) { + retval = + sf_reg_read_aux(__func__, sf_g, sf_r, kbuf + chunk, + &nread, offset); + if (retval < 0) + goto err; + if (nread == 0) + break; + chunk += nread; + offset += nread; + nread = req_size - chunk; + } + if (!pipe->readers) { + send_sig(SIGPIPE, current, 0); + retval = -EPIPE; + goto err; + } + if (pipe->nrbufs < PIPE_BUFFERS) { + struct pipe_buffer *pipebuf = + pipe->bufs + + ((pipe->curbuf + pipe->nrbufs) & (PIPE_BUFFERS - + 1)); + pipebuf->page = kpage; + pipebuf->ops = &sf_pipe_buf_ops; + pipebuf->len = req_size; + pipebuf->offset = 0; + pipebuf->private = 0; + pipebuf->flags = 0; + pipe->nrbufs++; + nsent += req_size; + bytes_remaining -= req_size; + if (signal_pending(current)) + break; + } else { /* pipe full */ + + if (flags & SPLICE_F_NONBLOCK) { + retval = -EAGAIN; + goto err; + } + free_pipebuf(kpage); + break; + } + } + UNLOCK_PIPE(pipe); + if (!nsent && signal_pending(current)) + return -ERESTARTSYS; + *poffset += nsent; + return offset - orig_offset; + + err: + UNLOCK_PIPE(pipe); + free_pipebuf(kpage); + return retval; +} + +#endif /* 2.6.23 <= LINUX_VERSION_CODE < 2.6.31 */ + +/** + * 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 + * @returns the number of read bytes on success, Linux error code otherwise + */ +static ssize_t sf_reg_read(struct file *file, char *buf, size_t size, + loff_t * off) +{ + int err; + void *tmp; + RTCCPHYS tmp_phys; + size_t tmp_size; + size_t left = size; + ssize_t total_bytes_read = 0; + struct inode *inode = GET_F_DENTRY(file)->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + loff_t pos = *off; + + TRACE(); + 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; + + tmp = + alloc_bounce_buffer(&tmp_size, &tmp_phys, size, + __PRETTY_FUNCTION__); + if (!tmp) + return -ENOMEM; + + while (left) { + uint32_t to_read, nread; + + to_read = tmp_size; + if (to_read > left) + to_read = (uint32_t) left; + + nread = to_read; + + err = sf_reg_read_aux(__func__, sf_g, sf_r, tmp, &nread, pos); + if (err) + goto fail; + + if (copy_to_user(buf, tmp, nread)) { + err = -EFAULT; + goto fail; + } + + pos += nread; + left -= nread; + buf += nread; + total_bytes_read += nread; + if (nread != to_read) + break; + } + + *off += total_bytes_read; + free_bounce_buffer(tmp); + return total_bytes_read; + + fail: + free_bounce_buffer(tmp); + return err; +} + +/** + * 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 sf_reg_write(struct file *file, const char *buf, size_t size, + loff_t * off) +{ + int err; + void *tmp; + RTCCPHYS tmp_phys; + size_t tmp_size; + size_t left = size; + ssize_t total_bytes_written = 0; + struct inode *inode = GET_F_DENTRY(file)->d_inode; + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + loff_t pos; + + TRACE(); + BUG_ON(!sf_i); + BUG_ON(!sf_g); + BUG_ON(!sf_r); + + if (!S_ISREG(inode->i_mode)) { + LogFunc(("write to non regular file %d\n", inode->i_mode)); + return -EINVAL; + } + + pos = *off; + if (file->f_flags & O_APPEND) { + pos = inode->i_size; + *off = pos; + } + + /** @todo XXX Check write permission according to inode->i_mode! */ + + if (!size) + return 0; + + tmp = + alloc_bounce_buffer(&tmp_size, &tmp_phys, size, + __PRETTY_FUNCTION__); + if (!tmp) + return -ENOMEM; + + while (left) { + uint32_t to_write, nwritten; + + to_write = tmp_size; + if (to_write > left) + to_write = (uint32_t) left; + + nwritten = to_write; + + if (copy_from_user(tmp, buf, to_write)) { + err = -EFAULT; + goto fail; + } + + err = + VbglR0SfWritePhysCont(&client_handle, &sf_g->map, + sf_r->handle, pos, &nwritten, + tmp_phys); + err = RT_FAILURE(err) ? -EPROTO : 0; + if (err) + goto fail; + + pos += nwritten; + left -= nwritten; + buf += nwritten; + total_bytes_written += nwritten; + if (nwritten != to_write) + break; + } + + *off += total_bytes_written; + if (*off > inode->i_size) + inode->i_size = *off; + + sf_i->force_restat = 1; + free_bounce_buffer(tmp); + return total_bytes_written; + + fail: + free_bounce_buffer(tmp); + return err; +} + +/** + * Open a regular file. + * + * @param inode the inode + * @param file the file + * @returns 0 on success, Linux error code otherwise + */ +static int sf_reg_open(struct inode *inode, struct file *file) +{ + int rc, rc_linux = 0; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + struct sf_reg_info *sf_r; + SHFLCREATEPARMS params; + + TRACE(); + BUG_ON(!sf_g); + BUG_ON(!sf_i); + + LogFunc(("open %s\n", sf_i->path->String.utf8)); + + sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL); + if (!sf_r) { + LogRelFunc(("could not allocate reg info\n")); + return -ENOMEM; + } + + /* Already open? */ + if (sf_i->handle != SHFL_HANDLE_NIL) { + /* + * This inode was created with sf_create_aux(). 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 = sf_i->handle; + sf_i->handle = SHFL_HANDLE_NIL; + sf_i->file = file; + file->private_data = sf_r; + return 0; + } + + RT_ZERO(params); + params.Handle = SHFL_HANDLE_NIL; + /* We check the value of params.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 params.Handle to SHFL_HANDLE_NIL to + * make the shared folders host service use our fMode parameter */ + + if (file->f_flags & O_CREAT) { + LogFunc(("O_CREAT set\n")); + params.CreateFlags |= SHFL_CF_ACT_CREATE_IF_NEW; + /* We ignore O_EXCL, as the Linux kernel seems to call create + beforehand itself, so O_EXCL should always fail. */ + if (file->f_flags & O_TRUNC) { + LogFunc(("O_TRUNC set\n")); + params.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS; + } else + params.CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS; + } else { + params.CreateFlags |= SHFL_CF_ACT_FAIL_IF_NEW; + if (file->f_flags & O_TRUNC) { + LogFunc(("O_TRUNC set\n")); + params.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS; + } + } + + switch (file->f_flags & O_ACCMODE) { + case O_RDONLY: + params.CreateFlags |= SHFL_CF_ACCESS_READ; + break; + + case O_WRONLY: + params.CreateFlags |= SHFL_CF_ACCESS_WRITE; + break; + + case O_RDWR: + params.CreateFlags |= SHFL_CF_ACCESS_READWRITE; + break; + + default: + BUG(); + } + + if (file->f_flags & O_APPEND) { + LogFunc(("O_APPEND set\n")); + params.CreateFlags |= SHFL_CF_ACCESS_APPEND; + } + + params.Info.Attr.fMode = inode->i_mode; + LogFunc(("sf_reg_open: calling VbglR0SfCreate, file %s, flags=%#x, %#x\n", sf_i->path->String.utf8, file->f_flags, params.CreateFlags)); + rc = VbglR0SfCreate(&client_handle, &sf_g->map, sf_i->path, ¶ms); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfCreate failed flags=%d,%#x rc=%Rrc\n", + file->f_flags, params.CreateFlags, rc)); + kfree(sf_r); + return -RTErrConvertToErrno(rc); + } + + if (SHFL_HANDLE_NIL == params.Handle) { + switch (params.Result) { + case SHFL_PATH_NOT_FOUND: + case SHFL_FILE_NOT_FOUND: + rc_linux = -ENOENT; + break; + case SHFL_FILE_EXISTS: + rc_linux = -EEXIST; + break; + default: + break; + } + } + + sf_i->force_restat = 1; + sf_r->handle = params.Handle; + sf_i->file = file; + file->private_data = sf_r; + 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 sf_reg_release(struct inode *inode, struct file *file) +{ + int rc; + struct sf_reg_info *sf_r; + struct sf_glob_info *sf_g; + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + + TRACE(); + sf_g = GET_GLOB_INFO(inode->i_sb); + sf_r = file->private_data; + + BUG_ON(!sf_g); + BUG_ON(!sf_r); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + /* See the smbfs source (file.c). mmap in particular can cause data to be + * written to the file after it is closed, which we can't cope with. We + * copy and paste the body of filemap_write_and_wait() here as it was not + * defined before 2.6.6 and not exported until quite a bit later. */ + /* filemap_write_and_wait(inode->i_mapping); */ + if (inode->i_mapping->nrpages + && filemap_fdatawrite(inode->i_mapping) != -EIO) + filemap_fdatawait(inode->i_mapping); +#endif + rc = VbglR0SfClose(&client_handle, &sf_g->map, sf_r->handle); + if (RT_FAILURE(rc)) + LogFunc(("VbglR0SfClose failed rc=%Rrc\n", rc)); + + kfree(sf_r); + sf_i->file = NULL; + sf_i->handle = SHFL_HANDLE_NIL; + file->private_data = NULL; + return 0; +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +static int sf_reg_fault(struct vm_fault *vmf) +#elif LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) +static int sf_reg_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +static struct page *sf_reg_nopage(struct vm_area_struct *vma, + unsigned long vaddr, int *type) +# define SET_TYPE(t) *type = (t) +#else /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) */ +static struct page *sf_reg_nopage(struct vm_area_struct *vma, + unsigned long vaddr, int unused) +# define SET_TYPE(t) +#endif +{ + struct page *page; + char *buf; + loff_t off; + uint32_t nread = PAGE_SIZE; + int err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + struct vm_area_struct *vma = vmf->vma; +#endif + struct file *file = vma->vm_file; + struct inode *inode = GET_F_DENTRY(file)->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + + TRACE(); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + if (vmf->pgoff > vma->vm_end) + return VM_FAULT_SIGBUS; +#else + if (vaddr > vma->vm_end) { + SET_TYPE(VM_FAULT_SIGBUS); + return NOPAGE_SIGBUS; + } +#endif + + /* Don't use GFP_HIGHUSER as long as sf_reg_read_aux() calls VbglR0SfRead() + * which works on virtual addresses. On Linux cannot reliably determine the + * physical address for high memory, see rtR0MemObjNativeLockKernel(). */ + page = alloc_page(GFP_USER); + if (!page) { + LogRelFunc(("failed to allocate page\n")); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + return VM_FAULT_OOM; +#else + SET_TYPE(VM_FAULT_OOM); + return NOPAGE_OOM; +#endif + } + + buf = kmap(page); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + off = (vmf->pgoff << PAGE_SHIFT); +#else + off = (vaddr - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); +#endif + err = sf_reg_read_aux(__func__, sf_g, sf_r, buf, &nread, off); + if (err) { + kunmap(page); + put_page(page); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + return VM_FAULT_SIGBUS; +#else + SET_TYPE(VM_FAULT_SIGBUS); + return NOPAGE_SIGBUS; +#endif + } + + BUG_ON(nread > PAGE_SIZE); + if (!nread) { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + clear_user_page(page_address(page), vmf->pgoff, page); +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + clear_user_page(page_address(page), vaddr, page); +#else + clear_user_page(page_address(page), vaddr); +#endif + } else + memset(buf + nread, 0, PAGE_SIZE - nread); + + flush_dcache_page(page); + kunmap(page); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + vmf->page = page; + return 0; +#else + SET_TYPE(VM_FAULT_MAJOR); + return page; +#endif +} + +static struct vm_operations_struct sf_vma_ops = { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 25) + .fault = sf_reg_fault +#else + .nopage = sf_reg_nopage +#endif +}; + +static int sf_reg_mmap(struct file *file, struct vm_area_struct *vma) +{ + TRACE(); + if (vma->vm_flags & VM_SHARED) { + LogFunc(("shared mmapping not available\n")); + return -EINVAL; + } + + vma->vm_ops = &sf_vma_ops; + return 0; +} + +struct file_operations sf_reg_fops = { + .read = sf_reg_read, + .open = sf_reg_open, + .write = sf_reg_write, + .release = sf_reg_release, + .mmap = sf_reg_mmap, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) +/** @todo This code is known to cause caching of data which should not be + * cached. Investigate. */ +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 23) + .splice_read = sf_splice_read, +# else + .sendfile = generic_file_sendfile, +# endif + .aio_read = generic_file_aio_read, + .aio_write = generic_file_aio_write, +# endif +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) + .fsync = noop_fsync, +# else + .fsync = simple_sync_file, +# endif + .llseek = generic_file_llseek, +#endif +}; + +struct inode_operations sf_reg_iops = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) + .revalidate = sf_inode_revalidate +#else + .getattr = sf_getattr, + .setattr = sf_setattr +#endif +}; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + +static int sf_readpage(struct file *file, struct page *page) +{ + struct inode *inode = GET_F_DENTRY(file)->d_inode; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + uint32_t nread = PAGE_SIZE; + char *buf; + loff_t off = ((loff_t) page->index) << PAGE_SHIFT; + int ret; + + TRACE(); + + buf = kmap(page); + ret = sf_reg_read_aux(__func__, sf_g, sf_r, buf, &nread, off); + if (ret) { + kunmap(page); + if (PageLocked(page)) + unlock_page(page); + return ret; + } + BUG_ON(nread > PAGE_SIZE); + memset(&buf[nread], 0, PAGE_SIZE - nread); + flush_dcache_page(page); + kunmap(page); + SetPageUptodate(page); + unlock_page(page); + return 0; +} + +static int sf_writepage(struct page *page, struct writeback_control *wbc) +{ + struct address_space *mapping = page->mapping; + struct inode *inode = mapping->host; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_inode_info *sf_i = GET_INODE_INFO(inode); + struct file *file = sf_i->file; + struct sf_reg_info *sf_r = file->private_data; + char *buf; + uint32_t nwritten = PAGE_SIZE; + int end_index = inode->i_size >> PAGE_SHIFT; + loff_t off = ((loff_t) page->index) << PAGE_SHIFT; + int err; + + TRACE(); + + if (page->index >= end_index) + nwritten = inode->i_size & (PAGE_SIZE - 1); + + buf = kmap(page); + + err = sf_reg_write_aux(__func__, sf_g, sf_r, buf, &nwritten, off); + if (err < 0) { + ClearPageUptodate(page); + goto out; + } + + if (off > inode->i_size) + inode->i_size = off; + + if (PageError(page)) + ClearPageError(page); + err = 0; + + out: + kunmap(page); + + unlock_page(page); + return err; +} + +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + +int sf_write_begin(struct file *file, struct address_space *mapping, loff_t pos, + unsigned len, unsigned flags, struct page **pagep, + void **fsdata) +{ + TRACE(); + + return simple_write_begin(file, mapping, pos, len, flags, pagep, + fsdata); +} + +int sf_write_end(struct file *file, struct address_space *mapping, loff_t pos, + unsigned len, unsigned copied, struct page *page, void *fsdata) +{ + struct inode *inode = mapping->host; + struct sf_glob_info *sf_g = GET_GLOB_INFO(inode->i_sb); + struct sf_reg_info *sf_r = file->private_data; + void *buf; + unsigned from = pos & (PAGE_SIZE - 1); + uint32_t nwritten = len; + int err; + + TRACE(); + + buf = kmap(page); + err = + sf_reg_write_aux(__func__, sf_g, sf_r, buf + from, &nwritten, pos); + kunmap(page); + + if (err >= 0) { + if (!PageUptodate(page) && nwritten == PAGE_SIZE) + SetPageUptodate(page); + + pos += nwritten; + if (pos > inode->i_size) + inode->i_size = pos; + } + + unlock_page(page); +# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) + put_page(page); +# else + page_cache_release(page); +# endif + + return nwritten; +} + +# endif /* KERNEL_VERSION >= 2.6.24 */ + +struct address_space_operations sf_reg_aops = { + .readpage = sf_readpage, + .writepage = sf_writepage, +# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + .write_begin = sf_write_begin, + .write_end = sf_write_end, +# else + .prepare_write = simple_prepare_write, + .commit_write = simple_commit_write, +# endif +}; + +#endif /* LINUX_VERSION_CODE >= 2.6.0 */ + diff --git a/src/VBox/Additions/linux/sharedfolders/utils.c b/src/VBox/Additions/linux/sharedfolders/utils.c new file mode 100644 index 00000000..d14bbb31 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/utils.c @@ -0,0 +1,883 @@ +/* $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-2019 Oracle Corporation + * + * 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/nfs_fs.h> +#include <linux/vfs.h> + +/* #define USE_VMALLOC */ + +/* + * sf_reg_aops and sf_backing_dev_info are just quick implementations to make + * sendfile work. For more information have a look at + * + * http://us1.samba.org/samba/ftp/cifs-cvs/ols2006-fs-tutorial-smf.odp + * + * and the sample implementation + * + * http://pserver.samba.org/samba/ftp/cifs-cvs/samplefs.tar.gz + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) +static void sf_ftime_from_timespec(time_t * time, RTTIMESPEC * ts) +{ + int64_t t = RTTimeSpecGetNano(ts); + + do_div(t, 1000000000); + *time = t; +} + +static void sf_timespec_from_ftime(RTTIMESPEC * ts, time_t * time) +{ + int64_t t = 1000000000 * *time; + RTTimeSpecSetNano(ts, t); +} +#else /* >= 2.6.0 */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 18, 0) +static void sf_ftime_from_timespec(struct timespec *tv, RTTIMESPEC *ts) +#else +static void sf_ftime_from_timespec(struct timespec64 *tv, RTTIMESPEC *ts) +#endif +{ + int64_t t = RTTimeSpecGetNano(ts); + int64_t nsec; + + nsec = do_div(t, 1000000000); + tv->tv_sec = t; + tv->tv_nsec = nsec; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 18, 0) +static void sf_timespec_from_ftime(RTTIMESPEC *ts, struct timespec *tv) +#else +static void sf_timespec_from_ftime(RTTIMESPEC *ts, struct timespec64 *tv) +#endif +{ + int64_t t = (int64_t) tv->tv_nsec + (int64_t) tv->tv_sec * 1000000000; + RTTimeSpecSetNano(ts, t); +} +#endif /* >= 2.6.0 */ + +/* set [inode] attributes based on [info], uid/gid based on [sf_g] */ +void sf_init_inode(struct sf_glob_info *sf_g, struct inode *inode, + PSHFLFSOBJINFO info) +{ + PSHFLFSOBJATTR attr; + int mode; + + TRACE(); + + attr = &info->Attr; + +#define mode_set(r) attr->fMode & (RTFS_UNIX_##r) ? (S_##r) : 0; + mode = mode_set(IRUSR); + mode |= mode_set(IWUSR); + mode |= mode_set(IXUSR); + + mode |= mode_set(IRGRP); + mode |= mode_set(IWGRP); + mode |= mode_set(IXGRP); + + mode |= mode_set(IROTH); + mode |= mode_set(IWOTH); + mode |= mode_set(IXOTH); + +#undef mode_set + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + inode->i_mapping->a_ops = &sf_reg_aops; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 19, 0) + /* XXX Was this ever necessary? */ + inode->i_mapping->backing_dev_info = &sf_g->bdi; +#endif +#endif + + if (RTFS_IS_DIRECTORY(attr->fMode)) { + inode->i_mode = sf_g->dmode != ~0 ? (sf_g->dmode & 0777) : mode; + inode->i_mode &= ~sf_g->dmask; + inode->i_mode |= S_IFDIR; + inode->i_op = &sf_dir_iops; + inode->i_fop = &sf_dir_fops; + /* XXX: this probably should be set to the number of entries + in the directory plus two (. ..) */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) + set_nlink(inode, 1); +#else + inode->i_nlink = 1; +#endif + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + else if (RTFS_IS_SYMLINK(attr->fMode)) { + inode->i_mode = sf_g->fmode != ~0 ? (sf_g->fmode & 0777) : mode; + inode->i_mode &= ~sf_g->fmask; + inode->i_mode |= S_IFLNK; + inode->i_op = &sf_lnk_iops; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) + set_nlink(inode, 1); +#else + inode->i_nlink = 1; +#endif + } +#endif + else { + inode->i_mode = sf_g->fmode != ~0 ? (sf_g->fmode & 0777) : mode; + inode->i_mode &= ~sf_g->fmask; + inode->i_mode |= S_IFREG; + inode->i_op = &sf_reg_iops; + inode->i_fop = &sf_reg_fops; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0) + set_nlink(inode, 1); +#else + inode->i_nlink = 1; +#endif + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + inode->i_uid = make_kuid(current_user_ns(), sf_g->uid); + inode->i_gid = make_kgid(current_user_ns(), sf_g->gid); +#else + inode->i_uid = sf_g->uid; + inode->i_gid = sf_g->gid; +#endif + + inode->i_size = info->cbObject; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) && !defined(KERNEL_FC6) + inode->i_blksize = 4096; +#endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 11) + inode->i_blkbits = 12; +#endif + /* i_blocks always in units of 512 bytes! */ + inode->i_blocks = (info->cbAllocated + 511) / 512; + + sf_ftime_from_timespec(&inode->i_atime, &info->AccessTime); + sf_ftime_from_timespec(&inode->i_ctime, &info->ChangeTime); + sf_ftime_from_timespec(&inode->i_mtime, &info->ModificationTime); +} + +int sf_stat(const char *caller, struct sf_glob_info *sf_g, + SHFLSTRING * path, PSHFLFSOBJINFO result, int ok_to_fail) +{ + int rc; + SHFLCREATEPARMS params; + NOREF(caller); + + TRACE(); + + RT_ZERO(params); + params.Handle = SHFL_HANDLE_NIL; + params.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW; + LogFunc(("sf_stat: calling VbglR0SfCreate, file %s, flags %#x\n", + path->String.utf8, params.CreateFlags)); + rc = VbglR0SfCreate(&client_handle, &sf_g->map, path, ¶ms); + if (rc == VERR_INVALID_NAME) { + /* this can happen for names like 'foo*' on a Windows host */ + return -ENOENT; + } + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfCreate(%s) failed. caller=%s, rc=%Rrc\n", + path->String.utf8, rc, caller)); + return -EPROTO; + } + if (params.Result != SHFL_FILE_EXISTS) { + if (!ok_to_fail) + LogFunc(("VbglR0SfCreate(%s) file does not exist. caller=%s, result=%d\n", path->String.utf8, params.Result, caller)); + return -ENOENT; + } + + *result = params.Info; + return 0; +} + +/* this is called directly as iop on 2.4, indirectly as dop + [sf_dentry_revalidate] on 2.4/2.6, indirectly as iop through + [sf_getattr] on 2.6. the job is to find out whether dentry/inode is + still valid. the test is failed if [dentry] does not have an inode + or [sf_stat] is unsuccessful, otherwise we return success and + update inode attributes */ +int sf_inode_revalidate(struct dentry *dentry) +{ + int err; + struct sf_glob_info *sf_g; + struct sf_inode_info *sf_i; + SHFLFSOBJINFO info; + + TRACE(); + if (!dentry || !dentry->d_inode) { + LogFunc(("no dentry(%p) or inode(%p)\n", dentry, + dentry->d_inode)); + return -EINVAL; + } + + sf_g = GET_GLOB_INFO(dentry->d_inode->i_sb); + sf_i = GET_INODE_INFO(dentry->d_inode); + +#if 0 + printk("%s called by %p:%p\n", + sf_i->path->String.utf8, + __builtin_return_address(0), __builtin_return_address(1)); +#endif + + BUG_ON(!sf_g); + BUG_ON(!sf_i); + + if (!sf_i->force_restat) { + if (jiffies - dentry->d_time < sf_g->ttl) + return 0; + } + + err = sf_stat(__func__, sf_g, sf_i->path, &info, 1); + if (err) + return err; + + dentry->d_time = jiffies; + sf_init_inode(sf_g, dentry->d_inode, &info); + return 0; +} + +/* this is called during name resolution/lookup to check if the + [dentry] in the cache is still valid. the job is handled by + [sf_inode_revalidate] */ +static int +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) +sf_dentry_revalidate(struct dentry *dentry, unsigned flags) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +sf_dentry_revalidate(struct dentry *dentry, struct nameidata *nd) +#else +sf_dentry_revalidate(struct dentry *dentry, int flags) +#endif +{ + TRACE(); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0) + if (flags & LOOKUP_RCU) + return -ECHILD; +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38) + /* see Documentation/filesystems/vfs.txt */ + if (nd && nd->flags & LOOKUP_RCU) + return -ECHILD; +#endif + + if (sf_inode_revalidate(dentry)) + return 0; + + return 1; +} + +/* 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 LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +int sf_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, + unsigned int flags) +#else +int sf_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *kstat) +#endif +{ + int err; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) + struct dentry *dentry = path->dentry; +#endif + + TRACE(); + err = sf_inode_revalidate(dentry); + if (err) + return err; + + generic_fillattr(dentry->d_inode, kstat); + return 0; +} + +int sf_setattr(struct dentry *dentry, struct iattr *iattr) +{ + struct sf_glob_info *sf_g; + struct sf_inode_info *sf_i; + SHFLCREATEPARMS params; + SHFLFSOBJINFO info; + uint32_t cbBuffer; + int rc, err; + + TRACE(); + + sf_g = GET_GLOB_INFO(dentry->d_inode->i_sb); + sf_i = GET_INODE_INFO(dentry->d_inode); + err = 0; + + RT_ZERO(params); + params.Handle = SHFL_HANDLE_NIL; + params.CreateFlags = SHFL_CF_ACT_OPEN_IF_EXISTS + | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_ATTR_WRITE; + + /* this is at least required for Posix hosts */ + if (iattr->ia_valid & ATTR_SIZE) + params.CreateFlags |= SHFL_CF_ACCESS_WRITE; + + rc = VbglR0SfCreate(&client_handle, &sf_g->map, sf_i->path, ¶ms); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfCreate(%s) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + err = -RTErrConvertToErrno(rc); + goto fail2; + } + if (params.Result != SHFL_FILE_EXISTS) { + LogFunc(("file %s does not exist\n", sf_i->path->String.utf8)); + err = -ENOENT; + goto fail1; + } + + /* Setting the file size and setting the other attributes has to be + * handled separately, see implementation of vbsfSetFSInfo() in + * vbsf.cpp */ + if (iattr->ia_valid & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME)) { +#define mode_set(r) ((iattr->ia_mode & (S_##r)) ? RTFS_UNIX_##r : 0) + + RT_ZERO(info); + if (iattr->ia_valid & ATTR_MODE) { + info.Attr.fMode = mode_set(IRUSR); + info.Attr.fMode |= mode_set(IWUSR); + info.Attr.fMode |= mode_set(IXUSR); + info.Attr.fMode |= mode_set(IRGRP); + info.Attr.fMode |= mode_set(IWGRP); + info.Attr.fMode |= mode_set(IXGRP); + info.Attr.fMode |= mode_set(IROTH); + info.Attr.fMode |= mode_set(IWOTH); + info.Attr.fMode |= mode_set(IXOTH); + + if (iattr->ia_mode & S_IFDIR) + info.Attr.fMode |= RTFS_TYPE_DIRECTORY; + else + info.Attr.fMode |= RTFS_TYPE_FILE; + } + + if (iattr->ia_valid & ATTR_ATIME) + sf_timespec_from_ftime(&info.AccessTime, + &iattr->ia_atime); + if (iattr->ia_valid & ATTR_MTIME) + sf_timespec_from_ftime(&info.ModificationTime, + &iattr->ia_mtime); + /* ignore ctime (inode change time) as it can't be set from userland anyway */ + + cbBuffer = sizeof(info); + rc = VbglR0SfFsInfo(&client_handle, &sf_g->map, params.Handle, + SHFL_INFO_SET | SHFL_INFO_FILE, &cbBuffer, + (PSHFLDIRINFO) & info); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfFsInfo(%s, FILE) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + err = -RTErrConvertToErrno(rc); + goto fail1; + } + } + + if (iattr->ia_valid & ATTR_SIZE) { + RT_ZERO(info); + info.cbObject = iattr->ia_size; + cbBuffer = sizeof(info); + rc = VbglR0SfFsInfo(&client_handle, &sf_g->map, params.Handle, + SHFL_INFO_SET | SHFL_INFO_SIZE, &cbBuffer, + (PSHFLDIRINFO) & info); + if (RT_FAILURE(rc)) { + LogFunc(("VbglR0SfFsInfo(%s, SIZE) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + err = -RTErrConvertToErrno(rc); + goto fail1; + } + } + + rc = VbglR0SfClose(&client_handle, &sf_g->map, params.Handle); + if (RT_FAILURE(rc)) + LogFunc(("VbglR0SfClose(%s) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + + return sf_inode_revalidate(dentry); + + fail1: + rc = VbglR0SfClose(&client_handle, &sf_g->map, params.Handle); + if (RT_FAILURE(rc)) + LogFunc(("VbglR0SfClose(%s) failed rc=%Rrc\n", + sf_i->path->String.utf8, rc)); + + fail2: + return err; +} +#endif /* >= 2.6.0 */ + +static int sf_make_path(const char *caller, struct sf_inode_info *sf_i, + const char *d_name, size_t d_len, SHFLSTRING ** result) +{ + size_t path_len, shflstring_len; + SHFLSTRING *tmp; + uint16_t p_len; + uint8_t *p_name; + int fRoot = 0; + + TRACE(); + p_len = sf_i->path->u16Length; + p_name = sf_i->path->String.utf8; + + if (p_len == 1 && *p_name == '/') { + path_len = d_len + 1; + fRoot = 1; + } else { + /* lengths of constituents plus terminating zero plus slash */ + path_len = p_len + d_len + 2; + if (path_len > 0xffff) { + LogFunc(("path too long. caller=%s, path_len=%zu\n", + caller, path_len)); + return -ENAMETOOLONG; + } + } + + shflstring_len = offsetof(SHFLSTRING, String.utf8) + path_len; + tmp = kmalloc(shflstring_len, GFP_KERNEL); + if (!tmp) { + LogRelFunc(("kmalloc failed, caller=%s\n", caller)); + return -ENOMEM; + } + tmp->u16Length = path_len - 1; + tmp->u16Size = path_len; + + if (fRoot) + memcpy(&tmp->String.utf8[0], d_name, d_len + 1); + else { + memcpy(&tmp->String.utf8[0], p_name, p_len); + tmp->String.utf8[p_len] = '/'; + memcpy(&tmp->String.utf8[p_len + 1], d_name, d_len); + tmp->String.utf8[p_len + 1 + d_len] = '\0'; + } + + *result = tmp; + return 0; +} + +/** + * [dentry] contains string encoded in coding system that corresponds + * to [sf_g]->nls, we must convert it to UTF8 here and pass down to + * [sf_make_path] which will allocate SHFLSTRING and fill it in + */ +int sf_path_from_dentry(const char *caller, struct sf_glob_info *sf_g, + struct sf_inode_info *sf_i, struct dentry *dentry, + SHFLSTRING ** result) +{ + 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 (sf_g->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 = sf_g->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 LINUX_VERSION_CODE >= KERNEL_VERSION(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 = sf_make_path(caller, sf_i, name, len, result); + if (name != d_name) + kfree(name); + + return err; + + fail1: + kfree(name); + return err; +} + +int sf_nlscpy(struct sf_glob_info *sf_g, + char *name, size_t name_bound_len, + const unsigned char *utf8_name, size_t utf8_len) +{ + if (sf_g->nls) { + const char *in; + char *out; + size_t out_len; + size_t out_bound_len; + size_t in_bound_len; + + in = utf8_name; + in_bound_len = utf8_len; + + out = name; + out_len = 0; + out_bound_len = name_bound_len; + + while (in_bound_len) { + int nb; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 31) + unicode_t uni; + + nb = utf8_to_utf32(in, in_bound_len, &uni); +#else + linux_wchar_t uni; + + nb = utf8_mbtowc(&uni, in, in_bound_len); +#endif + if (nb < 0) { + LogFunc(("utf8_mbtowc failed(%s) %x:%d\n", + (const char *)utf8_name, *in, + in_bound_len)); + return -EINVAL; + } + in += nb; + in_bound_len -= nb; + + nb = sf_g->nls->uni2char(uni, out, out_bound_len); + if (nb < 0) { + LogFunc(("nls->uni2char failed(%s) %x:%d\n", + utf8_name, uni, out_bound_len)); + return nb; + } + out += nb; + out_bound_len -= nb; + out_len += nb; + } + + *out = 0; + } else { + if (utf8_len + 1 > name_bound_len) + return -ENAMETOOLONG; + + memcpy(name, utf8_name, utf8_len + 1); + } + return 0; +} + +static struct sf_dir_buf *sf_dir_buf_alloc(void) +{ + struct sf_dir_buf *b; + + TRACE(); + b = kmalloc(sizeof(*b), GFP_KERNEL); + if (!b) { + LogRelFunc(("could not alloc directory buffer\n")); + return NULL; + } +#ifdef USE_VMALLOC + b->buf = vmalloc(DIR_BUFFER_SIZE); +#else + b->buf = kmalloc(DIR_BUFFER_SIZE, GFP_KERNEL); +#endif + if (!b->buf) { + kfree(b); + LogRelFunc(("could not alloc directory buffer storage\n")); + return NULL; + } + + INIT_LIST_HEAD(&b->head); + b->cEntries = 0; + b->cbUsed = 0; + b->cbFree = DIR_BUFFER_SIZE; + return b; +} + +static void sf_dir_buf_free(struct sf_dir_buf *b) +{ + BUG_ON(!b || !b->buf); + + TRACE(); + list_del(&b->head); +#ifdef USE_VMALLOC + vfree(b->buf); +#else + kfree(b->buf); +#endif + kfree(b); +} + +/** + * Free the directory buffer. + */ +void sf_dir_info_free(struct sf_dir_info *p) +{ + struct list_head *list, *pos, *tmp; + + TRACE(); + list = &p->info_list; + list_for_each_safe(pos, tmp, list) { + struct sf_dir_buf *b; + + b = list_entry(pos, struct sf_dir_buf, head); + sf_dir_buf_free(b); + } + kfree(p); +} + +/** + * Empty (but not free) the directory buffer. + */ +void sf_dir_info_empty(struct sf_dir_info *p) +{ + struct list_head *list, *pos, *tmp; + TRACE(); + list = &p->info_list; + list_for_each_safe(pos, tmp, list) { + struct sf_dir_buf *b; + b = list_entry(pos, struct sf_dir_buf, head); + b->cEntries = 0; + b->cbUsed = 0; + b->cbFree = DIR_BUFFER_SIZE; + } +} + +/** + * Create a new directory buffer descriptor. + */ +struct sf_dir_info *sf_dir_info_alloc(void) +{ + struct sf_dir_info *p; + + TRACE(); + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + LogRelFunc(("could not alloc directory info\n")); + return NULL; + } + + INIT_LIST_HEAD(&p->info_list); + return p; +} + +/** + * Search for an empty directory content buffer. + */ +static struct sf_dir_buf *sf_get_empty_dir_buf(struct sf_dir_info *sf_d) +{ + struct list_head *list, *pos; + + list = &sf_d->info_list; + list_for_each(pos, list) { + struct sf_dir_buf *b; + + b = list_entry(pos, struct sf_dir_buf, head); + if (!b) + return NULL; + else { + if (b->cbUsed == 0) + return b; + } + } + + return NULL; +} + +int sf_dir_read_all(struct sf_glob_info *sf_g, struct sf_inode_info *sf_i, + struct sf_dir_info *sf_d, SHFLHANDLE handle) +{ + int err; + SHFLSTRING *mask; + struct sf_dir_buf *b; + + TRACE(); + err = sf_make_path(__func__, sf_i, "*", 1, &mask); + if (err) + goto fail0; + + for (;;) { + int rc; + void *buf; + uint32_t cbSize; + uint32_t cEntries; + + b = sf_get_empty_dir_buf(sf_d); + if (!b) { + b = sf_dir_buf_alloc(); + if (!b) { + err = -ENOMEM; + LogRelFunc(("could not alloc directory buffer\n")); + goto fail1; + } + list_add(&b->head, &sf_d->info_list); + } + + buf = b->buf; + cbSize = b->cbFree; + + rc = VbglR0SfDirInfo(&client_handle, &sf_g->map, handle, mask, + 0, 0, &cbSize, buf, &cEntries); + switch (rc) { + case VINF_SUCCESS: + RT_FALL_THRU(); + case VERR_NO_MORE_FILES: + break; + case VERR_NO_TRANSLATION: + LogFunc(("host could not translate entry\n")); + /* XXX */ + break; + default: + err = -RTErrConvertToErrno(rc); + LogFunc(("VbglR0SfDirInfo failed rc=%Rrc\n", rc)); + goto fail1; + } + + b->cEntries += cEntries; + b->cbFree -= cbSize; + b->cbUsed += cbSize; + + if (RT_FAILURE(rc)) + break; + } + err = 0; + + fail1: + kfree(mask); + + fail0: + return err; +} + +int sf_get_volume_info(struct super_block *sb, STRUCT_STATFS * stat) +{ + struct sf_glob_info *sf_g; + SHFLVOLINFO SHFLVolumeInfo; + uint32_t cbBuffer; + int rc; + + sf_g = GET_GLOB_INFO(sb); + cbBuffer = sizeof(SHFLVolumeInfo); + rc = VbglR0SfFsInfo(&client_handle, &sf_g->map, 0, + SHFL_INFO_GET | SHFL_INFO_VOLUME, &cbBuffer, + (PSHFLDIRINFO) & SHFLVolumeInfo); + if (RT_FAILURE(rc)) + return -RTErrConvertToErrno(rc); + + stat->f_type = NFS_SUPER_MAGIC; /* XXX vboxsf type? */ + stat->f_bsize = SHFLVolumeInfo.ulBytesPerAllocationUnit; + stat->f_blocks = SHFLVolumeInfo.ullTotalAllocationBytes + / SHFLVolumeInfo.ulBytesPerAllocationUnit; + stat->f_bfree = SHFLVolumeInfo.ullAvailableAllocationBytes + / SHFLVolumeInfo.ulBytesPerAllocationUnit; + stat->f_bavail = SHFLVolumeInfo.ullAvailableAllocationBytes + / SHFLVolumeInfo.ulBytesPerAllocationUnit; + stat->f_files = 1000; + stat->f_ffree = 1000; /* 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; + return 0; +} + +struct dentry_operations sf_dentry_ops = { + .d_revalidate = sf_dentry_revalidate +}; + +int sf_init_backing_dev(struct sf_glob_info *sf_g) +{ + int rc = 0; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) && LINUX_VERSION_CODE <= KERNEL_VERSION(3, 19, 0) + /* Each new shared folder map gets a new uint64_t identifier, + * allocated in sequence. We ASSUME the sequence will not wrap. */ + static uint64_t s_u64Sequence = 0; + uint64_t u64CurrentSequence = ASMAtomicIncU64(&s_u64Sequence); + + sf_g->bdi.ra_pages = 0; /* No readahead */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 12) + sf_g->bdi.capabilities = BDI_CAP_MAP_DIRECT /* MAP_SHARED */ + | BDI_CAP_MAP_COPY /* MAP_PRIVATE */ + | BDI_CAP_READ_MAP /* can be mapped for reading */ + | BDI_CAP_WRITE_MAP /* can be mapped for writing */ + | BDI_CAP_EXEC_MAP; /* can be mapped for execution */ +#endif /* >= 2.6.12 */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + rc = bdi_init(&sf_g->bdi); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) + if (!rc) + rc = bdi_register(&sf_g->bdi, NULL, "vboxsf-%llu", + (unsigned long long)u64CurrentSequence); +#endif /* >= 2.6.26 */ +#endif /* >= 2.6.24 */ +#endif /* >= 2.6.0 && <= 3.19.0 */ + return rc; +} + +void sf_done_backing_dev(struct sf_glob_info *sf_g) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) && LINUX_VERSION_CODE <= KERNEL_VERSION(3, 19, 0) + bdi_destroy(&sf_g->bdi); /* includes bdi_unregister() */ +#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..5d68e931 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/vbsfmount.c @@ -0,0 +1,95 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include "vbsfmount.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include <ctype.h> +#include <mntent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> + + +/** @todo Use defines for return values! */ +int vbsfmount_complete(const char *host_name, const char *mount_point, + unsigned long flags, struct vbsf_mount_opts *opts) +{ + FILE *f, *m; + char *buf; + size_t size; + struct mntent e; + int rc = 0; + + m = open_memstream(&buf, &size); + if (!m) + return 1; /* Could not update mount table (failed to create memstream). */ + + if (opts->uid) + fprintf(m, "uid=%d,", opts->uid); + if (opts->gid) + fprintf(m, "gid=%d,", opts->gid); + if (opts->ttl) + fprintf(m, "ttl=%d,", opts->ttl); + if (*opts->nls_name) + fprintf(m, "iocharset=%s,", opts->nls_name); + if (flags & MS_NOSUID) + fprintf(m, "%s,", MNTOPT_NOSUID); + if (flags & MS_RDONLY) + fprintf(m, "%s,", MNTOPT_RO); + else + fprintf(m, "%s,", MNTOPT_RW); + + fclose(m); + + if (size > 0) + buf[size - 1] = 0; + else + buf = "defaults"; + + f = setmntent(MOUNTED, "a+"); + if (!f) + { + rc = 2; /* Could not open mount table for update. */ + } + else + { + e.mnt_fsname = (char*)host_name; + e.mnt_dir = (char*)mount_point; + e.mnt_type = "vboxsf"; + e.mnt_opts = buf; + e.mnt_freq = 0; + e.mnt_passno = 0; + + if (addmntent(f, &e)) + rc = 3; /* Could not add an entry to the mount table. */ + + endmntent(f); + } + + if (size > 0) + { + memset(buf, 0, size); + free(buf); + } + + 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..c14e5284 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/vbsfmount.h @@ -0,0 +1,90 @@ +/* $Id: vbsfmount.h $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, mount(2) parameter structure. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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 constraints the size of data mount argument to PAGE_SIZE - 1. */ +#define MAX_HOST_NAME 256 +#define MAX_NLS_NAME 32 + +#define VBSF_MOUNT_SIGNATURE_BYTE_0 '\377' +#define VBSF_MOUNT_SIGNATURE_BYTE_1 '\376' +#define VBSF_MOUNT_SIGNATURE_BYTE_2 '\375' + +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; + char signature[3]; /* signature */ + int length; /* length of the whole structure */ + char name[MAX_HOST_NAME]; /* share name */ + char nls_name[MAX_NLS_NAME]; /* name of an I/O charset */ + int uid; /* user ID for all entries, default 0=root */ + int gid; /* group ID for all entries, default 0=root */ + int ttl; /* time to live */ + int dmode; /* mode for directories if != 0xffffffff */ + int fmode; /* mode for regular files if != 0xffffffff */ + int dmask; /* umask applied to directories */ + int fmask; /* umask applied to regular files */ + char tag[32]; /**< Mount tag for VBoxService automounter. @since 6.0 */ +}; + +struct vbsf_mount_opts { + int uid; + int gid; + int ttl; + int dmode; + int fmode; + int dmask; + int fmask; + int ronly; + int sloppy; + int noexec; + int nodev; + int nosuid; + int remount; + char nls_name[MAX_NLS_NAME]; + char *convertcp; +}; + +/** Completes the mount operation by adding the new mount point to mtab if required. */ +int vbsfmount_complete(const char *host_name, const char *mount_point, + unsigned long flags, struct vbsf_mount_opts *opts); + +#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..209424a6 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.c @@ -0,0 +1,683 @@ +/* $Id: vfsmod.c $ */ +/** @file + * vboxsf - VBox Linux Shared Folders VFS, module init/term, super block management. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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. + */ + +#include "vfsmod.h" +#include "version-generated.h" +#include "revision-generated.h" +#include "product-generated.h" +#include "VBoxGuestR0LibInternal.h" +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) +# include <linux/mount.h> +#endif +#include <linux/seq_file.h> + +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 + +/* globals */ +VBGLSFCLIENT client_handle; + +/* forward declarations */ +static struct super_operations sf_super_ops; + +/* allocate global info, try to map host share */ +static int sf_glob_alloc(struct vbsf_mount_info_new *info, + struct sf_glob_info **sf_gp) +{ + int err, rc; + SHFLSTRING *str_name; + size_t name_len, str_len; + struct sf_glob_info *sf_g; + + TRACE(); + sf_g = kmalloc(sizeof(*sf_g), GFP_KERNEL); + if (!sf_g) { + err = -ENOMEM; + LogRelFunc(("could not allocate memory for global info\n")); + goto fail0; + } + + RT_ZERO(*sf_g); + + if (info->nullchar != '\0' + || info->signature[0] != VBSF_MOUNT_SIGNATURE_BYTE_0 + || info->signature[1] != VBSF_MOUNT_SIGNATURE_BYTE_1 + || info->signature[2] != VBSF_MOUNT_SIGNATURE_BYTE_2) { + err = -EINVAL; + goto fail1; + } + + info->name[sizeof(info->name) - 1] = 0; + info->nls_name[sizeof(info->nls_name) - 1] = 0; + + name_len = strlen(info->name); + str_len = offsetof(SHFLSTRING, String.utf8) + name_len + 1; + str_name = kmalloc(str_len, GFP_KERNEL); + if (!str_name) { + err = -ENOMEM; + LogRelFunc(("could not allocate memory for host name\n")); + goto fail1; + } + + str_name->u16Length = name_len; + str_name->u16Size = name_len + 1; + memcpy(str_name->String.utf8, info->name, name_len + 1); + +#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 */ + if (info->nls_name[0]) { + if (_IS_UTF8(info->nls_name)) + sf_g->nls = NULL; + else { + sf_g->nls = load_nls(info->nls_name); + if (!sf_g->nls) { + err = -EINVAL; + LogFunc(("failed to load nls %s\n", + info->nls_name)); + kfree(str_name); + goto fail1; + } + } + } 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)) + sf_g->nls = load_nls_default(); + else + sf_g->nls = NULL; +#else + sf_g->nls = NULL; +#endif + +#undef _IS_UTF8 +#undef _IS_EMPTY + } + + rc = VbglR0SfMapFolder(&client_handle, str_name, &sf_g->map); + kfree(str_name); + + if (RT_FAILURE(rc)) { + err = -EPROTO; + LogFunc(("VbglR0SfMapFolder failed rc=%d\n", rc)); + goto fail2; + } + + sf_g->ttl = info->ttl; + sf_g->uid = info->uid; + sf_g->gid = info->gid; + + if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, tag)) { + /* new fields */ + sf_g->dmode = info->dmode; + sf_g->fmode = info->fmode; + sf_g->dmask = info->dmask; + sf_g->fmask = info->fmask; + } else { + sf_g->dmode = ~0; + sf_g->fmode = ~0; + } + + if ((unsigned)info->length >= sizeof(struct vbsf_mount_info_new)) { + AssertCompile(sizeof(sf_g->tag) >= sizeof(info->tag)); + memcpy(sf_g->tag, info->tag, sizeof(info->tag)); + sf_g->tag[sizeof(sf_g->tag) - 1] = '\0'; + } else { + sf_g->tag[0] = '\0'; + } + + *sf_gp = sf_g; + return 0; + + fail2: + if (sf_g->nls) + unload_nls(sf_g->nls); + + fail1: + kfree(sf_g); + + fail0: + return err; +} + +/* unmap the share and free global info [sf_g] */ +static void sf_glob_free(struct sf_glob_info *sf_g) +{ + int rc; + + TRACE(); + rc = VbglR0SfUnmapFolder(&client_handle, &sf_g->map); + if (RT_FAILURE(rc)) + LogFunc(("VbglR0SfUnmapFolder failed rc=%d\n", rc)); + + if (sf_g->nls) + unload_nls(sf_g->nls); + + kfree(sf_g); +} + +/** + * This is called (by sf_read_super_[24|26] when vfs mounts the fs and + * wants to read super_block. + * + * calls [sf_glob_alloc] to map the folder and allocate global + * information structure. + * + * initializes [sb], initializes root inode and dentry. + * + * should respect [flags] + */ +static int sf_read_super_aux(struct super_block *sb, void *data, int flags) +{ + int err; + struct dentry *droot; + struct inode *iroot; + struct sf_inode_info *sf_i; + struct sf_glob_info *sf_g; + SHFLFSOBJINFO fsinfo; + struct vbsf_mount_info_new *info; + bool fInodePut = true; + + TRACE(); + if (!data) { + LogFunc(("no mount info specified\n")); + return -EINVAL; + } + + info = data; + + if (flags & MS_REMOUNT) { + LogFunc(("remounting is not supported\n")); + return -ENOSYS; + } + + err = sf_glob_alloc(info, &sf_g); + if (err) + goto fail0; + + sf_i = kmalloc(sizeof(*sf_i), GFP_KERNEL); + if (!sf_i) { + err = -ENOMEM; + LogRelFunc(("could not allocate memory for root inode info\n")); + goto fail1; + } + + sf_i->handle = SHFL_HANDLE_NIL; + sf_i->path = kmalloc(sizeof(SHFLSTRING) + 1, GFP_KERNEL); + if (!sf_i->path) { + err = -ENOMEM; + LogRelFunc(("could not allocate memory for root inode path\n")); + goto fail2; + } + + sf_i->path->u16Length = 1; + sf_i->path->u16Size = 2; + sf_i->path->String.utf8[0] = '/'; + sf_i->path->String.utf8[1] = 0; + sf_i->force_reread = 0; + + err = sf_stat(__func__, sf_g, sf_i->path, &fsinfo, 0); + if (err) { + LogFunc(("could not stat root of share\n")); + goto fail3; + } + + sb->s_magic = 0xface; + sb->s_blocksize = 1024; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 3) + /* Required for seek/sendfile. + * + * Must by less than or equal to INT64_MAX despite the fact that the + * declaration of this variable is unsigned long long. See determination + * of 'loff_t max' in fs/read_write.c / do_sendfile(). I don't know the + * correct limit but MAX_LFS_FILESIZE (8TB-1 on 32-bit boxes) takes the + * page cache into account and is the suggested limit. */ +# if defined MAX_LFS_FILESIZE + sb->s_maxbytes = MAX_LFS_FILESIZE; +# else + sb->s_maxbytes = 0x7fffffffffffffffULL; +# endif +#endif + sb->s_op = &sf_super_ops; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + iroot = iget_locked(sb, 0); +#else + iroot = iget(sb, 0); +#endif + if (!iroot) { + err = -ENOMEM; /* XXX */ + LogFunc(("could not get root inode\n")); + goto fail3; + } + + if (sf_init_backing_dev(sf_g)) { + err = -EINVAL; + LogFunc(("could not init bdi\n")); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + unlock_new_inode(iroot); +#endif + goto fail4; + } + + sf_init_inode(sf_g, iroot, &fsinfo); + SET_INODE_INFO(iroot, sf_i); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 25) + unlock_new_inode(iroot); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + droot = d_make_root(iroot); +#else + droot = d_alloc_root(iroot); +#endif + if (!droot) { + err = -ENOMEM; /* XXX */ + LogFunc(("d_alloc_root failed\n")); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) + fInodePut = false; +#endif + goto fail5; + } + + sb->s_root = droot; + SET_GLOB_INFO(sb, sf_g); + return 0; + + fail5: + sf_done_backing_dev(sf_g); + + fail4: + if (fInodePut) + iput(iroot); + + fail3: + kfree(sf_i->path); + + fail2: + kfree(sf_i); + + fail1: + sf_glob_free(sf_g); + + fail0: + return err; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) +static struct super_block *sf_read_super_24(struct super_block *sb, void *data, + int flags) +{ + int err; + + TRACE(); + err = sf_read_super_aux(sb, data, flags); + if (err) + return NULL; + + return sb; +} +#endif + +/* this is called when vfs is about to destroy the [inode]. all + resources associated with this [inode] must be cleared here */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) +static void sf_clear_inode(struct inode *inode) +{ + struct sf_inode_info *sf_i; + + TRACE(); + sf_i = GET_INODE_INFO(inode); + if (!sf_i) + return; + + BUG_ON(!sf_i->path); + kfree(sf_i->path); + kfree(sf_i); + SET_INODE_INFO(inode, NULL); +} +#else /* LINUX_VERSION_CODE >= 2.6.36 */ +static void sf_evict_inode(struct inode *inode) +{ + struct sf_inode_info *sf_i; + + TRACE(); + truncate_inode_pages(&inode->i_data, 0); +# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) + clear_inode(inode); +# else + end_writeback(inode); +# endif + + sf_i = GET_INODE_INFO(inode); + if (!sf_i) + return; + + BUG_ON(!sf_i->path); + kfree(sf_i->path); + kfree(sf_i); + SET_INODE_INFO(inode, NULL); +} +#endif /* LINUX_VERSION_CODE >= 2.6.36 */ + +/* 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 LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) +static void sf_read_inode(struct inode *inode) +{ +} +#endif + +/* vfs is done with [sb] (umount called) call [sf_glob_free] to unmap + the folder and free [sf_g] */ +static void sf_put_super(struct super_block *sb) +{ + struct sf_glob_info *sf_g; + + sf_g = GET_GLOB_INFO(sb); + BUG_ON(!sf_g); + sf_done_backing_dev(sf_g); + sf_glob_free(sf_g); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +static int sf_statfs(struct super_block *sb, STRUCT_STATFS * stat) +{ + return sf_get_volume_info(sb, stat); +} +#else +static int sf_statfs(struct dentry *dentry, STRUCT_STATFS * stat) +{ + struct super_block *sb = dentry->d_inode->i_sb; + return sf_get_volume_info(sb, stat); +} +#endif + +static int sf_remount_fs(struct super_block *sb, int *flags, char *data) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 23) + struct sf_glob_info *sf_g; + struct sf_inode_info *sf_i; + struct inode *iroot; + SHFLFSOBJINFO fsinfo; + int err; + + sf_g = GET_GLOB_INFO(sb); + BUG_ON(!sf_g); + if (data && data[0] != 0) { + struct vbsf_mount_info_new *info = + (struct vbsf_mount_info_new *)data; + if (info->signature[0] == VBSF_MOUNT_SIGNATURE_BYTE_0 + && info->signature[1] == VBSF_MOUNT_SIGNATURE_BYTE_1 + && info->signature[2] == VBSF_MOUNT_SIGNATURE_BYTE_2) { + sf_g->uid = info->uid; + sf_g->gid = info->gid; + sf_g->ttl = info->ttl; + if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, tag)) { + sf_g->dmode = info->dmode; + sf_g->fmode = info->fmode; + sf_g->dmask = info->dmask; + sf_g->fmask = info->fmask; + } else { + sf_g->dmode = ~0; + sf_g->fmode = ~0; + } + if ((unsigned)info->length >= sizeof(struct vbsf_mount_info_new)) { + AssertCompile(sizeof(sf_g->tag) >= sizeof(info->tag)); + memcpy(sf_g->tag, info->tag, sizeof(info->tag)); + sf_g->tag[sizeof(sf_g->tag) - 1] = '\0'; + } else { + sf_g->tag[0] = '\0'; + } + } + } + + iroot = ilookup(sb, 0); + if (!iroot) + return -ENOSYS; + + sf_i = GET_INODE_INFO(iroot); + err = sf_stat(__func__, sf_g, sf_i->path, &fsinfo, 0); + BUG_ON(err != 0); + sf_init_inode(sf_g, iroot, &fsinfo); + /*unlock_new_inode(iroot); */ + return 0; +#else /* LINUX_VERSION_CODE < 2.4.23 */ + return -ENOSYS; +#endif /* LINUX_VERSION_CODE < 2.4.23 */ +} + +/** Show mount options. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) +static int sf_show_options(struct seq_file *m, struct vfsmount *mnt) +#else +static int sf_show_options(struct seq_file *m, struct dentry *root) +#endif +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 3, 0) + struct super_block *sb = mnt->mnt_sb; +#else + struct super_block *sb = root->d_sb; +#endif + struct sf_glob_info *sf_g = GET_GLOB_INFO(sb); + if (sf_g) { + seq_printf(m, ",uid=%u,gid=%u,ttl=%u,dmode=0%o,fmode=0%o,dmask=0%o,fmask=0%o", + sf_g->uid, sf_g->gid, sf_g->ttl, sf_g->dmode, sf_g->fmode, sf_g->dmask, sf_g->fmask); + if (sf_g->tag[0] != '\0') { + seq_puts(m, ",tag="); + seq_escape(m, sf_g->tag, " \t\n\\"); + } + } + + return 0; +} + +static struct super_operations sf_super_ops = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) + .clear_inode = sf_clear_inode, +#else + .evict_inode = sf_evict_inode, +#endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 25) + .read_inode = sf_read_inode, +#endif + .put_super = sf_put_super, + .statfs = sf_statfs, + .remount_fs = sf_remount_fs, + .show_options = sf_show_options +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) +static DECLARE_FSTYPE(vboxsf_fs_type, "vboxsf", sf_read_super_24, 0); +#else +static int sf_read_super_26(struct super_block *sb, void *data, int flags) +{ + int err; + + TRACE(); + err = sf_read_super_aux(sb, data, flags); + if (err) + printk(KERN_DEBUG "sf_read_super_aux err=%d\n", err); + + return err; +} + +# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18) +static struct super_block *sf_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *data) +{ + TRACE(); + return get_sb_nodev(fs_type, flags, data, sf_read_super_26); +} +# elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) +static int sf_get_sb(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data, struct vfsmount *mnt) +{ + TRACE(); + return get_sb_nodev(fs_type, flags, data, sf_read_super_26, mnt); +} +# else /* LINUX_VERSION_CODE >= 2.6.39 */ +static struct dentry *sf_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data) +{ + TRACE(); + return mount_nodev(fs_type, flags, data, sf_read_super_26); +} +# endif /* LINUX_VERSION_CODE >= 2.6.39 */ + +static struct file_system_type vboxsf_fs_type = { + .owner = THIS_MODULE, + .name = "vboxsf", +# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) + .get_sb = sf_get_sb, +# else + .mount = sf_mount, +# endif + .kill_sb = kill_anon_super +}; + +#endif /* LINUX_VERSION_CODE >= 2.6.0 */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +static int follow_symlinks = 0; +module_param(follow_symlinks, int, 0); +MODULE_PARM_DESC(follow_symlinks, + "Let host resolve symlinks rather than showing them"); +#endif + +/* Module initialization/finalization handlers */ +static int __init init(void) +{ + int rcVBox; + int rcRet = 0; + int err; + + TRACE(); + + if (sizeof(struct vbsf_mount_info_new) > PAGE_SIZE) { + printk(KERN_ERR + "Mount information structure is too large %lu\n" + "Must be less than or equal to %lu\n", + (unsigned long)sizeof(struct vbsf_mount_info_new), + (unsigned long)PAGE_SIZE); + return -EINVAL; + } + + err = register_filesystem(&vboxsf_fs_type); + if (err) { + LogFunc(("register_filesystem err=%d\n", err)); + return err; + } + + rcVBox = VbglR0HGCMInit(); + if (RT_FAILURE(rcVBox)) { + LogRelFunc(("VbglR0HGCMInit failed, rc=%d\n", rcVBox)); + rcRet = -EPROTO; + goto fail0; + } + + rcVBox = VbglR0SfConnect(&client_handle); + if (RT_FAILURE(rcVBox)) { + LogRelFunc(("VbglR0SfConnect failed, rc=%d\n", rcVBox)); + rcRet = -EPROTO; + goto fail1; + } + + rcVBox = VbglR0SfSetUtf8(&client_handle); + if (RT_FAILURE(rcVBox)) { + LogRelFunc(("VbglR0SfSetUtf8 failed, rc=%d\n", rcVBox)); + rcRet = -EPROTO; + goto fail2; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + if (!follow_symlinks) { + rcVBox = VbglR0SfSetSymlinks(&client_handle); + if (RT_FAILURE(rcVBox)) { + printk(KERN_WARNING + "vboxsf: Host unable to show symlinks, rc=%d\n", + rcVBox); + } + } +#endif + + printk(KERN_DEBUG + "vboxsf: Successfully loaded version " VBOX_VERSION_STRING + " (interface " RT_XSTR(VMMDEV_VERSION) ")\n"); + + return 0; + + fail2: + VbglR0SfDisconnect(&client_handle); + + fail1: + VbglR0HGCMTerminate(); + + fail0: + unregister_filesystem(&vboxsf_fs_type); + return rcRet; +} + +static void __exit fini(void) +{ + TRACE(); + + VbglR0SfDisconnect(&client_handle); + VbglR0HGCMTerminate(); + unregister_filesystem(&vboxsf_fs_type); +} + +module_init(init); +module_exit(fini); + +/* C++ hack */ +int __gxx_personality_v0 = 0xdeadbeef; diff --git a/src/VBox/Additions/linux/sharedfolders/vfsmod.h b/src/VBox/Additions/linux/sharedfolders/vfsmod.h new file mode 100644 index 00000000..f7b71831 --- /dev/null +++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.h @@ -0,0 +1,181 @@ +/* $Id: vfsmod.h $ */ +/** @file + * vboxsf - Linux Shared Folders VFS, internal header. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * 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 + +#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS +#include "the-linux-kernel.h" +#include <VBox/log.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +# include <linux/backing-dev.h> +#endif + +#include <VBox/VBoxGuestLibSharedFolders.h> +#include "vbsfmount.h" + +#define DIR_BUFFER_SIZE (16*_1K) + +/* per-shared folder information */ +struct sf_glob_info { + VBGLSFMAP map; + struct nls_table *nls; + int ttl; + int uid; + int gid; + int dmode; + int fmode; + int dmask; + int fmask; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) + struct backing_dev_info bdi; +#endif + char tag[32]; /**< Mount tag for VBoxService automounter. @since 6.0 */ +}; + +/* per-inode information */ +struct sf_inode_info { + /* which file */ + SHFLSTRING *path; + /* some information was changed, update data on next revalidate */ + int force_restat; + /* directory content changed, update the whole directory on next sf_getdent */ + int force_reread; + /* file structure, only valid between open() and release() */ + struct file *file; + /* handle valid if a file was created with sf_create_aux until it will + * be opened with sf_reg_open() */ + SHFLHANDLE handle; +}; + +struct sf_dir_info { + struct list_head info_list; +}; + +struct sf_dir_buf { + size_t cEntries; + size_t cbFree; + size_t cbUsed; + void *buf; + struct list_head head; +}; + +struct sf_reg_info { + SHFLHANDLE handle; +}; + +/* globals */ +extern VBGLSFCLIENT client_handle; + +/* forward declarations */ +extern struct inode_operations sf_dir_iops; +extern struct inode_operations sf_lnk_iops; +extern struct inode_operations sf_reg_iops; +extern struct file_operations sf_dir_fops; +extern struct file_operations sf_reg_fops; +extern struct dentry_operations sf_dentry_ops; +extern struct address_space_operations sf_reg_aops; + +extern void sf_init_inode(struct sf_glob_info *sf_g, struct inode *inode, + PSHFLFSOBJINFO info); +extern int sf_stat(const char *caller, struct sf_glob_info *sf_g, + SHFLSTRING * path, PSHFLFSOBJINFO result, int ok_to_fail); +extern int sf_inode_revalidate(struct dentry *dentry); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0) +# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +extern int sf_getattr(const struct path *path, struct kstat *kstat, + u32 request_mask, unsigned int query_flags); +# else +extern int sf_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *kstat); +# endif +extern int sf_setattr(struct dentry *dentry, struct iattr *iattr); +#endif +extern int sf_path_from_dentry(const char *caller, struct sf_glob_info *sf_g, + struct sf_inode_info *sf_i, + struct dentry *dentry, SHFLSTRING ** result); +extern int sf_nlscpy(struct sf_glob_info *sf_g, char *name, + size_t name_bound_len, const unsigned char *utf8_name, + size_t utf8_len); +extern void sf_dir_info_free(struct sf_dir_info *p); +extern void sf_dir_info_empty(struct sf_dir_info *p); +extern struct sf_dir_info *sf_dir_info_alloc(void); +extern int sf_dir_read_all(struct sf_glob_info *sf_g, + struct sf_inode_info *sf_i, struct sf_dir_info *sf_d, + SHFLHANDLE handle); +extern int sf_init_backing_dev(struct sf_glob_info *sf_g); +extern void sf_done_backing_dev(struct sf_glob_info *sf_g); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) +# define STRUCT_STATFS struct statfs +#else +# define STRUCT_STATFS struct kstatfs +#endif +int sf_get_volume_info(struct super_block *sb, STRUCT_STATFS * stat); + +#ifdef __cplusplus +# define CMC_API __attribute__ ((cdecl, regparm (0))) +#else +# define CMC_API __attribute__ ((regparm (0))) +#endif + +#define TRACE() LogFunc(("tracepoint\n")) + +/* Following casts are here to prevent assignment of void * to + pointers of arbitrary type */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0) +# define GET_GLOB_INFO(sb) ((struct sf_glob_info *) (sb)->u.generic_sbp) +# define SET_GLOB_INFO(sb, sf_g) (sb)->u.generic_sbp = sf_g +#else +# define GET_GLOB_INFO(sb) ((struct sf_glob_info *) (sb)->s_fs_info) +# define SET_GLOB_INFO(sb, sf_g) (sb)->s_fs_info = sf_g +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) || defined(KERNEL_FC6) +/* FC6 kernel 2.6.18, vanilla kernel 2.6.19+ */ +# define GET_INODE_INFO(i) ((struct sf_inode_info *) (i)->i_private) +# define SET_INODE_INFO(i, sf_i) (i)->i_private = sf_i +#else +/* vanilla kernel up to 2.6.18 */ +# define GET_INODE_INFO(i) ((struct sf_inode_info *) (i)->u.generic_ip) +# define SET_INODE_INFO(i, sf_i) (i)->u.generic_ip = sf_i +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0) +# define GET_F_DENTRY(f) (f->f_path.dentry) +#else +# define GET_F_DENTRY(f) (f->f_dentry) +#endif + +#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..82fb82c8 --- /dev/null +++ b/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c @@ -0,0 +1,93 @@ +/** @file + * + * VirtualBox Timesync using temporary Backdoor + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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; +} |