summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions')
-rw-r--r--src/VBox/Additions/.scm-settings37
-rw-r--r--src/VBox/Additions/3D/Config.kmk141
-rw-r--r--src/VBox/Additions/3D/Makefile.kmk46
-rw-r--r--src/VBox/Additions/3D/mesa/.scm-settings30
-rw-r--r--src/VBox/Additions/3D/mesa/Makefile.kmk1516
-rw-r--r--src/VBox/Additions/3D/mesa/include/assert.h47
-rw-r--r--src/VBox/Additions/3D/mesa/include/git_sha1.h38
-rw-r--r--src/VBox/Additions/3D/win/Makefile.kmk37
-rw-r--r--src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.cpp1396
-rw-r--r--src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.h58
-rw-r--r--src/VBox/Additions/3D/win/VBoxGL/Makefile.kmk87
-rw-r--r--src/VBox/Additions/3D/win/VBoxGL/VBoxGL.c580
-rw-r--r--src/VBox/Additions/3D/win/VBoxGL/VBoxGL.rc66
-rw-r--r--src/VBox/Additions/3D/win/VBoxICD/.scm-settings29
-rw-r--r--src/VBox/Additions/3D/win/VBoxICD/Makefile.kmk85
-rw-r--r--src/VBox/Additions/3D/win/VBoxICD/VBoxICD.c165
-rw-r--r--src/VBox/Additions/3D/win/VBoxICD/VBoxICD.rc66
-rwxr-xr-xsrc/VBox/Additions/3D/win/VBoxICD/icd_forwarders.py105
-rwxr-xr-xsrc/VBox/Additions/3D/win/VBoxICD/icd_pfns.py73
-rw-r--r--src/VBox/Additions/3D/win/VBoxICD/opengl32.def388
-rw-r--r--src/VBox/Additions/3D/win/VBoxICD/opengl32.mingw.def388
-rw-r--r--src/VBox/Additions/3D/win/VBoxNine/Makefile.kmk74
-rw-r--r--src/VBox/Additions/3D/win/VBoxNine/VBoxNine.c181
-rw-r--r--src/VBox/Additions/3D/win/VBoxNine/VBoxNine.def34
-rw-r--r--src/VBox/Additions/3D/win/VBoxNine/VBoxNine.rc66
-rw-r--r--src/VBox/Additions/3D/win/VBoxNine/nine/nine_memory_helper.c176
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/.scm-settings28
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/Makefile.kmk85
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.c146
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.def36
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.rc66
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/wddm_screen.h47
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/winsys/Makefile.kup0
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen.c134
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_ioctl.c979
-rw-r--r--src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_wddm.c177
-rw-r--r--src/VBox/Additions/3D/win/VBoxWddmUmHlp/D3DKMT.cpp260
-rw-r--r--src/VBox/Additions/3D/win/VBoxWddmUmHlp/Makefile.kmk54
-rw-r--r--src/VBox/Additions/3D/win/VBoxWddmUmHlp/UmHlpInternal.h36
-rw-r--r--src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxMpLogger.cpp167
-rw-r--r--src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxWddmUmHlp.h116
-rw-r--r--src/VBox/Additions/3D/win/include/VBoxGaDriver.h115
-rw-r--r--src/VBox/Additions/3D/win/include/VBoxGaHWInfo.h64
-rw-r--r--src/VBox/Additions/3D/win/include/VBoxGaHwSVGA.h66
-rw-r--r--src/VBox/Additions/3D/win/include/VBoxGaNine.h58
-rw-r--r--src/VBox/Additions/3D/win/include/VBoxGaTypes.h114
-rw-r--r--src/VBox/Additions/Makefile.kmk405
-rw-r--r--src/VBox/Additions/common/Makefile.kmk39
-rw-r--r--src/VBox/Additions/common/VBoxControl/Makefile.kmk61
-rw-r--r--src/VBox/Additions/common/VBoxControl/VBoxControl.cpp2209
-rw-r--r--src/VBox/Additions/common/VBoxControl/VBoxControl.rc61
-rw-r--r--src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk48
-rw-r--r--src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp226
-rw-r--r--src/VBox/Additions/common/VBoxGuest/.scm-settings65
-rw-r--r--src/VBox/Additions/common/VBoxGuest/Makefile.kmk287
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c446
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp1393
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c799
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c471
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c588
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h248
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c1470
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c1094
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp698
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def55
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c1138
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf41
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp3503
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp4516
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm1679
-rw-r--r--src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h415
-rw-r--r--src/VBox/Additions/common/VBoxGuest/darwin/Info.plist51
-rw-r--r--src/VBox/Additions/common/VBoxGuest/freebsd/Makefile202
-rw-r--r--src/VBox/Additions/common/VBoxGuest/freebsd/Makefile.kup0
-rwxr-xr-xsrc/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest240
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk254
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibCrOgl.cpp134
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp183
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp240
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp1175
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-os2.cpp85
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-solaris.cpp97
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp64
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp208
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp205
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp333
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h212
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibMouse.cpp130
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp1197
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c716
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp51
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp485
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp363
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp130
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp83
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp2609
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp56
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp133
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp222
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp326
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp1948
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDrmClient.cpp199
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp103
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp90
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp2214
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp1032
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp119
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp108
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp235
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp226
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h131
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp94
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp135
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp180
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp90
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp151
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp108
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp209
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp432
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp79
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp55
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp618
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp64
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/VbglR0CanUsePhysPageList.cpp52
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/testcase/Makefile.kmk52
-rw-r--r--src/VBox/Additions/common/VBoxGuest/lib/testcase/tstVbglR0PhysHeap-1.cpp415
-rw-r--r--src/VBox/Additions/common/VBoxGuest/linux/Makefile213
-rw-r--r--src/VBox/Additions/common/VBoxGuest/linux/Makefile.kup0
-rw-r--r--src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c189
-rw-r--r--src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c61
-rwxr-xr-xsrc/VBox/Additions/common/VBoxGuest/linux/files_vboxguest237
-rw-r--r--src/VBox/Additions/common/VBoxGuest/netbsd/locators.h8
-rw-r--r--src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf66
-rw-r--r--src/VBox/Additions/common/VBoxGuest/solaris/deps.asm48
-rwxr-xr-xsrc/VBox/Additions/common/VBoxGuest/solaris/load.sh108
-rw-r--r--src/VBox/Additions/common/VBoxGuest/win/Makefile.kup0
-rw-r--r--src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf98
-rw-r--r--src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc72
-rw-r--r--src/VBox/Additions/common/VBoxGuest/win/VBoxGuestEarlyNT.inf100
-rw-r--r--src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp227
-rw-r--r--src/VBox/Additions/common/VBoxService/Makefile.kmk220
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxService-os2.def33
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxService-win.cpp670
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxService.cpp1311
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp2194
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp457
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp1140
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp629
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControl.h297
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp2201
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp2886
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp672
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h284
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp803
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp439
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h66
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h37
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp747
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp807
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp1769
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h42
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp324
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h49
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp1363
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp1707
-rw-r--r--src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h42
-rw-r--r--src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk42
-rw-r--r--src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp87
-rw-r--r--src/VBox/Additions/common/VBoxVideo/.scm-settings33
-rw-r--r--src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp300
-rw-r--r--src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp124
-rw-r--r--src/VBox/Additions/common/VBoxVideo/HGSMIHostCmd.cpp245
-rw-r--r--src/VBox/Additions/common/VBoxVideo/Modesetting.cpp419
-rw-r--r--src/VBox/Additions/common/VBoxVideo/VBVABase.cpp378
-rw-r--r--src/VBox/Additions/common/VBoxVideo/todo-create-library-from-these-files-for-windows0
-rw-r--r--src/VBox/Additions/common/pam/Makefile.kmk40
-rw-r--r--src/VBox/Additions/common/pam/pam_vbox.cpp879
-rw-r--r--src/VBox/Additions/common/testcase/Makefile.kmk53
-rwxr-xr-xsrc/VBox/Additions/common/testcase/led-lights.sh276
-rw-r--r--src/VBox/Additions/common/testcase/tstPageFusion.cpp389
-rw-r--r--src/VBox/Additions/darwin/Installer/.scm-settings37
-rwxr-xr-xsrc/VBox/Additions/darwin/Installer/DiskImage/Uninstall.tool124
-rw-r--r--src/VBox/Additions/darwin/Installer/Makefile.kmk414
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/PkgBuildComponent.plist15
-rwxr-xr-xsrc/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/postflight104
-rwxr-xr-xsrc/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper63
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxclient.plist17
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxservice.plist15
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Conclusion.rtf9
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Localizable.strings14
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Welcome.rtf13
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-amd64.dist70
-rw-r--r--src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-x86.dist70
-rw-r--r--src/VBox/Additions/darwin/Makefile.kmk35
-rw-r--r--src/VBox/Additions/darwin/VBoxClient/Makefile.kmk57
-rw-r--r--src/VBox/Additions/darwin/VBoxClient/VBoxClient.cpp298
-rw-r--r--src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboard.cpp341
-rw-r--r--src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardGuestToHost.cpp390
-rw-r--r--src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardHostToGuest.cpp315
-rw-r--r--src/VBox/Additions/darwin/VBoxClient/VBoxClientInternal.h112
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/.scm-settings29
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/Info.plist44
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/Makefile.kmk80
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp608
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/VBoxSF-VNodeOps.cpp843
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/VBoxSF-VfsOps.cpp639
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/VBoxSF.cpp261
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/VBoxSFInternal.h117
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/VBoxSFMount.h54
-rw-r--r--src/VBox/Additions/darwin/VBoxSF/mount.vboxsf.cpp97
-rw-r--r--src/VBox/Additions/freebsd/.scm-settings30
-rw-r--r--src/VBox/Additions/freebsd/Installer/pkg-descr3
-rwxr-xr-xsrc/VBox/Additions/freebsd/Installer/vboxguest.sh149
-rw-r--r--src/VBox/Additions/freebsd/Makefile62
-rw-r--r--src/VBox/Additions/freebsd/Makefile.kmk195
-rw-r--r--src/VBox/Additions/freebsd/drm/Makefile36
-rw-r--r--src/VBox/Additions/freebsd/drm/Makefile.kmk82
-rwxr-xr-xsrc/VBox/Additions/freebsd/drm/files_vboxvideo_drm35
-rw-r--r--src/VBox/Additions/freebsd/drm/vboxvideo_drm.c173
-rw-r--r--src/VBox/Additions/freebsd/vboxvfs/Makefile.kmk74
-rw-r--r--src/VBox/Additions/freebsd/vboxvfs/vboxvfs.h105
-rw-r--r--src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vfsops.c268
-rw-r--r--src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vnops.c251
-rw-r--r--src/VBox/Additions/haiku/.scm-settings30
-rw-r--r--src/VBox/Additions/haiku/Makefile.kmk73
-rw-r--r--src/VBox/Additions/haiku/SharedFolders/Makefile.kmk89
-rw-r--r--src/VBox/Additions/haiku/SharedFolders/OpenHashTable.h515
-rw-r--r--src/VBox/Additions/haiku/SharedFolders/kernel_cpp.h122
-rw-r--r--src/VBox/Additions/haiku/SharedFolders/lock.h315
-rw-r--r--src/VBox/Additions/haiku/SharedFolders/vboxsf.c1055
-rw-r--r--src/VBox/Additions/haiku/SharedFolders/vboxsf.h120
-rw-r--r--src/VBox/Additions/haiku/SharedFolders/vnode_cache.cpp144
-rw-r--r--src/VBox/Additions/haiku/VBoxMouse/Makefile.kmk90
-rw-r--r--src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.cpp318
-rw-r--r--src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.h91
-rw-r--r--src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.cpp117
-rw-r--r--src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.h87
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/Makefile.kmk128
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.cpp468
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.h88
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.cpp177
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.h86
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.cpp101
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.h89
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.cpp297
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.h109
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxServiceDescriptor.h78
-rw-r--r--src/VBox/Additions/haiku/VBoxTray/VBoxTray.rdef93
-rw-r--r--src/VBox/Additions/haiku/VBoxVideo/Makefile.kmk64
-rw-r--r--src/VBox/Additions/haiku/VBoxVideo/accelerant/Makefile.kmk75
-rw-r--r--src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.cpp473
-rw-r--r--src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.h115
-rw-r--r--src/VBox/Additions/haiku/VBoxVideo/common/VBoxVideo_common.h114
-rw-r--r--src/VBox/Additions/haiku/VBoxVideo/driver/Makefile.kmk95
-rw-r--r--src/VBox/Additions/haiku/VBoxVideo/driver/driver.cpp382
-rw-r--r--src/VBox/Additions/haiku/include/VBoxGuestInternal.h74
-rw-r--r--src/VBox/Additions/haiku/include/lock.h318
-rwxr-xr-xsrc/VBox/Additions/haiku/load.sh52
-rwxr-xr-xsrc/VBox/Additions/haiku/unload.sh36
-rw-r--r--src/VBox/Additions/linux/Makefile136
-rw-r--r--src/VBox/Additions/linux/Makefile.kmk449
-rw-r--r--src/VBox/Additions/linux/drm/.scm-settings35
-rw-r--r--src/VBox/Additions/linux/drm/Makefile.kmk52
-rw-r--r--src/VBox/Additions/linux/drm/Makefile.module.kms72
-rw-r--r--src/VBox/Additions/linux/drm/README.testing13
-rwxr-xr-xsrc/VBox/Additions/linux/drm/files_vboxvideo_drv59
-rw-r--r--src/VBox/Additions/linux/drm/indent.sed285
-rw-r--r--src/VBox/Additions/linux/drm/vbox_drv.c455
-rw-r--r--src/VBox/Additions/linux/drm/vbox_drv.h557
-rw-r--r--src/VBox/Additions/linux/drm/vbox_fb.c541
-rw-r--r--src/VBox/Additions/linux/drm/vbox_hgsmi.c130
-rw-r--r--src/VBox/Additions/linux/drm/vbox_irq.c225
-rw-r--r--src/VBox/Additions/linux/drm/vbox_main.c704
-rw-r--r--src/VBox/Additions/linux/drm/vbox_mode.c949
-rw-r--r--src/VBox/Additions/linux/drm/vbox_prime.c82
-rw-r--r--src/VBox/Additions/linux/drm/vbox_ttm.c843
-rwxr-xr-xsrc/VBox/Additions/linux/export_modules.sh162
-rw-r--r--src/VBox/Additions/linux/installer/.scm-settings30
-rwxr-xr-xsrc/VBox/Additions/linux/installer/autorun.sh204
-rw-r--r--src/VBox/Additions/linux/installer/deffiles80
-rwxr-xr-xsrc/VBox/Additions/linux/installer/install.sh.in640
-rw-r--r--src/VBox/Additions/linux/installer/module-autologon.sh182
-rwxr-xr-xsrc/VBox/Additions/linux/installer/vboxadd-service.sh171
-rwxr-xr-xsrc/VBox/Additions/linux/installer/vboxadd-x11.sh625
-rwxr-xr-xsrc/VBox/Additions/linux/installer/vboxadd.sh1325
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/.scm-settings30
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/Config.kmk41
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk116
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/banner-dummy.pngbin0 -> 1622 bytes
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk61
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h90
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c1442
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c416
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c344
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c211
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c388
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c42
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c1655
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp1531
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop5
-rw-r--r--src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui227
-rw-r--r--src/VBox/Additions/linux/sharedfolders/.scm-settings39
-rw-r--r--src/VBox/Additions/linux/sharedfolders/Makefile.kmk57
-rw-r--r--src/VBox/Additions/linux/sharedfolders/Makefile.module119
-rw-r--r--src/VBox/Additions/linux/sharedfolders/dirops.c1427
-rwxr-xr-xsrc/VBox/Additions/linux/sharedfolders/files_vboxsf107
-rw-r--r--src/VBox/Additions/linux/sharedfolders/lnkops.c305
-rw-r--r--src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c702
-rw-r--r--src/VBox/Additions/linux/sharedfolders/regops.c3902
-rw-r--r--src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c126
-rw-r--r--src/VBox/Additions/linux/sharedfolders/utils.c1288
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vbsfmount.c113
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vbsfmount.h142
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vfsmod.c1753
-rw-r--r--src/VBox/Additions/linux/sharedfolders/vfsmod.h483
-rw-r--r--src/VBox/Additions/linux/testcase/TimesyncBackdoor.c103
-rw-r--r--src/VBox/Additions/solaris/.scm-settings54
-rw-r--r--src/VBox/Additions/solaris/DRM/Makefile.kmk71
-rw-r--r--src/VBox/Additions/solaris/DRM/deps.asm47
-rw-r--r--src/VBox/Additions/solaris/DRM/include/drm.h820
-rw-r--r--src/VBox/Additions/solaris/DRM/include/drmP.h908
-rw-r--r--src/VBox/Additions/solaris/DRM/include/drm_atomic.h94
-rw-r--r--src/VBox/Additions/solaris/DRM/include/drm_linux_list.h71
-rw-r--r--src/VBox/Additions/solaris/DRM/include/queue.h585
-rw-r--r--src/VBox/Additions/solaris/DRM/vboxvideo_drm.c409
-rwxr-xr-xsrc/VBox/Additions/solaris/Installer/VBox.sh61
-rwxr-xr-xsrc/VBox/Additions/solaris/Installer/makepackage.sh160
-rwxr-xr-xsrc/VBox/Additions/solaris/Installer/postinstall.sh448
-rwxr-xr-xsrc/VBox/Additions/solaris/Installer/preremove.sh98
-rwxr-xr-xsrc/VBox/Additions/solaris/Installer/vbox_vendor_select88
-rw-r--r--src/VBox/Additions/solaris/Installer/vboxguest.depend1
-rw-r--r--src/VBox/Additions/solaris/Installer/vboxguest.pkginfo15
-rwxr-xr-xsrc/VBox/Additions/solaris/Installer/vboxguest.sh310
-rw-r--r--src/VBox/Additions/solaris/Installer/vboxguest.space5
-rw-r--r--src/VBox/Additions/solaris/Installer/vboxservice.xml88
-rw-r--r--src/VBox/Additions/solaris/Makefile.kmk413
-rw-r--r--src/VBox/Additions/solaris/Mouse/Makefile.kmk81
-rw-r--r--src/VBox/Additions/solaris/Mouse/deps.asm49
-rw-r--r--src/VBox/Additions/solaris/Mouse/testcase/Makefile.kup0
-rw-r--r--src/VBox/Additions/solaris/Mouse/testcase/solaris.h454
-rw-r--r--src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c170
-rw-r--r--src/VBox/Additions/solaris/Mouse/vboxms.c1450
-rw-r--r--src/VBox/Additions/solaris/Mouse/vboxms.conf42
-rw-r--r--src/VBox/Additions/solaris/Mouse/vboxmslnk.c217
-rw-r--r--src/VBox/Additions/solaris/Mouse/vboxmslnk.xml92
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/Makefile.kmk113
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/deps.asm49
-rwxr-xr-xsrc/VBox/Additions/solaris/SharedFolders/loadfs.sh105
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs.h106
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c179
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c1070
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h200
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c641
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h90
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c2500
-rw-r--r--src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h99
-rw-r--r--src/VBox/Additions/solaris/Virtio/Makefile.kmk68
-rw-r--r--src/VBox/Additions/solaris/Virtio/Virtio-solaris.c224
-rw-r--r--src/VBox/Additions/solaris/Virtio/Virtio-solaris.h205
-rw-r--r--src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c852
-rw-r--r--src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c665
-rw-r--r--src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h48
-rw-r--r--src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c148
-rw-r--r--src/VBox/Additions/x11/.scm-settings48
-rwxr-xr-xsrc/VBox/Additions/x11/Installer/98vboxadd-xclient48
-rw-r--r--src/VBox/Additions/x11/Installer/linux_xorg_suse11.conf130
-rw-r--r--src/VBox/Additions/x11/Installer/solaris_xorg.conf113
-rw-r--r--src/VBox/Additions/x11/Installer/solaris_xorg_modeless.conf81
-rw-r--r--src/VBox/Additions/x11/Installer/vboxclient.desktop13
-rw-r--r--src/VBox/Additions/x11/Installer/vboxvideo.ids1
-rwxr-xr-xsrc/VBox/Additions/x11/Installer/x11config.pl139
-rwxr-xr-xsrc/VBox/Additions/x11/Installer/x11config.sh171
-rwxr-xr-xsrc/VBox/Additions/x11/Installer/x11config15.pl97
-rwxr-xr-xsrc/VBox/Additions/x11/Installer/x11config15sol.pl123
-rwxr-xr-xsrc/VBox/Additions/x11/Installer/x11config15suse.pl166
-rwxr-xr-xsrc/VBox/Additions/x11/Installer/x11restore.pl79
-rw-r--r--src/VBox/Additions/x11/Makefile.kmk43
-rw-r--r--src/VBox/Additions/x11/VBoxClient/Makefile.kmk236
-rw-r--r--src/VBox/Additions/x11/VBoxClient/VBoxClient.h148
-rw-r--r--src/VBox/Additions/x11/VBoxClient/chk_stubs.c69
-rw-r--r--src/VBox/Additions/x11/VBoxClient/clipboard.cpp440
-rw-r--r--src/VBox/Additions/x11/VBoxClient/clipboard.h49
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-drm.cpp1369
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp421
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp1019
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-helper.h130
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-ipc.cpp451
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-ipc.h242
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp460
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp1402
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp310
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h56
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display.cpp315
-rw-r--r--src/VBox/Additions/x11/VBoxClient/draganddrop.cpp3877
-rw-r--r--src/VBox/Additions/x11/VBoxClient/hostversion.cpp130
-rw-r--r--src/VBox/Additions/x11/VBoxClient/logging.cpp458
-rw-r--r--src/VBox/Additions/x11/VBoxClient/main.cpp885
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp568
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless-x11.h275
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless.cpp360
-rw-r--r--src/VBox/Additions/x11/VBoxClient/seamless.h109
-rw-r--r--src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup0
-rw-r--r--src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp791
-rw-r--r--src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp185
-rw-r--r--src/VBox/Additions/x11/undefined_xfree861188
-rw-r--r--src/VBox/Additions/x11/undefined_xfree86_modules16
-rw-r--r--src/VBox/Additions/x11/undefined_xorg184
-rw-r--r--src/VBox/Additions/x11/vboxmouse/Makefile.kmk296
-rw-r--r--src/VBox/Additions/x11/vboxmouse/vboxmouse.c374
-rw-r--r--src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h63
-rw-r--r--src/VBox/Additions/x11/vboxvideo/Makefile.kmk467
-rw-r--r--src/VBox/Additions/x11/vboxvideo/README.testing33
-rw-r--r--src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h243
-rw-r--r--src/VBox/Additions/x11/vboxvideo/edid.c165
-rw-r--r--src/VBox/Additions/x11/vboxvideo/getmode.c325
-rw-r--r--src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c104
-rw-r--r--src/VBox/Additions/x11/vboxvideo/pointer.c496
-rw-r--r--src/VBox/Additions/x11/vboxvideo/setmode.c129
-rw-r--r--src/VBox/Additions/x11/vboxvideo/vboxvideo.c1491
-rw-r--r--src/VBox/Additions/x11/vboxvideo/vboxvideo.h240
-rw-r--r--src/VBox/Additions/x11/vboxvideo/vbva.c254
421 files changed, 152251 insertions, 0 deletions
diff --git a/src/VBox/Additions/.scm-settings b/src/VBox/Additions/.scm-settings
new file mode 100644
index 00000000..10ae8fa8
--- /dev/null
+++ b/src/VBox/Additions/.scm-settings
@@ -0,0 +1,37 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the Additions.
+#
+
+#
+# Copyright (C) 2019-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+/*.h: --guard-relative-to-dir . --guard-prefix GA_INCLUDED_SRC_
+/3D/mesa/include/*.h: --guard-relative-to-dir 3D/mesa/include --guard-prefix GA_INCLUDED_3D_MESA_
+/3D/win/include/*.h: --guard-relative-to-dir 3D/win/include --guard-prefix GA_INCLUDED_3D_WIN_
+/WINNT/include/*.h: --guard-relative-to-dir WINNT/include --guard-prefix GA_INCLUDED_WINNT_
+/haiku/include/*.h: --guard-relative-to-dir haiku/include --guard-prefix GA_INCLUDED_HAIKU_
+
+# Some drop in header replacement weirdness:
+/x11/vboxvideo/VBoxVideoIPRT.h|/x11/vboxvideo/HGSMIMemAlloc.h: \
+ --guard-relative-to-dir x11/vboxvideo --guard-prefix VBOX_INCLUDED_Graphics_
+
diff --git a/src/VBox/Additions/3D/Config.kmk b/src/VBox/Additions/3D/Config.kmk
new file mode 100644
index 00000000..1f6fc5c3
--- /dev/null
+++ b/src/VBox/Additions/3D/Config.kmk
@@ -0,0 +1,141 @@
+# $Id: Config.kmk $
+## @file
+# kBuild Configuration file for the Mesa3D.
+#
+
+#
+# Copyright (C) 2016-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+VBOX_MESA3D_CONFIG_KMK_INCLUDED := 1
+
+# Include the top-level configure file.
+ifndef VBOX_ROOT_CONFIG_KMK_INCLUDED
+ include $(PATH_ROOT)/Config.kmk
+endif
+
+VBOX_MESA := mesa-21.3.8
+VBOX_PATH_3D := $(PATH_ROOT)/src/VBox/Additions/3D
+VBOX_PATH_MESA := $(PATH_ROOT)/src/VBox/Additions/3D/mesa/$(VBOX_MESA)
+VBOX_PATH_WDDM := $(PATH_ROOT)/src/VBox/Additions/WINNT/Graphics/Video
+VBOX_PATH_VMSVGA_INC := $(VBOX_PATH_MESA)/src/gallium/drivers/svga/include
+
+#
+# Base template for Mesa3D code and code which uses Mesa3D libraries.
+#
+TEMPLATE_VBoxMesa3DGuestR3Dll = VBox Mesa 3D Guest User Mode DLL
+TEMPLATE_VBoxMesa3DGuestR3Dll_EXTENDS = NewerVccVBoxGuestR3Dll
+TEMPLATE_VBoxMesa3DGuestR3Dll_INST = $(INST_ADDITIONS)
+TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS = $(TEMPLATE_NewerVccVBoxGuestR3Dll_DEFS) \
+ VBOX_WITH_MESA3D
+ifdef VBOX_WITH_VMSVGA
+ TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS += \
+ VBOX_WITH_VMSVGA
+ # treat as error: warning C4013: 'close' undefined; assuming extern returning int
+ TEMPLATE_VBoxMesa3DGuestR3Dll_CFLAGS = $(TEMPLATE_NewerVccVBoxGuestR3Dll_CFLAGS) -we4013
+endif
+TEMPLATE_VBoxMesa3DGuestR3Dll_SDKS.win = $(TEMPLATE_NewerVccVBoxGuestR3Dll_SDKS) \
+ $(VBOX_WINDDK_GST_W8)
+# VirtualBox specific modifications of the Mesa3D code.
+# All modified places can be found by searching for VBOX
+#
+# Each define represents a group of related modifications.
+# The purpose of the separation is to document why each modification was necessary.
+#
+# Modifications which would be nice to have in upstream Mesa code are marked with '*' here.
+#
+# VBOX Modifications in headers shared with VBox WDDM driver code (see comments).
+# VBOX_WITH_MESA3D_COMPILE Tweaks to compile Mesa as part of VBox WDDM.
+# VBOX_WITH_MESA3D_D3D_FROM_SYSTEMMEM Create D3DPOOL_SYSTEMMEM textures from provided system memory pointer.
+# VBOX_WITH_MESA3D_D3D_THREADPOOL (No) threadpool for VBox build.
+# VBOX_WITH_MESA3D_DBG Tweaks for easier debugging and better logging.
+# VBOX_WITH_MESA3D_HACKS Hacks to make it work (need a proper solutions).
+# *VBOX_WITH_MESA3D_MSC Tweaks for Microsoft VCC.
+# VBOX_WITH_MESA3D_NINE_SVGA Make the D3D state tracker to work together with VMSVGA.
+# VBOX_WITH_MESA3D_SVGA_GPU_FINISHED PIPE_QUERY_GPU_FINISHED in VMSVGA driver.
+# VBOX_WITH_MESA3D_SVGA_HALFZ D3D Z coord [0.0;1.0] in the Gallium SVGA driver (VGPU9 only).
+# VBOX_WITH_MESA3D_SVGA_INSTANCING Instancing for DrawPrimitives in the Gallium SVGA driver
+# (VGPU9 only, VGPU10 has it).
+TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS += \
+ VBOX_WITH_MESA3D_COMPILE \
+ VBOX_WITH_MESA3D_D3D_FROM_SYSTEMMEM \
+ VBOX_WITH_MESA3D_D3D_THREADPOOL \
+ VBOX_WITH_MESA3D_DBG \
+ VBOX_WITH_MESA3D_HACKS \
+ VBOX_WITH_MESA3D_MSC \
+ VBOX_WITH_MESA3D_NINE_SVGA \
+ VBOX_WITH_MESA3D_SVGA_GPU_FINISHED \
+ VBOX_WITH_MESA3D_SVGA_HALFZ \
+ VBOX_WITH_MESA3D_SVGA_INSTANCING
+ifdef VBOX_WITH_NOCRT_STATIC
+ TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS += \
+ IPRT_NO_CRT_FOR_3RD_PARTY \
+ RT_WITHOUT_NOCRT_WRAPPERS \
+ RT_WITHOUT_NOCRT_WRAPPER_ALIASES
+endif
+TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS.win = $(TEMPLATE_NewerVccVBoxGuestR3Dll_DEFS.win) \
+ _USE_MATH_DEFINES \
+ WINAPI=__stdcall \
+ _WIN32
+TEMPLATE_VBoxMesa3DGuestR3Dll_INCS = $(TEMPLATE_NewerVccVBoxGuestR3Dll_INCS) \
+ $(VBOX_PATH_MESA)/include \
+ $(VBOX_PATH_MESA)/include/c99 \
+ $(VBOX_PATH_MESA)/src \
+ $(VBOX_PATH_MESA)/src/mesa \
+ $(VBOX_PATH_MESA)/src/gallium/auxiliary \
+ $(VBOX_PATH_MESA)/src/gallium/include \
+ $(VBOX_PATH_3D)/win/VBoxWddmUmHlp
+ifdef VBOX_WITH_NOCRT_STATIC # Only the softfloat libs are added here as mesa includes a softfloat.h and we wish for no conflicts.
+ TEMPLATE_VBoxMesa3DGuestR3Dll_LIBS.x86 += $(TEMPLATE_NewerVccVBoxGuestR3Dll_LIBS.x86) \
+ $(VBOX_LIB_IPRT_GUEST_R3_SHARED_X86) \
+ $(SDK_VBoxSoftFloatGuestR3Shared_LIBS.x86)
+ TEMPLATE_VBoxMesa3DGuestR3Dll_LIBS.$(KBUILD_TARGET_ARCH) += $(TEMPLATE_NewerVccVBoxGuestR3Dll_LIBS.$(KBUILD_TARGET)) \
+ $(VBOX_LIB_IPRT_GUEST_R3_SHARED) \
+ $(SDK_VBoxSoftFloatGuestR3Shared_LIBS.$(KBUILD_TARGET_ARCH))
+ TEMPLATE_VBoxMesa3DGuestR3Dll_LDFLAGS.win += $(TEMPLATE_NewerVccVBoxGuestR3Dll_LDFLAGS.win)
+ # The -Oi- disable optimizations of math functions like sqrt(), that takes all
+ # parameters on the stack, into calls to __CIsqrt that takes parameters in FPU
+ # registers. While we could implement the __CIxxxx functions too, they would
+ # be difficult to test properly given that they're not directly callable from
+ # C. Also, there could be other aspects to these functions that we don't know
+ # about, given that they aren't documented all that well. See:
+ # https://docs.microsoft.com/en-us/cpp/preprocessor/intrinsic?view=msvc-160#intrinsic-floating-point-functions
+ # https://docs.microsoft.com/en-us/cpp/build/reference/oi-generate-intrinsic-functions?view=msvc-160
+ # Unforutnately, this does mean that we will miss out on a little bit of
+ # performance in 32-bit binaries.
+ TEMPLATE_VBoxMesa3DGuestR3Dll_CFLAGS.win.x86 += $(TEMPLATE_NewerVccVBoxGuestR3Dll_CFLAGS.win.x86) -Oi-
+ TEMPLATE_VBoxMesa3DGuestR3Dll_CXXFLAGS.win.x86 += $(TEMPLATE_NewerVccVBoxGuestR3Dll_CXXFLAGS.win.x86) -Oi-
+endif
+
+#
+# Variant of VBoxMesa3DGuestR3Dll that requires Windows Vista or later.
+#
+TEMPLATE_VBoxMesa3DGuestR3DllMinVista = VBox Mesa 3D Guest User Mode DLL (Windows Vista or newer)
+TEMPLATE_VBoxMesa3DGuestR3DllMinVista_EXTENDS = VBoxMesa3DGuestR3Dll
+TEMPLATE_VBoxMesa3DGuestR3DllMinVista_VBOX_IMPORT_CHECKER.win.x86 := vista
+TEMPLATE_VBoxMesa3DGuestR3DllMinVista_VBOX_IMPORT_CHECKER.win.amd64 := vista
+ifeq ($(KBUILD_TARGET),win)
+ TEMPLATE_VBoxMesa3DGuestR3DllMinVista_LDFLAGS.win.x86 = $(filter-out -Section:.bss$(COMMA)RW!K,$(TEMPLATE_VBoxMesa3DGuestR3Dll_LDFLAGS.win.x86))
+ TEMPLATE_VBoxMesa3DGuestR3DllMinVista_POST_CMDS.win.x86 = $(subst $(VBOX_PE_SET_VERSION), $(VBOX_PE_SET_VERSION) --vista,$(TEMPLATE_VBoxMesa3DGuestR3Dll_POST_CMDS.win.x86))
+ TEMPLATE_VBoxMesa3DGuestR3DllMinVista_POST_CMDS.win.amd64 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION) --vista $(out)$$(NLTAB)$(TEMPLATE_VBoxMesa3DGuestR3Dll_POST_CMDS.win.amd64))
+ TEMPLATE_VBoxMesa3DGuestR3DllMinVista_LNK_DEPS.win.amd64 = $(if $(eq $(tool_do),LINK_LIBRARY),,$(VBOX_PE_SET_VERSION)) $(TEMPLATE_VBoxMesa3DGuestR3Dll_LNK_DEPS.win.amd64)
+endif
+
diff --git a/src/VBox/Additions/3D/Makefile.kmk b/src/VBox/Additions/3D/Makefile.kmk
new file mode 100644
index 00000000..eca2615f
--- /dev/null
+++ b/src/VBox/Additions/3D/Makefile.kmk
@@ -0,0 +1,46 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Mesa3D code.
+#
+
+#
+# Copyright (C) 2016-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Make sure our Config.kmk is included.
+#
+ifndef VBOX_MESA3D_CONFIG_KMK_INCLUDED
+ include $(PATH_SUB_CURRENT)/Config.kmk
+endif
+
+#
+# Include sub-makefile.
+#
+include $(PATH_SUB_CURRENT)/mesa/Makefile.kmk
+if1of ($(KBUILD_TARGET), win)
+ include $(PATH_SUB_CURRENT)/win/Makefile.kmk
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/3D/mesa/.scm-settings b/src/VBox/Additions/3D/mesa/.scm-settings
new file mode 100644
index 00000000..6deb7778
--- /dev/null
+++ b/src/VBox/Additions/3D/mesa/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for 3D additions stuff.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+--filter-out-dirs "/mesa-*/."
+
diff --git a/src/VBox/Additions/3D/mesa/Makefile.kmk b/src/VBox/Additions/3D/mesa/Makefile.kmk
new file mode 100644
index 00000000..8ce09bf0
--- /dev/null
+++ b/src/VBox/Additions/3D/mesa/Makefile.kmk
@@ -0,0 +1,1516 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox Guest Mesa 3D components
+#
+
+#
+# Copyright (C) 2016-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Make sure our Config.kmk is included.
+#
+ifndef VBOX_MESA3D_CONFIG_KMK_INCLUDED
+ include $(PATH_SUB_CURRENT)/../Config.kmk
+endif
+
+PYTHON_CMD = $(VBOX_BLD_PYTHON) -B
+
+#
+# Target lists.
+#
+LIBRARIES.win += \
+ VBoxMesaUtilLib \
+ VBoxMesaLib \
+ VBoxMesaWglLib \
+ VBoxMesaGalliumAuxLib \
+ VBoxMesaNineLib \
+ VBoxMesaSVGALib \
+ VBoxMesaSVGAWinsysLib
+LIBRARIES.win.amd64 += \
+ VBoxMesaUtilLib-x86 \
+ VBoxMesaLib-x86 \
+ VBoxMesaWglLib-x86 \
+ VBoxMesaGalliumAuxLib-x86 \
+ VBoxMesaNineLib-x86 \
+ VBoxMesaSVGALib-x86 \
+ VBoxMesaSVGAWinsysLib-x86
+
+
+TEMPLATE_VBoxMesa3DGuestR3Lib = VBox Mesa 3D Guest User Mode Library
+TEMPLATE_VBoxMesa3DGuestR3Lib_EXTENDS = VBoxMesa3DGuestR3Dll
+TEMPLATE_VBoxMesa3DGuestR3Lib_INST = $(INST_ADDITIONS_LIB)
+ifeq ($(KBUILD_TARGET),win)
+ # Do not treat warnings as errors, because Mesa code produces too many warnings with MSC.
+ TEMPLATE_VBoxMesa3DGuestR3Lib_CFLAGS = $(filter-out -WX,$(TEMPLATE_VBoxMesa3DGuestR3Dll_CFLAGS))
+ TEMPLATE_VBoxMesa3DGuestR3Lib_CXXFLAGS = $(filter-out -WX,$(TEMPLATE_VBoxMesa3DGuestR3Dll_CXXFLAGS))
+ # -wd4005: vcc120: '__useHeader' : macro redefinition
+ # -wd4018: signed/unsigned mismatch
+ # -wd4054: 'type cast' : from function pointer to data pointer 'void *'
+ # -wd4057: 'function' : 'int *' differs in indirection to slightly different base types from 'uint32_t *'
+ # -wd4090: 'function' : different 'const' qualifiers
+ # -wd4098: 'void' function returning a value
+ # -wd4099: 'st_src_reg' : type name first seen using 'class' now seen using 'struct'
+ # -wd4100: unreferenced formal parameter
+ # -wd4101: unreferenced local variable
+ # -wd4130: '==' : logical operation on address of string constant
+ # -wd4132: 'color' : const object should be initialized
+ # -wd4146: unary minus operator applied to unsigned type, result still unsigned
+ # -wd4152: nonstandard extension, function/data pointer conversion in expression
+ # -wd4189: 'signo' : local variable is initialized but not referenced
+ # -wd4200: nonstandard extension used : zero-sized array in struct/union
+ # -wd4204: nonstandard extension used : non-constant aggregate initializer
+ # -wd4206: nonstandard extension used : translation unit is empty
+ # -wd4211: nonstandard extension used : redefined extern to static
+ # -wd4221: nonstandard extension used : 'tokens' : cannot be initialized using address of automatic variable 'tokens'
+ # -wd4245: '=' : conversion from 'int' to 'unsigned int', signed/unsigned mismatch
+ # -wd4255: no function prototype given
+ # -wd4258: 'i' : definition from the for loop is ignored; the definition from the enclosing scope is used
+ # -wd4265: 'ir_variable_refcount_visitor' : class has virtual functions, but destructor is not virtual
+ # -wd4267: '=' : conversion from 'size_t' to 'unsigned int', possible loss of data
+ # -wd4266: 'void ir_visitor::visit(ir_rvalue *)' : no override available for virtual member function from base 'ir_visitor'; function is hidden
+ # -wd4287: unsigned/negative constant mismatch
+ # -wd4291: 'void *exec_node::operator new(size_t,void *)' : no matching operator delete found; memory will not be freed if initialization throws an exception
+ # -wd4305: truncation from 'double' to 'const float'
+ # -wd4306: 'type cast' : conversion from 'int' to 'void *' of greater size
+ # -wd4310: cast truncates constant value
+ # -wd4311: 'type cast' : pointer truncation from 'void *' to 'unsigned long'
+ # -wd4351: new behavior: elements of array '_mesa_glsl_parse_state::cs_input_local_size' will be default initialized
+ # -wd4355: 'this' : used in base member initializer list
+ # -wd4388: '==' : signed/unsigned mismatch
+ # -wd4389: '==' : signed/unsigned mismatch
+ # -wd4640: 'ts' : construction of local static object is not thread-safe
+ # -wd4668: '__STDC_VERSION__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
+ # -wd4700: uninitialized local variable 'tmp' used
+ # -wd4701: potentially uninitialized local variable 'query' used
+ # -wd4702: unreachable code
+ # -wd4703: vcc120: potentially uninitialized local pointer variable 'gen_func' used
+ # -wd4756: overflow in constant arithmetic
+ # -wd4800: 'int' : forcing value to bool 'true' or 'false' (performance warning)
+ # -wd4805: '|=' : unsafe mix of type 'GLboolean' and type 'bool' in operation
+ # -wd4918: 'y' : invalid character in pragma optimization list
+ VBOX_MESA3D_VCC_DISABLED_WARNINGS := \
+ -wd4005 -wd4018 -wd4054 -wd4057 -wd4090 -wd4098 -wd4099 -wd4100 -wd4101 -wd4130 -wd4132 -wd4146 \
+ -wd4152 -wd4189 -wd4200 -wd4204 -wd4206 -wd4211 -wd4221 -wd4245 -wd4255 -wd4258 -wd4265 -wd4267 -wd4266 \
+ -wd4287 -wd4291 -wd4305 -wd4306 -wd4310 -wd4311 -wd4351 -wd4355 -wd4388 -wd4389 -wd4640 -wd4668 -wd4700 \
+ -wd4701 -wd4702 -wd4703 -wd4756 -wd4800 -wd4805 -wd4918
+ # -wd4458: declaration of 'array' hides class member
+ # -wd4477: 'fprintf' : format string '%u' requires an argument of type 'unsigned int', but variadic argument 1 has type 'LONGLONG'
+ # -wd4774: 'printf' : format string expected in argument 1 is not a string literal
+ # -wd4456: declaration of 'pos_dst' hides previous local declaration
+ # -wd4777: '_snprintf' : format string '%u' requires an argument of type 'unsigned int', but variadic argument 1 has type 'const DWORD'
+ # -wd4459: declaration of 'stw_dev' hides global declaration
+ # -wd4457: declaration of 'usage' hides function parameter
+ VBOX_MESA3D_VCC_DISABLED_WARNINGS += \
+ -wd4458 -wd4477 -wd4774 -wd4456 -wd4777 -wd4459 -wd4457
+ # -wd4254: '=': conversion from 'unsigned int':'3' to 'unsigned int':'2', possible loss of data
+ # -wd5039: pointer or reference to potentially throwing function passed to 'extern "C"' function under -EHc. Undefined behavior may occur if this function throws an exception
+ # -wd5204: class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed correctly
+ # -wd5219: implicit conversion from 'int' to 'float', possible loss of data
+ VBOX_MESA3D_VCC_DISABLED_WARNINGS += \
+ -wd4254 -wd5039 -wd5204 -wd5219
+
+ TEMPLATE_VBoxMesa3DGuestR3Lib_CFLAGS.win += $(VBOX_MESA3D_VCC_DISABLED_WARNINGS)
+ TEMPLATE_VBoxMesa3DGuestR3Lib_CXXFLAGS.win += $(VBOX_MESA3D_VCC_DISABLED_WARNINGS)
+endif
+TEMPLATE_VBoxMesa3DGuestR3Lib_INCS = \
+ include \
+ $(VBOX_MESA)/include \
+ $(VBOX_MESA)/include/GL \
+ $(VBOX_MESA)/src \
+ $(VBOX_MESA)/src/mapi \
+ $(VBOX_MESA)/src/util \
+ $(VBOX_MESA)/src/mesa \
+ $(VBOX_MESA)/src/mesa/main \
+ $(VBOX_MESA)/src/compiler \
+ $(VBOX_MESA)/src/compiler/nir \
+ $(VBOX_MESA)/src/gallium/include \
+ $(VBOX_MESA)/src/gallium/auxiliary \
+ $(VBOX_MESA)/src/gallium/state_trackers/wgl
+ifdef VBOX_WITH_NOCRT_STATIC
+ TEMPLATE_VBoxMesa3DGuestR3Lib_INCS += \
+ $(TEMPLATE_VBoxMesa3DGuestR3Dll_INCS)
+endif
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ TEMPLATE_VBoxMesa3DGuestR3Lib_DEFS = \
+ $(TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS) \
+ PACKAGE_VERSION="$(VBOX_MESA)" \
+ PACKAGE_BUGREPORT="$(VBOX_MESA)"
+else
+ TEMPLATE_VBoxMesa3DGuestR3Lib_DEFS = \
+ $(TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS) \
+ PACKAGE_VERSION=\"$(VBOX_MESA)\" \
+ PACKAGE_BUGREPORT=\"$(VBOX_MESA)\"
+endif
+# For wgl, glapi and mesa
+TEMPLATE_VBoxMesa3DGuestR3Lib_DEFS += \
+ MAPI_MODE_UTIL _GDI32_ BUILD_GL32 KHRONOS_DLL_EXPORTS GL_API=GLAPI GL_APIENTRY=GLAPIENTRY _GLAPI_NO_EXPORTS
+TEMPLATE_VBoxMesa3DGuestR3Lib_DEFS.release = \
+ $(TEMPLATE_VBoxMesa3DGuestR3Dll_DEFS.release) \
+ NDEBUG
+
+
+#
+# VBoxMesaUtilLib
+#
+VBoxMesaUtilLib_TEMPLATE = VBoxMesa3DGuestR3Lib
+VBoxMesaUtilLib_INCS = \
+ $(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src \
+ $(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src/util/format \
+ $(VBOX_MESA)/src/util/format
+
+VBoxMesaUtilLib_DEPS = \
+ $(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src/util/format/u_format_pack.h
+
+VBoxMesaUtilLib_SOURCES = \
+ $(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src/util/format/u_format_pack.c \
+ $(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src/util/format_srgb.c
+
+$$(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src/util/format/u_format_pack.c: \
+ $(VBOX_PATH_MESA)/src/util/format/u_format_table.py \
+ $(VBOX_PATH_MESA)/src/util/format/u_format.csv | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< $(VBOX_PATH_MESA)/src/util/format/u_format.csv >$@
+
+$$(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src/util/format/u_format_pack.h: \
+ $(VBOX_PATH_MESA)/src/util/format/u_format_table.py \
+ $(VBOX_PATH_MESA)/src/util/format/u_format.csv | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< --header $(VBOX_PATH_MESA)/src/util/format/u_format.csv >$@
+
+$$(VBoxMesaUtilLib_0_OUTDIR)/$(VBOX_MESA)/src/util/format_srgb.c: \
+ $(VBOX_PATH_MESA)/src/util/format_srgb.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+VBoxMesaUtilLib_SOURCES += \
+ $(VBOX_MESA)/src/util/anon_file.c \
+ $(VBOX_MESA)/src/util/bitscan.c \
+ $(VBOX_MESA)/src/util/blob.c \
+ $(VBOX_MESA)/src/util/build_id.c \
+ $(VBOX_MESA)/src/util/compress.c \
+ $(VBOX_MESA)/src/util/crc32.c \
+ $(VBOX_MESA)/src/util/dag.c \
+ $(VBOX_MESA)/src/util/debug.c \
+ $(VBOX_MESA)/src/util/disk_cache.c \
+ $(VBOX_MESA)/src/util/disk_cache_os.c \
+ $(VBOX_MESA)/src/util/double.c \
+ $(VBOX_MESA)/src/util/fast_idiv_by_const.c \
+ $(VBOX_MESA)/src/util/fossilize_db.c \
+ $(VBOX_MESA)/src/util/half_float.c \
+ $(VBOX_MESA)/src/util/hash_table.c \
+ $(VBOX_MESA)/src/util/log.c \
+ $(VBOX_MESA)/src/util/memstream.c \
+ $(VBOX_MESA)/src/util/mesa-sha1.c \
+ $(VBOX_MESA)/src/util/os_file.c \
+ $(VBOX_MESA)/src/util/os_memory_fd.c \
+ $(VBOX_MESA)/src/util/os_misc.c \
+ $(VBOX_MESA)/src/util/os_socket.c \
+ $(VBOX_MESA)/src/util/os_time.c \
+ $(VBOX_MESA)/src/util/ralloc.c \
+ $(VBOX_MESA)/src/util/rand_xor.c \
+ $(VBOX_MESA)/src/util/rb_tree.c \
+ $(VBOX_MESA)/src/util/register_allocate.c \
+ $(VBOX_MESA)/src/util/rgtc.c \
+ $(VBOX_MESA)/src/util/set.c \
+ $(VBOX_MESA)/src/util/slab.c \
+ $(VBOX_MESA)/src/util/softfloat.c \
+ $(VBOX_MESA)/src/util/sparse_array.c \
+ $(VBOX_MESA)/src/util/string_buffer.c \
+ $(VBOX_MESA)/src/util/strtod.c \
+ $(VBOX_MESA)/src/util/u_atomic.c \
+ $(VBOX_MESA)/src/util/u_cpu_detect.c \
+ $(VBOX_MESA)/src/util/u_debug.c \
+ $(VBOX_MESA)/src/util/u_debug_describe.c \
+ $(VBOX_MESA)/src/util/u_debug_memory.c \
+ $(VBOX_MESA)/src/util/u_debug_refcnt.c \
+ $(VBOX_MESA)/src/util/u_debug_stack.c \
+ $(VBOX_MESA)/src/util/u_debug_symbol.c \
+ $(VBOX_MESA)/src/util/u_hash_table.c \
+ $(VBOX_MESA)/src/util/u_idalloc.c \
+ $(VBOX_MESA)/src/util/u_math.c \
+ $(VBOX_MESA)/src/util/u_mm.c \
+ $(VBOX_MESA)/src/util/u_process.c \
+ $(VBOX_MESA)/src/util/u_queue.c \
+ $(VBOX_MESA)/src/util/u_vector.c \
+ $(VBOX_MESA)/src/util/vma.c \
+ $(VBOX_MESA)/src/util/format/u_format.c \
+ $(VBOX_MESA)/src/util/format/u_format_bptc.c \
+ $(VBOX_MESA)/src/util/format/u_format_etc.c \
+ $(VBOX_MESA)/src/util/format/u_format_fxt1.c \
+ $(VBOX_MESA)/src/util/format/u_format_latc.c \
+ $(VBOX_MESA)/src/util/format/u_format_other.c \
+ $(VBOX_MESA)/src/util/format/u_format_rgtc.c \
+ $(VBOX_MESA)/src/util/format/u_format_s3tc.c \
+ $(VBOX_MESA)/src/util/format/u_format_tests.c \
+ $(VBOX_MESA)/src/util/format/u_format_unpack_neon.c \
+ $(VBOX_MESA)/src/util/format/u_format_yuv.c \
+ $(VBOX_MESA)/src/util/format/u_format_zs.c \
+ $(VBOX_MESA)/src/util/u_printf.cpp \
+ $(VBOX_MESA)/src/util/u_qsort.cpp \
+ $(VBOX_MESA)/src/util/sha1/sha1.c
+
+# Unused
+# $(VBOX_MESA)/src/util/xmlconfig.c
+
+# 32 bit lib for 64 bit build
+VBoxMesaUtilLib-x86_EXTENDS = VBoxMesaUtilLib
+VBoxMesaUtilLib-x86_BLD_TRG_ARCH = x86
+
+
+#
+# VBoxMesaLib
+#
+VBoxMesaLib_TEMPLATE = VBoxMesa3DGuestR3Lib
+VBoxMesaLib_INCS = \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/spirv \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program \
+ $(VBOX_MESA)/src/compiler/glsl \
+ $(VBOX_MESA)/src/compiler/glsl/glcpp \
+ $(VBOX_MESA)/src/compiler/spirv
+
+VBoxMesaLib_DEPS = \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/ir_expression_operation.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/ir_expression_operation_strings.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/ir_expression_operation_constant.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/float64_glsl.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glsl_parser.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glcpp/glcpp-parse.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_builder_opcodes.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics_indices.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opcodes.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/spirv/vtn_generator_ids.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/glapitable.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/glapitemp.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/glprocs.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/dispatch.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/format_info.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/get_hash.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/remap_helper.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program/program_parse.tab.h
+
+VBoxMesaLib_SOURCES = \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glsl_lexer.cpp \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glsl_parser.cpp \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glcpp/glcpp-lex.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glcpp/glcpp-parse.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_constant_expressions.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opcodes.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opt_algebraic.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/spirv/spirv_info.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/spirv/vtn_gather_types.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/enums.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/api_exec.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/format_fallback.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated0.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated1.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated2.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated3.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated4.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated5.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated6.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated7.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program/program_parse.tab.c \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program/lex.yy.c
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_builder_opcodes.h: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_builder_opcodes_h.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_constant_expressions.c: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_constant_expressions.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opcodes.h: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_opcodes_h.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opcodes.c: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_opcodes_c.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opt_algebraic.c: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_opt_algebraic.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics.h: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_intrinsics_h.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< --outdir $(dir $@)
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics_indices.h: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_intrinsics_indices_h.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< --outdir $(dir $@)
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics.c: \
+ $(VBOX_PATH_MESA)/src/compiler/nir/nir_intrinsics_c.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< --outdir $(dir $@)
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/ir_expression_operation.h: \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/ir_expression_operation.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< enum >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/ir_expression_operation_strings.h: \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/ir_expression_operation.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< strings >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/ir_expression_operation_constant.h: \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/ir_expression_operation.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< constant >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/dispatch.h: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_table.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -m remap_table >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated.h: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal_h.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated0.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 0 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated1.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 1 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated2.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 2 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated3.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 3 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated4.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 4 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated5.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 5 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated6.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 6 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/marshal_generated7.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_marshal.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml -i 7 -n 8 >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/api_exec.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_genexec.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/format_info.h: \
+ $(VBOX_PATH_MESA)/src/mesa/main/format_info.py \
+ $(VBOX_PATH_MESA)/src/mesa/main/formats.csv | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< $(VBOX_PATH_MESA)/src/mesa/main/formats.csv >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/format_fallback.c: \
+ $(VBOX_PATH_MESA)/src/mesa/main/format_fallback.py \
+ $(VBOX_PATH_MESA)/src/mesa/main/formats.csv | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< $(VBOX_PATH_MESA)/src/mesa/main/formats.csv $@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/get_hash.h: \
+ $(VBOX_PATH_MESA)/src/mesa/main/get_hash_generator.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/main/remap_helper.h: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/remap_helper.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glsl_parser.cpp \
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glsl_parser.h: \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/glsl_parser.yy | $$(dir $$@)
+ $(call MSG_GENERATE,bison,$@,$$@)
+ $(QUIET)$(TOOL_BISON_YACC) -o $@ -p _mesa_glsl_ --defines=$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glsl_parser.h $<
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glsl_lexer.cpp: \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/glsl_lexer.ll | $$(dir $$@)
+ $(call MSG_GENERATE,flex,$@,$$@)
+ $(QUIET)$(TOOL_FLEX_LEX) -o $@ $<
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glcpp/glcpp-parse.c \
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glcpp/glcpp-parse.h: \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/glcpp/glcpp-parse.y | $$(dir $$@)
+ $(call MSG_GENERATE,bison,$@,$$@)
+ $(QUIET)$(TOOL_BISON_YACC) -o $@ -p glcpp_parser_ --defines=$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glcpp/glcpp-parse.h $<
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/glcpp/glcpp-lex.c: \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/glcpp/glcpp-lex.l | $$(dir $$@)
+ $(call MSG_GENERATE,flex,$@,$$@)
+ $(QUIET)$(TOOL_FLEX_LEX) -o $@ $<
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/glsl/float64_glsl.h: \
+ $(VBOX_PATH_MESA)/src/util/xxd.py \
+ $(VBOX_PATH_MESA)/src/compiler/glsl/float64.glsl | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< $(VBOX_PATH_MESA)/src/compiler/glsl/float64.glsl $@ -n float64_source
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/glapitemp.h: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_apitemp.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/glapitable.h: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_table.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/glprocs.h: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_procs.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -c -f $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_and_es_API.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mapi/glapi/enums.c: \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/gen/gl_enums.py \
+ $(VBOX_PATH_MESA)/src/mapi/glapi/registry/gl.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< -f $(VBOX_PATH_MESA)/src/mapi/glapi/registry/gl.xml >$@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/spirv/spirv_info.c: \
+ $(VBOX_PATH_MESA)/src/compiler/spirv/spirv_info_c.py \
+ $(VBOX_PATH_MESA)/src/compiler/spirv/spirv.core.grammar.json | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< $(VBOX_PATH_MESA)/src/compiler/spirv/spirv.core.grammar.json $@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/spirv/vtn_gather_types.c: \
+ $(VBOX_PATH_MESA)/src/compiler/spirv/vtn_gather_types_c.py \
+ $(VBOX_PATH_MESA)/src/compiler/spirv/spirv.core.grammar.json | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< $(VBOX_PATH_MESA)/src/compiler/spirv/spirv.core.grammar.json $@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/spirv/vtn_generator_ids.h: \
+ $(VBOX_PATH_MESA)/src/compiler/spirv/vtn_generator_ids_h.py \
+ $(VBOX_PATH_MESA)/src/compiler/spirv/spir-v.xml | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< $(VBOX_PATH_MESA)/src/compiler/spirv/spir-v.xml $@
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program/program_parse.tab.c \
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program/program_parse.tab.h: \
+ $(VBOX_PATH_MESA)/src/mesa/program/program_parse.y | $$(dir $$@)
+ $(call MSG_GENERATE,bison,$@,$$@)
+ $(QUIET)$(TOOL_BISON_YACC) -o $@ --defines=$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program/program_parse.tab.h $<
+
+$$(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/mesa/program/lex.yy.c: \
+ $(VBOX_PATH_MESA)/src/mesa/program/program_lexer.l | $$(dir $$@)
+ $(call MSG_GENERATE,flex,$@,$$@)
+ $(QUIET)$(TOOL_FLEX_LEX) -o $@ $<
+
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atifs_to_nir.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_array.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_atomicbuf.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_blend.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_clip.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_constbuf.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_depth.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_framebuffer.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_image.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_msaa.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_pixeltransfer.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_rasterizer.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_sampler.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_scissor.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_shader.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_stipple.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_storagebuf.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_tess.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_texture.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_atom_viewport.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_bitmap.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_bitmap_shader.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_blit.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_bufferobjects.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_clear.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_compute.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_condrender.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_copyimage.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_drawpixels.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_drawpixels_shader.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_drawtex.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_eglimage.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_fbo.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_feedback.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_flush.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_memoryobjects.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_msaa.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_perfmon.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_perfquery.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_program.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_queryobj.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_rasterpos.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_readpixels.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_semaphoreobjects.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_strings.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_syncobj.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_texture.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_texturebarrier.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_viewport.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_cb_xformfb.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_context.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_copytex.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_debug.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_draw.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_draw_feedback.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_extensions.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_format.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_gen_mipmap.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_glsl_to_ir.cpp \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_glsl_to_nir.cpp \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_glsl_to_tgsi.cpp \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_glsl_to_tgsi_array_merge.cpp \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_glsl_to_tgsi_private.cpp \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_glsl_to_tgsi_temprename.cpp \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_manager.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_nir_builtins.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_nir_lower_builtin.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_nir_lower_tex_src_plane.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_pbo.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_program.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_sampler_view.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_scissor.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_shader_cache.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_texture.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_tgsi_lower_depth_clamp.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_tgsi_lower_yuv.c \
+ $(VBOX_MESA)/src/mesa/state_tracker/st_vdpau.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/mesa/program/arbprogparse.c \
+ $(VBOX_MESA)/src/mesa/program/ir_to_mesa.cpp \
+ $(VBOX_MESA)/src/mesa/program/prog_cache.c \
+ $(VBOX_MESA)/src/mesa/program/prog_execute.c \
+ $(VBOX_MESA)/src/mesa/program/prog_instruction.c \
+ $(VBOX_MESA)/src/mesa/program/prog_noise.c \
+ $(VBOX_MESA)/src/mesa/program/prog_opt_constant_fold.c \
+ $(VBOX_MESA)/src/mesa/program/prog_optimize.c \
+ $(VBOX_MESA)/src/mesa/program/prog_parameter.c \
+ $(VBOX_MESA)/src/mesa/program/prog_parameter_layout.c \
+ $(VBOX_MESA)/src/mesa/program/prog_print.c \
+ $(VBOX_MESA)/src/mesa/program/prog_statevars.c \
+ $(VBOX_MESA)/src/mesa/program/prog_to_nir.c \
+ $(VBOX_MESA)/src/mesa/program/program.c \
+ $(VBOX_MESA)/src/mesa/program/program_parse_extra.c \
+ $(VBOX_MESA)/src/mesa/program/programopt.c \
+ $(VBOX_MESA)/src/mesa/program/symbol_table.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/mesa/main/accum.c \
+ $(VBOX_MESA)/src/mesa/main/api_arrayelt.c \
+ $(VBOX_MESA)/src/mesa/main/arbprogram.c \
+ $(VBOX_MESA)/src/mesa/main/arrayobj.c \
+ $(VBOX_MESA)/src/mesa/main/atifragshader.c \
+ $(VBOX_MESA)/src/mesa/main/attrib.c \
+ $(VBOX_MESA)/src/mesa/main/barrier.c \
+ $(VBOX_MESA)/src/mesa/main/bbox.c \
+ $(VBOX_MESA)/src/mesa/main/blend.c \
+ $(VBOX_MESA)/src/mesa/main/blit.c \
+ $(VBOX_MESA)/src/mesa/main/bufferobj.c \
+ $(VBOX_MESA)/src/mesa/main/buffers.c \
+ $(VBOX_MESA)/src/mesa/main/clear.c \
+ $(VBOX_MESA)/src/mesa/main/clip.c \
+ $(VBOX_MESA)/src/mesa/main/colortab.c \
+ $(VBOX_MESA)/src/mesa/main/compute.c \
+ $(VBOX_MESA)/src/mesa/main/condrender.c \
+ $(VBOX_MESA)/src/mesa/main/conservativeraster.c \
+ $(VBOX_MESA)/src/mesa/main/context.c \
+ $(VBOX_MESA)/src/mesa/main/convolve.c \
+ $(VBOX_MESA)/src/mesa/main/copyimage.c \
+ $(VBOX_MESA)/src/mesa/main/cpuinfo.c \
+ $(VBOX_MESA)/src/mesa/main/debug.c \
+ $(VBOX_MESA)/src/mesa/main/debug_output.c \
+ $(VBOX_MESA)/src/mesa/main/depth.c \
+ $(VBOX_MESA)/src/mesa/main/dlist.c \
+ $(VBOX_MESA)/src/mesa/main/draw.c \
+ $(VBOX_MESA)/src/mesa/main/draw_validate.c \
+ $(VBOX_MESA)/src/mesa/main/drawpix.c \
+ $(VBOX_MESA)/src/mesa/main/drawtex.c \
+ $(VBOX_MESA)/src/mesa/main/enable.c \
+ $(VBOX_MESA)/src/mesa/main/errors.c \
+ $(VBOX_MESA)/src/mesa/main/es1_conversion.c \
+ $(VBOX_MESA)/src/mesa/main/eval.c \
+ $(VBOX_MESA)/src/mesa/main/execmem.c \
+ $(VBOX_MESA)/src/mesa/main/extensions.c \
+ $(VBOX_MESA)/src/mesa/main/extensions_table.c \
+ $(VBOX_MESA)/src/mesa/main/externalobjects.c \
+ $(VBOX_MESA)/src/mesa/main/fbobject.c \
+ $(VBOX_MESA)/src/mesa/main/feedback.c \
+ $(VBOX_MESA)/src/mesa/main/ff_fragment_shader.cpp \
+ $(VBOX_MESA)/src/mesa/main/ffvertex_prog.c \
+ $(VBOX_MESA)/src/mesa/main/fog.c \
+ $(VBOX_MESA)/src/mesa/main/format_utils.c \
+ $(VBOX_MESA)/src/mesa/main/formatquery.c \
+ $(VBOX_MESA)/src/mesa/main/formats.c \
+ $(VBOX_MESA)/src/mesa/main/framebuffer.c \
+ $(VBOX_MESA)/src/mesa/main/genmipmap.c \
+ $(VBOX_MESA)/src/mesa/main/get.c \
+ $(VBOX_MESA)/src/mesa/main/getstring.c \
+ $(VBOX_MESA)/src/mesa/main/glformats.c \
+ $(VBOX_MESA)/src/mesa/main/glspirv.c \
+ $(VBOX_MESA)/src/mesa/main/glthread.c \
+ $(VBOX_MESA)/src/mesa/main/glthread_bufferobj.c \
+ $(VBOX_MESA)/src/mesa/main/glthread_draw.c \
+ $(VBOX_MESA)/src/mesa/main/glthread_get.c \
+ $(VBOX_MESA)/src/mesa/main/glthread_list.c \
+ $(VBOX_MESA)/src/mesa/main/glthread_shaderobj.c \
+ $(VBOX_MESA)/src/mesa/main/glthread_varray.c \
+ $(VBOX_MESA)/src/mesa/main/hash.c \
+ $(VBOX_MESA)/src/mesa/main/hint.c \
+ $(VBOX_MESA)/src/mesa/main/histogram.c \
+ $(VBOX_MESA)/src/mesa/main/image.c \
+ $(VBOX_MESA)/src/mesa/main/light.c \
+ $(VBOX_MESA)/src/mesa/main/lines.c \
+ $(VBOX_MESA)/src/mesa/main/matrix.c \
+ $(VBOX_MESA)/src/mesa/main/mipmap.c \
+ $(VBOX_MESA)/src/mesa/main/multisample.c \
+ $(VBOX_MESA)/src/mesa/main/objectlabel.c \
+ $(VBOX_MESA)/src/mesa/main/objectpurge.c \
+ $(VBOX_MESA)/src/mesa/main/pack.c \
+ $(VBOX_MESA)/src/mesa/main/pbo.c \
+ $(VBOX_MESA)/src/mesa/main/performance_monitor.c \
+ $(VBOX_MESA)/src/mesa/main/performance_query.c \
+ $(VBOX_MESA)/src/mesa/main/pipelineobj.c \
+ $(VBOX_MESA)/src/mesa/main/pixel.c \
+ $(VBOX_MESA)/src/mesa/main/pixelstore.c \
+ $(VBOX_MESA)/src/mesa/main/pixeltransfer.c \
+ $(VBOX_MESA)/src/mesa/main/points.c \
+ $(VBOX_MESA)/src/mesa/main/polygon.c \
+ $(VBOX_MESA)/src/mesa/main/program_binary.c \
+ $(VBOX_MESA)/src/mesa/main/program_resource.c \
+ $(VBOX_MESA)/src/mesa/main/querymatrix.c \
+ $(VBOX_MESA)/src/mesa/main/queryobj.c \
+ $(VBOX_MESA)/src/mesa/main/rastpos.c \
+ $(VBOX_MESA)/src/mesa/main/readpix.c \
+ $(VBOX_MESA)/src/mesa/main/remap.c \
+ $(VBOX_MESA)/src/mesa/main/renderbuffer.c \
+ $(VBOX_MESA)/src/mesa/main/robustness.c \
+ $(VBOX_MESA)/src/mesa/main/samplerobj.c \
+ $(VBOX_MESA)/src/mesa/main/scissor.c \
+ $(VBOX_MESA)/src/mesa/main/shader_query.cpp \
+ $(VBOX_MESA)/src/mesa/main/shaderapi.c \
+ $(VBOX_MESA)/src/mesa/main/shaderimage.c \
+ $(VBOX_MESA)/src/mesa/main/shaderobj.c \
+ $(VBOX_MESA)/src/mesa/main/shared.c \
+ $(VBOX_MESA)/src/mesa/main/spirv_extensions.c \
+ $(VBOX_MESA)/src/mesa/main/state.c \
+ $(VBOX_MESA)/src/mesa/main/stencil.c \
+ $(VBOX_MESA)/src/mesa/main/streaming-load-memcpy.c \
+ $(VBOX_MESA)/src/mesa/main/syncobj.c \
+ $(VBOX_MESA)/src/mesa/main/texcompress.c \
+ $(VBOX_MESA)/src/mesa/main/texcompress_astc.cpp \
+ $(VBOX_MESA)/src/mesa/main/texcompress_bptc.c \
+ $(VBOX_MESA)/src/mesa/main/texcompress_cpal.c \
+ $(VBOX_MESA)/src/mesa/main/texcompress_etc.c \
+ $(VBOX_MESA)/src/mesa/main/texcompress_fxt1.c \
+ $(VBOX_MESA)/src/mesa/main/texcompress_rgtc.c \
+ $(VBOX_MESA)/src/mesa/main/texcompress_s3tc.c \
+ $(VBOX_MESA)/src/mesa/main/texenv.c \
+ $(VBOX_MESA)/src/mesa/main/texformat.c \
+ $(VBOX_MESA)/src/mesa/main/texgen.c \
+ $(VBOX_MESA)/src/mesa/main/texgetimage.c \
+ $(VBOX_MESA)/src/mesa/main/teximage.c \
+ $(VBOX_MESA)/src/mesa/main/texobj.c \
+ $(VBOX_MESA)/src/mesa/main/texparam.c \
+ $(VBOX_MESA)/src/mesa/main/texstate.c \
+ $(VBOX_MESA)/src/mesa/main/texstorage.c \
+ $(VBOX_MESA)/src/mesa/main/texstore.c \
+ $(VBOX_MESA)/src/mesa/main/texturebindless.c \
+ $(VBOX_MESA)/src/mesa/main/textureview.c \
+ $(VBOX_MESA)/src/mesa/main/transformfeedback.c \
+ $(VBOX_MESA)/src/mesa/main/uniform_query.cpp \
+ $(VBOX_MESA)/src/mesa/main/uniforms.c \
+ $(VBOX_MESA)/src/mesa/main/varray.c \
+ $(VBOX_MESA)/src/mesa/main/vdpau.c \
+ $(VBOX_MESA)/src/mesa/main/version.c \
+ $(VBOX_MESA)/src/mesa/main/viewport.c \
+ $(VBOX_MESA)/src/mesa/main/vtxfmt.c
+#VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated0.c \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated1.c \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated2.c \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated3.c \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated4.c \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated5.c \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated6.c \
+ $(VBOX_MESA)/src/mesa/main/marshal_generated7.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/mesa/math/m_debug_clip.c \
+ $(VBOX_MESA)/src/mesa/math/m_debug_norm.c \
+ $(VBOX_MESA)/src/mesa/math/m_debug_xform.c \
+ $(VBOX_MESA)/src/mesa/math/m_eval.c \
+ $(VBOX_MESA)/src/mesa/math/m_matrix.c \
+ $(VBOX_MESA)/src/mesa/math/m_translate.c \
+ $(VBOX_MESA)/src/mesa/math/m_vector.c \
+ $(VBOX_MESA)/src/mesa/math/m_xform.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_context.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_exec.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_exec_api.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_exec_draw.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_exec_eval.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_minmax_index.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_noop.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_save.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_save_api.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_save_draw.c \
+ $(VBOX_MESA)/src/mesa/vbo/vbo_save_loopback.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/compiler/glsl_types.cpp \
+ $(VBOX_MESA)/src/compiler/nir_types.cpp \
+ $(VBOX_MESA)/src/compiler/shader_enums.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/compiler/glsl/ast_array_index.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ast_expr.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ast_function.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ast_to_hir.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ast_type.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/builtin_functions.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/builtin_types.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/builtin_variables.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/generate_ir.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_link_atomics.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_link_uniform_blocks.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_link_uniform_initializers.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_link_uniforms.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_link_xfb.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_linker.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_lower_atomics.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_lower_buffers.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_lower_images.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_lower_samplers.c \
+ $(VBOX_MESA)/src/compiler/glsl/gl_nir_lower_samplers_as_deref.c \
+ $(VBOX_MESA)/src/compiler/glsl/glsl_parser_extras.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/glsl_symbol_table.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/glsl_to_nir.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/hir_field_selection.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_array_refcount.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_basic_block.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_builder.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_builder_print_visitor.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_clone.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_constant_expression.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_equals.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_expression_flattening.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_function.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_function_can_inline.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_function_detect_recursion.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_hierarchical_visitor.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_hv_accept.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_print_visitor.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_reader.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_rvalue_visitor.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_set_program_inouts.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_validate.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/ir_variable_refcount.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_atomics.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_functions.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_interface_blocks.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_uniform_block_active_visitor.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_uniform_blocks.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_uniform_initializers.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_uniforms.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/link_varyings.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/linker.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/linker_util.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/loop_analysis.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/loop_unroll.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_blend_equation_advanced.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_buffer_access.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_builtins.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_const_arrays_to_uniforms.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_cs_derived.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_discard.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_discard_flow.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_distance.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_if_to_cond_assign.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_instructions.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_int64.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_jumps.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_mat_op_to_vec.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_named_interface_blocks.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_offset_array.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_output_reads.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_packed_varyings.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_packing_builtins.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_precision.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_shared_reference.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_subroutine.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_tess_level.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_ubo_reference.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_variable_index_to_cond_assign.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_vec_index_to_cond_assign.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_vec_index_to_swizzle.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_vector.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_vector_derefs.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_vector_insert.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_vertex_id.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/lower_xfb_varying.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_algebraic.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_array_splitting.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_conditional_discard.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_constant_folding.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_constant_propagation.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_constant_variable.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_copy_propagation_elements.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_dead_builtin_variables.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_dead_builtin_varyings.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_dead_code.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_dead_code_local.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_dead_functions.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_flatten_nested_if_blocks.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_flip_matrices.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_function_inlining.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_if_simplification.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_minmax.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_rebalance_tree.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_redundant_jumps.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_structure_splitting.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_swizzle.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_tree_grafting.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/opt_vectorize.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/propagate_invariance.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/s_expression.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/serialize.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/shader_cache.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/string_to_uint_map.cpp \
+ $(VBOX_MESA)/src/compiler/glsl/glcpp/pp.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/compiler/nir/nir.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_builtin_builder.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_clone.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_control_flow.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_convert_ycbcr.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_deref.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_divergence_analysis.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_dominance.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_from_ssa.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_gather_info.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_gather_ssa_types.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_gather_xfb_info.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_gs_count_vertices.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_inline_functions.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_inline_uniforms.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_instr_set.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_linking_helpers.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_liveness.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_loop_analyze.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_alpha_test.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_alu.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_alu_to_scalar.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_amul.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_array_deref_of_vec.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_atomics_to_ssbo.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_bit_size.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_bitmap.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_blend.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_bool_to_bitsize.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_bool_to_float.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_bool_to_int32.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_clamp_color_outputs.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_clip.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_clip_cull_distance_arrays.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_clip_disable.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_clip_halfz.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_convert_alu_types.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_discard_or_demote.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_double_ops.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_drawpixels.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_fb_read.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_flatshade.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_flrp.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_fp16_conv.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_fragcolor.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_fragcoord_wtrans.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_frexp.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_global_vars_to_local.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_goto_ifs.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_gs_intrinsics.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_idiv.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_image.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_indirect_derefs.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_input_attachments.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_int_to_float.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_int64.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_interpolation.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_io.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_io_arrays_to_elements.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_io_to_scalar.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_io_to_temporaries.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_io_to_vector.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_is_helper_invocation.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_load_const_to_scalar.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_locals_to_regs.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_mediump.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_memcpy.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_memory_model.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_multiview.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_non_uniform_access.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_packing.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_passthrough_edgeflags.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_patch_vertices.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_phis_to_scalar.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_pntc_ytransform.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_point_size.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_point_size_mov.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_printf.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_readonly_images_to_tex.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_regs_to_ssa.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_returns.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_samplers.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_scratch.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_shader_calls.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_ssbo.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_subgroups.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_system_values.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_sysvals_to_varyings.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_tex.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_texcoord_replace.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_to_source_mods.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_two_sided_color.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_ubo_vec4.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_undef_to_zero.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_uniforms_to_ubo.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_var_copies.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_variable_initializers.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_vars_to_ssa.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_vec_to_movs.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_vec3_to_vec4.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_viewport_transform.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_wpos_center.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_wpos_ytransform.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_lower_wrmasks.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_metadata.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_move_vec_src_uses_to_dest.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_normalize_cubemap_coords.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_access.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_barriers.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_combine_stores.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_comparison_pre.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_conditional_discard.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_constant_folding.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_copy_prop_vars.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_copy_propagate.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_cse.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_dce.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_dead_cf.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_dead_write_vars.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_find_array_copies.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_fragdepth.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_gcm.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_idiv_const.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_if.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_intrinsics.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_large_constants.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_load_store_vectorize.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_loop_unroll.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_memcpy.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_move.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_move_discards_to_top.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_offsets.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_peephole_select.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_phi_precision.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_rematerialize_compares.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_remove_phis.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_shrink_vectors.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_sink.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_trivial_continues.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_undef.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_uniform_atomics.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_opt_vectorize.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_phi_builder.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_print.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_propagate_invariant.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_range_analysis.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_remove_dead_variables.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_repair_ssa.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_schedule.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_search.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_serialize.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_split_per_member_structs.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_split_var_copies.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_split_vars.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_sweep.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_to_lcssa.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_validate.c \
+ $(VBOX_MESA)/src/compiler/nir/nir_worklist.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/compiler/spirv/gl_spirv.c \
+ $(VBOX_MESA)/src/compiler/spirv/nir_load_libclc.c \
+ $(VBOX_MESA)/src/compiler/spirv/nir_lower_libclc.c \
+ $(VBOX_MESA)/src/compiler/spirv/spirv_to_nir.c \
+ $(VBOX_MESA)/src/compiler/spirv/vtn_alu.c \
+ $(VBOX_MESA)/src/compiler/spirv/vtn_amd.c \
+ $(VBOX_MESA)/src/compiler/spirv/vtn_cfg.c \
+ $(VBOX_MESA)/src/compiler/spirv/vtn_glsl450.c \
+ $(VBOX_MESA)/src/compiler/spirv/vtn_opencl.c \
+ $(VBOX_MESA)/src/compiler/spirv/vtn_subgroup.c \
+ $(VBOX_MESA)/src/compiler/spirv/vtn_variables.c
+VBoxMesaLib_SOURCES += \
+ $(VBOX_MESA)/src/mapi/glapi/glapi.c \
+ $(VBOX_MESA)/src/mapi/glapi/glapi_dispatch.c \
+ $(VBOX_MESA)/src/mapi/glapi/glapi_entrypoint.c \
+ $(VBOX_MESA)/src/mapi/glapi/glapi_getproc.c \
+ $(VBOX_MESA)/src/mapi/glapi/glapi_nop.c \
+ $(VBOX_MESA)/src/mapi/u_current.c \
+ $(VBOX_MESA)/src/mapi/u_execmem.c
+
+# 32 bit lib for 64 bit build
+VBoxMesaLib-x86_EXTENDS = VBoxMesaLib
+VBoxMesaLib-x86_BLD_TRG_ARCH = x86
+
+
+#
+# VBoxMesaWglLib
+#
+VBoxMesaWglLib_TEMPLATE = VBoxMesa3DGuestR3Lib
+VBoxMesaWglLib_INCS = \
+ $(VBOX_MESA)/src/gallium/frontends/wgl
+VBoxMesaWglLib_SOURCES = \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_context.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_device.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_ext_context.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_ext_extensionsstring.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_ext_pbuffer.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_ext_pixelformat.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_ext_rendertexture.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_ext_swapinterval.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_framebuffer.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_getprocaddress.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_nopfuncs.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_pixelformat.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_st.c \
+ $(VBOX_MESA)/src/gallium/frontends/wgl/stw_tls.c
+VBoxMesaWglLib_SOURCES += \
+ $(VBOX_MESA)/src/gallium/targets/libgl-gdi/stw_wgl.c
+
+# 32 bit lib for 64 bit build
+VBoxMesaWglLib-x86_EXTENDS = VBoxMesaWglLib
+VBoxMesaWglLib-x86_BLD_TRG_ARCH = x86
+
+
+#
+# VBoxMesaGalliumAuxLib
+#
+VBoxMesaGalliumAuxLib_TEMPLATE = VBoxMesa3DGuestR3Lib
+VBoxMesaGalliumAuxLib_INCS = \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir
+VBoxMesaGalliumAuxLib_DEPS = \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_builder_opcodes.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics_indices.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opcodes.h
+
+# Auto-generated
+VBoxMesaGalliumAuxLib_SOURCES = \
+ $(VBoxMesaGalliumAuxLib_0_OUTDIR)/$(VBOX_MESA)/src/gallium/auxiliary/indices/u_indices_gen.c \
+ $(VBoxMesaGalliumAuxLib_0_OUTDIR)/$(VBOX_MESA)/src/gallium/auxiliary/indices/u_unfilled_gen.c
+
+$$(VBoxMesaGalliumAuxLib_0_OUTDIR)/$(VBOX_MESA)/src/gallium/auxiliary/indices/u_indices_gen.c: \
+ $(VBOX_PATH_MESA)/src/gallium/auxiliary/indices/u_indices_gen.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+$$(VBoxMesaGalliumAuxLib_0_OUTDIR)/$(VBOX_MESA)/src/gallium/auxiliary/indices/u_unfilled_gen.c: \
+ $(VBOX_PATH_MESA)/src/gallium/auxiliary/indices/u_unfilled_gen.py | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$$@)
+ $(QUIET)$(PYTHON_CMD) $< >$@
+
+VBoxMesaGalliumAuxLib_SOURCES += \
+ $(VBOX_MESA)/src/gallium/auxiliary/cso_cache/cso_cache.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/cso_cache/cso_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/cso_cache/cso_hash.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_fs.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_gs.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_aaline.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_aapoint.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_clip.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_cull.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_flatshade.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_offset.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_pstipple.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_stipple.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_twoside.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_unfilled.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_user_cull.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_util.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_validate.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_vbuf.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_wide_line.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pipe_wide_point.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_prim_assembler.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_emit.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_fetch.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_fetch_shade_emit.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_fetch_shade_pipeline.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_post_vs.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_so_emit.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_util.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_pt_vsplit.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_tess.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_vertex.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_vs.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_vs_exec.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/draw/draw_vs_variant.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_ddebug/dd_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_ddebug/dd_draw.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_ddebug/dd_screen.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_noop/noop_pipe.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_noop/noop_state.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_rbug/rbug_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_rbug/rbug_core.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_rbug/rbug_objects.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_rbug/rbug_screen.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_trace/tr_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_trace/tr_dump.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_trace/tr_dump_state.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_trace/tr_screen.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/driver_trace/tr_texture.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/font.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_cpu.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_nic.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_cpufreq.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_diskstat.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_sensors_temp.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_driver_query.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/hud/hud_fps.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/indices/u_primconvert.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/os/os_process.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_buffer_fenced.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_bufmgr_cache.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_bufmgr_debug.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_bufmgr_mm.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_bufmgr_slab.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_cache.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_slab.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/pipebuffer/pb_validate.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/postprocess/pp_celshade.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/postprocess/pp_colors.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/postprocess/pp_init.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/postprocess/pp_mlaa.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/postprocess/pp_program.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/postprocess/pp_run.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rbug/rbug_connection.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rbug/rbug_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rbug/rbug_core.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rbug/rbug_demarshal.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rbug/rbug_shader.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rbug/rbug_texture.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rtasm/rtasm_cpu.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rtasm/rtasm_execmem.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/rtasm/rtasm_x86sse.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_aa_point.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_build.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_dump.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_dynamic_indexing.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_exec.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_emulate.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_from_mesa.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_info.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_iterate.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_lowering.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_parse.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_point_sprite.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_sanity.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_scan.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_strings.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_text.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_transform.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_two_side.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_ureg.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_util.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/tgsi/tgsi_vpos.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/translate/translate.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/translate/translate_cache.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/translate/translate_generic.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/translate/translate_sse.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_async_debug.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_bitmask.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_blitter.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_cache.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_compute.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_debug_flush.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_debug_image.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_dl.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_draw.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_draw_quad.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_driconf.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_dump_defines.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_dump_state.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_framebuffer.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_gen_mipmap.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_handle_table.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_helpers.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_index_modify.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_linear.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_live_shader_cache.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_log.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_network.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_prim.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_prim_restart.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_pstipple.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_resource.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_sampler.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_screen.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_simple_shaders.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_split_draw.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_suballoc.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_surface.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_tests.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_texture.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_tile.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_transfer.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_transfer_helper.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_threaded_context.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_upload_mgr.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_vbuf.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/util/u_vertex_state_cache.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/nir/tgsi_to_nir.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/nir/nir_to_tgsi.c \
+ $(VBOX_MESA)/src/gallium/auxiliary/nir/nir_draw_helpers.c
+
+# 32 bit lib for 64 bit build
+VBoxMesaGalliumAuxLib-x86_EXTENDS = VBoxMesaGalliumAuxLib
+VBoxMesaGalliumAuxLib-x86_BLD_TRG_ARCH = x86
+
+
+#
+# VBoxMesaNineLib
+#
+VBoxMesaNineLib_TEMPLATE = VBoxMesa3DGuestR3Lib
+VBoxMesaNineLib_INCS = \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir \
+ $(VBOX_MESA)/include/D3D9
+VBoxMesaNineLib_DEPS = \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_builder_opcodes.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_intrinsics_indices.h \
+ $(VBoxMesaLib_0_OUTDIR)/$(VBOX_MESA)/src/compiler/nir/nir_opcodes.h
+VBoxMesaNineLib_DEFS.win = COBJMACROS INC_OLE2
+# -wd4028: formal parameter 4 different from declaration
+# 'nine_context_set_vertex_shader_constant_f' parameter is declared 'const unsigned pConstantData_size'
+# but autogenerated code produces 'unsigned pConstantData_size'.
+VBoxMesaNineLib_CFLAGS.win = -wd4028
+VBoxMesaNineLib_SOURCES = \
+ $(VBOX_MESA)/src/gallium/frontends/nine/adapter9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/authenticatedchannel9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/basetexture9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/buffer9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/cryptosession9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/cubetexture9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/device9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/device9ex.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/device9video.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/guid.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/indexbuffer9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/iunknown.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_buffer_upload.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_debug.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_dump.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_ff.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_helpers.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_lock.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_pipe.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_queue.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_quirk.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_shader.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nine_state.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/nineexoverlayextension.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/pixelshader9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/query9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/resource9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/stateblock9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/surface9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/swapchain9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/swapchain9ex.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/texture9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/threadpool.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/vertexbuffer9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/vertexdeclaration9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/vertexshader9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/volume9.c \
+ $(VBOX_MESA)/src/gallium/frontends/nine/volumetexture9.c
+
+# 32 bit lib for 64 bit build
+VBoxMesaNineLib-x86_EXTENDS = VBoxMesaNineLib
+VBoxMesaNineLib-x86_BLD_TRG_ARCH = x86
+
+
+#
+# VBoxMesaSVGALib
+#
+VBoxMesaSVGALib_TEMPLATE = VBoxMesa3DGuestR3Lib
+VBoxMesaSVGALib_INCS = \
+ $(VBOX_MESA)/src/gallium/drivers/svga/include
+VBoxMesaSVGALib_SOURCES = \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_cmd.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_cmd_vgpu10.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_context.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_draw.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_draw_arrays.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_draw_elements.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_format.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_link.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_blend.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_blit.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_clear.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_constants.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_depthstencil.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_draw.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_flush.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_fs.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_gs.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_misc.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_query.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_rasterizer.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_sampler.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_streamout.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_ts.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_vertex.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_pipe_vs.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_resource.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_resource_buffer.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_resource_buffer_upload.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_resource_texture.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_sampler_view.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_screen.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_screen_cache.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_shader.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_constants.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_framebuffer.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_fs.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_gs.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_need_swtnl.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_rss.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_sampler.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_tgsi_transform.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_ts.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_tss.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_vdecl.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_state_vs.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_surface.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_swtnl_backend.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_swtnl_draw.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_swtnl_state.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_tgsi.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_tgsi_decl_sm30.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_tgsi_insn.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svga_tgsi_vgpu10.c
+
+VBoxMesaSVGALib_SOURCES.debug += \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svgadump/svga_dump.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svgadump/svga_shader_dump.c \
+ $(VBOX_MESA)/src/gallium/drivers/svga/svgadump/svga_shader_op.c
+
+# 32 bit lib for 64 bit build
+VBoxMesaSVGALib-x86_EXTENDS = VBoxMesaSVGALib
+VBoxMesaSVGALib-x86_BLD_TRG_ARCH = x86
+
+
+#
+# VBoxMesaSVGAWinsysLib
+#
+VBoxMesaSVGAWinsysLib_TEMPLATE = VBoxMesa3DGuestR3Lib
+VBoxMesaSVGAWinsysLib_INCS += \
+ $(VBOX_MESA)/src/gallium/drivers/svga/include \
+ $(VBOX_MESA)/src/gallium/drivers/svga
+VBoxMesaSVGAWinsysLib_SOURCES = \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/pb_buffer_simple_fenced.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_buffer.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_context.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_fence.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_query.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_screen_pools.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_screen_svga.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_shader.c \
+ $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_surface.c
+
+# These will be reimplemented for WDDM
+# $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_screen.c
+# $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_screen_dri.c
+# $(VBOX_MESA)/src/gallium/winsys/svga/drm/vmw_screen_ioctl.c
+
+# 32 bit lib for 64 bit build
+VBoxMesaSVGAWinsysLib-x86_EXTENDS = VBoxMesaSVGAWinsysLib
+VBoxMesaSVGAWinsysLib-x86_BLD_TRG_ARCH = x86
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/3D/mesa/include/assert.h b/src/VBox/Additions/3D/mesa/include/assert.h
new file mode 100644
index 00000000..fc9b0c81
--- /dev/null
+++ b/src/VBox/Additions/3D/mesa/include/assert.h
@@ -0,0 +1,47 @@
+/* $Id: assert.h $ */
+/** @file
+ * Replaces C runtime assert with a simplified version which just hits breakpoint.
+ *
+ * Mesa code uses assert.h a lot, which is inconvenient because the C runtime
+ * implementation wants to open a message box and it does not work in the
+ * graphics driver.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_3D_MESA_assert_h
+#define GA_INCLUDED_3D_MESA_assert_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/asm.h>
+
+#undef assert
+#ifdef DEBUG
+#define assert(_e) (void)( (!!(_e)) || (ASMBreakpoint(), 0) )
+#else
+#define assert(_e) (void)(0)
+#endif
+
+#endif /* !GA_INCLUDED_3D_MESA_assert_h */
diff --git a/src/VBox/Additions/3D/mesa/include/git_sha1.h b/src/VBox/Additions/3D/mesa/include/git_sha1.h
new file mode 100644
index 00000000..fdfb99b3
--- /dev/null
+++ b/src/VBox/Additions/3D/mesa/include/git_sha1.h
@@ -0,0 +1,38 @@
+/* $Id: git_sha1.h $ */
+/** @file
+ * Provides this include file for Mesa.
+ *
+ * Mesa code does not check MESA_GIT_SHA1 define before including the file.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_3D_MESA_git_sha1_h
+#define GA_INCLUDED_3D_MESA_git_sha1_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define MESA_GIT_SHA1 ""
+
+#endif /* !GA_INCLUDED_3D_MESA_git_sha1_h */
diff --git a/src/VBox/Additions/3D/win/Makefile.kmk b/src/VBox/Additions/3D/win/Makefile.kmk
new file mode 100644
index 00000000..23a82fc6
--- /dev/null
+++ b/src/VBox/Additions/3D/win/Makefile.kmk
@@ -0,0 +1,37 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Windows guest components using Mesa3D.
+#
+
+#
+# Copyright (C) 2016-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Include sub-makefile.
+include $(PATH_SUB_CURRENT)/VBoxGL/Makefile.kmk
+include $(PATH_SUB_CURRENT)/VBoxNine/Makefile.kmk
+include $(PATH_SUB_CURRENT)/VBoxSVGA/Makefile.kmk
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.cpp b/src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.cpp
new file mode 100644
index 00000000..39da970c
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.cpp
@@ -0,0 +1,1396 @@
+/* $Id: GaDrvEnvKMT.cpp $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Gallium driver interface to the WDDM miniport driver using Kernel Mode Thunks.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "GaDrvEnvKMT.h"
+
+#include <UmHlpInternal.h>
+
+#include "svga3d_reg.h"
+
+#include <common/wddm/VBoxMPIf.h>
+
+#include <iprt/assertcompile.h>
+#include <iprt/param.h> /* For PAGE_SIZE */
+
+AssertCompile(sizeof(HANDLE) >= sizeof(D3DKMT_HANDLE));
+
+
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlU32##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLU32NODECORE
+#define PKAVLNODECORE PAVLU32NODECORE
+#define PPKAVLNODECORE PPAVLU32NODECORE
+#define KAVLKEY AVLU32KEY
+#define PKAVLKEY PAVLU32KEY
+#define KAVLENUMDATA AVLU32ENUMDATA
+#define PKAVLENUMDATA PAVLU32ENUMDATA
+#define PKAVLCALLBACK PAVLU32CALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (key1) > (key2) )
+#define KAVL_E(key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+#include <iprt/avl.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT(_e) do { } while (0)
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+//#include "avl_GetBestFit.cpp.h"
+//#include "avl_RemoveBestFit.cpp.h"
+//#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
+
+typedef struct GaKmtCallbacks
+{
+ D3DKMT_HANDLE hAdapter;
+ D3DKMT_HANDLE hDevice;
+ D3DKMTFUNCTIONS const *d3dkmt;
+ LUID AdapterLuid;
+} GaKmtCallbacks;
+
+class GaDrvEnvKmt
+{
+ public:
+ GaDrvEnvKmt();
+ ~GaDrvEnvKmt();
+
+ HRESULT Init(void);
+
+ const WDDMGalliumDriverEnv *Env();
+
+ /*
+ * KMT specific helpers.
+ */
+ bool drvEnvKmtRenderCompose(uint32_t u32Cid,
+ void *pvCommands,
+ uint32_t cbCommands,
+ ULONGLONG PresentHistoryToken);
+ D3DKMT_HANDLE drvEnvKmtContextHandle(uint32_t u32Cid);
+ D3DKMT_HANDLE drvEnvKmtSurfaceHandle(uint32_t u32Sid);
+
+ GaKmtCallbacks mKmtCallbacks;
+
+ private:
+
+ VBOXGAHWINFO mHWInfo;
+
+ /* Map to convert context id (cid) to WDDM context information (GAWDDMCONTEXTINFO).
+ * Key is the 32 bit context id.
+ */
+ AVLU32TREE mContextTree;
+
+ /* Map to convert surface id (sid) to WDDM surface information (GAWDDMSURFACEINFO).
+ * Key is the 32 bit surface id.
+ */
+ AVLU32TREE mSurfaceTree;
+
+ WDDMGalliumDriverEnv mEnv;
+
+ static DECLCALLBACK(uint32_t) gaEnvContextCreate(void *pvEnv,
+ boolean extended,
+ boolean vgpu10);
+ static DECLCALLBACK(void) gaEnvContextDestroy(void *pvEnv,
+ uint32_t u32Cid);
+ static DECLCALLBACK(int) gaEnvSurfaceDefine(void *pvEnv,
+ GASURFCREATE *pCreateParms,
+ GASURFSIZE *paSizes,
+ uint32_t cSizes,
+ uint32_t *pu32Sid);
+ static DECLCALLBACK(void) gaEnvSurfaceDestroy(void *pvEnv,
+ uint32_t u32Sid);
+ static DECLCALLBACK(int) gaEnvRender(void *pvEnv,
+ uint32_t u32Cid,
+ void *pvCommands,
+ uint32_t cbCommands,
+ GAFENCEQUERY *pFenceQuery);
+ static DECLCALLBACK(void) gaEnvFenceUnref(void *pvEnv,
+ uint32_t u32FenceHandle);
+ static DECLCALLBACK(int) gaEnvFenceQuery(void *pvEnv,
+ uint32_t u32FenceHandle,
+ GAFENCEQUERY *pFenceQuery);
+ static DECLCALLBACK(int) gaEnvFenceWait(void *pvEnv,
+ uint32_t u32FenceHandle,
+ uint32_t u32TimeoutUS);
+ static DECLCALLBACK(int) gaEnvRegionCreate(void *pvEnv,
+ uint32_t u32RegionSize,
+ uint32_t *pu32GmrId,
+ void **ppvMap);
+ static DECLCALLBACK(void) gaEnvRegionDestroy(void *pvEnv,
+ uint32_t u32GmrId,
+ void *pvMap);
+
+ /* VGPU10 */
+ static DECLCALLBACK(int) gaEnvGBSurfaceDefine(void *pvEnv,
+ SVGAGBSURFCREATE *pCreateParms);
+
+ /*
+ * Internal.
+ */
+ bool doRender(uint32_t u32Cid, void *pvCommands, uint32_t cbCommands,
+ GAFENCEQUERY *pFenceQuery, ULONGLONG PresentHistoryToken, bool fPresentRedirected);
+};
+
+typedef struct GAWDDMCONTEXTINFO
+{
+ AVLU32NODECORE Core;
+ D3DKMT_HANDLE hContext;
+ VOID *pCommandBuffer;
+ UINT CommandBufferSize;
+ D3DDDI_ALLOCATIONLIST *pAllocationList;
+ UINT AllocationListSize;
+ D3DDDI_PATCHLOCATIONLIST *pPatchLocationList;
+ UINT PatchLocationListSize;
+} GAWDDMCONTEXTINFO;
+
+typedef struct GAWDDMSURFACEINFO
+{
+ AVLU32NODECORE Core;
+ D3DKMT_HANDLE hAllocation;
+} GAWDDMSURFACEINFO;
+
+
+/// @todo vboxDdi helpers must return a boof success indicator
+static bool
+vboxDdiQueryAdapterInfo(GaKmtCallbacks *pKmtCallbacks,
+ D3DKMT_HANDLE hAdapter,
+ VBOXWDDM_QAI *pAdapterInfo,
+ uint32_t cbAdapterInfo)
+{
+ D3DKMT_QUERYADAPTERINFO QAI;
+ memset(&QAI, 0, sizeof(QAI));
+ QAI.hAdapter = hAdapter;
+ QAI.Type = KMTQAITYPE_UMDRIVERPRIVATE;
+ QAI.pPrivateDriverData = pAdapterInfo;
+ QAI.PrivateDriverDataSize = cbAdapterInfo;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTQueryAdapterInfo(&QAI);
+ return Status == STATUS_SUCCESS;
+}
+
+static void
+vboxDdiDeviceDestroy(GaKmtCallbacks *pKmtCallbacks,
+ D3DKMT_HANDLE hDevice)
+{
+ if (hDevice)
+ {
+ D3DKMT_DESTROYDEVICE DestroyDeviceData;
+ memset(&DestroyDeviceData, 0, sizeof(DestroyDeviceData));
+ DestroyDeviceData.hDevice = hDevice;
+ pKmtCallbacks->d3dkmt->pfnD3DKMTDestroyDevice(&DestroyDeviceData);
+ }
+}
+
+static bool
+vboxDdiDeviceCreate(GaKmtCallbacks *pKmtCallbacks,
+ D3DKMT_HANDLE *phDevice)
+{
+ D3DKMT_CREATEDEVICE CreateDeviceData;
+ memset(&CreateDeviceData, 0, sizeof(CreateDeviceData));
+ CreateDeviceData.hAdapter = pKmtCallbacks->hAdapter;
+ // CreateDeviceData.Flags = 0;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTCreateDevice(&CreateDeviceData);
+ if (Status == STATUS_SUCCESS)
+ {
+ *phDevice = CreateDeviceData.hDevice;
+ return true;
+ }
+ return false;
+}
+
+static bool
+vboxDdiContextGetId(GaKmtCallbacks *pKmtCallbacks,
+ D3DKMT_HANDLE hContext,
+ uint32_t *pu32Cid)
+{
+ VBOXDISPIFESCAPE_GAGETCID data;
+ memset(&data, 0, sizeof(data));
+ data.EscapeHdr.escapeCode = VBOXESC_GAGETCID;
+ // data.EscapeHdr.cmdSpecific = 0;
+ // data.u32Cid = 0;
+
+ /* If the user-mode display driver sets hContext to a non-NULL value, the driver must
+ * have also set hDevice to a non-NULL value...
+ */
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pKmtCallbacks->hAdapter;
+ EscapeData.hDevice = pKmtCallbacks->hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = &data;
+ EscapeData.PrivateDriverDataSize = sizeof(data);
+ EscapeData.hContext = hContext;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ if (Status == STATUS_SUCCESS)
+ {
+ *pu32Cid = data.u32Cid;
+ return true;
+ }
+ return false;
+}
+
+static void
+vboxDdiContextDestroy(GaKmtCallbacks *pKmtCallbacks,
+ GAWDDMCONTEXTINFO *pContextInfo)
+{
+ if (pContextInfo->hContext)
+ {
+ D3DKMT_DESTROYCONTEXT DestroyContextData;
+ memset(&DestroyContextData, 0, sizeof(DestroyContextData));
+ DestroyContextData.hContext = pContextInfo->hContext;
+ pKmtCallbacks->d3dkmt->pfnD3DKMTDestroyContext(&DestroyContextData);
+ }
+}
+
+static bool
+vboxDdiContextCreate(GaKmtCallbacks *pKmtCallbacks,
+ void *pvPrivateData, uint32_t cbPrivateData,
+ GAWDDMCONTEXTINFO *pContextInfo)
+{
+ D3DKMT_CREATECONTEXT CreateContextData;
+ memset(&CreateContextData, 0, sizeof(CreateContextData));
+ CreateContextData.hDevice = pKmtCallbacks->hDevice;
+ // CreateContextData.NodeOrdinal = 0;
+ // CreateContextData.EngineAffinity = 0;
+ // CreateContextData.Flags.Value = 0;
+ CreateContextData.pPrivateDriverData = pvPrivateData;
+ CreateContextData.PrivateDriverDataSize = cbPrivateData;
+ CreateContextData.ClientHint = D3DKMT_CLIENTHINT_OPENGL;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTCreateContext(&CreateContextData);
+ if (Status == STATUS_SUCCESS)
+ {
+ /* Query cid. */
+ uint32_t u32Cid = 0;
+ bool fSuccess = vboxDdiContextGetId(pKmtCallbacks, CreateContextData.hContext, &u32Cid);
+ if (fSuccess)
+ {
+ pContextInfo->Core.Key = u32Cid;
+ pContextInfo->hContext = CreateContextData.hContext;
+ pContextInfo->pCommandBuffer = CreateContextData.pCommandBuffer;
+ pContextInfo->CommandBufferSize = CreateContextData.CommandBufferSize;
+ pContextInfo->pAllocationList = CreateContextData.pAllocationList;
+ pContextInfo->AllocationListSize = CreateContextData.AllocationListSize;
+ pContextInfo->pPatchLocationList = CreateContextData.pPatchLocationList;
+ pContextInfo->PatchLocationListSize = CreateContextData.PatchLocationListSize;
+
+ return true;
+ }
+
+ vboxDdiContextDestroy(pKmtCallbacks, pContextInfo);
+ }
+
+ return false;
+}
+
+/* static */ DECLCALLBACK(void)
+GaDrvEnvKmt::gaEnvContextDestroy(void *pvEnv,
+ uint32_t u32Cid)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ GAWDDMCONTEXTINFO *pContextInfo = (GAWDDMCONTEXTINFO *)RTAvlU32Remove(&pThis->mContextTree, u32Cid);
+ if (pContextInfo)
+ {
+ vboxDdiContextDestroy(&pThis->mKmtCallbacks, pContextInfo);
+ memset(pContextInfo, 0, sizeof(*pContextInfo));
+ free(pContextInfo);
+ }
+}
+
+D3DKMT_HANDLE GaDrvEnvKmt::drvEnvKmtContextHandle(uint32_t u32Cid)
+{
+ GAWDDMCONTEXTINFO *pContextInfo = (GAWDDMCONTEXTINFO *)RTAvlU32Get(&mContextTree, u32Cid);
+ Assert(pContextInfo);
+ return pContextInfo ? pContextInfo->hContext : 0;
+}
+
+/* static */ DECLCALLBACK(uint32_t)
+GaDrvEnvKmt::gaEnvContextCreate(void *pvEnv,
+ boolean extended,
+ boolean vgpu10)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ GAWDDMCONTEXTINFO *pContextInfo;
+ pContextInfo = (GAWDDMCONTEXTINFO *)malloc(sizeof(GAWDDMCONTEXTINFO));
+ if (!pContextInfo)
+ return (uint32_t)-1;
+
+ VBOXWDDM_CREATECONTEXT_INFO privateData;
+ memset(&privateData, 0, sizeof(privateData));
+ privateData.u32IfVersion = 9;
+ privateData.enmType = VBOXWDDM_CONTEXT_TYPE_GA_3D;
+ privateData.u.vmsvga.u32Flags = extended? VBOXWDDM_F_GA_CONTEXT_EXTENDED: 0;
+ privateData.u.vmsvga.u32Flags |= vgpu10? VBOXWDDM_F_GA_CONTEXT_VGPU10: 0;
+
+ bool fSuccess = vboxDdiContextCreate(&pThis->mKmtCallbacks,
+ &privateData, sizeof(privateData), pContextInfo);
+ if (fSuccess)
+ {
+ if (RTAvlU32Insert(&pThis->mContextTree, &pContextInfo->Core))
+ {
+ return pContextInfo->Core.Key;
+ }
+
+ vboxDdiContextDestroy(&pThis->mKmtCallbacks,
+ pContextInfo);
+ }
+
+ Assert(0);
+ free(pContextInfo);
+ return (uint32_t)-1;
+}
+
+static D3DDDIFORMAT svgaToD3DDDIFormat(SVGA3dSurfaceFormat format)
+{
+ /* The returning D3DDDIFMT_ value is used only to compute bpp, pitch, etc,
+ * so there is not need for an exact match.
+ */
+ switch (format)
+ {
+ case SVGA3D_X8R8G8B8: return D3DDDIFMT_X8R8G8B8;
+ case SVGA3D_A8R8G8B8: return D3DDDIFMT_A8R8G8B8;
+ case SVGA3D_ALPHA8: return D3DDDIFMT_A8;
+ case SVGA3D_A4R4G4B4: return D3DDDIFMT_A4R4G4B4;
+ case SVGA3D_LUMINANCE8: return D3DDDIFMT_L8;
+ case SVGA3D_A1R5G5B5: return D3DDDIFMT_A1R5G5B5;
+ case SVGA3D_LUMINANCE8_ALPHA8: return D3DDDIFMT_A8L8;
+ case SVGA3D_R5G6B5: return D3DDDIFMT_R5G6B5;
+ case SVGA3D_ARGB_S10E5: return D3DDDIFMT_A16B16G16R16F;
+ case SVGA3D_ARGB_S23E8: return D3DDDIFMT_A32B32G32R32F;
+ case SVGA3D_A8_UNORM: return D3DDDIFMT_A8;
+ case SVGA3D_B5G5R5A1_UNORM: return D3DDDIFMT_A1R5G5B5;
+
+ case SVGA3D_B8G8R8X8_TYPELESS:
+ case SVGA3D_B8G8R8X8_UNORM: return D3DDDIFMT_X8R8G8B8;
+ case SVGA3D_R16_FLOAT: return D3DDDIFMT_R16F;
+ case SVGA3D_R16G16_FLOAT: return D3DDDIFMT_G16R16F;
+ case SVGA3D_R16G16B16A16_FLOAT: return D3DDDIFMT_A16B16G16R16F;
+ case SVGA3D_R32_FLOAT: return D3DDDIFMT_R32F;
+ case SVGA3D_R32G32_FLOAT: return D3DDDIFMT_G32R32F;
+ case SVGA3D_R32G32B32A32_FLOAT: return D3DDDIFMT_A32B32G32R32F;
+ case SVGA3D_R8_TYPELESS:
+ case SVGA3D_R8_SINT:
+ case SVGA3D_R8_UINT:
+ case SVGA3D_R8_SNORM:
+ case SVGA3D_R8_UNORM: return D3DDDIFMT_L8;
+ case SVGA3D_R8G8_TYPELESS:
+ case SVGA3D_R8G8_SINT:
+ case SVGA3D_R8G8_UINT:
+ case SVGA3D_R8G8_SNORM:
+ case SVGA3D_R8G8_UNORM: return D3DDDIFMT_A8L8;
+ case SVGA3D_R8G8B8A8_TYPELESS:
+ case SVGA3D_R8G8B8A8_SINT:
+ case SVGA3D_R8G8B8A8_UINT:
+ case SVGA3D_R8G8B8A8_SNORM:
+ case SVGA3D_R8G8B8A8_UNORM: return D3DDDIFMT_A8R8G8B8;
+ case SVGA3D_R16_TYPELESS:
+ case SVGA3D_R16_SINT:
+ case SVGA3D_R16_UINT:
+ case SVGA3D_R16_SNORM:
+ case SVGA3D_R16_UNORM: return D3DDDIFMT_L16;
+ case SVGA3D_R16G16_TYPELESS:
+ case SVGA3D_R16G16_SINT:
+ case SVGA3D_R16G16_UINT:
+ case SVGA3D_R16G16_SNORM:
+ case SVGA3D_R16G16_UNORM: return D3DDDIFMT_G16R16;
+ case SVGA3D_R16G16B16A16_TYPELESS:
+ case SVGA3D_R16G16B16A16_SINT:
+ case SVGA3D_R16G16B16A16_UINT:
+ case SVGA3D_R16G16B16A16_SNORM:
+ case SVGA3D_R16G16B16A16_UNORM: return D3DDDIFMT_A16B16G16R16;
+ case SVGA3D_R32_TYPELESS:
+ case SVGA3D_R32_SINT:
+ case SVGA3D_R32_UINT: return D3DDDIFMT_R32F; /* Same size in bytes. */
+ case SVGA3D_R32G32_TYPELESS:
+ case SVGA3D_R32G32_SINT:
+ case SVGA3D_R32G32_UINT: return D3DDDIFMT_G32R32F; /* Same size in bytes. */
+ case SVGA3D_R32G32B32A32_TYPELESS:
+ case SVGA3D_R32G32B32A32_SINT:
+ case SVGA3D_R32G32B32A32_UINT: return D3DDDIFMT_A32B32G32R32F; /* Same size in bytes. */
+ case SVGA3D_R10G10B10A2_TYPELESS:
+ case SVGA3D_R10G10B10A2_UINT:
+ case SVGA3D_R10G10B10A2_UNORM: return D3DDDIFMT_A2B10G10R10;
+ case SVGA3D_B5G6R5_UNORM: return D3DDDIFMT_R5G6B5;
+ case SVGA3D_R11G11B10_FLOAT: return D3DDDIFMT_R32F;
+ case SVGA3D_B8G8R8A8_UNORM: return D3DDDIFMT_A8R8G8B8;
+ default: break;
+ }
+
+ VBoxDispMpLoggerLogF("WDDM: EnvKMT: unsupported surface format %d\n", format);
+ Assert(0);
+ return D3DDDIFMT_UNKNOWN;
+}
+
+/* static */ DECLCALLBACK(int)
+GaDrvEnvKmt::gaEnvSurfaceDefine(void *pvEnv,
+ GASURFCREATE *pCreateParms,
+ GASURFSIZE *paSizes,
+ uint32_t cSizes,
+ uint32_t *pu32Sid)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ D3DKMT_ESCAPE EscapeData;
+ VBOXDISPIFESCAPE_GASURFACEDEFINE *pData;
+ uint32_t cbAlloc;
+ uint8_t *pu8Req;
+ uint32_t cbReq;
+
+ /* Size of the SVGA request data */
+ cbReq = sizeof(GASURFCREATE) + cSizes * sizeof(GASURFSIZE);
+ /* How much to allocate for WDDM escape data. */
+ cbAlloc = sizeof(VBOXDISPIFESCAPE_GASURFACEDEFINE)
+ + cbReq;
+
+ pData = (VBOXDISPIFESCAPE_GASURFACEDEFINE *)malloc(cbAlloc);
+ if (!pData)
+ return -1;
+
+ pData->EscapeHdr.escapeCode = VBOXESC_GASURFACEDEFINE;
+ // pData->EscapeHdr.cmdSpecific = 0;
+ // pData->u32Sid = 0;
+ pData->cbReq = cbReq;
+ pData->cSizes = cSizes;
+
+ pu8Req = (uint8_t *)&pData[1];
+ memcpy(pu8Req, pCreateParms, sizeof(GASURFCREATE));
+ memcpy(&pu8Req[sizeof(GASURFCREATE)], paSizes, cSizes * sizeof(GASURFSIZE));
+
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pThis->mKmtCallbacks.hAdapter;
+ EscapeData.hDevice = pThis->mKmtCallbacks.hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ EscapeData.Flags.HardwareAccess = 1;
+ EscapeData.pPrivateDriverData = pData;
+ EscapeData.PrivateDriverDataSize = cbAlloc;
+ // EscapeData.hContext = 0;
+
+ NTSTATUS Status = pThis->mKmtCallbacks.d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ if (Status == STATUS_SUCCESS)
+ {
+ /* Create a kernel mode allocation for render targets,
+ * because we will need kernel mode handles for Present.
+ */
+ if (pCreateParms->flags & SVGA3D_SURFACE_HINT_RENDERTARGET)
+ {
+ /* First check if the format is supported. */
+ D3DDDIFORMAT const ddiFormat = svgaToD3DDDIFormat((SVGA3dSurfaceFormat)pCreateParms->format);
+ if (ddiFormat != D3DDDIFMT_UNKNOWN)
+ {
+ GAWDDMSURFACEINFO *pSurfaceInfo = (GAWDDMSURFACEINFO *)malloc(sizeof(GAWDDMSURFACEINFO));
+ if (pSurfaceInfo)
+ {
+ memset(pSurfaceInfo, 0, sizeof(GAWDDMSURFACEINFO));
+
+ VBOXWDDM_ALLOCINFO wddmAllocInfo;
+ memset(&wddmAllocInfo, 0, sizeof(wddmAllocInfo));
+
+ wddmAllocInfo.enmType = VBOXWDDM_ALLOC_TYPE_UMD_RC_GENERIC;
+ wddmAllocInfo.fFlags.RenderTarget = 1;
+ wddmAllocInfo.hSharedHandle = 0;
+ wddmAllocInfo.hostID = pData->u32Sid;
+ wddmAllocInfo.SurfDesc.slicePitch = 0;
+ wddmAllocInfo.SurfDesc.depth = paSizes[0].cDepth;
+ wddmAllocInfo.SurfDesc.width = paSizes[0].cWidth;
+ wddmAllocInfo.SurfDesc.height = paSizes[0].cHeight;
+ wddmAllocInfo.SurfDesc.format = ddiFormat;
+ wddmAllocInfo.SurfDesc.VidPnSourceId = 0;
+ wddmAllocInfo.SurfDesc.bpp = vboxWddmCalcBitsPerPixel(wddmAllocInfo.SurfDesc.format);
+ wddmAllocInfo.SurfDesc.pitch = vboxWddmCalcPitch(wddmAllocInfo.SurfDesc.width,
+ wddmAllocInfo.SurfDesc.format);
+ wddmAllocInfo.SurfDesc.cbSize = vboxWddmCalcSize(wddmAllocInfo.SurfDesc.pitch,
+ wddmAllocInfo.SurfDesc.height,
+ wddmAllocInfo.SurfDesc.format);
+ wddmAllocInfo.SurfDesc.d3dWidth = vboxWddmCalcWidthForPitch(wddmAllocInfo.SurfDesc.pitch,
+ wddmAllocInfo.SurfDesc.format);
+
+ D3DDDI_ALLOCATIONINFO AllocationInfo;
+ memset(&AllocationInfo, 0, sizeof(AllocationInfo));
+ // AllocationInfo.hAllocation = NULL;
+ // AllocationInfo.pSystemMem = NULL;
+ AllocationInfo.pPrivateDriverData = &wddmAllocInfo;
+ AllocationInfo.PrivateDriverDataSize = sizeof(wddmAllocInfo);
+
+ D3DKMT_CREATEALLOCATION CreateAllocation;
+ memset(&CreateAllocation, 0, sizeof(CreateAllocation));
+ CreateAllocation.hDevice = pThis->mKmtCallbacks.hDevice;
+ CreateAllocation.NumAllocations = 1;
+ CreateAllocation.pAllocationInfo = &AllocationInfo;
+
+ Status = pThis->mKmtCallbacks.d3dkmt->pfnD3DKMTCreateAllocation(&CreateAllocation);
+ if (Status == STATUS_SUCCESS)
+ {
+ pSurfaceInfo->Core.Key = pData->u32Sid;
+ pSurfaceInfo->hAllocation = AllocationInfo.hAllocation;
+ if (!RTAvlU32Insert(&pThis->mSurfaceTree, &pSurfaceInfo->Core))
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+ if (Status != STATUS_SUCCESS)
+ {
+ free(pSurfaceInfo);
+ }
+ }
+ else
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ /* Unsupported render target format. */
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+ if (Status != STATUS_SUCCESS)
+ {
+ gaEnvSurfaceDestroy(pvEnv, pData->u32Sid);
+ }
+ }
+
+ if (Status == STATUS_SUCCESS)
+ {
+ *pu32Sid = pData->u32Sid;
+ free(pData);
+ return 0;
+ }
+
+ Assert(0);
+ free(pData);
+ return -1;
+}
+
+/* static */ DECLCALLBACK(void)
+GaDrvEnvKmt::gaEnvSurfaceDestroy(void *pvEnv,
+ uint32_t u32Sid)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ VBOXDISPIFESCAPE_GASURFACEDESTROY data;
+ memset(&data, 0, sizeof(data));
+ data.EscapeHdr.escapeCode = VBOXESC_GASURFACEDESTROY;
+ // data.EscapeHdr.cmdSpecific = 0;
+ data.u32Sid = u32Sid;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pThis->mKmtCallbacks.hAdapter;
+ EscapeData.hDevice = pThis->mKmtCallbacks.hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ EscapeData.Flags.HardwareAccess = 1;
+ EscapeData.pPrivateDriverData = &data;
+ EscapeData.PrivateDriverDataSize = sizeof(data);
+ // EscapeData.hContext = 0;
+
+ NTSTATUS Status = pThis->mKmtCallbacks.d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ Assert(Status == STATUS_SUCCESS);
+
+ /* Try to remove from sid -> hAllocation map. */
+ GAWDDMSURFACEINFO *pSurfaceInfo = (GAWDDMSURFACEINFO *)RTAvlU32Remove(&pThis->mSurfaceTree, u32Sid);
+ if (pSurfaceInfo)
+ {
+ D3DKMT_DESTROYALLOCATION DestroyAllocation;
+ memset(&DestroyAllocation, 0, sizeof(DestroyAllocation));
+ DestroyAllocation.hDevice = pThis->mKmtCallbacks.hDevice;
+ // DestroyAllocation.hResource = 0;
+ DestroyAllocation.phAllocationList = &pSurfaceInfo->hAllocation;
+ DestroyAllocation.AllocationCount = 1;
+
+ Status = pThis->mKmtCallbacks.d3dkmt->pfnD3DKMTDestroyAllocation(&DestroyAllocation);
+ Assert(Status == STATUS_SUCCESS);
+
+ free(pSurfaceInfo);
+ }
+}
+
+D3DKMT_HANDLE GaDrvEnvKmt::drvEnvKmtSurfaceHandle(uint32_t u32Sid)
+{
+ GAWDDMSURFACEINFO *pSurfaceInfo = (GAWDDMSURFACEINFO *)RTAvlU32Get(&mSurfaceTree, u32Sid);
+ return pSurfaceInfo ? pSurfaceInfo->hAllocation : 0;
+}
+
+static bool
+vboxDdiFenceCreate(GaKmtCallbacks *pKmtCallbacks,
+ GAWDDMCONTEXTINFO *pContextInfo,
+ uint32_t *pu32FenceHandle)
+{
+ VBOXDISPIFESCAPE_GAFENCECREATE fenceCreate;
+ memset(&fenceCreate, 0, sizeof(fenceCreate));
+ fenceCreate.EscapeHdr.escapeCode = VBOXESC_GAFENCECREATE;
+ // fenceCreate.EscapeHdr.cmdSpecific = 0;
+
+ /* If the user-mode display driver sets hContext to a non-NULL value, the driver must
+ * have also set hDevice to a non-NULL value...
+ */
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pKmtCallbacks->hAdapter;
+ EscapeData.hDevice = pKmtCallbacks->hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = &fenceCreate;
+ EscapeData.PrivateDriverDataSize = sizeof(fenceCreate);
+ EscapeData.hContext = pContextInfo->hContext;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ if (Status == STATUS_SUCCESS)
+ {
+ *pu32FenceHandle = fenceCreate.u32FenceHandle;
+ return true;
+ }
+
+ Assert(0);
+ return false;
+}
+
+static bool
+vboxDdiFenceQuery(GaKmtCallbacks *pKmtCallbacks,
+ uint32_t u32FenceHandle,
+ GAFENCEQUERY *pFenceQuery)
+{
+ VBOXDISPIFESCAPE_GAFENCEQUERY fenceQuery;
+ memset(&fenceQuery, 0, sizeof(fenceQuery));
+ fenceQuery.EscapeHdr.escapeCode = VBOXESC_GAFENCEQUERY;
+ // fenceQuery.EscapeHdr.cmdSpecific = 0;
+ fenceQuery.u32FenceHandle = u32FenceHandle;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pKmtCallbacks->hAdapter;
+ EscapeData.hDevice = pKmtCallbacks->hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = &fenceQuery;
+ EscapeData.PrivateDriverDataSize = sizeof(fenceQuery);
+ EscapeData.hContext = 0;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ if (Status == STATUS_SUCCESS)
+ {
+ pFenceQuery->u32FenceHandle = fenceQuery.u32FenceHandle;
+ pFenceQuery->u32SubmittedSeqNo = fenceQuery.u32SubmittedSeqNo;
+ pFenceQuery->u32ProcessedSeqNo = fenceQuery.u32ProcessedSeqNo;
+ pFenceQuery->u32FenceStatus = fenceQuery.u32FenceStatus;
+ return true;
+ }
+
+ Assert(0);
+ return false;
+}
+
+/* static */ DECLCALLBACK(int)
+GaDrvEnvKmt::gaEnvFenceQuery(void *pvEnv,
+ uint32_t u32FenceHandle,
+ GAFENCEQUERY *pFenceQuery)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ if (!pThis->mKmtCallbacks.hDevice)
+ {
+ pFenceQuery->u32FenceStatus = GA_FENCE_STATUS_NULL;
+ return 0;
+ }
+
+ bool fSuccess = vboxDdiFenceQuery(&pThis->mKmtCallbacks, u32FenceHandle, pFenceQuery);
+ return fSuccess ? 0: -1;
+}
+
+static bool
+vboxDdiFenceWait(GaKmtCallbacks *pKmtCallbacks,
+ uint32_t u32FenceHandle,
+ uint32_t u32TimeoutUS)
+{
+ VBOXDISPIFESCAPE_GAFENCEWAIT fenceWait;
+ memset(&fenceWait, 0, sizeof(fenceWait));
+ fenceWait.EscapeHdr.escapeCode = VBOXESC_GAFENCEWAIT;
+ // pFenceWait->EscapeHdr.cmdSpecific = 0;
+ fenceWait.u32FenceHandle = u32FenceHandle;
+ fenceWait.u32TimeoutUS = u32TimeoutUS;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pKmtCallbacks->hAdapter;
+ EscapeData.hDevice = pKmtCallbacks->hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = &fenceWait;
+ EscapeData.PrivateDriverDataSize = sizeof(fenceWait);
+ EscapeData.hContext = 0;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ Assert(Status == STATUS_SUCCESS);
+ return Status == STATUS_SUCCESS;
+}
+
+/* static */ DECLCALLBACK(int)
+GaDrvEnvKmt::gaEnvFenceWait(void *pvEnv,
+ uint32_t u32FenceHandle,
+ uint32_t u32TimeoutUS)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ if (!pThis->mKmtCallbacks.hDevice)
+ return 0;
+
+ bool fSuccess = vboxDdiFenceWait(&pThis->mKmtCallbacks, u32FenceHandle, u32TimeoutUS);
+ return fSuccess ? 0 : -1;
+}
+
+static bool
+vboxDdiFenceUnref(GaKmtCallbacks *pKmtCallbacks,
+ uint32_t u32FenceHandle)
+{
+ VBOXDISPIFESCAPE_GAFENCEUNREF fenceUnref;
+ memset(&fenceUnref, 0, sizeof(fenceUnref));
+ fenceUnref.EscapeHdr.escapeCode = VBOXESC_GAFENCEUNREF;
+ // pFenceUnref->EscapeHdr.cmdSpecific = 0;
+ fenceUnref.u32FenceHandle = u32FenceHandle;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pKmtCallbacks->hAdapter;
+ EscapeData.hDevice = pKmtCallbacks->hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = &fenceUnref;
+ EscapeData.PrivateDriverDataSize = sizeof(fenceUnref);
+ EscapeData.hContext = 0;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ Assert(Status == STATUS_SUCCESS);
+ return Status == STATUS_SUCCESS;
+}
+
+/* static */ DECLCALLBACK(void)
+GaDrvEnvKmt::gaEnvFenceUnref(void *pvEnv,
+ uint32_t u32FenceHandle)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ if (!pThis->mKmtCallbacks.hDevice)
+ return;
+
+ vboxDdiFenceUnref(&pThis->mKmtCallbacks, u32FenceHandle);
+}
+
+/** Calculate how many commands will fit in the buffer.
+ *
+ * @param pu8Commands Command buffer.
+ * @param cbCommands Size of command buffer.
+ * @param cbAvail Available buffer size..
+ * @param pu32Length Size of commands which will fit in cbAvail bytes.
+ */
+static bool
+vboxCalcCommandLength(const uint8_t *pu8Commands, uint32_t cbCommands, uint32_t cbAvail, uint32_t *pu32Length)
+{
+ uint32_t u32Length = 0;
+ const uint8_t *pu8Src = pu8Commands;
+ const uint8_t *pu8SrcEnd = pu8Commands + cbCommands;
+
+ while (pu8SrcEnd > pu8Src)
+ {
+ const uint32_t cbSrcLeft = pu8SrcEnd - pu8Src;
+ if (cbSrcLeft < sizeof(uint32_t))
+ {
+ return false;
+ }
+
+ /* Get the command id and command length. */
+ const uint32_t u32CmdId = *(uint32_t *)pu8Src;
+ uint32_t cbCmd = 0;
+
+ if (SVGA_3D_CMD_BASE <= u32CmdId && u32CmdId < SVGA_3D_CMD_MAX)
+ {
+ if (cbSrcLeft < sizeof(SVGA3dCmdHeader))
+ {
+ return false;
+ }
+
+ const SVGA3dCmdHeader *pHeader = (SVGA3dCmdHeader *)pu8Src;
+ cbCmd = sizeof(SVGA3dCmdHeader) + pHeader->size;
+ if (cbCmd % sizeof(uint32_t) != 0)
+ {
+ return false;
+ }
+ if (cbSrcLeft < cbCmd)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ /* It is not expected that any of common SVGA commands will be in the command buffer
+ * because the SVGA gallium driver does not use them.
+ */
+ return false;
+ }
+
+ if (u32Length + cbCmd > cbAvail)
+ {
+ if (u32Length == 0)
+ {
+ /* No commands fit into the buffer. */
+ return false;
+ }
+ break;
+ }
+
+ pu8Src += cbCmd;
+ u32Length += cbCmd;
+ }
+
+ *pu32Length = u32Length;
+ return true;
+}
+
+static bool
+vboxDdiRender(GaKmtCallbacks *pKmtCallbacks,
+ GAWDDMCONTEXTINFO *pContextInfo, uint32_t u32FenceHandle, void *pvCommands, uint32_t cbCommands,
+ ULONGLONG PresentHistoryToken, bool fPresentRedirected)
+{
+ uint32_t cbLeft;
+ const uint8_t *pu8Src;
+
+ cbLeft = cbCommands;
+ pu8Src = (uint8_t *)pvCommands;
+ /* Even when cbCommands is 0, submit the fence. The following code deals with this. */
+ do
+ {
+ /* Actually available space. */
+ const uint32_t cbAvail = pContextInfo->CommandBufferSize;
+ if (cbAvail <= sizeof(u32FenceHandle))
+ {
+ return false;
+ }
+
+ /* How many bytes of command data still to copy. */
+ uint32_t cbCommandChunk = cbLeft;
+
+ /* How many bytes still to copy. */
+ uint32_t cbToCopy = sizeof(u32FenceHandle) + cbCommandChunk;
+
+ /* Copy the buffer identifier. */
+ if (cbToCopy <= cbAvail)
+ {
+ /* Command buffer is big enough. */
+ *(uint32_t *)pContextInfo->pCommandBuffer = u32FenceHandle;
+ }
+ else
+ {
+ /* Split. Write zero as buffer identifier. */
+ *(uint32_t *)pContextInfo->pCommandBuffer = 0;
+
+ /* Get how much commands data will fit in the buffer. */
+ if (!vboxCalcCommandLength(pu8Src, cbCommandChunk, cbAvail - sizeof(u32FenceHandle), &cbCommandChunk))
+ {
+ return false;
+ }
+
+ cbToCopy = sizeof(u32FenceHandle) + cbCommandChunk;
+ }
+
+ if (cbCommandChunk)
+ {
+ /* Copy the command data. */
+ memcpy((uint8_t *)pContextInfo->pCommandBuffer + sizeof(u32FenceHandle), pu8Src, cbCommandChunk);
+ }
+
+ /* Advance the command position. */
+ pu8Src += cbCommandChunk;
+ cbLeft -= cbCommandChunk;
+
+ D3DKMT_RENDER RenderData;
+ memset(&RenderData, 0, sizeof(RenderData));
+ RenderData.hContext = pContextInfo->hContext;
+ // RenderData.CommandOffset = 0;
+ RenderData.CommandLength = cbToCopy;
+ // RenderData.AllocationCount = 0;
+ // RenderData.PatchLocationCount = 0;
+ RenderData.PresentHistoryToken = PresentHistoryToken;
+ RenderData.Flags.PresentRedirected = fPresentRedirected;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTRender(&RenderData);
+ Assert(Status == STATUS_SUCCESS);
+ if (Status != STATUS_SUCCESS)
+ {
+ return false;
+ }
+
+ pContextInfo->pCommandBuffer = RenderData.pNewCommandBuffer;
+ pContextInfo->CommandBufferSize = RenderData.NewCommandBufferSize;
+ pContextInfo->pAllocationList = RenderData.pNewAllocationList;
+ pContextInfo->AllocationListSize = RenderData.NewAllocationListSize;
+ pContextInfo->pPatchLocationList = RenderData.pNewPatchLocationList;
+ pContextInfo->PatchLocationListSize = RenderData.NewPatchLocationListSize;
+ } while (cbLeft);
+
+ return true;
+}
+
+bool GaDrvEnvKmt::doRender(uint32_t u32Cid, void *pvCommands, uint32_t cbCommands,
+ GAFENCEQUERY *pFenceQuery, ULONGLONG PresentHistoryToken, bool fPresentRedirected)
+{
+ uint32_t u32FenceHandle;
+ GAWDDMCONTEXTINFO *pContextInfo = (GAWDDMCONTEXTINFO *)RTAvlU32Get(&mContextTree, u32Cid);
+ if (!pContextInfo)
+ return false;
+
+ bool fSuccess = true;
+ u32FenceHandle = 0;
+ if (pFenceQuery)
+ {
+ fSuccess = vboxDdiFenceCreate(&mKmtCallbacks, pContextInfo, &u32FenceHandle);
+ }
+
+ if (fSuccess)
+ {
+ fSuccess = vboxDdiRender(&mKmtCallbacks, pContextInfo, u32FenceHandle,
+ pvCommands, cbCommands, PresentHistoryToken, fPresentRedirected);
+ if (fSuccess)
+ {
+ if (pFenceQuery)
+ {
+ if (!vboxDdiFenceQuery(&mKmtCallbacks, u32FenceHandle, pFenceQuery))
+ {
+ pFenceQuery->u32FenceStatus = GA_FENCE_STATUS_NULL;
+ }
+ }
+ }
+ }
+ return fSuccess;
+}
+
+/* static */ DECLCALLBACK(int)
+GaDrvEnvKmt::gaEnvRender(void *pvEnv,
+ uint32_t u32Cid,
+ void *pvCommands,
+ uint32_t cbCommands,
+ GAFENCEQUERY *pFenceQuery)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+ return pThis->doRender(u32Cid, pvCommands, cbCommands, pFenceQuery, 0, false) ? 1 : 0;
+}
+
+bool GaDrvEnvKmt::drvEnvKmtRenderCompose(uint32_t u32Cid,
+ void *pvCommands,
+ uint32_t cbCommands,
+ ULONGLONG PresentHistoryToken)
+{
+ return doRender(u32Cid, pvCommands, cbCommands, NULL, PresentHistoryToken, true);
+}
+
+
+static bool
+vboxDdiRegionCreate(GaKmtCallbacks *pKmtCallbacks,
+ uint32_t u32RegionSize,
+ uint32_t *pu32GmrId,
+ void **ppvMap)
+{
+ VBOXDISPIFESCAPE_GAREGION data;
+ memset(&data, 0, sizeof(data));
+ data.EscapeHdr.escapeCode = VBOXESC_GAREGION;
+ // data.EscapeHdr.cmdSpecific = 0;
+ data.u32Command = GA_REGION_CMD_CREATE;
+ data.u32NumPages = (u32RegionSize + PAGE_SIZE - 1) / PAGE_SIZE;
+ // data.u32GmrId = 0;
+ // data.u64UserAddress = 0;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pKmtCallbacks->hAdapter;
+ EscapeData.hDevice = pKmtCallbacks->hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = &data;
+ EscapeData.PrivateDriverDataSize = sizeof(data);
+ // EscapeData.hContext = 0;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ if (Status == STATUS_SUCCESS)
+ {
+ *pu32GmrId = data.u32GmrId;
+ *ppvMap = (void *)(uintptr_t)data.u64UserAddress;
+ return true;
+ }
+
+ Assert(0);
+ return false;
+}
+
+/* static */ DECLCALLBACK(int)
+GaDrvEnvKmt::gaEnvRegionCreate(void *pvEnv,
+ uint32_t u32RegionSize,
+ uint32_t *pu32GmrId,
+ void **ppvMap)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ if (pThis->mKmtCallbacks.hDevice)
+ {
+ /* That is a real device */
+ bool fSuccess = vboxDdiRegionCreate(&pThis->mKmtCallbacks, u32RegionSize, pu32GmrId, ppvMap);
+ return fSuccess ? 0: -1;
+ }
+
+ /* That is a fake device, created when WDDM adapter is initialized. */
+ *ppvMap = malloc(u32RegionSize);
+ if (*ppvMap)
+ {
+ *pu32GmrId = 0;
+ return 0;
+ }
+
+ return -1;
+}
+
+static bool
+vboxDdiRegionDestroy(GaKmtCallbacks *pKmtCallbacks,
+ uint32_t u32GmrId)
+{
+ VBOXDISPIFESCAPE_GAREGION data;
+ memset(&data, 0, sizeof(data));
+ data.EscapeHdr.escapeCode = VBOXESC_GAREGION;
+ // data.EscapeHdr.cmdSpecific = 0;
+ data.u32Command = GA_REGION_CMD_DESTROY;
+ // data.u32NumPages = 0;
+ data.u32GmrId = u32GmrId;
+ // data.u64UserAddress = 0;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pKmtCallbacks->hAdapter;
+ EscapeData.hDevice = pKmtCallbacks->hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ EscapeData.Flags.HardwareAccess = 1; /* Sync with submitted commands. */
+ EscapeData.pPrivateDriverData = &data;
+ EscapeData.PrivateDriverDataSize = sizeof(data);
+ // EscapeData.hContext = 0;
+
+ NTSTATUS Status = pKmtCallbacks->d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ Assert(Status == STATUS_SUCCESS);
+ return Status == STATUS_SUCCESS;
+}
+
+/* static */ DECLCALLBACK(void)
+GaDrvEnvKmt::gaEnvRegionDestroy(void *pvEnv,
+ uint32_t u32GmrId,
+ void *pvMap)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ if (pThis->mKmtCallbacks.hDevice)
+ {
+ vboxDdiRegionDestroy(&pThis->mKmtCallbacks, u32GmrId);
+ }
+ else
+ {
+ free(pvMap);
+ }
+}
+
+/* static */ DECLCALLBACK(int)
+GaDrvEnvKmt::gaEnvGBSurfaceDefine(void *pvEnv,
+ SVGAGBSURFCREATE *pCreateParms)
+{
+ GaDrvEnvKmt *pThis = (GaDrvEnvKmt *)pvEnv;
+
+ VBOXDISPIFESCAPE_SVGAGBSURFACEDEFINE data;
+ data.EscapeHdr.escapeCode = VBOXESC_SVGAGBSURFACEDEFINE;
+ data.EscapeHdr.u32CmdSpecific = 0;
+ data.CreateParms = *pCreateParms;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = pThis->mKmtCallbacks.hAdapter;
+ EscapeData.hDevice = pThis->mKmtCallbacks.hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ EscapeData.Flags.HardwareAccess = 1;
+ EscapeData.pPrivateDriverData = &data;
+ EscapeData.PrivateDriverDataSize = sizeof(data);
+ // EscapeData.hContext = 0;
+
+ NTSTATUS Status = pThis->mKmtCallbacks.d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ if (Status == STATUS_SUCCESS)
+ {
+ pCreateParms->gmrid = data.CreateParms.gmrid;
+ pCreateParms->cbGB = data.CreateParms.cbGB;
+ pCreateParms->u64UserAddress = data.CreateParms.u64UserAddress;
+ pCreateParms->u32Sid = data.CreateParms.u32Sid;
+
+ /* Create a kernel mode allocation for render targets,
+ * because we will need kernel mode handles for Present.
+ */
+ if (pCreateParms->s.flags & SVGA3D_SURFACE_HINT_RENDERTARGET)
+ {
+ /* First check if the format is supported. */
+ D3DDDIFORMAT const ddiFormat = svgaToD3DDDIFormat((SVGA3dSurfaceFormat)pCreateParms->s.format);
+ if (ddiFormat != D3DDDIFMT_UNKNOWN)
+ {
+ GAWDDMSURFACEINFO *pSurfaceInfo = (GAWDDMSURFACEINFO *)malloc(sizeof(GAWDDMSURFACEINFO));
+ if (pSurfaceInfo)
+ {
+ memset(pSurfaceInfo, 0, sizeof(GAWDDMSURFACEINFO));
+
+ VBOXWDDM_ALLOCINFO wddmAllocInfo;
+ memset(&wddmAllocInfo, 0, sizeof(wddmAllocInfo));
+
+ wddmAllocInfo.enmType = VBOXWDDM_ALLOC_TYPE_UMD_RC_GENERIC;
+ wddmAllocInfo.fFlags.RenderTarget = 1;
+ wddmAllocInfo.hSharedHandle = 0;
+ wddmAllocInfo.hostID = pCreateParms->u32Sid;
+ wddmAllocInfo.SurfDesc.slicePitch = 0;
+ wddmAllocInfo.SurfDesc.depth = pCreateParms->s.size.depth;
+ wddmAllocInfo.SurfDesc.width = pCreateParms->s.size.width;
+ wddmAllocInfo.SurfDesc.height = pCreateParms->s.size.height;
+ wddmAllocInfo.SurfDesc.format = ddiFormat;
+ wddmAllocInfo.SurfDesc.VidPnSourceId = 0;
+ wddmAllocInfo.SurfDesc.bpp = vboxWddmCalcBitsPerPixel(wddmAllocInfo.SurfDesc.format);
+ wddmAllocInfo.SurfDesc.pitch = vboxWddmCalcPitch(wddmAllocInfo.SurfDesc.width,
+ wddmAllocInfo.SurfDesc.format);
+ wddmAllocInfo.SurfDesc.cbSize = vboxWddmCalcSize(wddmAllocInfo.SurfDesc.pitch,
+ wddmAllocInfo.SurfDesc.height,
+ wddmAllocInfo.SurfDesc.format);
+ wddmAllocInfo.SurfDesc.d3dWidth = vboxWddmCalcWidthForPitch(wddmAllocInfo.SurfDesc.pitch,
+ wddmAllocInfo.SurfDesc.format);
+
+ D3DDDI_ALLOCATIONINFO AllocationInfo;
+ memset(&AllocationInfo, 0, sizeof(AllocationInfo));
+ // AllocationInfo.hAllocation = NULL;
+ // AllocationInfo.pSystemMem = NULL;
+ AllocationInfo.pPrivateDriverData = &wddmAllocInfo;
+ AllocationInfo.PrivateDriverDataSize = sizeof(wddmAllocInfo);
+
+ D3DKMT_CREATEALLOCATION CreateAllocation;
+ memset(&CreateAllocation, 0, sizeof(CreateAllocation));
+ CreateAllocation.hDevice = pThis->mKmtCallbacks.hDevice;
+ CreateAllocation.NumAllocations = 1;
+ CreateAllocation.pAllocationInfo = &AllocationInfo;
+
+ Status = pThis->mKmtCallbacks.d3dkmt->pfnD3DKMTCreateAllocation(&CreateAllocation);
+ if (Status == STATUS_SUCCESS)
+ {
+ pSurfaceInfo->Core.Key = pCreateParms->u32Sid;
+ pSurfaceInfo->hAllocation = AllocationInfo.hAllocation;
+ if (!RTAvlU32Insert(&pThis->mSurfaceTree, &pSurfaceInfo->Core))
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+ if (Status != STATUS_SUCCESS)
+ {
+ free(pSurfaceInfo);
+ }
+ }
+ else
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ /* Unsupported render target format. */
+ Assert(0);
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ }
+
+ if (Status != STATUS_SUCCESS)
+ {
+ gaEnvSurfaceDestroy(pvEnv, pCreateParms->u32Sid);
+ }
+ }
+
+ if (Status == STATUS_SUCCESS)
+ return 0;
+
+ Assert(0);
+ return -1;
+}
+
+GaDrvEnvKmt::GaDrvEnvKmt()
+ :
+ mContextTree(0),
+ mSurfaceTree(0)
+{
+ RT_ZERO(mKmtCallbacks);
+ RT_ZERO(mHWInfo);
+ RT_ZERO(mEnv);
+}
+
+GaDrvEnvKmt::~GaDrvEnvKmt()
+{
+}
+
+HRESULT GaDrvEnvKmt::Init(void)
+{
+ mKmtCallbacks.d3dkmt = D3DKMTFunctions();
+
+ /* Figure out which adapter to use. */
+ NTSTATUS Status = vboxDispKmtOpenAdapter2(&mKmtCallbacks.hAdapter, &mKmtCallbacks.AdapterLuid);
+ Assert(Status == STATUS_SUCCESS);
+ if (Status == STATUS_SUCCESS)
+ {
+ VBOXWDDM_QAI adapterInfo;
+ bool fSuccess = vboxDdiQueryAdapterInfo(&mKmtCallbacks, mKmtCallbacks.hAdapter, &adapterInfo, sizeof(adapterInfo));
+ Assert(fSuccess);
+ if (fSuccess)
+ {
+ fSuccess = vboxDdiDeviceCreate(&mKmtCallbacks, &mKmtCallbacks.hDevice);
+ Assert(fSuccess);
+ if (fSuccess)
+ {
+ mHWInfo = adapterInfo.u.vmsvga.HWInfo;
+
+ /*
+ * Success.
+ */
+ return S_OK;
+ }
+
+ vboxDdiDeviceDestroy(&mKmtCallbacks, mKmtCallbacks.hDevice);
+ }
+
+ vboxDispKmtCloseAdapter(mKmtCallbacks.hAdapter);
+ }
+
+ return E_FAIL;
+}
+
+const WDDMGalliumDriverEnv *GaDrvEnvKmt::Env()
+{
+ if (mEnv.cb == 0)
+ {
+ mEnv.cb = sizeof(WDDMGalliumDriverEnv);
+ mEnv.pHWInfo = &mHWInfo;
+ mEnv.pvEnv = this;
+ mEnv.pfnContextCreate = gaEnvContextCreate;
+ mEnv.pfnContextDestroy = gaEnvContextDestroy;
+ mEnv.pfnSurfaceDefine = gaEnvSurfaceDefine;
+ mEnv.pfnSurfaceDestroy = gaEnvSurfaceDestroy;
+ mEnv.pfnRender = gaEnvRender;
+ mEnv.pfnFenceUnref = gaEnvFenceUnref;
+ mEnv.pfnFenceQuery = gaEnvFenceQuery;
+ mEnv.pfnFenceWait = gaEnvFenceWait;
+ mEnv.pfnRegionCreate = gaEnvRegionCreate;
+ mEnv.pfnRegionDestroy = gaEnvRegionDestroy;
+ /* VGPU10 */
+ mEnv.pfnGBSurfaceDefine = gaEnvGBSurfaceDefine;
+ }
+
+ return &mEnv;
+}
+
+RT_C_DECLS_BEGIN
+
+const WDDMGalliumDriverEnv *GaDrvEnvKmtCreate(void)
+{
+ GaDrvEnvKmt *p = new GaDrvEnvKmt();
+ if (p)
+ {
+ HRESULT hr = p->Init();
+ if (hr != S_OK)
+ {
+ delete p;
+ p = NULL;
+ }
+ }
+ return p ? p->Env() : NULL;
+}
+
+void GaDrvEnvKmtDelete(const WDDMGalliumDriverEnv *pEnv)
+{
+ if (pEnv)
+ {
+ GaDrvEnvKmt *p = (GaDrvEnvKmt *)pEnv->pvEnv;
+ delete p;
+ }
+}
+
+D3DKMT_HANDLE GaDrvEnvKmtContextHandle(const WDDMGalliumDriverEnv *pEnv, uint32_t u32Cid)
+{
+ GaDrvEnvKmt *p = (GaDrvEnvKmt *)pEnv->pvEnv;
+ return p->drvEnvKmtContextHandle(u32Cid);
+}
+
+D3DKMT_HANDLE GaDrvEnvKmtSurfaceHandle(const WDDMGalliumDriverEnv *pEnv, uint32_t u32Sid)
+{
+ GaDrvEnvKmt *p = (GaDrvEnvKmt *)pEnv->pvEnv;
+ return p->drvEnvKmtSurfaceHandle(u32Sid);
+}
+
+void GaDrvEnvKmtAdapterLUID(const WDDMGalliumDriverEnv *pEnv, LUID *pAdapterLuid)
+{
+ GaDrvEnvKmt *p = (GaDrvEnvKmt *)pEnv->pvEnv;
+ *pAdapterLuid = p->mKmtCallbacks.AdapterLuid;
+}
+
+D3DKMT_HANDLE GaDrvEnvKmtAdapterHandle(const WDDMGalliumDriverEnv *pEnv)
+{
+ GaDrvEnvKmt *p = (GaDrvEnvKmt *)pEnv->pvEnv;
+ return p->mKmtCallbacks.hAdapter;
+}
+
+D3DKMT_HANDLE GaDrvEnvKmtDeviceHandle(const WDDMGalliumDriverEnv *pEnv)
+{
+ GaDrvEnvKmt *p = (GaDrvEnvKmt *)pEnv->pvEnv;
+ return p->mKmtCallbacks.hDevice;
+}
+
+void GaDrvEnvKmtRenderCompose(const WDDMGalliumDriverEnv *pEnv,
+ uint32_t u32Cid,
+ void *pvCommands,
+ uint32_t cbCommands,
+ ULONGLONG PresentHistoryToken)
+{
+ GaDrvEnvKmt *p = (GaDrvEnvKmt *)pEnv->pvEnv;
+ p->drvEnvKmtRenderCompose(u32Cid, pvCommands, cbCommands, PresentHistoryToken);
+}
+
+RT_C_DECLS_END
diff --git a/src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.h b/src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.h
new file mode 100644
index 00000000..44cad791
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxGL/GaDrvEnvKMT.h
@@ -0,0 +1,58 @@
+/* $Id: GaDrvEnvKMT.h $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Gallium driver interface to the WDDM miniport driver.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_3D_win_VBoxGL_GaDrvEnvKMT_h
+#define GA_INCLUDED_SRC_3D_win_VBoxGL_GaDrvEnvKMT_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBoxGaDriver.h>
+#include <VBoxWddmUmHlp.h>
+
+RT_C_DECLS_BEGIN
+
+const WDDMGalliumDriverEnv *GaDrvEnvKmtCreate(void);
+void GaDrvEnvKmtDelete(const WDDMGalliumDriverEnv *pEnv);
+
+D3DKMT_HANDLE GaDrvEnvKmtContextHandle(const WDDMGalliumDriverEnv *pEnv,
+ uint32_t u32Cid);
+D3DKMT_HANDLE GaDrvEnvKmtSurfaceHandle(const WDDMGalliumDriverEnv *pEnv,
+ uint32_t u32Sid);
+void GaDrvEnvKmtAdapterLUID(const WDDMGalliumDriverEnv *pEnv,
+ LUID *pAdapterLuid);
+D3DKMT_HANDLE GaDrvEnvKmtAdapterHandle(const WDDMGalliumDriverEnv *pEnv);
+D3DKMT_HANDLE GaDrvEnvKmtDeviceHandle(const WDDMGalliumDriverEnv *pEnv);
+void GaDrvEnvKmtRenderCompose(const WDDMGalliumDriverEnv *pEnv,
+ uint32_t u32Cid,
+ void *pvCommands,
+ uint32_t cbCommands,
+ ULONGLONG PresentHistoryToken);
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_3D_win_VBoxGL_GaDrvEnvKMT_h */
diff --git a/src/VBox/Additions/3D/win/VBoxGL/Makefile.kmk b/src/VBox/Additions/3D/win/VBoxGL/Makefile.kmk
new file mode 100644
index 00000000..0a330258
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxGL/Makefile.kmk
@@ -0,0 +1,87 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxGL OpenGL state tracker.
+#
+
+#
+# Copyright (C) 2018-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+DLLS += VBoxGL
+DLLS.amd64 += VBoxGL-x86
+
+VBoxGL_TEMPLATE = VBoxMesa3DGuestR3DllMinVista
+VBoxGL_DEFS = VBOXGL
+# -wd4005: '__useHeader' : redefinition
+VBOXGL_DISABLED_WARNINGS := -wd4005
+# -wd4204: nonstandard extension used: non-constant aggregate initializer
+# -wd4267: 'initializing': conversion from 'size_t' to 'unsigned int', possible loss of data
+# -wd4459: stw_device.h(102): warning C4459: declaration of 'stw_dev' hides global declaration
+# -wd4668: c99_compat.h(99): warning C4668: '__STDC_VERSION__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
+VBOXGL_DISABLED_WARNINGS += -wd4204 -wd4267 -wd4459 -wd4668
+VBoxGL_CFLAGS = $(VBOXGL_DISABLED_WARNINGS)
+VBoxGL_CXXFLAGS = $(VBOXGL_DISABLED_WARNINGS)
+VBoxGL_INCS = \
+ $(VBOX_PATH_3D)/win/include \
+ $(VBOX_PATH_MESA)/include/GL \
+ $(VBOX_PATH_MESA)/src/gallium/frontends/wgl \
+ $(VBOX_PATH_MESA)/src/gallium/winsys/sw \
+ $(VBOX_PATH_MESA)/src/gallium/drivers \
+ $(PATH_ROOT)/src/VBox/Additions/WINNT/Graphics/Video \
+ $(PATH_ROOT)/src/VBox/Runtime/common/table \
+ $(VBOX_PATH_VMSVGA_INC) \
+ $(VBOX_GRAPHICS_INCS)
+VBoxGL_SOURCES = \
+ $(VBOX_PATH_MESA)/src/gallium/targets/libgl-gdi/opengl32.def \
+ VBoxGL.rc
+VBoxGL_SOURCES += \
+ GaDrvEnvKMT.cpp \
+ VBoxGL.c
+VBoxGL_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaGalliumAuxLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaWglLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaUtilLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaLib$(VBOX_SUFF_LIB)
+
+if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS)
+ VBoxGL_INSTTYPE = none
+ VBoxGL_DEBUG_INSTTYPE = both
+endif
+
+#
+# VBoxGL-x86 - x86 version of VBoxGL built for amd64 build
+#
+VBoxGL-x86_EXTENDS = VBoxGL
+VBoxGL-x86_BLD_TRG_ARCH = x86
+VBoxGL-x86_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaGalliumAuxLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaWglLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaUtilLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaLib-x86$(VBOX_SUFF_LIB)
+VBoxGL-x86_DEFS = $(VBoxGL_DEFS) VBOX_WOW64
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/3D/win/VBoxGL/VBoxGL.c b/src/VBox/Additions/3D/win/VBoxGL/VBoxGL.c
new file mode 100644
index 00000000..01b6cf89
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxGL/VBoxGL.c
@@ -0,0 +1,580 @@
+/* $Id: VBoxGL.c $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - OpenGL driver.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "GaDrvEnvKMT.h"
+
+#include "stw_winsys.h"
+#include "stw_device.h"
+#include "stw_context.h"
+
+#include "pipe/p_state.h"
+#include "svga3d_reg.h"
+
+#include <iprt/asm.h>
+
+#include <common/wddm/VBoxMPIf.h>
+
+#include <Psapi.h>
+
+static const char *g_pszSvgaDll =
+#ifdef VBOX_WOW64
+ "VBoxSVGA-x86.dll"
+#else
+ "VBoxSVGA.dll"
+#endif
+;
+
+static struct GaDrvFunctions
+{
+ PFNGaDrvScreenCreate pfnGaDrvScreenCreate;
+ PFNGaDrvScreenDestroy pfnGaDrvScreenDestroy;
+ PFNGaDrvGetWDDMEnv pfnGaDrvGetWDDMEnv;
+ PFNGaDrvGetContextId pfnGaDrvGetContextId;
+ PFNGaDrvGetSurfaceId pfnGaDrvGetSurfaceId;
+ PFNGaDrvContextFlush pfnGaDrvContextFlush;
+} g_drvfuncs;
+
+
+static HMODULE gaDrvLoadSVGA(struct GaDrvFunctions *pDrvFuncs)
+{
+ struct VBOXWDDMDLLPROC aDrvProcs[] =
+ {
+ { "GaDrvScreenCreate", (FARPROC *)&pDrvFuncs->pfnGaDrvScreenCreate },
+ { "GaDrvScreenDestroy", (FARPROC *)&pDrvFuncs->pfnGaDrvScreenDestroy },
+ { "GaDrvGetWDDMEnv", (FARPROC *)&pDrvFuncs->pfnGaDrvGetWDDMEnv },
+ { "GaDrvGetContextId", (FARPROC *)&pDrvFuncs->pfnGaDrvGetContextId },
+ { "GaDrvGetSurfaceId", (FARPROC *)&pDrvFuncs->pfnGaDrvGetSurfaceId },
+ { "GaDrvContextFlush", (FARPROC *)&pDrvFuncs->pfnGaDrvContextFlush },
+ { NULL, NULL }
+ };
+
+ HMODULE hmod = VBoxWddmLoadSystemDll(g_pszSvgaDll);
+ if (hmod)
+ {
+ VBoxWddmLoadAdresses(hmod, aDrvProcs);
+ }
+ return hmod;
+}
+
+struct stw_shared_surface
+{
+ D3DKMT_HANDLE hResource;
+ D3DKMT_HANDLE hSurface;
+ uint32_t u32Sid;
+};
+
+static NTSTATUS vboxKmtPresent(D3DKMT_HANDLE hContext, HWND hwnd, D3DKMT_HANDLE hSource, LONG lWidth, LONG lHeight)
+{
+ RECT r;
+ r.left = 0;
+ r.top = 0;
+ r.right = lWidth;
+ r.bottom = lHeight;
+
+ D3DKMT_PRESENT PresentData;
+ memset(&PresentData, 0, sizeof(PresentData));
+ PresentData.hContext = hContext;
+ PresentData.hWindow = hwnd;
+ PresentData.hSource = hSource;
+ PresentData.hDestination = 0;
+ PresentData.Flags.Blt = 1;
+ PresentData.Flags.SrcRectValid = 1;
+ PresentData.Flags.DstRectValid = 1;
+ PresentData.SrcRect = r;
+ PresentData.SubRectCnt = 1;
+ PresentData.pSrcSubRects = &r;
+ PresentData.DstRect = r;
+
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+ NTSTATUS Status = d3dkmt->pfnD3DKMTPresent(&PresentData);
+ return Status;
+}
+
+NTSTATUS vboxKmtOpenSharedSurface(D3DKMT_HANDLE hAdapter, D3DKMT_HANDLE hDevice, D3DKMT_HANDLE hSharedSurface, struct stw_shared_surface *pSurf)
+{
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+
+ D3DKMT_QUERYRESOURCEINFO QueryResourceInfoData;
+ memset(&QueryResourceInfoData, 0, sizeof(QueryResourceInfoData));
+ QueryResourceInfoData.hDevice = hDevice;
+ QueryResourceInfoData.hGlobalShare = hSharedSurface;
+
+ NTSTATUS Status = d3dkmt->pfnD3DKMTQueryResourceInfo(&QueryResourceInfoData);
+ if (Status == STATUS_SUCCESS)
+ {
+ D3DDDI_OPENALLOCATIONINFO OpenAllocationInfoData;
+ memset(&OpenAllocationInfoData, 0, sizeof(OpenAllocationInfoData));
+
+ D3DKMT_OPENRESOURCE OpenResourceData;
+ memset(&OpenResourceData, 0, sizeof(OpenResourceData));
+ OpenResourceData.hDevice = hDevice;
+ OpenResourceData.hGlobalShare = hSharedSurface;
+ OpenResourceData.NumAllocations = 1;
+ OpenResourceData.pOpenAllocationInfo = &OpenAllocationInfoData;
+ if (QueryResourceInfoData.PrivateRuntimeDataSize)
+ {
+ OpenResourceData.pPrivateRuntimeData = malloc(QueryResourceInfoData.PrivateRuntimeDataSize);
+ if (OpenResourceData.pPrivateRuntimeData == NULL)
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ OpenResourceData.PrivateRuntimeDataSize = QueryResourceInfoData.PrivateRuntimeDataSize;
+ }
+ if (QueryResourceInfoData.ResourcePrivateDriverDataSize)
+ {
+ OpenResourceData.pResourcePrivateDriverData = malloc(QueryResourceInfoData.ResourcePrivateDriverDataSize);
+ if (OpenResourceData.pResourcePrivateDriverData == NULL)
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ OpenResourceData.ResourcePrivateDriverDataSize = QueryResourceInfoData.ResourcePrivateDriverDataSize;
+ }
+ if (QueryResourceInfoData.TotalPrivateDriverDataSize)
+ {
+ OpenResourceData.pTotalPrivateDriverDataBuffer = malloc(QueryResourceInfoData.TotalPrivateDriverDataSize);
+ if (OpenResourceData.pTotalPrivateDriverDataBuffer == NULL)
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+ OpenResourceData.TotalPrivateDriverDataBufferSize = QueryResourceInfoData.TotalPrivateDriverDataSize;
+ }
+
+ if (Status == STATUS_SUCCESS)
+ {
+ Status = d3dkmt->pfnD3DKMTOpenResource(&OpenResourceData);
+ if (Status == STATUS_SUCCESS)
+ {
+ if (OpenAllocationInfoData.PrivateDriverDataSize == sizeof(VBOXWDDM_ALLOCINFO))
+ {
+ VBOXWDDM_ALLOCINFO *pVBoxAllocInfo = (VBOXWDDM_ALLOCINFO *)OpenAllocationInfoData.pPrivateDriverData;
+ pSurf->hResource = OpenResourceData.hResource;
+ pSurf->hSurface = OpenAllocationInfoData.hAllocation;
+ pSurf->u32Sid = pVBoxAllocInfo->hostID;
+ }
+ else if (OpenAllocationInfoData.PrivateDriverDataSize == sizeof(VBOXDXALLOCATIONDESC))
+ {
+ //VBOXDXALLOCATIONDESC *pAllocDesc = (VBOXDXALLOCATIONDESC *)OpenAllocationInfoData.PrivateDriverDataSize;
+ pSurf->hResource = OpenResourceData.hResource;
+ pSurf->hSurface = OpenAllocationInfoData.hAllocation;
+
+ VBOXDISPIFESCAPE_SVGAGETSID data;
+ memset(&data, 0, sizeof(data));
+ data.EscapeHdr.escapeCode = VBOXESC_SVGAGETSID;
+ data.hAllocation = OpenAllocationInfoData.hAllocation;
+ // data.u32Sid = 0;
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = hAdapter;
+ EscapeData.hDevice = hDevice;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = &data;
+ EscapeData.PrivateDriverDataSize = sizeof(data);
+ // EscapeData.hContext = 0;
+ Status = d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ if (Status == STATUS_SUCCESS)
+ pSurf->u32Sid = data.u32Sid;
+ else
+ Assert(0);
+ }
+ else
+ Assert(0);
+ }
+ }
+
+ if (OpenResourceData.pPrivateRuntimeData != NULL)
+ {
+ free(OpenResourceData.pPrivateRuntimeData);
+ }
+ if (OpenResourceData.pResourcePrivateDriverData != NULL)
+ {
+ free(OpenResourceData.pResourcePrivateDriverData);
+ }
+ if (OpenResourceData.pTotalPrivateDriverDataBuffer != NULL)
+ {
+ free(OpenResourceData.pTotalPrivateDriverDataBuffer);
+ }
+ }
+
+ return Status;
+}
+
+NTSTATUS vboxKmtCloseSharedSurface(D3DKMT_HANDLE hDevice, struct stw_shared_surface *pSurf)
+{
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+
+ D3DKMT_DESTROYALLOCATION DestroyAllocationData;
+ memset(&DestroyAllocationData, 0, sizeof(DestroyAllocationData));
+ DestroyAllocationData.hDevice = hDevice;
+ DestroyAllocationData.hResource = pSurf->hResource;
+ /* "If the OpenGL ICD sets the handle in the hResource member to a non-NULL value,
+ * the ICD must set phAllocationList to NULL." and
+ * "the AllocationCount member is ignored by the OpenGL runtime."
+ */
+ // DestroyAllocationData.phAllocationList = NULL;
+ // DestroyAllocationData.AllocationCount = 0;
+
+ NTSTATUS Status = d3dkmt->pfnD3DKMTDestroyAllocation(&DestroyAllocationData);
+ Assert(Status == STATUS_SUCCESS);
+ return Status;
+}
+
+
+static struct pipe_screen *
+wddm_screen_create(HDC hDC)
+{
+ RT_NOREF(hDC); /** @todo Use it? */
+ struct pipe_screen *screen = NULL;
+
+ if (gaDrvLoadSVGA(&g_drvfuncs))
+ {
+ WDDMGalliumDriverEnv const *pEnv = GaDrvEnvKmtCreate();
+ if (pEnv)
+ {
+ /// @todo pEnv to include destructor callback, to be called from winsys screen destructor?
+ screen = g_drvfuncs.pfnGaDrvScreenCreate(pEnv);
+ }
+ }
+
+ return screen;
+}
+
+static void
+wddm_present(struct pipe_screen *screen,
+ struct pipe_context *context,
+ struct pipe_resource *res,
+ HDC hDC)
+{
+ RT_NOREF(context);
+ struct stw_context *ctx = stw_current_context();
+ struct pipe_context *pipe = ctx->st->pipe;
+
+ const WDDMGalliumDriverEnv *pEnv = g_drvfuncs.pfnGaDrvGetWDDMEnv(screen);
+ if (pEnv)
+ {
+ /* Get context and kernel-mode handle of the resource. */
+ uint32_t u32Cid = g_drvfuncs.pfnGaDrvGetContextId(pipe);
+ D3DKMT_HANDLE hContext = GaDrvEnvKmtContextHandle(pEnv, u32Cid);
+
+ uint32_t u32SourceSid = g_drvfuncs.pfnGaDrvGetSurfaceId(screen, res);
+ D3DKMT_HANDLE hSource = GaDrvEnvKmtSurfaceHandle(pEnv, u32SourceSid);
+
+ HWND hwnd = WindowFromDC(hDC);
+
+ vboxKmtPresent(hContext, hwnd, hSource, res->width0, res->height0);
+ }
+}
+
+static boolean
+wddm_get_adapter_luid(struct pipe_screen *screen,
+ HDC hDC,
+ LUID *pAdapterLuid)
+{
+ RT_NOREF(hDC); /** @todo Use it? */
+ const WDDMGalliumDriverEnv *pEnv = g_drvfuncs.pfnGaDrvGetWDDMEnv(screen);
+ if (pEnv)
+ {
+ GaDrvEnvKmtAdapterLUID(pEnv, pAdapterLuid);
+ return true;
+ }
+
+ return false;
+}
+
+static struct stw_shared_surface *
+wddm_shared_surface_open(struct pipe_screen *screen,
+ HANDLE hSharedSurface)
+{
+ struct stw_shared_surface *surface = NULL;
+
+ const WDDMGalliumDriverEnv *pEnv = g_drvfuncs.pfnGaDrvGetWDDMEnv(screen);
+ if (pEnv)
+ {
+ surface = (struct stw_shared_surface *)malloc(sizeof(struct stw_shared_surface));
+ if (surface)
+ {
+ D3DKMT_HANDLE hAdapter = GaDrvEnvKmtAdapterHandle(pEnv);
+ D3DKMT_HANDLE hDevice = GaDrvEnvKmtDeviceHandle(pEnv);
+ NTSTATUS Status = vboxKmtOpenSharedSurface(hAdapter, hDevice, (D3DKMT_HANDLE)(uintptr_t)hSharedSurface, surface);
+ if (Status != STATUS_SUCCESS)
+ {
+ free(surface);
+ surface = NULL;
+ }
+ }
+ }
+ return surface;
+}
+
+static void
+wddm_shared_surface_close(struct pipe_screen *screen,
+ struct stw_shared_surface *surface)
+{
+ const WDDMGalliumDriverEnv *pEnv = g_drvfuncs.pfnGaDrvGetWDDMEnv(screen);
+ if (pEnv)
+ {
+ D3DKMT_HANDLE hDevice = GaDrvEnvKmtDeviceHandle(pEnv);
+ vboxKmtCloseSharedSurface(hDevice, surface);
+ }
+ free(surface);
+}
+
+static void
+wddm_compose(struct pipe_screen *screen,
+ struct pipe_resource *res,
+ struct stw_shared_surface *dest,
+ LPCRECT pRect,
+ ULONGLONG PresentHistoryToken)
+{
+ struct stw_context *ctx = stw_current_context();
+ struct pipe_context *pipe = ctx->st->pipe;
+
+ /* The ICD asked to present something, make sure that any outstanding commends are submitted. */
+ g_drvfuncs.pfnGaDrvContextFlush(pipe);
+
+ uint32_t u32SourceSid = g_drvfuncs.pfnGaDrvGetSurfaceId(screen, res);
+
+ /* Generate SVGA_3D_CMD_SURFACE_COPY command for these resources. */
+ struct
+ {
+ SVGA3dCmdHeader header;
+ SVGA3dCmdSurfaceCopy surfaceCopy;
+ SVGA3dCopyBox box;
+ } command;
+
+ command.header.id = SVGA_3D_CMD_SURFACE_COPY;
+ command.header.size = sizeof(command) - sizeof(SVGA3dCmdHeader);
+
+ command.surfaceCopy.src.sid = u32SourceSid;
+ command.surfaceCopy.src.face = 0;
+ command.surfaceCopy.src.mipmap = 0;
+ command.surfaceCopy.dest.sid = dest->u32Sid;
+ command.surfaceCopy.dest.face = 0;
+ command.surfaceCopy.dest.mipmap = 0;
+
+ command.box.x = pRect->left;
+ command.box.y = pRect->top;
+ command.box.z = 0;
+ command.box.w = pRect->right - pRect->left;
+ command.box.h = pRect->bottom - pRect->top;
+ command.box.d = 1;
+ command.box.srcx = 0;
+ command.box.srcy = 0;
+ command.box.srcz = 0;
+
+ const WDDMGalliumDriverEnv *pEnv = g_drvfuncs.pfnGaDrvGetWDDMEnv(screen);
+ if (pEnv)
+ {
+ uint32_t u32Cid = g_drvfuncs.pfnGaDrvGetContextId(pipe);
+ GaDrvEnvKmtRenderCompose(pEnv, u32Cid, &command, sizeof(command), PresentHistoryToken);
+ }
+}
+
+static unsigned
+wddm_get_pfd_flags(struct pipe_screen *screen)
+{
+ (void)screen;
+ return stw_pfd_gdi_support | stw_pfd_double_buffer;
+}
+
+static const char *
+wddm_get_name(void)
+{
+ return "VBoxGL";
+}
+
+static const struct stw_winsys stw_winsys = {
+ wddm_screen_create,
+ wddm_present,
+ wddm_get_adapter_luid,
+ wddm_shared_surface_open,
+ wddm_shared_surface_close,
+ wddm_compose,
+ wddm_get_pfd_flags,
+ NULL, /* create_framebuffer */
+ wddm_get_name,
+};
+
+#ifdef DEBUG
+typedef BOOL WINAPI FNGetModuleInformation(HANDLE hProcess, HMODULE hModule, LPMODULEINFO lpmodinfo, DWORD cb);
+typedef FNGetModuleInformation *PFNGetModuleInformation;
+
+static PFNGetModuleInformation g_pfnGetModuleInformation = NULL;
+static HMODULE g_hModPsapi = NULL;
+static PVOID g_VBoxWDbgVEHandler = NULL;
+
+static bool vboxVDbgIsAddressInModule(PVOID pv, const char *pszModuleName)
+{
+ HMODULE hMod = GetModuleHandleA(pszModuleName);
+ if (!hMod)
+ return false;
+
+ if (!g_pfnGetModuleInformation)
+ return false;
+
+ HANDLE hProcess = GetCurrentProcess();
+ MODULEINFO ModuleInfo = {0};
+ if (!g_pfnGetModuleInformation(hProcess, hMod, &ModuleInfo, sizeof(ModuleInfo)))
+ return false;
+
+ return (uintptr_t)ModuleInfo.lpBaseOfDll <= (uintptr_t)pv
+ && (uintptr_t)pv < (uintptr_t)ModuleInfo.lpBaseOfDll + ModuleInfo.SizeOfImage;
+}
+
+static bool vboxVDbgIsExceptionIgnored(PEXCEPTION_RECORD pExceptionRecord)
+{
+ /* Module (dll) names for GetModuleHandle.
+ * Exceptions originated from these modules will be ignored.
+ */
+ static const char *apszIgnoredModuleNames[] =
+ {
+ NULL
+ };
+
+ int i = 0;
+ while (apszIgnoredModuleNames[i])
+ {
+ if (vboxVDbgIsAddressInModule(pExceptionRecord->ExceptionAddress, apszIgnoredModuleNames[i]))
+ return true;
+
+ ++i;
+ }
+
+ return false;
+}
+
+static LONG WINAPI vboxVDbgVectoredHandler(struct _EXCEPTION_POINTERS *pExceptionInfo) RT_NOTHROW_DEF
+{
+ static volatile bool g_fAllowIgnore = true; /* Might be changed in kernel debugger. */
+
+ PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;
+ /* PCONTEXT pContextRecord = pExceptionInfo->ContextRecord; */
+
+ switch (pExceptionRecord->ExceptionCode)
+ {
+ default:
+ break;
+ case EXCEPTION_BREAKPOINT:
+ case EXCEPTION_ACCESS_VIOLATION:
+ case EXCEPTION_STACK_OVERFLOW:
+ case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
+ case EXCEPTION_FLT_DIVIDE_BY_ZERO:
+ case EXCEPTION_FLT_INVALID_OPERATION:
+ case EXCEPTION_INT_DIVIDE_BY_ZERO:
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ if (g_fAllowIgnore && vboxVDbgIsExceptionIgnored(pExceptionRecord))
+ break;
+ ASMBreakpoint();
+ break;
+ case 0x40010006: /* OutputDebugStringA? */
+ case 0x4001000a: /* OutputDebugStringW? */
+ break;
+ }
+ return EXCEPTION_CONTINUE_SEARCH;
+}
+
+static void vboxVDbgVEHandlerRegister(void)
+{
+ Assert(!g_VBoxWDbgVEHandler);
+ g_VBoxWDbgVEHandler = AddVectoredExceptionHandler(1, vboxVDbgVectoredHandler);
+ Assert(g_VBoxWDbgVEHandler);
+
+ g_hModPsapi = GetModuleHandleA("Psapi.dll"); /* Usually already loaded. */
+ if (g_hModPsapi)
+ g_pfnGetModuleInformation = (PFNGetModuleInformation)GetProcAddress(g_hModPsapi, "GetModuleInformation");
+}
+
+static void vboxVDbgVEHandlerUnregister(void)
+{
+ Assert(g_VBoxWDbgVEHandler);
+ ULONG uResult = RemoveVectoredExceptionHandler(g_VBoxWDbgVEHandler);
+ Assert(uResult); RT_NOREF(uResult);
+ g_VBoxWDbgVEHandler = NULL;
+
+ g_hModPsapi = NULL;
+ g_pfnGetModuleInformation = NULL;
+}
+#endif /* DEBUG */
+
+BOOL WINAPI DllMain(HINSTANCE hDLLInst,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ RT_NOREF2(hDLLInst, lpvReserved);
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+#ifdef DEBUG
+ vboxVDbgVEHandlerRegister();
+#endif
+ D3DKMTLoad();
+ stw_init(&stw_winsys);
+ stw_init_thread();
+ break;
+
+ case DLL_PROCESS_DETACH:
+#ifdef DEBUG
+ vboxVDbgVEHandlerUnregister();
+#endif
+ break;
+
+ case DLL_THREAD_ATTACH:
+ stw_init_thread();
+ break;
+
+ case DLL_THREAD_DETACH:
+ stw_cleanup_thread();
+ break;
+
+ default:
+ if (lpvReserved == NULL)
+ {
+ // We're being unloaded from the process.
+ stw_cleanup_thread();
+ stw_cleanup();
+ }
+ else
+ {
+ // Process itself is terminating, and all threads and modules are
+ // being detached.
+ //
+ // The order threads (including llvmpipe rasterizer threads) are
+ // destroyed can not be relied up, so it's not safe to cleanup.
+ //
+ // However global destructors (e.g., LLVM's) will still be called, and
+ // if Microsoft OPENGL32.DLL's DllMain is called after us, it will
+ // still try to invoke DrvDeleteContext to destroys all outstanding,
+ // so set stw_dev to NULL to return immediately if that happens.
+ stw_dev = NULL;
+ }
+ break;
+ }
+
+ return TRUE;
+}
diff --git a/src/VBox/Additions/3D/win/VBoxGL/VBoxGL.rc b/src/VBox/Additions/3D/win/VBoxGL/VBoxGL.rc
new file mode 100644
index 00000000..6c96f47e
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxGL/VBoxGL.rc
@@ -0,0 +1,66 @@
+/* $Id: VBoxGL.rc $ */
+/** @file
+ * VBoxGL - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "VirtualBox OpenGL Mesa State Tracker\0"
+ VALUE "InternalName", "VBoxGL\0"
+#ifdef VBOX_WOW64
+ VALUE "OriginalFilename", "VBoxGL-x86.DLL\0"
+#else
+ VALUE "OriginalFilename", "VBoxGL.DLL\0"
+#endif
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Additions/3D/win/VBoxICD/.scm-settings b/src/VBox/Additions/3D/win/VBoxICD/.scm-settings
new file mode 100644
index 00000000..f4fffd8f
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/.scm-settings
@@ -0,0 +1,29 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for VBoxGL.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+--filter-out-files "opengl32*def"
diff --git a/src/VBox/Additions/3D/win/VBoxICD/Makefile.kmk b/src/VBox/Additions/3D/win/VBoxICD/Makefile.kmk
new file mode 100644
index 00000000..93434205
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/Makefile.kmk
@@ -0,0 +1,85 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for OpenGL ICD loader.
+#
+
+#
+# Copyright (C) 2018-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+VBOX_GL_ICD_DEF_FILE = $(PATH_ROOT)/src/VBox/Additions/3D/win/VBoxICD/opengl32.mingw.def
+
+DLLS += VBoxICD
+DLLS.amd64 += VBoxICD-x86
+
+VBoxICD_TEMPLATE = VBoxMesa3DGuestR3DllMinVista
+# -wd4005: '__useHeader' : redefinition
+VBoxICD_CFLAGS := -wd4005
+if "$(VBOX_NEWER_VCC_TOOL_STEM)" >= "VCC141"
+ # -wd4255: 'PFND3DKMT_CHECKEXCLUSIVEOWNERSHIP': no function prototype given: converting '()' to '(void)'
+ VBoxICD_CFLAGS += -wd4255
+endif
+
+VBoxICD_INCS = \
+ $(VBOX_PATH_3D)/win/include \
+ $(PATH_ROOT)/src/VBox/Additions/WINNT/Graphics/Video \
+ $(PATH_ROOT)/src/VBox/Devices/Graphics/vmsvga_include \
+ $(VBOX_GRAPHICS_INCS)
+VBoxICD_SOURCES = \
+ opengl32.def \
+ $(VBoxICD_0_OUTDIR)/forwarders.asm \
+ $(VBoxICD_0_OUTDIR)/pfns.c \
+ VBoxICD.c \
+ VBoxICD.rc
+VBoxICD_CLEAN = \
+ $(VBoxICD_0_OUTDIR)/forwarders.asm \
+ $(VBoxICD_0_OUTDIR)/pfns.c
+VBoxICD_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp$(VBOX_SUFF_LIB)
+
+$$(VBoxICD_0_OUTDIR)/forwarders.asm: \
+ $(PATH_SUB_CURRENT)/icd_forwarders.py $(VBOX_GL_ICD_DEF_FILE) | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$(VBOX_GL_ICD_DEF_FILE))
+ $(QUIET)$(VBOX_BLD_PYTHON) $< $(VBOX_GL_ICD_DEF_FILE) $@
+$$(VBoxICD_0_OUTDIR)/pfns.c: \
+ $(PATH_SUB_CURRENT)/icd_pfns.py $(VBOX_GL_ICD_DEF_FILE) | $$(dir $$@)
+ $(call MSG_GENERATE,python,$@,$(VBOX_GL_ICD_DEF_FILE))
+ $(QUIET)$(VBOX_BLD_PYTHON) $< $(VBOX_GL_ICD_DEF_FILE) $@
+
+if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS)
+ VBoxICD_INSTTYPE = none
+ VBoxICD_DEBUG_INSTTYPE = both
+endif
+
+#
+# x86 version built for amd64 build
+#
+VBoxICD-x86_EXTENDS = VBoxICD
+VBoxICD-x86_BLD_TRG_ARCH = x86
+VBoxICD-x86_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp-x86$(VBOX_SUFF_LIB)
+VBoxICD-x86_DEFS = $(VBoxICD_DEFS) VBOX_WOW64
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/3D/win/VBoxICD/VBoxICD.c b/src/VBox/Additions/3D/win/VBoxICD/VBoxICD.c
new file mode 100644
index 00000000..cc5aa9ad
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/VBoxICD.c
@@ -0,0 +1,165 @@
+/* $Id: VBoxICD.c $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - OpenGL driver loader.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBoxWddmUmHlp.h>
+
+#include <common/wddm/VBoxMPIf.h>
+
+static const char *g_pszGalliumDll =
+#ifdef VBOX_WOW64
+ "VBoxGL-x86.dll"
+#else
+ "VBoxGL.dll"
+#endif
+;
+
+static const char *g_pszChromiumDll =
+#ifdef VBOX_WOW64
+ "VBoxOGL-x86.dll"
+#else
+ "VBoxOGL.dll"
+#endif
+;
+
+extern struct VBOXWDDMDLLPROC aIcdProcs[];
+
+HMODULE volatile g_hmodICD = NULL;
+
+static NTSTATUS
+vboxDdiQueryAdapterInfo(D3DKMT_HANDLE hAdapter,
+ VBOXWDDM_QAI *pAdapterInfo,
+ uint32_t cbAdapterInfo)
+{
+ NTSTATUS Status;
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+
+ if (d3dkmt->pfnD3DKMTQueryAdapterInfo)
+ {
+ D3DKMT_QUERYADAPTERINFO QAI;
+ memset(&QAI, 0, sizeof(QAI));
+ QAI.hAdapter = hAdapter;
+ QAI.Type = KMTQAITYPE_UMDRIVERPRIVATE;
+ QAI.pPrivateDriverData = pAdapterInfo;
+ QAI.PrivateDriverDataSize = cbAdapterInfo;
+
+ Status = d3dkmt->pfnD3DKMTQueryAdapterInfo(&QAI);
+ }
+ else
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+
+ return Status;
+}
+
+void VBoxLoadICD(void)
+{
+ NTSTATUS Status;
+ D3DKMT_HANDLE hAdapter = 0;
+
+ D3DKMTLoad();
+
+ Status = vboxDispKmtOpenAdapter(&hAdapter);
+ if (Status == STATUS_SUCCESS)
+ {
+ VBOXWDDM_QAI adapterInfo;
+ Status = vboxDdiQueryAdapterInfo(hAdapter, &adapterInfo, sizeof(adapterInfo));
+ if (Status == STATUS_SUCCESS)
+ {
+ const char *pszDll = NULL;
+ switch (adapterInfo.enmHwType)
+ {
+ case VBOXVIDEO_HWTYPE_VBOX: pszDll = g_pszChromiumDll; break;
+ default:
+ case VBOXVIDEO_HWTYPE_VMSVGA: pszDll = g_pszGalliumDll; break;
+ }
+
+ if (pszDll)
+ {
+ g_hmodICD = VBoxWddmLoadSystemDll(pszDll);
+ if (g_hmodICD)
+ {
+ VBoxWddmLoadAdresses(g_hmodICD, aIcdProcs);
+ }
+ }
+ }
+
+ vboxDispKmtCloseAdapter(hAdapter);
+ }
+}
+
+/*
+ * MSDN says:
+ * "You should never perform the following tasks from within DllMain:
+ * Call LoadLibrary or LoadLibraryEx (either directly or indirectly)."
+ *
+ * However it turned out that loading the real ICD from DLL_PROCESS_ATTACH works,
+ * and loading it in a lazy way fails for unknown reason on 64 bit Windows.
+ *
+ * So just call VBoxLoadICD from DLL_PROCESS_ATTACH.
+ */
+BOOL WINAPI DllMain(HINSTANCE hDLLInst,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ RT_NOREF(hDLLInst);
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ VBoxLoadICD();
+ break;
+
+ case DLL_PROCESS_DETACH:
+ if (lpvReserved == NULL)
+ {
+ /* "The DLL is being unloaded because of a call to FreeLibrary." */
+ if (g_hmodICD)
+ {
+ FreeLibrary(g_hmodICD);
+ g_hmodICD = NULL;
+ }
+ }
+ else
+ {
+ /* "The DLL is being unloaded due to process termination." */
+ /* Do not bother. */
+ }
+ break;
+
+ case DLL_THREAD_ATTACH:
+ break;
+
+ case DLL_THREAD_DETACH:
+ break;
+
+ default:
+ break;
+ }
+
+ return TRUE;
+}
diff --git a/src/VBox/Additions/3D/win/VBoxICD/VBoxICD.rc b/src/VBox/Additions/3D/win/VBoxICD/VBoxICD.rc
new file mode 100644
index 00000000..80ef8df7
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/VBoxICD.rc
@@ -0,0 +1,66 @@
+/* $Id: VBoxICD.rc $ */
+/** @file
+ * VBoxGL - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "VirtualBox OpenGL ICD Loader\0"
+ VALUE "InternalName", "VBoxICD\0"
+#ifdef VBOX_WOW64
+ VALUE "OriginalFilename", "VBoxICD-x86.dll\0"
+#else
+ VALUE "OriginalFilename", "VBoxICD.dll\0"
+#endif
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Additions/3D/win/VBoxICD/icd_forwarders.py b/src/VBox/Additions/3D/win/VBoxICD/icd_forwarders.py
new file mode 100755
index 00000000..c9ffe2f5
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/icd_forwarders.py
@@ -0,0 +1,105 @@
+"""
+Copyright (C) 2018-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+SPDX-License-Identifier: GPL-3.0-only
+"""
+
+import sys
+
+def GenerateForwarders():
+
+ # Get list of functions.
+ exports_file = open(sys.argv[1], "r")
+ if not exports_file:
+ print("Error: couldn't open %s file!" % filename)
+ sys.exit()
+
+ names = []
+ cbArgs = []
+ for line in exports_file.readlines():
+ line = line.strip()
+ if len(line) > 0 and line[0] != ';' and line != 'EXPORTS':
+ # Parse 'glAccum = glAccum@8'
+ words = line.split('=', 1)
+
+ # Function name
+ names.append(words[0].strip())
+
+ # Size of arguments in bytes
+ words = words[1].split('@')
+ cbArgs.append(words[1].strip())
+
+ exports_file.close()
+
+
+ #
+ # Assembler forwarders
+ #
+ asm_file = open(sys.argv[2], "w")
+ if not asm_file:
+ print("Error: couldn't open %s file!" % filename)
+ sys.exit()
+
+ asm_file.write('%include "iprt/asmdefs.mac"\n')
+ asm_file.write('\n')
+ asm_file.write(';;;; Enable ICD_LAZY_LOAD to lazy load the ICD DLL (does not work on Win64)\n')
+ asm_file.write('; %define ICD_LAZY_LOAD 1\n')
+ asm_file.write('\n')
+ asm_file.write('%ifdef RT_ARCH_AMD64\n')
+ asm_file.write('%define PTR_SIZE_PREFIX qword\n')
+ asm_file.write('%else ; X86\n')
+ asm_file.write('%define PTR_SIZE_PREFIX dword\n')
+ asm_file.write('%endif\n')
+ asm_file.write('\n')
+ asm_file.write('%ifdef ICD_LAZY_LOAD\n')
+ asm_file.write('extern NAME(VBoxLoadICD)\n')
+ asm_file.write('%endif\n')
+ asm_file.write('extern NAME(g_hmodICD)\n')
+
+ for index in range(len(names)):
+ fn = names[index]
+ cbRet = cbArgs[index]
+ asm_file.write('\n')
+ asm_file.write('BEGINPROC_EXPORTED %s\n' % fn)
+ asm_file.write(' extern NAME(pfn_%s)\n' % fn)
+ asm_file.write('; int3\n')
+ asm_file.write('%ifdef ICD_LAZY_LOAD\n')
+ asm_file.write(' mov xAX, PTR_SIZE_PREFIX NAME(g_hmodICD)\n')
+ asm_file.write(' mov xAX, [xAX]\n')
+ asm_file.write(' or xAX, xAX\n')
+ asm_file.write(' jnz l_icd_loaded_%s\n' % fn)
+ asm_file.write(' call NAME(VBoxLoadICD)\n')
+ asm_file.write('l_icd_loaded_%s:\n' % fn)
+ asm_file.write('%endif\n')
+ asm_file.write(' mov xAX, PTR_SIZE_PREFIX NAME(pfn_%s)\n' % fn)
+ asm_file.write(' mov xAX, [xAX]\n')
+ asm_file.write(' or xAX, xAX\n')
+ asm_file.write(' jnz l_jmp_to_%s\n' % fn)
+ asm_file.write('%ifdef RT_ARCH_AMD64\n')
+ asm_file.write(' ret\n')
+ asm_file.write('%else ; X86\n')
+ asm_file.write(' ret %s\n' % cbRet)
+ asm_file.write('%endif\n')
+ asm_file.write('l_jmp_to_%s:\n' % fn)
+ asm_file.write(' jmp xAX\n')
+ asm_file.write('ENDPROC %s\n' % fn)
+
+ asm_file.close()
+
+GenerateForwarders()
diff --git a/src/VBox/Additions/3D/win/VBoxICD/icd_pfns.py b/src/VBox/Additions/3D/win/VBoxICD/icd_pfns.py
new file mode 100755
index 00000000..7099a714
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/icd_pfns.py
@@ -0,0 +1,73 @@
+"""
+Copyright (C) 2018-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+SPDX-License-Identifier: GPL-3.0-only
+"""
+
+import sys
+
+def GeneratePfns():
+
+ # Get list of functions.
+ exports_file = open(sys.argv[1], "r")
+ if not exports_file:
+ print("Error: couldn't open %s file!" % filename)
+ sys.exit()
+
+ names = []
+ for line in exports_file.readlines():
+ line = line.strip()
+ if len(line) > 0 and line[0] != ';' and line != 'EXPORTS':
+ # Parse 'glAccum = glAccum@8'
+ words = line.split('=')
+
+ # Function name
+ names.append(words[0].strip())
+
+ exports_file.close()
+
+
+ #
+ # C loader data
+ #
+ c_file = open(sys.argv[2], "w")
+ if not c_file:
+ print("Error: couldn't open %s file!" % filename)
+ sys.exit()
+
+ c_file.write('#include <iprt/win/windows.h>\n')
+ c_file.write('#include <VBoxWddmUmHlp.h>\n')
+ c_file.write('\n')
+
+ for index in range(len(names)):
+ fn = names[index]
+ c_file.write('FARPROC pfn_%s;\n' % fn)
+ c_file.write('\n')
+
+ c_file.write("struct VBOXWDDMDLLPROC aIcdProcs[] =\n")
+ c_file.write('{\n')
+ for index in range(len(names)):
+ fn = names[index]
+ c_file.write(' { "%s", &pfn_%s },\n' % (fn, fn) )
+ c_file.write(' { NULL, NULL }\n')
+ c_file.write('};\n')
+
+ c_file.close()
+
+GeneratePfns()
diff --git a/src/VBox/Additions/3D/win/VBoxICD/opengl32.def b/src/VBox/Additions/3D/win/VBoxICD/opengl32.def
new file mode 100644
index 00000000..c35c23b5
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/opengl32.def
@@ -0,0 +1,388 @@
+EXPORTS
+; GlmfBeginGlsBlock
+; GlmfCloseMetaFile
+; GlmfEndGlsBlock
+; GlmfEndPlayback
+; GlmfInitPlayback
+; GlmfPlayGlsRecord
+ glAccum
+ glAlphaFunc
+ glAreTexturesResident
+ glArrayElement
+ glBegin
+ glBindTexture
+ glBitmap
+ glBlendFunc
+ glCallList
+ glCallLists
+ glClear
+ glClearAccum
+ glClearColor
+ glClearDepth
+ glClearIndex
+ glClearStencil
+ glClipPlane
+ glColor3b
+ glColor3bv
+ glColor3d
+ glColor3dv
+ glColor3f
+ glColor3fv
+ glColor3i
+ glColor3iv
+ glColor3s
+ glColor3sv
+ glColor3ub
+ glColor3ubv
+ glColor3ui
+ glColor3uiv
+ glColor3us
+ glColor3usv
+ glColor4b
+ glColor4bv
+ glColor4d
+ glColor4dv
+ glColor4f
+ glColor4fv
+ glColor4i
+ glColor4iv
+ glColor4s
+ glColor4sv
+ glColor4ub
+ glColor4ubv
+ glColor4ui
+ glColor4uiv
+ glColor4us
+ glColor4usv
+ glColorMask
+ glColorMaterial
+ glColorPointer
+ glCopyPixels
+ glCopyTexImage1D
+ glCopyTexImage2D
+ glCopyTexSubImage1D
+ glCopyTexSubImage2D
+ glCullFace
+; glDebugEntry
+ glDeleteLists
+ glDeleteTextures
+ glDepthFunc
+ glDepthMask
+ glDepthRange
+ glDisable
+ glDisableClientState
+ glDrawArrays
+ glDrawBuffer
+ glDrawElements
+ glDrawPixels
+ glEdgeFlag
+ glEdgeFlagPointer
+ glEdgeFlagv
+ glEnable
+ glEnableClientState
+ glEnd
+ glEndList
+ glEvalCoord1d
+ glEvalCoord1dv
+ glEvalCoord1f
+ glEvalCoord1fv
+ glEvalCoord2d
+ glEvalCoord2dv
+ glEvalCoord2f
+ glEvalCoord2fv
+ glEvalMesh1
+ glEvalMesh2
+ glEvalPoint1
+ glEvalPoint2
+ glFeedbackBuffer
+ glFinish
+ glFlush
+ glFogf
+ glFogfv
+ glFogi
+ glFogiv
+ glFrontFace
+ glFrustum
+ glGenLists
+ glGenTextures
+ glGetBooleanv
+ glGetClipPlane
+ glGetDoublev
+ glGetError
+ glGetFloatv
+ glGetIntegerv
+ glGetLightfv
+ glGetLightiv
+ glGetMapdv
+ glGetMapfv
+ glGetMapiv
+ glGetMaterialfv
+ glGetMaterialiv
+ glGetPixelMapfv
+ glGetPixelMapuiv
+ glGetPixelMapusv
+ glGetPointerv
+ glGetPolygonStipple
+ glGetString
+ glGetTexEnvfv
+ glGetTexEnviv
+ glGetTexGendv
+ glGetTexGenfv
+ glGetTexGeniv
+ glGetTexImage
+ glGetTexLevelParameterfv
+ glGetTexLevelParameteriv
+ glGetTexParameterfv
+ glGetTexParameteriv
+ glHint
+ glIndexMask
+ glIndexPointer
+ glIndexd
+ glIndexdv
+ glIndexf
+ glIndexfv
+ glIndexi
+ glIndexiv
+ glIndexs
+ glIndexsv
+ glIndexub
+ glIndexubv
+ glInitNames
+ glInterleavedArrays
+ glIsEnabled
+ glIsList
+ glIsTexture
+ glLightModelf
+ glLightModelfv
+ glLightModeli
+ glLightModeliv
+ glLightf
+ glLightfv
+ glLighti
+ glLightiv
+ glLineStipple
+ glLineWidth
+ glListBase
+ glLoadIdentity
+ glLoadMatrixd
+ glLoadMatrixf
+ glLoadName
+ glLogicOp
+ glMap1d
+ glMap1f
+ glMap2d
+ glMap2f
+ glMapGrid1d
+ glMapGrid1f
+ glMapGrid2d
+ glMapGrid2f
+ glMaterialf
+ glMaterialfv
+ glMateriali
+ glMaterialiv
+ glMatrixMode
+ glMultMatrixd
+ glMultMatrixf
+ glNewList
+ glNormal3b
+ glNormal3bv
+ glNormal3d
+ glNormal3dv
+ glNormal3f
+ glNormal3fv
+ glNormal3i
+ glNormal3iv
+ glNormal3s
+ glNormal3sv
+ glNormalPointer
+ glOrtho
+ glPassThrough
+ glPixelMapfv
+ glPixelMapuiv
+ glPixelMapusv
+ glPixelStoref
+ glPixelStorei
+ glPixelTransferf
+ glPixelTransferi
+ glPixelZoom
+ glPointSize
+ glPolygonMode
+ glPolygonOffset
+ glPolygonStipple
+ glPopAttrib
+ glPopClientAttrib
+ glPopMatrix
+ glPopName
+ glPrioritizeTextures
+ glPushAttrib
+ glPushClientAttrib
+ glPushMatrix
+ glPushName
+ glRasterPos2d
+ glRasterPos2dv
+ glRasterPos2f
+ glRasterPos2fv
+ glRasterPos2i
+ glRasterPos2iv
+ glRasterPos2s
+ glRasterPos2sv
+ glRasterPos3d
+ glRasterPos3dv
+ glRasterPos3f
+ glRasterPos3fv
+ glRasterPos3i
+ glRasterPos3iv
+ glRasterPos3s
+ glRasterPos3sv
+ glRasterPos4d
+ glRasterPos4dv
+ glRasterPos4f
+ glRasterPos4fv
+ glRasterPos4i
+ glRasterPos4iv
+ glRasterPos4s
+ glRasterPos4sv
+ glReadBuffer
+ glReadPixels
+ glRectd
+ glRectdv
+ glRectf
+ glRectfv
+ glRecti
+ glRectiv
+ glRects
+ glRectsv
+ glRenderMode
+ glRotated
+ glRotatef
+ glScaled
+ glScalef
+ glScissor
+ glSelectBuffer
+ glShadeModel
+ glStencilFunc
+ glStencilMask
+ glStencilOp
+ glTexCoord1d
+ glTexCoord1dv
+ glTexCoord1f
+ glTexCoord1fv
+ glTexCoord1i
+ glTexCoord1iv
+ glTexCoord1s
+ glTexCoord1sv
+ glTexCoord2d
+ glTexCoord2dv
+ glTexCoord2f
+ glTexCoord2fv
+ glTexCoord2i
+ glTexCoord2iv
+ glTexCoord2s
+ glTexCoord2sv
+ glTexCoord3d
+ glTexCoord3dv
+ glTexCoord3f
+ glTexCoord3fv
+ glTexCoord3i
+ glTexCoord3iv
+ glTexCoord3s
+ glTexCoord3sv
+ glTexCoord4d
+ glTexCoord4dv
+ glTexCoord4f
+ glTexCoord4fv
+ glTexCoord4i
+ glTexCoord4iv
+ glTexCoord4s
+ glTexCoord4sv
+ glTexCoordPointer
+ glTexEnvf
+ glTexEnvfv
+ glTexEnvi
+ glTexEnviv
+ glTexGend
+ glTexGendv
+ glTexGenf
+ glTexGenfv
+ glTexGeni
+ glTexGeniv
+ glTexImage1D
+ glTexImage2D
+ glTexParameterf
+ glTexParameterfv
+ glTexParameteri
+ glTexParameteriv
+ glTexSubImage1D
+ glTexSubImage2D
+ glTranslated
+ glTranslatef
+ glVertex2d
+ glVertex2dv
+ glVertex2f
+ glVertex2fv
+ glVertex2i
+ glVertex2iv
+ glVertex2s
+ glVertex2sv
+ glVertex3d
+ glVertex3dv
+ glVertex3f
+ glVertex3fv
+ glVertex3i
+ glVertex3iv
+ glVertex3s
+ glVertex3sv
+ glVertex4d
+ glVertex4dv
+ glVertex4f
+ glVertex4fv
+ glVertex4i
+ glVertex4iv
+ glVertex4s
+ glVertex4sv
+ glVertexPointer
+ glViewport
+ wglChoosePixelFormat
+ wglCopyContext
+ wglCreateContext
+ wglCreateLayerContext
+ wglDeleteContext
+ wglDescribeLayerPlane
+ wglDescribePixelFormat
+ wglGetCurrentContext
+ wglGetCurrentDC
+; wglGetDefaultProcAddress
+ wglGetLayerPaletteEntries
+ wglGetPixelFormat
+ wglGetProcAddress
+ wglMakeCurrent
+ wglRealizeLayerPalette
+ wglSetLayerPaletteEntries
+ wglSetPixelFormat
+ wglShareLists
+ wglSwapBuffers
+ wglSwapLayerBuffers
+ wglSwapMultipleBuffers
+ wglUseFontBitmapsA
+ wglUseFontBitmapsW
+ wglUseFontOutlinesA
+ wglUseFontOutlinesW
+ DrvCopyContext
+ DrvCreateContext
+ DrvCreateLayerContext
+ DrvDeleteContext
+ DrvDescribeLayerPlane
+ DrvDescribePixelFormat
+ DrvGetLayerPaletteEntries
+ DrvGetProcAddress
+ DrvPresentBuffers
+ DrvRealizeLayerPalette
+ DrvReleaseContext
+ DrvSetCallbackProcs
+ DrvSetContext
+ DrvSetLayerPaletteEntries
+ DrvSetPixelFormat
+ DrvShareLists
+ DrvSwapBuffers
+ DrvSwapLayerBuffers
+ DrvValidateVersion
diff --git a/src/VBox/Additions/3D/win/VBoxICD/opengl32.mingw.def b/src/VBox/Additions/3D/win/VBoxICD/opengl32.mingw.def
new file mode 100644
index 00000000..0bceee06
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxICD/opengl32.mingw.def
@@ -0,0 +1,388 @@
+EXPORTS
+; GlmfBeginGlsBlock = GlmfBeginGlsBlock@4
+; GlmfCloseMetaFile = GlmfCloseMetaFile@4
+; GlmfEndGlsBlock = GlmfEndGlsBlock@4
+; GlmfEndPlayback = GlmfEndPlayback@4
+; GlmfInitPlayback = GlmfInitPlayback@12
+; GlmfPlayGlsRecord = GlmfPlayGlsRecord@16
+ glAccum = glAccum@8
+ glAlphaFunc = glAlphaFunc@8
+ glAreTexturesResident = glAreTexturesResident@12
+ glArrayElement = glArrayElement@4
+ glBegin = glBegin@4
+ glBindTexture = glBindTexture@8
+ glBitmap = glBitmap@28
+ glBlendFunc = glBlendFunc@8
+ glCallList = glCallList@4
+ glCallLists = glCallLists@12
+ glClear = glClear@4
+ glClearAccum = glClearAccum@16
+ glClearColor = glClearColor@16
+ glClearDepth = glClearDepth@8
+ glClearIndex = glClearIndex@4
+ glClearStencil = glClearStencil@4
+ glClipPlane = glClipPlane@8
+ glColor3b = glColor3b@12
+ glColor3bv = glColor3bv@4
+ glColor3d = glColor3d@24
+ glColor3dv = glColor3dv@4
+ glColor3f = glColor3f@12
+ glColor3fv = glColor3fv@4
+ glColor3i = glColor3i@12
+ glColor3iv = glColor3iv@4
+ glColor3s = glColor3s@12
+ glColor3sv = glColor3sv@4
+ glColor3ub = glColor3ub@12
+ glColor3ubv = glColor3ubv@4
+ glColor3ui = glColor3ui@12
+ glColor3uiv = glColor3uiv@4
+ glColor3us = glColor3us@12
+ glColor3usv = glColor3usv@4
+ glColor4b = glColor4b@16
+ glColor4bv = glColor4bv@4
+ glColor4d = glColor4d@32
+ glColor4dv = glColor4dv@4
+ glColor4f = glColor4f@16
+ glColor4fv = glColor4fv@4
+ glColor4i = glColor4i@16
+ glColor4iv = glColor4iv@4
+ glColor4s = glColor4s@16
+ glColor4sv = glColor4sv@4
+ glColor4ub = glColor4ub@16
+ glColor4ubv = glColor4ubv@4
+ glColor4ui = glColor4ui@16
+ glColor4uiv = glColor4uiv@4
+ glColor4us = glColor4us@16
+ glColor4usv = glColor4usv@4
+ glColorMask = glColorMask@16
+ glColorMaterial = glColorMaterial@8
+ glColorPointer = glColorPointer@16
+ glCopyPixels = glCopyPixels@20
+ glCopyTexImage1D = glCopyTexImage1D@28
+ glCopyTexImage2D = glCopyTexImage2D@32
+ glCopyTexSubImage1D = glCopyTexSubImage1D@24
+ glCopyTexSubImage2D = glCopyTexSubImage2D@32
+ glCullFace = glCullFace@4
+; glDebugEntry = glDebugEntry@8
+ glDeleteLists = glDeleteLists@8
+ glDeleteTextures = glDeleteTextures@8
+ glDepthFunc = glDepthFunc@4
+ glDepthMask = glDepthMask@4
+ glDepthRange = glDepthRange@16
+ glDisable = glDisable@4
+ glDisableClientState = glDisableClientState@4
+ glDrawArrays = glDrawArrays@12
+ glDrawBuffer = glDrawBuffer@4
+ glDrawElements = glDrawElements@16
+ glDrawPixels = glDrawPixels@20
+ glEdgeFlag = glEdgeFlag@4
+ glEdgeFlagPointer = glEdgeFlagPointer@8
+ glEdgeFlagv = glEdgeFlagv@4
+ glEnable = glEnable@4
+ glEnableClientState = glEnableClientState@4
+ glEnd = glEnd@0
+ glEndList = glEndList@0
+ glEvalCoord1d = glEvalCoord1d@8
+ glEvalCoord1dv = glEvalCoord1dv@4
+ glEvalCoord1f = glEvalCoord1f@4
+ glEvalCoord1fv = glEvalCoord1fv@4
+ glEvalCoord2d = glEvalCoord2d@16
+ glEvalCoord2dv = glEvalCoord2dv@4
+ glEvalCoord2f = glEvalCoord2f@8
+ glEvalCoord2fv = glEvalCoord2fv@4
+ glEvalMesh1 = glEvalMesh1@12
+ glEvalMesh2 = glEvalMesh2@20
+ glEvalPoint1 = glEvalPoint1@4
+ glEvalPoint2 = glEvalPoint2@8
+ glFeedbackBuffer = glFeedbackBuffer@12
+ glFinish = glFinish@0
+ glFlush = glFlush@0
+ glFogf = glFogf@8
+ glFogfv = glFogfv@8
+ glFogi = glFogi@8
+ glFogiv = glFogiv@8
+ glFrontFace = glFrontFace@4
+ glFrustum = glFrustum@48
+ glGenLists = glGenLists@4
+ glGenTextures = glGenTextures@8
+ glGetBooleanv = glGetBooleanv@8
+ glGetClipPlane = glGetClipPlane@8
+ glGetDoublev = glGetDoublev@8
+ glGetError = glGetError@0
+ glGetFloatv = glGetFloatv@8
+ glGetIntegerv = glGetIntegerv@8
+ glGetLightfv = glGetLightfv@12
+ glGetLightiv = glGetLightiv@12
+ glGetMapdv = glGetMapdv@12
+ glGetMapfv = glGetMapfv@12
+ glGetMapiv = glGetMapiv@12
+ glGetMaterialfv = glGetMaterialfv@12
+ glGetMaterialiv = glGetMaterialiv@12
+ glGetPixelMapfv = glGetPixelMapfv@8
+ glGetPixelMapuiv = glGetPixelMapuiv@8
+ glGetPixelMapusv = glGetPixelMapusv@8
+ glGetPointerv = glGetPointerv@8
+ glGetPolygonStipple = glGetPolygonStipple@4
+ glGetString = glGetString@4
+ glGetTexEnvfv = glGetTexEnvfv@12
+ glGetTexEnviv = glGetTexEnviv@12
+ glGetTexGendv = glGetTexGendv@12
+ glGetTexGenfv = glGetTexGenfv@12
+ glGetTexGeniv = glGetTexGeniv@12
+ glGetTexImage = glGetTexImage@20
+ glGetTexLevelParameterfv = glGetTexLevelParameterfv@16
+ glGetTexLevelParameteriv = glGetTexLevelParameteriv@16
+ glGetTexParameterfv = glGetTexParameterfv@12
+ glGetTexParameteriv = glGetTexParameteriv@12
+ glHint = glHint@8
+ glIndexMask = glIndexMask@4
+ glIndexPointer = glIndexPointer@12
+ glIndexd = glIndexd@8
+ glIndexdv = glIndexdv@4
+ glIndexf = glIndexf@4
+ glIndexfv = glIndexfv@4
+ glIndexi = glIndexi@4
+ glIndexiv = glIndexiv@4
+ glIndexs = glIndexs@4
+ glIndexsv = glIndexsv@4
+ glIndexub = glIndexub@4
+ glIndexubv = glIndexubv@4
+ glInitNames = glInitNames@0
+ glInterleavedArrays = glInterleavedArrays@12
+ glIsEnabled = glIsEnabled@4
+ glIsList = glIsList@4
+ glIsTexture = glIsTexture@4
+ glLightModelf = glLightModelf@8
+ glLightModelfv = glLightModelfv@8
+ glLightModeli = glLightModeli@8
+ glLightModeliv = glLightModeliv@8
+ glLightf = glLightf@12
+ glLightfv = glLightfv@12
+ glLighti = glLighti@12
+ glLightiv = glLightiv@12
+ glLineStipple = glLineStipple@8
+ glLineWidth = glLineWidth@4
+ glListBase = glListBase@4
+ glLoadIdentity = glLoadIdentity@0
+ glLoadMatrixd = glLoadMatrixd@4
+ glLoadMatrixf = glLoadMatrixf@4
+ glLoadName = glLoadName@4
+ glLogicOp = glLogicOp@4
+ glMap1d = glMap1d@32
+ glMap1f = glMap1f@24
+ glMap2d = glMap2d@56
+ glMap2f = glMap2f@40
+ glMapGrid1d = glMapGrid1d@20
+ glMapGrid1f = glMapGrid1f@12
+ glMapGrid2d = glMapGrid2d@40
+ glMapGrid2f = glMapGrid2f@24
+ glMaterialf = glMaterialf@12
+ glMaterialfv = glMaterialfv@12
+ glMateriali = glMateriali@12
+ glMaterialiv = glMaterialiv@12
+ glMatrixMode = glMatrixMode@4
+ glMultMatrixd = glMultMatrixd@4
+ glMultMatrixf = glMultMatrixf@4
+ glNewList = glNewList@8
+ glNormal3b = glNormal3b@12
+ glNormal3bv = glNormal3bv@4
+ glNormal3d = glNormal3d@24
+ glNormal3dv = glNormal3dv@4
+ glNormal3f = glNormal3f@12
+ glNormal3fv = glNormal3fv@4
+ glNormal3i = glNormal3i@12
+ glNormal3iv = glNormal3iv@4
+ glNormal3s = glNormal3s@12
+ glNormal3sv = glNormal3sv@4
+ glNormalPointer = glNormalPointer@12
+ glOrtho = glOrtho@48
+ glPassThrough = glPassThrough@4
+ glPixelMapfv = glPixelMapfv@12
+ glPixelMapuiv = glPixelMapuiv@12
+ glPixelMapusv = glPixelMapusv@12
+ glPixelStoref = glPixelStoref@8
+ glPixelStorei = glPixelStorei@8
+ glPixelTransferf = glPixelTransferf@8
+ glPixelTransferi = glPixelTransferi@8
+ glPixelZoom = glPixelZoom@8
+ glPointSize = glPointSize@4
+ glPolygonMode = glPolygonMode@8
+ glPolygonOffset = glPolygonOffset@8
+ glPolygonStipple = glPolygonStipple@4
+ glPopAttrib = glPopAttrib@0
+ glPopClientAttrib = glPopClientAttrib@0
+ glPopMatrix = glPopMatrix@0
+ glPopName = glPopName@0
+ glPrioritizeTextures = glPrioritizeTextures@12
+ glPushAttrib = glPushAttrib@4
+ glPushClientAttrib = glPushClientAttrib@4
+ glPushMatrix = glPushMatrix@0
+ glPushName = glPushName@4
+ glRasterPos2d = glRasterPos2d@16
+ glRasterPos2dv = glRasterPos2dv@4
+ glRasterPos2f = glRasterPos2f@8
+ glRasterPos2fv = glRasterPos2fv@4
+ glRasterPos2i = glRasterPos2i@8
+ glRasterPos2iv = glRasterPos2iv@4
+ glRasterPos2s = glRasterPos2s@8
+ glRasterPos2sv = glRasterPos2sv@4
+ glRasterPos3d = glRasterPos3d@24
+ glRasterPos3dv = glRasterPos3dv@4
+ glRasterPos3f = glRasterPos3f@12
+ glRasterPos3fv = glRasterPos3fv@4
+ glRasterPos3i = glRasterPos3i@12
+ glRasterPos3iv = glRasterPos3iv@4
+ glRasterPos3s = glRasterPos3s@12
+ glRasterPos3sv = glRasterPos3sv@4
+ glRasterPos4d = glRasterPos4d@32
+ glRasterPos4dv = glRasterPos4dv@4
+ glRasterPos4f = glRasterPos4f@16
+ glRasterPos4fv = glRasterPos4fv@4
+ glRasterPos4i = glRasterPos4i@16
+ glRasterPos4iv = glRasterPos4iv@4
+ glRasterPos4s = glRasterPos4s@16
+ glRasterPos4sv = glRasterPos4sv@4
+ glReadBuffer = glReadBuffer@4
+ glReadPixels = glReadPixels@28
+ glRectd = glRectd@32
+ glRectdv = glRectdv@8
+ glRectf = glRectf@16
+ glRectfv = glRectfv@8
+ glRecti = glRecti@16
+ glRectiv = glRectiv@8
+ glRects = glRects@16
+ glRectsv = glRectsv@8
+ glRenderMode = glRenderMode@4
+ glRotated = glRotated@32
+ glRotatef = glRotatef@16
+ glScaled = glScaled@24
+ glScalef = glScalef@12
+ glScissor = glScissor@16
+ glSelectBuffer = glSelectBuffer@8
+ glShadeModel = glShadeModel@4
+ glStencilFunc = glStencilFunc@12
+ glStencilMask = glStencilMask@4
+ glStencilOp = glStencilOp@12
+ glTexCoord1d = glTexCoord1d@8
+ glTexCoord1dv = glTexCoord1dv@4
+ glTexCoord1f = glTexCoord1f@4
+ glTexCoord1fv = glTexCoord1fv@4
+ glTexCoord1i = glTexCoord1i@4
+ glTexCoord1iv = glTexCoord1iv@4
+ glTexCoord1s = glTexCoord1s@4
+ glTexCoord1sv = glTexCoord1sv@4
+ glTexCoord2d = glTexCoord2d@16
+ glTexCoord2dv = glTexCoord2dv@4
+ glTexCoord2f = glTexCoord2f@8
+ glTexCoord2fv = glTexCoord2fv@4
+ glTexCoord2i = glTexCoord2i@8
+ glTexCoord2iv = glTexCoord2iv@4
+ glTexCoord2s = glTexCoord2s@8
+ glTexCoord2sv = glTexCoord2sv@4
+ glTexCoord3d = glTexCoord3d@24
+ glTexCoord3dv = glTexCoord3dv@4
+ glTexCoord3f = glTexCoord3f@12
+ glTexCoord3fv = glTexCoord3fv@4
+ glTexCoord3i = glTexCoord3i@12
+ glTexCoord3iv = glTexCoord3iv@4
+ glTexCoord3s = glTexCoord3s@12
+ glTexCoord3sv = glTexCoord3sv@4
+ glTexCoord4d = glTexCoord4d@32
+ glTexCoord4dv = glTexCoord4dv@4
+ glTexCoord4f = glTexCoord4f@16
+ glTexCoord4fv = glTexCoord4fv@4
+ glTexCoord4i = glTexCoord4i@16
+ glTexCoord4iv = glTexCoord4iv@4
+ glTexCoord4s = glTexCoord4s@16
+ glTexCoord4sv = glTexCoord4sv@4
+ glTexCoordPointer = glTexCoordPointer@16
+ glTexEnvf = glTexEnvf@12
+ glTexEnvfv = glTexEnvfv@12
+ glTexEnvi = glTexEnvi@12
+ glTexEnviv = glTexEnviv@12
+ glTexGend = glTexGend@16
+ glTexGendv = glTexGendv@12
+ glTexGenf = glTexGenf@12
+ glTexGenfv = glTexGenfv@12
+ glTexGeni = glTexGeni@12
+ glTexGeniv = glTexGeniv@12
+ glTexImage1D = glTexImage1D@32
+ glTexImage2D = glTexImage2D@36
+ glTexParameterf = glTexParameterf@12
+ glTexParameterfv = glTexParameterfv@12
+ glTexParameteri = glTexParameteri@12
+ glTexParameteriv = glTexParameteriv@12
+ glTexSubImage1D = glTexSubImage1D@28
+ glTexSubImage2D = glTexSubImage2D@36
+ glTranslated = glTranslated@24
+ glTranslatef = glTranslatef@12
+ glVertex2d = glVertex2d@16
+ glVertex2dv = glVertex2dv@4
+ glVertex2f = glVertex2f@8
+ glVertex2fv = glVertex2fv@4
+ glVertex2i = glVertex2i@8
+ glVertex2iv = glVertex2iv@4
+ glVertex2s = glVertex2s@8
+ glVertex2sv = glVertex2sv@4
+ glVertex3d = glVertex3d@24
+ glVertex3dv = glVertex3dv@4
+ glVertex3f = glVertex3f@12
+ glVertex3fv = glVertex3fv@4
+ glVertex3i = glVertex3i@12
+ glVertex3iv = glVertex3iv@4
+ glVertex3s = glVertex3s@12
+ glVertex3sv = glVertex3sv@4
+ glVertex4d = glVertex4d@32
+ glVertex4dv = glVertex4dv@4
+ glVertex4f = glVertex4f@16
+ glVertex4fv = glVertex4fv@4
+ glVertex4i = glVertex4i@16
+ glVertex4iv = glVertex4iv@4
+ glVertex4s = glVertex4s@16
+ glVertex4sv = glVertex4sv@4
+ glVertexPointer = glVertexPointer@16
+ glViewport = glViewport@16
+ wglChoosePixelFormat = wglChoosePixelFormat@8
+ wglCopyContext = wglCopyContext@12
+ wglCreateContext = wglCreateContext@4
+ wglCreateLayerContext = wglCreateLayerContext@8
+ wglDeleteContext = wglDeleteContext@4
+ wglDescribeLayerPlane = wglDescribeLayerPlane@20
+ wglDescribePixelFormat = wglDescribePixelFormat@16
+ wglGetCurrentContext = wglGetCurrentContext@0
+ wglGetCurrentDC = wglGetCurrentDC@0
+; wglGetDefaultProcAddress = wglGetDefaultProcAddress@4
+ wglGetLayerPaletteEntries = wglGetLayerPaletteEntries@20
+ wglGetPixelFormat = wglGetPixelFormat@4
+ wglGetProcAddress = wglGetProcAddress@4
+ wglMakeCurrent = wglMakeCurrent@8
+ wglRealizeLayerPalette = wglRealizeLayerPalette@12
+ wglSetLayerPaletteEntries = wglSetLayerPaletteEntries@20
+ wglSetPixelFormat = wglSetPixelFormat@12
+ wglShareLists = wglShareLists@8
+ wglSwapBuffers = wglSwapBuffers@4
+ wglSwapLayerBuffers = wglSwapLayerBuffers@8
+ wglSwapMultipleBuffers = wglSwapMultipleBuffers@8
+ wglUseFontBitmapsA = wglUseFontBitmapsA@16
+ wglUseFontBitmapsW = wglUseFontBitmapsW@16
+ wglUseFontOutlinesA = wglUseFontOutlinesA@32
+ wglUseFontOutlinesW = wglUseFontOutlinesW@32
+ DrvCopyContext = DrvCopyContext@12
+ DrvCreateContext = DrvCreateContext@4
+ DrvCreateLayerContext = DrvCreateLayerContext@8
+ DrvDeleteContext = DrvDeleteContext@4
+ DrvDescribeLayerPlane = DrvDescribeLayerPlane@20
+ DrvDescribePixelFormat = DrvDescribePixelFormat@16
+ DrvGetLayerPaletteEntries = DrvGetLayerPaletteEntries@20
+ DrvGetProcAddress = DrvGetProcAddress@4
+ DrvPresentBuffers = DrvPresentBuffers@8
+ DrvRealizeLayerPalette = DrvRealizeLayerPalette@12
+ DrvReleaseContext = DrvReleaseContext@4
+ DrvSetCallbackProcs = DrvSetCallbackProcs@8
+ DrvSetContext = DrvSetContext@12
+ DrvSetLayerPaletteEntries = DrvSetLayerPaletteEntries@20
+ DrvSetPixelFormat = DrvSetPixelFormat@8
+ DrvShareLists = DrvShareLists@8
+ DrvSwapBuffers = DrvSwapBuffers@4
+ DrvSwapLayerBuffers = DrvSwapLayerBuffers@8
+ DrvValidateVersion = DrvValidateVersion@4
diff --git a/src/VBox/Additions/3D/win/VBoxNine/Makefile.kmk b/src/VBox/Additions/3D/win/VBoxNine/Makefile.kmk
new file mode 100644
index 00000000..43f4906b
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxNine/Makefile.kmk
@@ -0,0 +1,74 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxNine D3D9 state tracker.
+#
+
+#
+# Copyright (C) 2016-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+DLLS += VBoxNine
+DLLS.amd64 += VBoxNine-x86
+
+VBoxNine_TEMPLATE = VBoxMesa3DGuestR3DllMinVista
+# -wd4100: unreferenced formal parameter
+# -wd4200: nonstandard extension used : zero-sized array in struct/union
+# -wd4245: 'return' : conversion from 'int' to 'unsigned int', signed/unsigned mismatch
+# -wd4255: no function prototype given
+# -wd4668: 'something' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
+VBoxNine_CFLAGS = -wd4100 -wd4200 -wd4245 -wd4255 -wd4668
+VBoxNine_INCS = \
+ $(VBOX_PATH_MESA)/src/gallium/frontends/nine
+VBoxNine_SOURCES = \
+ nine/nine_memory_helper.c \
+ VBoxNine.c \
+ VBoxNine.rc \
+ VBoxNine.def
+VBoxNine_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaGalliumAuxLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaUtilLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaNineLib$(VBOX_SUFF_LIB)
+
+if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS)
+ VBoxNine_INSTTYPE = none
+ VBoxNine_DEBUG_INSTTYPE = both
+endif
+
+#
+# VBoxNine-x86 - x86 version of VBoxNine built for amd64 build
+#
+VBoxNine-x86_EXTENDS = VBoxNine
+VBoxNine-x86_BLD_TRG_ARCH = x86
+VBoxNine-x86_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaGalliumAuxLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaUtilLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaNineLib-x86$(VBOX_SUFF_LIB)
+VBoxNine-x86_DEFS = $(VBoxNine_DEFS) VBOX_WOW64
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.c b/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.c
new file mode 100644
index 00000000..59b73db6
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.c
@@ -0,0 +1,181 @@
+/* $Id: VBoxNine.c $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Direct3D9 state tracker.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <iprt/win/windows.h>
+#include <iprt/win/d3d9.h>
+//#include <d3dumddi.h>
+
+#include <VBoxWddmUmHlp.h>
+
+//#include <windef.h>
+//#include <winbase.h>
+//#include <winsvc.h>
+//#include <winnetwk.h>
+//#include <npapi.h>
+//#include <devioctl.h>
+//#include <stdio.h>
+
+//#include <iprt/alloc.h>
+//#include <iprt/initterm.h>
+//#include <iprt/string.h>
+//#include <iprt/log.h>
+//#include <VBox/version.h>
+//#include <VBox/VMMDev.h>
+//#include <VBox/Log.h>
+
+#include "adapter9.h"
+#include "surface9.h"
+#include "pipe/p_screen.h"
+
+#include <iprt/types.h>
+// #include <iprt/asm.h>
+
+// #include "VBoxNine.h"
+
+struct d3dadapter9_context_wddm
+{
+ struct d3dadapter9_context base;
+ void *reserved;
+};
+
+static void
+wddm_destroy(struct d3dadapter9_context *ctx)
+{
+ /* struct d3dadapter9_context_wddm *ctx_wddm = (struct d3dadapter9_context_wddm *)ctx; */
+
+/// @todo screen (hal) is deleted by the upper level. Do not delete here.
+// if (ctx->ref)
+// ctx->ref->destroy(ctx->ref);
+// /* because ref is a wrapper around hal, freeing ref frees hal too. */
+// else if (ctx->hal)
+// ctx->hal->destroy(ctx->hal);
+
+ FREE(ctx);
+}
+
+static HRESULT
+d3dadapter9_context_wddm_create(struct d3dadapter9_context_wddm **ppCtx, struct pipe_screen *s)
+{
+ struct d3dadapter9_context_wddm *ctx = CALLOC_STRUCT(d3dadapter9_context_wddm);
+
+ if (!ctx) { return E_OUTOFMEMORY; }
+
+ ctx->base.hal = s;
+ /** @todo Need software device here. Currently assigned to hw device to prevent NineDevice9_ctor crash. */
+ ctx->base.ref = ctx->base.hal;
+ // D3DADAPTER_IDENTIFIER9 identifier;
+ ctx->base.linear_framebuffer = TRUE;
+ ctx->base.throttling = FALSE;
+ ctx->base.throttling_value = 0;
+ ctx->base.vblank_mode = 1;
+ ctx->base.thread_submit = FALSE;
+ ctx->base.discard_delayed_release = FALSE;
+ ctx->base.tearfree_discard = FALSE;
+ ctx->base.csmt_force = FALSE;
+ ctx->base.dynamic_texture_workaround = FALSE;
+ ctx->base.shader_inline_constants = FALSE;
+ ctx->base.memfd_virtualsizelimit = -1;
+ ctx->base.override_vram_size = -1;
+ ctx->base.destroy = wddm_destroy;
+
+
+ /* read out PCI info */
+ /// @todo read_descriptor(&ctx->base, fd);
+
+ *ppCtx = ctx;
+ return D3D_OK;
+}
+
+HRESULT WINAPI
+GaNineD3DAdapter9Create(struct pipe_screen *s, ID3DAdapter9 **ppOut)
+{
+ HRESULT hr;
+ struct d3dadapter9_context_wddm *pCtx = NULL;
+ hr = d3dadapter9_context_wddm_create(&pCtx, s);
+ if (SUCCEEDED(hr))
+ {
+ hr = NineAdapter9_new(&pCtx->base, (struct NineAdapter9 **)ppOut);
+ if (FAILED(hr))
+ {
+ /// @todo NineAdapter9_new calls this as ctx->base.destroy,
+ // and will not call if memory allocation fails.
+ // wddm_destroy(&pCtx->base);
+ }
+ }
+ return hr;
+}
+
+struct pipe_resource * WINAPI
+GaNinePipeResourceFromSurface(IUnknown *pSurface)
+{
+ /// @todo QueryInterface?
+ struct NineResource9 *pResource = (struct NineResource9 *)pSurface;
+ return pResource->resource;
+}
+
+extern struct pipe_context *
+NineDevice9_GetPipe( struct NineDevice9 *This );
+
+struct pipe_context * WINAPI
+GaNinePipeContextFromDevice(IDirect3DDevice9 *pDevice)
+{
+ /// @todo Verify that this is a NineDevice?
+ struct pipe_context *pPipeContext = NineDevice9_GetPipe((struct NineDevice9 *)pDevice);
+ return pPipeContext;
+}
+
+BOOL WINAPI DllMain(HINSTANCE hDLLInst,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ BOOL fReturn = TRUE;
+
+ RT_NOREF2(hDLLInst, lpvReserved);
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ //RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE);
+ D3DKMTLoad();
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /// @todo RTR3Term();
+ break;
+
+ case DLL_THREAD_ATTACH:
+ break;
+
+ case DLL_THREAD_DETACH:
+ break;
+
+ default:
+ break;
+ }
+
+ return fReturn;
+}
diff --git a/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.def b/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.def
new file mode 100644
index 00000000..5197ba7b
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.def
@@ -0,0 +1,34 @@
+; $Id: VBoxNine.def $;
+;; @file
+; Export definition file for the VBoxNine D3D9 state tracker.
+;
+
+;
+; Copyright (C) 2016-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+LIBRARY VBOXNINE
+
+EXPORTS
+ GaNineD3DAdapter9Create @1
+ GaNinePipeResourceFromSurface @2
+ GaNinePipeContextFromDevice @3
+
diff --git a/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.rc b/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.rc
new file mode 100644
index 00000000..4ad22662
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxNine/VBoxNine.rc
@@ -0,0 +1,66 @@
+/* $Id: VBoxNine.rc $ */
+/** @file
+ * VBoxNine - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "VirtualBox D3D9 Mesa State Tracker\0"
+ VALUE "InternalName", "VBoxNine\0"
+#ifdef VBOX_WOW64
+ VALUE "OriginalFilename", "VBoxNine-x86.DLL\0"
+#else
+ VALUE "OriginalFilename", "VBoxNine.DLL\0"
+#endif
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Additions/3D/win/VBoxNine/nine/nine_memory_helper.c b/src/VBox/Additions/3D/win/VBoxNine/nine/nine_memory_helper.c
new file mode 100644
index 00000000..9fe5c652
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxNine/nine/nine_memory_helper.c
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * Copyright 2020 Axel Davy <davyaxel0@gmail.com>
+ *
+ * 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
+ * on 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 above copyright notice and this permission notice (including the next
+ * paragraph) 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 NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHOR(S) AND/OR THEIR 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. */
+
+#include "util/list.h"
+#include "util/u_memory.h"
+#include "util/slab.h"
+
+#include "nine_debug.h"
+#include "nine_memory_helper.h"
+#include "nine_state.h"
+
+#include <iprt/win/windows.h>
+
+#define DIVUP(a,b) (((a)+(b)-1)/(b))
+
+/* Required alignment for allocations */
+#define NINE_ALLOCATION_ALIGNMENT 32
+
+#define DBG_CHANNEL (DBG_BASETEXTURE|DBG_SURFACE|DBG_VOLUME|DBG_TEXTURE|DBG_CUBETEXTURE)
+
+struct nine_allocation {
+ unsigned is_external;
+ void *external;
+};
+
+struct nine_allocator {
+ struct slab_mempool external_allocation_pool;
+ CRITICAL_SECTION mutex_slab;
+};
+
+struct nine_allocation *
+nine_allocate(struct nine_allocator *allocator, unsigned size)
+{
+ struct nine_allocation *allocation;
+ (void)allocator;
+ assert(sizeof(struct nine_allocation) <= NINE_ALLOCATION_ALIGNMENT);
+ allocation = align_calloc(size + NINE_ALLOCATION_ALIGNMENT, NINE_ALLOCATION_ALIGNMENT);
+ allocation->is_external = false;
+ return allocation;
+}
+
+
+void nine_free(struct nine_allocator *allocator, struct nine_allocation *allocation)
+{
+ if (allocation->is_external) {
+ EnterCriticalSection(&allocator->mutex_slab);
+ slab_free_st(&allocator->external_allocation_pool, allocation);
+ LeaveCriticalSection(&allocator->mutex_slab);
+ } else
+ align_free(allocation);
+}
+
+void nine_free_worker(struct nine_allocator *allocator, struct nine_allocation *allocation)
+{
+ nine_free(allocator, allocation);
+}
+
+void *nine_get_pointer(struct nine_allocator *allocator, struct nine_allocation *allocation)
+{
+ (void)allocator;
+ if (allocation->is_external)
+ return allocation->external;
+ return (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT;
+}
+
+void nine_pointer_weakrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
+{
+ (void)allocator;
+ (void)allocation;
+}
+
+void nine_pointer_strongrelease(struct nine_allocator *allocator, struct nine_allocation *allocation)
+{
+ (void)allocator;
+ (void)allocation;
+}
+
+void nine_pointer_delayedstrongrelease(struct nine_allocator *allocator,
+ struct nine_allocation *allocation,
+ unsigned *counter)
+{
+ (void)allocator;
+ (void)allocation;
+ (void)counter;
+}
+
+struct nine_allocation *
+nine_suballocate(struct nine_allocator* allocator, struct nine_allocation *allocation, int offset)
+{
+ struct nine_allocation *new_allocation;
+ EnterCriticalSection(&allocator->mutex_slab);
+ new_allocation = slab_alloc_st(&allocator->external_allocation_pool);
+ LeaveCriticalSection(&allocator->mutex_slab);
+ new_allocation->is_external = true;
+ if (allocation->is_external)
+ new_allocation->external = (uint8_t *)allocation->external + offset;
+ else
+ new_allocation->external = (uint8_t *)allocation + NINE_ALLOCATION_ALIGNMENT + offset;
+ return new_allocation;
+}
+
+struct nine_allocation *
+nine_wrap_external_pointer(struct nine_allocator* allocator, void* data)
+{
+ struct nine_allocation *new_allocation;
+ EnterCriticalSection(&allocator->mutex_slab);
+ new_allocation = slab_alloc_st(&allocator->external_allocation_pool);
+ LeaveCriticalSection(&allocator->mutex_slab);
+ new_allocation->is_external = true;
+ new_allocation->external = data;
+ return new_allocation;
+}
+
+struct nine_allocator *
+nine_allocator_create(struct NineDevice9 *device, int memfd_virtualsizelimit)
+{
+ struct nine_allocator* allocator = MALLOC(sizeof(struct nine_allocator));
+ (void)device;
+ (void)memfd_virtualsizelimit;
+
+ if (!allocator)
+ return NULL;
+
+ slab_create(&allocator->external_allocation_pool, sizeof(struct nine_allocation), 4096);
+ InitializeCriticalSection(&allocator->mutex_slab);
+
+ return allocator;
+}
+
+void
+nine_allocator_destroy(struct nine_allocator *allocator)
+{
+ slab_destroy(&allocator->external_allocation_pool);
+ DeleteCriticalSection(&allocator->mutex_slab);
+}
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/.scm-settings b/src/VBox/Additions/3D/win/VBoxSVGA/.scm-settings
new file mode 100644
index 00000000..5e0d6e61
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/.scm-settings
@@ -0,0 +1,28 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for VBoxSVGA.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+/winsys/*.c: --no-convert-tabs --no-strip-trailing-blanks
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/Makefile.kmk b/src/VBox/Additions/3D/win/VBoxSVGA/Makefile.kmk
new file mode 100644
index 00000000..1c7c710d
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/Makefile.kmk
@@ -0,0 +1,85 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxSVGA hardware driver.
+#
+
+#
+# Copyright (C) 2016-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+DLLS += VBoxSVGA
+DLLS.amd64 += VBoxSVGA-x86
+
+VBoxSVGA_TEMPLATE = VBoxMesa3DGuestR3DllMinVista
+# -wd4005: vcc120: '__useHeader' : macro redefinition
+# -wd4100: unreferenced formal parameter
+# -wd4200: nonstandard extension used : zero-sized array in struct/union
+# -wd4204: vmw_screen_ioctl.c(392): warning C4204: nonstandard extension used: non-constant aggregate initializer
+# -wd4211: nonstandard extension used : redefined extern to static
+# -wd4245: 'return' : conversion from 'int' to 'unsigned int', signed/unsigned mismatch
+# -wd4255: no function prototype given
+# -wd4668: 'something' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif'
+VBoxSVGA_CFLAGS = -wd4005 -wd4100 -wd4200 -wd4204 -wd4211 -wd4245 -wd4255 -wd4668
+VBoxSVGA_INCS = \
+ $(VBoxSVGA_0_OUTDIR)/../VBoxMesaUtilLib/$(VBOX_MESA)/src \
+ $(VBOX_PATH_3D)/win/include \
+ $(VBOX_PATH_MESA)/src/gallium/drivers/svga \
+ $(VBOX_PATH_MESA)/src/gallium/drivers/svga/include \
+ $(VBOX_PATH_MESA)/src/gallium/winsys/svga/drm
+VBoxSVGA_SOURCES = \
+ winsys/vmw_screen.c \
+ winsys/vmw_screen_wddm.c \
+ winsys/vmw_screen_ioctl.c \
+ VBoxSVGA.c \
+ VBoxSVGA.rc \
+ VBoxSVGA.def
+VBoxSVGA_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaGalliumAuxLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaUtilLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaSVGAWinsysLib$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaSVGALib$(VBOX_SUFF_LIB)
+
+if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS)
+ VBoxSVGA_INSTTYPE = none
+ VBoxSVGA_DEBUG_INSTTYPE = both
+endif
+
+#
+# VBoxSVGA-x86 - x86 version of VBoxSVGA built for amd64 build
+#
+VBoxSVGA-x86_EXTENDS = VBoxSVGA
+VBoxSVGA-x86_BLD_TRG_ARCH = x86
+VBoxSVGA-x86_LIBS = \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxWddmUmHlp-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaGalliumAuxLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaUtilLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaSVGAWinsysLib-x86$(VBOX_SUFF_LIB) \
+ $(VBOX_PATH_ADDITIONS_LIB)/VBoxMesaSVGALib-x86$(VBOX_SUFF_LIB)
+VBoxSVGA-x86_DEFS = $(VBoxSVGA_DEFS) VBOX_WOW64
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.c b/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.c
new file mode 100644
index 00000000..f140d7bb
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.c
@@ -0,0 +1,146 @@
+/* $Id: VBoxSVGA.c $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - VMSVGA hardware driver.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBoxWddmUmHlp.h>
+
+#include "svga_public.h"
+#include "svga_screen.h"
+#include "pipe/p_screen.h"
+#include "pipe/p_context.h"
+#include "frontend/drm_driver.h"
+
+#include "wddm_screen.h"
+
+struct svga_winsys_screen *
+svga_wddm_winsys_screen_create(const WDDMGalliumDriverEnv *pEnv);
+
+struct pipe_screen * WINAPI
+GaDrvScreenCreate(const WDDMGalliumDriverEnv *pEnv)
+{
+ struct svga_winsys_screen *sws = svga_wddm_winsys_screen_create(pEnv);
+ if (sws)
+ return svga_screen_create(sws);
+ return NULL;
+}
+
+void WINAPI
+GaDrvScreenDestroy(struct pipe_screen *s)
+{
+ if (s)
+ s->destroy(s);
+}
+
+uint32_t WINAPI
+GaDrvGetSurfaceId(struct pipe_screen *pScreen, struct pipe_resource *pResource)
+{
+ uint32_t u32Sid = 0;
+
+ if ( pScreen
+ && pResource)
+ {
+ /* Get the sid (surface id). */
+ struct winsys_handle whandle;
+ memset(&whandle, 0, sizeof(whandle));
+ whandle.type = WINSYS_HANDLE_TYPE_SHARED;
+
+ if (pScreen->resource_get_handle(pScreen, NULL, pResource, &whandle, 0))
+ {
+ u32Sid = (uint32_t)whandle.handle;
+ }
+ }
+
+ return u32Sid;
+}
+
+const WDDMGalliumDriverEnv *WINAPI
+GaDrvGetWDDMEnv(struct pipe_screen *pScreen)
+{
+ HANDLE hAdapter = NULL;
+
+ if (pScreen)
+ {
+ struct svga_screen *pSvgaScreen = svga_screen(pScreen);
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)pSvgaScreen->sws;
+
+ return vws_wddm->pEnv;
+ }
+
+ return hAdapter;
+}
+
+uint32_t WINAPI
+GaDrvGetContextId(struct pipe_context *pPipeContext)
+{
+ uint32 u32Cid = ~0;
+
+ if (pPipeContext)
+ {
+ struct svga_winsys_context *pSWC = svga_winsys_context(pPipeContext);
+ u32Cid = pSWC->cid;
+ }
+
+ return u32Cid;
+}
+
+void WINAPI
+GaDrvContextFlush(struct pipe_context *pPipeContext)
+{
+ if (pPipeContext)
+ pPipeContext->flush(pPipeContext, NULL, PIPE_FLUSH_END_OF_FRAME);
+}
+
+BOOL WINAPI DllMain(HINSTANCE hDLLInst,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ BOOL fReturn = TRUE;
+
+ RT_NOREF2(hDLLInst, lpvReserved);
+
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ //RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE);
+ D3DKMTLoad();
+ break;
+
+ case DLL_PROCESS_DETACH:
+ /// @todo RTR3Term();
+ break;
+
+ case DLL_THREAD_ATTACH:
+ break;
+
+ case DLL_THREAD_DETACH:
+ break;
+
+ default:
+ break;
+ }
+
+ return fReturn;
+}
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.def b/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.def
new file mode 100644
index 00000000..4f161b98
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.def
@@ -0,0 +1,36 @@
+; $Id: VBoxSVGA.def $
+;; @file
+; Linker defintion file for the VBoxSVGA hardware driver.
+;
+
+;
+; Copyright (C) 2016-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+LIBRARY VBOXSVGA
+
+EXPORTS
+ GaDrvScreenCreate @1
+ GaDrvScreenDestroy @2
+ GaDrvGetWDDMEnv @3
+ GaDrvGetContextId @4
+ GaDrvGetSurfaceId @5
+ GaDrvContextFlush @6
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.rc b/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.rc
new file mode 100644
index 00000000..236c5dcd
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/VBoxSVGA.rc
@@ -0,0 +1,66 @@
+/* $Id: VBoxSVGA.rc $ */
+/** @file
+ * VBoxSVGA - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "VirtualBox D3D9 Mesa VMSVGA Hardware Driver\0"
+ VALUE "InternalName", "VBoxSVGA\0"
+#ifdef VBOX_WOW64
+ VALUE "OriginalFilename", "VBoxSVGA-x86.DLL\0"
+#else
+ VALUE "OriginalFilename", "VBoxSVGA.DLL\0"
+#endif
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/wddm_screen.h b/src/VBox/Additions/3D/win/VBoxSVGA/wddm_screen.h
new file mode 100644
index 00000000..cad1eaae
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/wddm_screen.h
@@ -0,0 +1,47 @@
+/* $Id: wddm_screen.h $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - VMSVGA hardware driver.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_3D_win_VBoxSVGA_wddm_screen_h
+#define GA_INCLUDED_SRC_3D_win_VBoxSVGA_wddm_screen_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBoxGaDriver.h>
+
+#include "vmw_screen.h"
+
+struct vmw_winsys_screen_wddm
+{
+ struct vmw_winsys_screen base;
+
+ const WDDMGalliumDriverEnv *pEnv;
+ VBOXGAHWINFOSVGA HwInfo;
+};
+
+#endif /* !GA_INCLUDED_SRC_3D_win_VBoxSVGA_wddm_screen_h */
+
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/winsys/Makefile.kup b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/Makefile.kup
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen.c b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen.c
new file mode 100644
index 00000000..b96a37ce
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**********************************************************
+ * Copyright 2009-2015 VMware, Inc. All rights reserved.
+ *
+ * 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 "../wddm_screen.h"
+#include "vmw_fence.h"
+#include "vmw_context.h"
+#include "vmwgfx_drm.h"
+
+#include "util/os_file.h"
+#include "util/u_memory.h"
+#include "pipe/p_compiler.h"
+#include "util/u_hash_table.h"
+
+/* Called from vmw_drm_create_screen(), creates and initializes the
+ * vmw_winsys_screen structure, which is the main entity in this
+ * module.
+ * First, check whether a vmw_winsys_screen object already exists for
+ * this device, and in that case return that one, making sure that we
+ * have our own file descriptor open to DRM.
+ */
+
+struct vmw_winsys_screen_wddm *
+vmw_winsys_create_wddm(const WDDMGalliumDriverEnv *pEnv)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm;
+ struct vmw_winsys_screen *vws;
+
+ if ( pEnv->pHWInfo == NULL
+ || pEnv->pHWInfo->u32HwType != VBOX_GA_HW_TYPE_VMSVGA)
+ return NULL;
+
+ vws_wddm = CALLOC_STRUCT(vmw_winsys_screen_wddm);
+ vws = &vws_wddm->base;
+ if (!vws)
+ goto out_no_vws;
+
+ vws_wddm->pEnv = pEnv;
+ vws_wddm->HwInfo = pEnv->pHWInfo->u.svga;
+
+ vws->device = 0; /* not used */
+ vws->open_count = 1;
+ vws->ioctl.drm_fd = -1; /* not used */
+ vws->force_coherent = FALSE;
+ if (!vmw_ioctl_init(vws))
+ goto out_no_ioctl;
+
+ vws->base.have_gb_dma = !vws->force_coherent;
+ vws->base.need_to_rebind_resources = FALSE;
+ vws->base.have_transfer_from_buffer_cmd = vws->base.have_vgpu10;
+ vws->base.have_constant_buffer_offset_cmd = FALSE;
+ vws->cache_maps = FALSE;
+ vws->fence_ops = vmw_fence_ops_create(vws);
+ if (!vws->fence_ops)
+ goto out_no_fence_ops;
+
+ if(!vmw_pools_init(vws))
+ goto out_no_pools;
+
+ if (!vmw_winsys_screen_init_svga(vws))
+ goto out_no_svga;
+
+ cnd_init(&vws->cs_cond);
+ mtx_init(&vws->cs_mutex, mtx_plain);
+
+ return vws_wddm;
+out_no_svga:
+ vmw_pools_cleanup(vws);
+out_no_pools:
+ vws->fence_ops->destroy(vws->fence_ops);
+out_no_fence_ops:
+ vmw_ioctl_cleanup(vws);
+out_no_ioctl:
+ FREE(vws);
+out_no_vws:
+ return NULL;
+}
+
+void
+vmw_winsys_destroy(struct vmw_winsys_screen *vws)
+{
+ if (--vws->open_count == 0) {
+ vmw_pools_cleanup(vws);
+ vws->fence_ops->destroy(vws->fence_ops);
+ vmw_ioctl_cleanup(vws);
+ mtx_destroy(&vws->cs_mutex);
+ cnd_destroy(&vws->cs_cond);
+ FREE(vws);
+ }
+}
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_ioctl.c b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_ioctl.c
new file mode 100644
index 00000000..106ff74a
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_ioctl.c
@@ -0,0 +1,979 @@
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**********************************************************
+ * Copyright 2009-2015 VMware, Inc. All rights reserved.
+ *
+ * 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.
+ *
+ **********************************************************/
+
+/**
+ * @file
+ *
+ * Wrappers for DRM ioctl functionlaity used by the rest of the vmw
+ * drm winsys.
+ *
+ * Based on svgaicd_escape.c
+ */
+
+
+#include "svga_cmd.h"
+#include "util/u_memory.h"
+#include "util/u_math.h"
+#include "svgadump/svga_dump.h"
+#include "frontend/drm_driver.h"
+#include "vmw_screen.h"
+#include "vmw_context.h"
+#include "vmw_fence.h"
+#include "vmwgfx_drm.h"
+#include "svga3d_caps.h"
+#include "svga3d_reg.h"
+#include "svga3d_surfacedefs.h"
+
+#include "../wddm_screen.h"
+#include <iprt/asm.h>
+
+#define VMW_MAX_DEFAULT_TEXTURE_SIZE (128 * 1024 * 1024)
+#define VMW_FENCE_TIMEOUT_SECONDS 3600UL
+
+#define SVGA3D_FLAGS_64(upper32, lower32) (((uint64_t)upper32 << 32) | lower32)
+#define SVGA3D_FLAGS_UPPER_32(svga3d_flags) (svga3d_flags >> 32)
+#define SVGA3D_FLAGS_LOWER_32(svga3d_flags) \
+ (svga3d_flags & ((uint64_t)UINT32_MAX))
+
+struct vmw_region
+{
+ uint32_t handle;
+ uint64_t map_handle;
+ void *data;
+ uint32_t map_count;
+ struct vmw_winsys_screen_wddm *vws_wddm;
+ uint32_t size;
+};
+
+uint32_t
+vmw_region_size(struct vmw_region *region)
+{
+ return region->size;
+}
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || \
+ defined(__NetBSD__) || defined(__OpenBSD__)
+#define ERESTART EINTR
+#endif
+
+uint32
+vmw_ioctl_context_create(struct vmw_winsys_screen *vws)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ return vws_wddm->pEnv->pfnContextCreate(vws_wddm->pEnv->pvEnv, false, false);
+}
+
+uint32
+vmw_ioctl_extended_context_create(struct vmw_winsys_screen *vws,
+ boolean vgpu10)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ return vws_wddm->pEnv->pfnContextCreate(vws_wddm->pEnv->pvEnv, true, vgpu10);
+}
+
+void
+vmw_ioctl_context_destroy(struct vmw_winsys_screen *vws, uint32 cid)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ vws_wddm->pEnv->pfnContextDestroy(vws_wddm->pEnv->pvEnv, cid);
+}
+
+uint32
+vmw_ioctl_surface_create(struct vmw_winsys_screen *vws,
+ SVGA3dSurface1Flags flags,
+ SVGA3dSurfaceFormat format,
+ unsigned usage,
+ SVGA3dSize size,
+ uint32_t numFaces, uint32_t numMipLevels,
+ unsigned sampleCount)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+
+ GASURFCREATE createParms;
+ GASURFSIZE sizes[DRM_VMW_MAX_SURFACE_FACES*
+ DRM_VMW_MAX_MIP_LEVELS];
+ GASURFSIZE *cur_size;
+ uint32_t iFace;
+ uint32_t iMipLevel;
+ uint32_t u32Sid;
+ int ret;
+
+ RT_NOREF(sampleCount);
+
+ memset(&createParms, 0, sizeof(createParms));
+ createParms.flags = (uint32_t) flags;
+ createParms.format = (uint32_t) format;
+ createParms.usage = (uint32_t) usage;
+
+ if (numFaces * numMipLevels >= GA_MAX_SURFACE_FACES*GA_MAX_MIP_LEVELS) {
+ return (uint32_t)-1;
+ }
+ cur_size = sizes;
+ for (iFace = 0; iFace < numFaces; ++iFace) {
+ SVGA3dSize mipSize = size;
+
+ createParms.mip_levels[iFace] = numMipLevels;
+ for (iMipLevel = 0; iMipLevel < numMipLevels; ++iMipLevel) {
+ cur_size->cWidth = mipSize.width;
+ cur_size->cHeight = mipSize.height;
+ cur_size->cDepth = mipSize.depth;
+ cur_size->u32Reserved = 0;
+ mipSize.width = MAX2(mipSize.width >> 1, 1);
+ mipSize.height = MAX2(mipSize.height >> 1, 1);
+ mipSize.depth = MAX2(mipSize.depth >> 1, 1);
+ cur_size++;
+ }
+ }
+ for (iFace = numFaces; iFace < SVGA3D_MAX_SURFACE_FACES; ++iFace) {
+ createParms.mip_levels[iFace] = 0;
+ }
+
+ ret = vws_wddm->pEnv->pfnSurfaceDefine(vws_wddm->pEnv->pvEnv, &createParms, &sizes[0], numFaces * numMipLevels, &u32Sid);
+ if (ret) {
+ return (uint32_t)-1;
+ }
+
+ return u32Sid;
+}
+
+
+uint32
+vmw_ioctl_gb_surface_create(struct vmw_winsys_screen *vws,
+ SVGA3dSurfaceAllFlags flags,
+ SVGA3dSurfaceFormat format,
+ unsigned usage,
+ SVGA3dSize size,
+ uint32_t numFaces,
+ uint32_t numMipLevels,
+ unsigned sampleCount,
+ uint32_t buffer_handle,
+ SVGA3dMSPattern multisamplePattern,
+ SVGA3dMSQualityLevel qualityLevel,
+ struct vmw_region **p_region)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+
+ struct vmw_region *region = NULL;
+ if (p_region)
+ {
+ region = CALLOC_STRUCT(vmw_region);
+ if (!region)
+ return SVGA3D_INVALID_ID;
+ }
+
+ SVGAGBSURFCREATE createParms;
+ createParms.s.flags = flags;
+ createParms.s.format = format;
+ createParms.s.usage = usage;
+ createParms.s.size = size;
+ createParms.s.numFaces = numFaces;
+ createParms.s.numMipLevels = numMipLevels;
+ createParms.s.sampleCount = sampleCount;
+ createParms.s.multisamplePattern = multisamplePattern;
+ createParms.s.qualityLevel = qualityLevel;
+ if (buffer_handle)
+ createParms.gmrid = buffer_handle;
+ else
+ createParms.gmrid = SVGA3D_INVALID_ID;
+ createParms.u64UserAddress = 0; /* out */
+ createParms.u32Sid = 0; /* out */
+
+ createParms.cbGB = svga3dsurface_get_serialized_size(format,
+ size,
+ numMipLevels,
+ numFaces);
+
+ int ret = vws_wddm->pEnv->pfnGBSurfaceDefine(vws_wddm->pEnv->pvEnv, &createParms);
+ if (ret)
+ {
+ FREE(region);
+ return SVGA3D_INVALID_ID;
+ }
+
+ if (p_region)
+ {
+ region->handle = createParms.gmrid;
+ region->map_handle = 0;
+ region->data = (void *)(uintptr_t)createParms.u64UserAddress;
+ region->map_count = 0;
+ region->size = createParms.cbGB;
+ region->vws_wddm = vws_wddm;
+ *p_region = region;
+ }
+ return createParms.u32Sid;
+}
+
+/**
+ * vmw_ioctl_surface_req - Fill in a struct surface_req
+ *
+ * @vws: Winsys screen
+ * @whandle: Surface handle
+ * @req: The struct surface req to fill in
+ * @needs_unref: This call takes a kernel surface reference that needs to
+ * be unreferenced.
+ *
+ * Returns 0 on success, negative error type otherwise.
+ * Fills in the surface_req structure according to handle type and kernel
+ * capabilities.
+ */
+static int
+vmw_ioctl_surface_req(const struct vmw_winsys_screen *vws,
+ const struct winsys_handle *whandle,
+ struct drm_vmw_surface_arg *req,
+ boolean *needs_unref)
+{
+ ASMBreakpoint();
+ RT_NOREF4(vws, whandle, req, needs_unref);
+ return -1;
+}
+
+/**
+ * vmw_ioctl_gb_surface_ref - Put a reference on a guest-backed surface and
+ * get surface information
+ *
+ * @vws: Screen to register the reference on
+ * @handle: Kernel handle of the guest-backed surface
+ * @flags: flags used when the surface was created
+ * @format: Format used when the surface was created
+ * @numMipLevels: Number of mipmap levels of the surface
+ * @p_region: On successful return points to a newly allocated
+ * struct vmw_region holding a reference to the surface backup buffer.
+ *
+ * Returns 0 on success, a system error on failure.
+ */
+int
+vmw_ioctl_gb_surface_ref(struct vmw_winsys_screen *vws,
+ const struct winsys_handle *whandle,
+ SVGA3dSurfaceAllFlags *flags,
+ SVGA3dSurfaceFormat *format,
+ uint32_t *numMipLevels,
+ uint32_t *handle,
+ struct vmw_region **p_region)
+{
+ ASMBreakpoint();
+ RT_NOREF7(vws, whandle, flags, format, numMipLevels, handle, p_region);
+ return -1;
+}
+
+void
+vmw_ioctl_surface_destroy(struct vmw_winsys_screen *vws, uint32 sid)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ vws_wddm->pEnv->pfnSurfaceDestroy(vws_wddm->pEnv->pvEnv, sid);
+}
+
+void
+vmw_ioctl_command(struct vmw_winsys_screen *vws, int32_t cid,
+ uint32_t throttle_us, void *commands, uint32_t size,
+ struct pipe_fence_handle **pfence, int32_t imported_fence_fd,
+ uint32_t flags)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ GAFENCEQUERY FenceQuery;
+ RT_NOREF3(throttle_us, imported_fence_fd, flags);
+#ifdef DEBUG
+ // svga_dump_commands(commands, size);
+#endif
+ memset(&FenceQuery, 0, sizeof(FenceQuery));
+ FenceQuery.u32FenceStatus = GA_FENCE_STATUS_NULL;
+
+ vws_wddm->pEnv->pfnRender(vws_wddm->pEnv->pvEnv, cid, commands, size, pfence? &FenceQuery: NULL);
+ if (FenceQuery.u32FenceStatus == GA_FENCE_STATUS_NULL)
+ {
+ /*
+ * Kernel has already synced, or caller requested no fence.
+ */
+ if (pfence)
+ *pfence = NULL;
+ }
+ else
+ {
+ if (pfence)
+ {
+ vmw_fences_signal(vws->fence_ops, FenceQuery.u32ProcessedSeqNo, FenceQuery.u32SubmittedSeqNo, TRUE);
+
+ *pfence = vmw_fence_create(vws->fence_ops, FenceQuery.u32FenceHandle,
+ FenceQuery.u32SubmittedSeqNo, /* mask */ 0, -1);
+ if (*pfence == NULL)
+ {
+ /*
+ * Fence creation failed. Need to sync.
+ */
+ (void) vmw_ioctl_fence_finish(vws, FenceQuery.u32FenceHandle, /* mask */ 0);
+ vmw_ioctl_fence_unref(vws, FenceQuery.u32FenceHandle);
+ }
+ }
+ }
+}
+
+
+struct vmw_region *
+vmw_ioctl_region_create(struct vmw_winsys_screen *vws, uint32_t size)
+{
+ /* 'region' is a buffer visible both for host and guest */
+ struct vmw_region *region;
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ uint32_t u32GmrId = 0;
+ void *pvMap = NULL;
+ int ret;
+
+ region = CALLOC_STRUCT(vmw_region);
+ if (!region)
+ goto out_err1;
+
+ ret = vws_wddm->pEnv->pfnRegionCreate(vws_wddm->pEnv->pvEnv, size, &u32GmrId, &pvMap);
+
+ if (ret) {
+ vmw_error("IOCTL failed %d: %s\n", ret, strerror(-ret));
+ goto out_err1;
+ }
+
+ region->handle = u32GmrId;
+ region->map_handle = 0;
+ region->data = pvMap;
+ region->map_count = 0;
+ region->size = size;
+ region->vws_wddm = vws_wddm;
+
+ return region;
+
+out_err1:
+ FREE(region);
+ return NULL;
+}
+
+void
+vmw_ioctl_region_destroy(struct vmw_region *region)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = region->vws_wddm;
+
+ vws_wddm->pEnv->pfnRegionDestroy(vws_wddm->pEnv->pvEnv, region->handle, region->data);
+
+ FREE(region);
+}
+
+SVGAGuestPtr
+vmw_ioctl_region_ptr(struct vmw_region *region)
+{
+ SVGAGuestPtr ptr = {region->handle, 0};
+ return ptr;
+}
+
+void *
+vmw_ioctl_region_map(struct vmw_region *region)
+{
+ debug_printf("%s: gmrId = %u\n", __FUNCTION__,
+ region->handle);
+
+ if (region->data == NULL)
+ {
+ /* Should not get here. */
+ return NULL;
+ }
+
+ ++region->map_count;
+
+ return region->data;
+}
+
+void
+vmw_ioctl_region_unmap(struct vmw_region *region)
+{
+ --region->map_count;
+}
+
+/**
+ * vmw_ioctl_syncforcpu - Synchronize a buffer object for CPU usage
+ *
+ * @region: Pointer to a struct vmw_region representing the buffer object.
+ * @dont_block: Dont wait for GPU idle, but rather return -EBUSY if the
+ * GPU is busy with the buffer object.
+ * @readonly: Hint that the CPU access is read-only.
+ * @allow_cs: Allow concurrent command submission while the buffer is
+ * synchronized for CPU. If FALSE command submissions referencing the
+ * buffer will block until a corresponding call to vmw_ioctl_releasefromcpu.
+ *
+ * This function idles any GPU activities touching the buffer and blocks
+ * command submission of commands referencing the buffer, even from
+ * other processes.
+ */
+int
+vmw_ioctl_syncforcpu(struct vmw_region *region,
+ boolean dont_block,
+ boolean readonly,
+ boolean allow_cs)
+{
+ ASMBreakpoint();
+ RT_NOREF4(region, dont_block, readonly, allow_cs);
+ return -1;
+}
+
+/**
+ * vmw_ioctl_releasefromcpu - Undo a previous syncforcpu.
+ *
+ * @region: Pointer to a struct vmw_region representing the buffer object.
+ * @readonly: Should hold the same value as the matching syncforcpu call.
+ * @allow_cs: Should hold the same value as the matching syncforcpu call.
+ */
+void
+vmw_ioctl_releasefromcpu(struct vmw_region *region,
+ boolean readonly,
+ boolean allow_cs)
+{
+ ASMBreakpoint();
+ RT_NOREF3(region, readonly, allow_cs);
+ return;
+}
+
+void
+vmw_ioctl_fence_unref(struct vmw_winsys_screen *vws,
+ uint32_t handle)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ vws_wddm->pEnv->pfnFenceUnref(vws_wddm->pEnv->pvEnv, handle);
+}
+
+static inline uint32_t
+vmw_drm_fence_flags(uint32_t flags)
+{
+ uint32_t dflags = 0;
+
+ if (flags & SVGA_FENCE_FLAG_EXEC)
+ dflags |= DRM_VMW_FENCE_FLAG_EXEC;
+ if (flags & SVGA_FENCE_FLAG_QUERY)
+ dflags |= DRM_VMW_FENCE_FLAG_QUERY;
+
+ return dflags;
+}
+
+
+int
+vmw_ioctl_fence_signalled(struct vmw_winsys_screen *vws,
+ uint32_t handle,
+ uint32_t flags)
+{
+ RT_NOREF(flags);
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+ GAFENCEQUERY FenceQuery;
+ int ret;
+
+ memset(&FenceQuery, 0, sizeof(FenceQuery));
+ FenceQuery.u32FenceStatus = GA_FENCE_STATUS_NULL;
+
+ ret = vws_wddm->pEnv->pfnFenceQuery(vws_wddm->pEnv->pvEnv, handle, &FenceQuery);
+
+ if (ret != 0)
+ return ret;
+
+ if (FenceQuery.u32FenceStatus == GA_FENCE_STATUS_NULL)
+ return 0; /* Treat as signalled. */
+
+ vmw_fences_signal(vws->fence_ops, FenceQuery.u32ProcessedSeqNo, FenceQuery.u32SubmittedSeqNo, TRUE);
+
+ return FenceQuery.u32FenceStatus == GA_FENCE_STATUS_SIGNALED ? 0 : -1;
+}
+
+
+
+int
+vmw_ioctl_fence_finish(struct vmw_winsys_screen *vws,
+ uint32_t handle,
+ uint32_t flags)
+{
+ RT_NOREF(flags);
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+
+ vws_wddm->pEnv->pfnFenceWait(vws_wddm->pEnv->pvEnv, handle, VMW_FENCE_TIMEOUT_SECONDS*1000000);
+
+ return 0; /* Regardless. */
+}
+
+uint32
+vmw_ioctl_shader_create(struct vmw_winsys_screen *vws,
+ SVGA3dShaderType type,
+ uint32 code_len)
+{
+ ASMBreakpoint();
+ RT_NOREF3(vws, type, code_len);
+ return 0;
+}
+
+void
+vmw_ioctl_shader_destroy(struct vmw_winsys_screen *vws, uint32 shid)
+{
+ ASMBreakpoint();
+ RT_NOREF2(vws, shid);
+ return;
+}
+
+static int
+vmw_ioctl_parse_caps(struct vmw_winsys_screen *vws,
+ const uint32_t *cap_buffer)
+{
+ unsigned i;
+
+ if (vws->base.have_gb_objects) {
+ for (i = 0; i < vws->ioctl.num_cap_3d; ++i) {
+ vws->ioctl.cap_3d[i].has_cap = TRUE;
+ vws->ioctl.cap_3d[i].result.u = cap_buffer[i];
+ }
+ return 0;
+ } else {
+ const uint32 *capsBlock;
+ const SVGA3dCapsRecord *capsRecord = NULL;
+ uint32 offset;
+ const SVGA3dCapPair *capArray;
+ unsigned numCaps, index;
+
+ /*
+ * Search linearly through the caps block records for the specified type.
+ */
+ capsBlock = cap_buffer;
+ for (offset = 0; capsBlock[offset] != 0; offset += capsBlock[offset]) {
+ const SVGA3dCapsRecord *record;
+ if (offset >= SVGA_FIFO_3D_CAPS_SIZE)
+ break;
+ record = (const SVGA3dCapsRecord *) (capsBlock + offset);
+ if ((record->header.type >= SVGA3DCAPS_RECORD_DEVCAPS_MIN) &&
+ (record->header.type <= SVGA3DCAPS_RECORD_DEVCAPS_MAX) &&
+ (!capsRecord || (record->header.type > capsRecord->header.type))) {
+ capsRecord = record;
+ }
+ }
+
+ if(!capsRecord)
+ return -1;
+
+ /*
+ * Calculate the number of caps from the size of the record.
+ */
+ capArray = (const SVGA3dCapPair *) capsRecord->data;
+ numCaps = (int) ((capsRecord->header.length * sizeof(uint32) -
+ sizeof capsRecord->header) / (2 * sizeof(uint32)));
+
+ for (i = 0; i < numCaps; i++) {
+ index = capArray[i][0];
+ if (index < vws->ioctl.num_cap_3d) {
+ vws->ioctl.cap_3d[index].has_cap = TRUE;
+ vws->ioctl.cap_3d[index].result.u = capArray[i][1];
+ } else {
+ debug_printf("Unknown devcaps seen: %d\n", index);
+ }
+ }
+ }
+ return 0;
+}
+
+#define SVGA_CAP2_DX2 0x00000004
+#define SVGA_CAP2_DX3 0x00000400
+
+enum SVGASHADERMODEL
+{
+ SVGA_SM_LEGACY = 0,
+ SVGA_SM_4,
+ SVGA_SM_4_1,
+ SVGA_SM_5,
+ SVGA_SM_MAX
+};
+
+static enum SVGASHADERMODEL vboxGetShaderModel(struct vmw_winsys_screen_wddm *vws_wddm)
+{
+#if 0
+ (void)vws_wddm;
+ return SVGA_SM_5;
+#else
+ enum SVGASHADERMODEL enmResult = SVGA_SM_LEGACY;
+
+ if ( (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_GBOBJECTS)
+ && (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_CMD_BUFFERS_3 /*=SVGA_CAP_DX*/)
+ && vws_wddm->HwInfo.au32Caps[SVGA3D_DEVCAP_DXCONTEXT])
+ {
+ enmResult = SVGA_SM_4;
+
+ if (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_CAP2_REGISTER)
+ {
+ if ( (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAP2] & SVGA_CAP2_DX2)
+ && vws_wddm->HwInfo.au32Caps[SVGA3D_DEVCAP_SM41])
+ {
+ enmResult = SVGA_SM_4_1;
+
+ if ( (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAP2] & SVGA_CAP2_DX3)
+ && vws_wddm->HwInfo.au32Caps[SVGA3D_DEVCAP_SM5])
+ {
+ enmResult = SVGA_SM_5;
+ }
+ }
+ }
+ }
+
+ return enmResult;
+#endif
+}
+
+static int
+vboxGetParam(struct vmw_winsys_screen_wddm *vws_wddm, struct drm_vmw_getparam_arg *gp_arg)
+{
+ /* DRM_VMW_GET_PARAM */
+ switch (gp_arg->param)
+ {
+ case DRM_VMW_PARAM_NUM_STREAMS:
+ gp_arg->value = 1; /* not used */
+ break;
+ case DRM_VMW_PARAM_NUM_FREE_STREAMS:
+ gp_arg->value = 1; /* not used */
+ break;
+ case DRM_VMW_PARAM_3D:
+ gp_arg->value = (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_3D) != 0;
+ break;
+ case DRM_VMW_PARAM_HW_CAPS:
+ gp_arg->value = vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES];
+ break;
+ case DRM_VMW_PARAM_FIFO_CAPS:
+ gp_arg->value = vws_wddm->HwInfo.au32Fifo[SVGA_FIFO_CAPABILITIES];
+ break;
+ case DRM_VMW_PARAM_MAX_FB_SIZE:
+ gp_arg->value = vws_wddm->HwInfo.au32Regs[SVGA_REG_MAX_PRIMARY_BOUNDING_BOX_MEM];
+ break;
+ case DRM_VMW_PARAM_FIFO_HW_VERSION:
+ if (vws_wddm->HwInfo.au32Fifo[SVGA_FIFO_CAPABILITIES] & SVGA_FIFO_CAP_3D_HWVERSION_REVISED)
+ {
+ gp_arg->value =
+ vws_wddm->HwInfo.au32Fifo[SVGA_FIFO_3D_HWVERSION_REVISED];
+ }
+ else
+ {
+ gp_arg->value =
+ vws_wddm->HwInfo.au32Fifo[SVGA_FIFO_3D_HWVERSION];
+ }
+ break;
+ case DRM_VMW_PARAM_MAX_SURF_MEMORY:
+ if (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_GBOBJECTS)
+ gp_arg->value = vws_wddm->HwInfo.au32Regs[SVGA_REG_SUGGESTED_GBOBJECT_MEM_SIZE_KB] * 1024 / 2;
+ else
+ gp_arg->value = vws_wddm->HwInfo.au32Regs[SVGA_REG_MEMORY_SIZE];
+ break;
+ case DRM_VMW_PARAM_3D_CAPS_SIZE:
+ if (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_GBOBJECTS)
+ gp_arg->value = SVGA3D_DEVCAP_MAX * sizeof(uint32_t);
+ else
+ gp_arg->value = (SVGA_FIFO_3D_CAPS_LAST - SVGA_FIFO_3D_CAPS + 1) * sizeof(uint32_t);
+ break;
+ case DRM_VMW_PARAM_MAX_MOB_MEMORY:
+ gp_arg->value = vws_wddm->HwInfo.au32Regs[SVGA_REG_SUGGESTED_GBOBJECT_MEM_SIZE_KB] * 1024;
+ break;
+ case DRM_VMW_PARAM_MAX_MOB_SIZE:
+ gp_arg->value = vws_wddm->HwInfo.au32Regs[SVGA_REG_MOB_MAX_SIZE];
+ break;
+ case DRM_VMW_PARAM_SCREEN_TARGET:
+ gp_arg->value = 1; /* not used */
+ break;
+ case DRM_VMW_PARAM_DX:
+ gp_arg->value = (vboxGetShaderModel(vws_wddm) >= SVGA_SM_4);
+ break;
+ case DRM_VMW_PARAM_HW_CAPS2:
+ if (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_CAP2_REGISTER)
+ gp_arg->value = vws_wddm->HwInfo.au32Regs[SVGA_REG_CAP2];
+ else
+ gp_arg->value = 0;
+ break;
+ case DRM_VMW_PARAM_SM4_1:
+ gp_arg->value = (vboxGetShaderModel(vws_wddm) >= SVGA_SM_4_1);
+ break;
+ case DRM_VMW_PARAM_SM5:
+ gp_arg->value = (vboxGetShaderModel(vws_wddm) >= SVGA_SM_5);
+ break;
+ default: return -1;
+ }
+ return 0;
+}
+
+static int
+vboxGet3DCap(struct vmw_winsys_screen_wddm *vws_wddm, void *pvCap, size_t cbCap)
+{
+ /* DRM_VMW_GET_3D_CAP */
+ if (vws_wddm->HwInfo.au32Regs[SVGA_REG_CAPABILITIES] & SVGA_CAP_GBOBJECTS)
+ memcpy(pvCap, vws_wddm->HwInfo.au32Caps, cbCap);
+ else
+ memcpy(pvCap, &vws_wddm->HwInfo.au32Fifo[SVGA_FIFO_3D_CAPS], cbCap);
+ return 0;
+}
+
+boolean
+vmw_ioctl_init(struct vmw_winsys_screen *vws)
+{
+ struct vmw_winsys_screen_wddm *vws_wddm = (struct vmw_winsys_screen_wddm *)vws;
+
+ struct drm_vmw_getparam_arg gp_arg;
+ unsigned int size;
+ int ret;
+ uint32_t *cap_buffer;
+ boolean drm_gb_capable;
+ boolean have_drm_2_5;
+
+ VMW_FUNC;
+
+ have_drm_2_5 = 1;
+ vws->ioctl.have_drm_2_6 = 1;
+ vws->ioctl.have_drm_2_9 = 1;
+ vws->ioctl.have_drm_2_15 = 1;
+ vws->ioctl.have_drm_2_16 = 1;
+ vws->ioctl.have_drm_2_17 = 1;
+ vws->ioctl.have_drm_2_18 = 1;
+ vws->ioctl.have_drm_2_19 = 1;
+
+ vws->ioctl.drm_execbuf_version = vws->ioctl.have_drm_2_9 ? 2 : 1;
+
+ drm_gb_capable = have_drm_2_5;
+
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_3D;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret || gp_arg.value == 0) {
+ vmw_error("No 3D enabled (%i, %s).\n", ret, strerror(-ret));
+ goto out_no_3d;
+ }
+
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_FIFO_HW_VERSION;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret) {
+ vmw_error("Failed to get fifo hw version (%i, %s).\n",
+ ret, strerror(-ret));
+ goto out_no_3d;
+ }
+ vws->ioctl.hwversion = gp_arg.value;
+
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_HW_CAPS;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret)
+ vws->base.have_gb_objects = FALSE;
+ else
+ vws->base.have_gb_objects =
+ !!(gp_arg.value & (uint64_t) SVGA_CAP_GBOBJECTS);
+
+ if (vws->base.have_gb_objects && !drm_gb_capable)
+ goto out_no_3d;
+
+ vws->base.have_vgpu10 = FALSE;
+ vws->base.have_sm4_1 = FALSE;
+ vws->base.have_intra_surface_copy = FALSE;
+
+ if (vws->base.have_gb_objects) {
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_MAX_MOB_MEMORY;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret) {
+ /* Just guess a large enough value. */
+ vws->ioctl.max_mob_memory = 256*1024*1024;
+ } else {
+ vws->ioctl.max_mob_memory = gp_arg.value;
+ }
+
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_MAX_MOB_SIZE;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+
+ if (ret || gp_arg.value == 0) {
+ vws->ioctl.max_texture_size = VMW_MAX_DEFAULT_TEXTURE_SIZE;
+ } else {
+ vws->ioctl.max_texture_size = gp_arg.value;
+ }
+
+ /* Never early flush surfaces, mobs do accounting. */
+ vws->ioctl.max_surface_memory = -1;
+
+ if (vws->ioctl.have_drm_2_9) {
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_DX;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret == 0 && gp_arg.value != 0) {
+ const char *vgpu10_val;
+
+ debug_printf("Have VGPU10 interface and hardware.\n");
+ vws->base.have_vgpu10 = TRUE;
+ vgpu10_val = getenv("SVGA_VGPU10");
+ if (vgpu10_val && strcmp(vgpu10_val, "0") == 0) {
+ debug_printf("Disabling VGPU10 interface.\n");
+ vws->base.have_vgpu10 = FALSE;
+ } else {
+ debug_printf("Enabling VGPU10 interface.\n");
+ }
+ }
+ }
+
+ if (vws->ioctl.have_drm_2_15 && vws->base.have_vgpu10) {
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_HW_CAPS2;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret == 0 && gp_arg.value != 0) {
+ vws->base.have_intra_surface_copy = TRUE;
+ }
+
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_SM4_1;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret == 0 && gp_arg.value != 0) {
+ vws->base.have_sm4_1 = TRUE;
+ }
+ }
+
+ if (vws->ioctl.have_drm_2_18 && vws->base.have_sm4_1) {
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_SM5;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret == 0 && gp_arg.value != 0) {
+ vws->base.have_sm5 = TRUE;
+ }
+ }
+
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_3D_CAPS_SIZE;
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (ret)
+ size = SVGA_FIFO_3D_CAPS_SIZE * sizeof(uint32_t);
+ else
+ size = gp_arg.value;
+
+ if (vws->base.have_gb_objects)
+ vws->ioctl.num_cap_3d = size / sizeof(uint32_t);
+ else
+ vws->ioctl.num_cap_3d = SVGA3D_DEVCAP_MAX;
+
+ if (vws->ioctl.have_drm_2_16) {
+ vws->base.have_coherent = TRUE;
+ }
+ } else {
+ vws->ioctl.num_cap_3d = SVGA3D_DEVCAP_MAX;
+
+ memset(&gp_arg, 0, sizeof(gp_arg));
+ gp_arg.param = DRM_VMW_PARAM_MAX_SURF_MEMORY;
+ if (have_drm_2_5)
+ ret = vboxGetParam(vws_wddm, &gp_arg);
+ if (!have_drm_2_5 || ret) {
+ /* Just guess a large enough value, around 800mb. */
+ vws->ioctl.max_surface_memory = 0x30000000;
+ } else {
+ vws->ioctl.max_surface_memory = gp_arg.value;
+ }
+
+ vws->ioctl.max_texture_size = VMW_MAX_DEFAULT_TEXTURE_SIZE;
+
+ size = SVGA_FIFO_3D_CAPS_SIZE * sizeof(uint32_t);
+ }
+
+ debug_printf("VGPU10 interface is %s.\n",
+ vws->base.have_vgpu10 ? "on" : "off");
+
+ cap_buffer = calloc(1, size);
+ if (!cap_buffer) {
+ debug_printf("Failed alloc fifo 3D caps buffer.\n");
+ goto out_no_3d;
+ }
+
+ vws->ioctl.cap_3d = calloc(vws->ioctl.num_cap_3d,
+ sizeof(*vws->ioctl.cap_3d));
+ if (!vws->ioctl.cap_3d) {
+ debug_printf("Failed alloc fifo 3D caps buffer.\n");
+ goto out_no_caparray;
+ }
+
+ /*
+ * This call must always be after DRM_VMW_PARAM_MAX_MOB_MEMORY and
+ * DRM_VMW_PARAM_SM4_1. This is because, based on these calls, kernel
+ * driver sends the supported cap.
+ */
+ ret = vboxGet3DCap(vws_wddm, cap_buffer, size);
+
+ if (ret) {
+ debug_printf("Failed to get 3D capabilities"
+ " (%i, %s).\n", ret, strerror(-ret));
+ goto out_no_caps;
+ }
+
+ ret = vmw_ioctl_parse_caps(vws, cap_buffer);
+ if (ret) {
+ debug_printf("Failed to parse 3D capabilities"
+ " (%i, %s).\n", ret, strerror(-ret));
+ goto out_no_caps;
+ }
+
+ if (vws->ioctl.have_drm_2_15 && vws->base.have_vgpu10) {
+
+ /* support for these commands didn't make it into vmwgfx kernel
+ * modules before 2.10.
+ */
+ vws->base.have_generate_mipmap_cmd = TRUE;
+ vws->base.have_set_predication_cmd = TRUE;
+ }
+
+ if (vws->ioctl.have_drm_2_15) {
+ vws->base.have_fence_fd = TRUE;
+ }
+
+ free(cap_buffer);
+ vmw_printf("%s OK\n", __FUNCTION__);
+ return TRUE;
+ out_no_caps:
+ free(vws->ioctl.cap_3d);
+ out_no_caparray:
+ free(cap_buffer);
+ out_no_3d:
+ vws->ioctl.num_cap_3d = 0;
+ debug_printf("%s Failed\n", __FUNCTION__);
+ return FALSE;
+}
+
+
+
+void
+vmw_ioctl_cleanup(struct vmw_winsys_screen *vws)
+{
+ VMW_FUNC;
+
+ free(vws->ioctl.cap_3d);
+}
diff --git a/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_wddm.c b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_wddm.c
new file mode 100644
index 00000000..16b0ac21
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxSVGA/winsys/vmw_screen_wddm.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**********************************************************
+ * Copyright 2009-2015 VMware, Inc. All rights reserved.
+ *
+ * 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 "pipe/p_compiler.h"
+#include "util/u_inlines.h"
+#include "util/u_memory.h"
+
+#include "vmw_context.h"
+#include "vmw_screen.h"
+#include "vmw_surface.h"
+#include "vmw_buffer.h"
+#include "svga_drm_public.h"
+#include "svga3d_surfacedefs.h"
+
+#include "frontend/drm_driver.h"
+
+#include "vmwgfx_drm.h"
+
+#include "../wddm_screen.h"
+
+#include <iprt/asm.h>
+
+static struct svga_winsys_surface *
+vmw_drm_surface_from_handle(struct svga_winsys_screen *sws,
+ struct winsys_handle *whandle,
+ SVGA3dSurfaceFormat *format);
+
+static struct svga_winsys_surface *
+vmw_drm_gb_surface_from_handle(struct svga_winsys_screen *sws,
+ struct winsys_handle *whandle,
+ SVGA3dSurfaceFormat *format);
+static boolean
+vmw_drm_surface_get_handle(struct svga_winsys_screen *sws,
+ struct svga_winsys_surface *surface,
+ unsigned stride,
+ struct winsys_handle *whandle);
+
+struct vmw_winsys_screen_wddm *
+vmw_winsys_create_wddm(const WDDMGalliumDriverEnv *pEnv);
+
+/* This is actually the entrypoint to the entire driver,
+ * called by the target bootstrap code.
+ */
+struct svga_winsys_screen *
+svga_wddm_winsys_screen_create(const WDDMGalliumDriverEnv *pEnv)
+{
+ struct vmw_winsys_screen_wddm *vws;
+
+ if (pEnv->cb < sizeof(WDDMGalliumDriverEnv))
+ goto out_no_vws;
+
+ vws = vmw_winsys_create_wddm(pEnv);
+ if (!vws)
+ goto out_no_vws;
+
+ /* XXX do this properly */
+ vws->base.base.surface_from_handle = vws->base.base.have_gb_objects ?
+ vmw_drm_gb_surface_from_handle : vmw_drm_surface_from_handle;
+ vws->base.base.surface_get_handle = vmw_drm_surface_get_handle;
+
+ return &vws->base.base;
+
+out_no_vws:
+ return NULL;
+}
+
+static struct svga_winsys_surface *
+vmw_drm_gb_surface_from_handle(struct svga_winsys_screen *sws,
+ struct winsys_handle *whandle,
+ SVGA3dSurfaceFormat *format)
+{
+ RT_NOREF3(sws, whandle, format);
+ return 0;
+}
+
+static struct svga_winsys_surface *
+vmw_drm_surface_from_handle(struct svga_winsys_screen *sws,
+ struct winsys_handle *whandle,
+ SVGA3dSurfaceFormat *format)
+{
+ RT_NOREF3(sws, whandle, format);
+ return 0;
+}
+
+/*
+ * The user mode driver asks the kernel driver to create a resource
+ * (vmw_ioctl_surface_create) and gets a sid (surface id).
+ * This function is supposed to convert the sid to a handle (file descriptor)
+ * which can be used to access the surface.
+ */
+static boolean
+vmw_drm_surface_get_handle(struct svga_winsys_screen *sws,
+ struct svga_winsys_surface *surface,
+ unsigned stride,
+ struct winsys_handle *whandle)
+{
+ struct vmw_winsys_screen *vws = vmw_winsys_screen(sws);
+ struct vmw_svga_winsys_surface *vsrf;
+
+ RT_NOREF(vws);
+
+ if (!surface)
+ return FALSE;
+
+ vsrf = vmw_svga_winsys_surface(surface);
+ whandle->handle = vsrf->sid;
+ whandle->stride = stride;
+ whandle->offset = 0;
+
+ switch (whandle->type) {
+ case WINSYS_HANDLE_TYPE_SHARED:
+ case WINSYS_HANDLE_TYPE_KMS:
+ whandle->handle = vsrf->sid;
+ break;
+ case WINSYS_HANDLE_TYPE_FD:
+ whandle->handle = vsrf->sid; /// @todo will this be enough for WDDM?
+ break;
+ default:
+ vmw_error("Attempt to export unsupported handle type %d.\n",
+ whandle->type);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+vmw_svga_winsys_host_log(struct svga_winsys_screen *sws, const char *log)
+{
+ (void)sws;
+ _debug_printf("%s\n", log);
+}
diff --git a/src/VBox/Additions/3D/win/VBoxWddmUmHlp/D3DKMT.cpp b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/D3DKMT.cpp
new file mode 100644
index 00000000..b30698a5
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/D3DKMT.cpp
@@ -0,0 +1,260 @@
+/* $Id: D3DKMT.cpp $ */
+/** @file
+ * WDDM Kernel Mode Thunks helpers.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* We're unable to use standard r3 vbgl-based backdoor logging API because win8 Metro apps
+ * can not do CreateFile/Read/Write by default.
+ * This is why we use miniport escape functionality to issue backdoor log string to the miniport
+ * and submit it to host via standard r0 backdoor logging api accordingly
+ */
+
+#include "UmHlpInternal.h"
+
+
+/** Loads a system DLL.
+ *
+ * @returns Module handle or NULL
+ * @param pszName The DLL name.
+ */
+DECLCALLBACK(HMODULE) VBoxWddmLoadSystemDll(const char *pszName)
+{
+ char szPath[MAX_PATH];
+ UINT cchPath = GetSystemDirectoryA(szPath, sizeof(szPath));
+ size_t cbName = strlen(pszName) + 1;
+ if (cchPath + 1 + cbName > sizeof(szPath))
+ {
+ SetLastError(ERROR_FILENAME_EXCED_RANGE);
+ return NULL;
+ }
+ szPath[cchPath] = '\\';
+ memcpy(&szPath[cchPath + 1], pszName, cbName);
+ return LoadLibraryA(szPath);
+}
+
+DECLCALLBACK(void) VBoxWddmLoadAdresses(HMODULE hmod, VBOXWDDMDLLPROC *paProcs)
+{
+ struct VBOXWDDMDLLPROC *pIter = paProcs;
+ while (pIter->pszName)
+ {
+ FARPROC pfn = GetProcAddress(hmod, pIter->pszName);
+ *pIter->ppfn = pfn;
+ ++pIter;
+ }
+}
+
+/*
+ * Kernel Mode Thunks (KMT) initialization.
+ */
+
+#define D3DKMT_LOAD_ENTRY(a) { #a, (FARPROC *)&g_D3DKMT.pfn##a }
+
+static D3DKMTFUNCTIONS g_D3DKMT = { 0 };
+static VBOXWDDMDLLPROC g_D3DKMTLoadTable[] =
+{
+ D3DKMT_LOAD_ENTRY(D3DKMTOpenAdapterFromHdc),
+ D3DKMT_LOAD_ENTRY(D3DKMTOpenAdapterFromDeviceName),
+ D3DKMT_LOAD_ENTRY(D3DKMTCloseAdapter),
+ D3DKMT_LOAD_ENTRY(D3DKMTQueryAdapterInfo),
+ D3DKMT_LOAD_ENTRY(D3DKMTEscape),
+ D3DKMT_LOAD_ENTRY(D3DKMTCreateDevice),
+ D3DKMT_LOAD_ENTRY(D3DKMTDestroyDevice),
+ D3DKMT_LOAD_ENTRY(D3DKMTCreateContext),
+ D3DKMT_LOAD_ENTRY(D3DKMTDestroyContext),
+ D3DKMT_LOAD_ENTRY(D3DKMTCreateAllocation),
+ D3DKMT_LOAD_ENTRY(D3DKMTDestroyAllocation),
+ D3DKMT_LOAD_ENTRY(D3DKMTRender),
+ D3DKMT_LOAD_ENTRY(D3DKMTPresent),
+ D3DKMT_LOAD_ENTRY(D3DKMTGetSharedPrimaryHandle),
+ D3DKMT_LOAD_ENTRY(D3DKMTQueryResourceInfo),
+ D3DKMT_LOAD_ENTRY(D3DKMTOpenResource),
+ D3DKMT_LOAD_ENTRY(D3DKMTEnumAdapters),
+ D3DKMT_LOAD_ENTRY(D3DKMTOpenAdapterFromLuid),
+ {NULL, NULL},
+};
+
+#undef D3DKMT_LOAD_ENTRY
+
+/** Initialize Kernel Mode Thunks (KMT) pointers in the g_D3DKMT structure.
+ *
+ * @returns True if successful.
+ */
+DECLCALLBACK(int) D3DKMTLoad(void)
+{
+ /* Modules which use D3DKMT must link with gdi32. */
+ HMODULE hmod = GetModuleHandleA("gdi32.dll");
+ Assert(hmod);
+ if (hmod)
+ {
+ VBoxWddmLoadAdresses(hmod, g_D3DKMTLoadTable);
+ }
+ return hmod != NULL;
+}
+
+DECLCALLBACK(D3DKMTFUNCTIONS const *) D3DKMTFunctions(void)
+{
+ return &g_D3DKMT;
+}
+
+
+/*
+ * Getting VirtualBox Graphics Adapter handle.
+ */
+
+static NTSTATUS vboxDispKmtOpenAdapterFromHdc(D3DKMT_HANDLE *phAdapter, LUID *pLuid)
+{
+ *phAdapter = 0;
+
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+ if (d3dkmt->pfnD3DKMTOpenAdapterFromHdc == NULL)
+ return STATUS_NOT_SUPPORTED;
+
+ D3DKMT_OPENADAPTERFROMHDC OpenAdapterData;
+ memset(&OpenAdapterData, 0, sizeof(OpenAdapterData));
+
+ for (int i = 0; ; ++i)
+ {
+ DISPLAY_DEVICEA dd;
+ memset(&dd, 0, sizeof(dd));
+ dd.cb = sizeof(dd);
+
+ if (!EnumDisplayDevicesA(NULL, i, &dd, 0))
+ break;
+
+ if (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
+ {
+ OpenAdapterData.hDc = CreateDCA(NULL, dd.DeviceName, NULL, NULL);
+ break;
+ }
+ }
+
+ Assert(OpenAdapterData.hDc);
+
+ NTSTATUS Status;
+ if (OpenAdapterData.hDc)
+ {
+ Status = d3dkmt->pfnD3DKMTOpenAdapterFromHdc(&OpenAdapterData);
+ Assert(Status == STATUS_SUCCESS);
+ if (Status == STATUS_SUCCESS)
+ {
+ *phAdapter = OpenAdapterData.hAdapter;
+ if (pLuid)
+ {
+ *pLuid = OpenAdapterData.AdapterLuid;
+ }
+ }
+
+ DeleteDC(OpenAdapterData.hDc);
+ }
+ else
+ {
+ Status = STATUS_NOT_SUPPORTED;
+ }
+
+ return Status;
+}
+
+static NTSTATUS vboxDispKmtOpenAdapterFromLuid(D3DKMT_HANDLE *phAdapter, LUID *pLuid)
+{
+ *phAdapter = 0;
+
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+ if ( d3dkmt->pfnD3DKMTOpenAdapterFromLuid == NULL
+ || d3dkmt->pfnD3DKMTEnumAdapters == NULL)
+ return STATUS_NOT_SUPPORTED;
+
+ D3DKMT_ENUMADAPTERS EnumAdaptersData;
+ memset(&EnumAdaptersData, 0, sizeof(EnumAdaptersData));
+ EnumAdaptersData.NumAdapters = RT_ELEMENTS(EnumAdaptersData.Adapters);
+
+ NTSTATUS Status = d3dkmt->pfnD3DKMTEnumAdapters(&EnumAdaptersData);
+ Assert(Status == STATUS_SUCCESS);
+ if (Status == STATUS_SUCCESS)
+ {
+ Assert(EnumAdaptersData.NumAdapters);
+
+ /* Try the same twice: if we fail to open the adapter containing present sources,
+ * then try to open any adapter.
+ */
+ for (int iPass = 0; iPass < 2 && *phAdapter == 0; ++iPass)
+ {
+ for (ULONG i = 0; i < EnumAdaptersData.NumAdapters; ++i)
+ {
+ if (iPass > 0 || EnumAdaptersData.Adapters[i].NumOfSources)
+ {
+ D3DKMT_OPENADAPTERFROMLUID OpenAdapterData;
+ memset(&OpenAdapterData, 0, sizeof(OpenAdapterData));
+ OpenAdapterData.AdapterLuid = EnumAdaptersData.Adapters[i].AdapterLuid;
+
+ Status = d3dkmt->pfnD3DKMTOpenAdapterFromLuid(&OpenAdapterData);
+ Assert(Status == STATUS_SUCCESS);
+ if (Status == STATUS_SUCCESS)
+ {
+ *phAdapter = OpenAdapterData.hAdapter;
+ if (pLuid)
+ {
+ *pLuid = EnumAdaptersData.Adapters[i].AdapterLuid;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return Status;
+}
+
+NTSTATUS vboxDispKmtOpenAdapter2(D3DKMT_HANDLE *phAdapter, LUID *pLuid)
+{
+ NTSTATUS Status = vboxDispKmtOpenAdapterFromLuid(phAdapter, pLuid);
+ if (Status != STATUS_SUCCESS)
+ {
+ /* Fallback for pre-Windows8 */
+ Status = vboxDispKmtOpenAdapterFromHdc(phAdapter, pLuid);
+ }
+
+ return Status;
+}
+
+NTSTATUS vboxDispKmtOpenAdapter(D3DKMT_HANDLE *phAdapter)
+{
+ return vboxDispKmtOpenAdapter2(phAdapter, NULL);
+}
+
+NTSTATUS vboxDispKmtCloseAdapter(D3DKMT_HANDLE hAdapter)
+{
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+ if (d3dkmt->pfnD3DKMTCloseAdapter == NULL)
+ return STATUS_NOT_SUPPORTED;
+
+ D3DKMT_CLOSEADAPTER CloseAdapterData;
+ CloseAdapterData.hAdapter = hAdapter;
+
+ NTSTATUS Status = d3dkmt->pfnD3DKMTCloseAdapter(&CloseAdapterData);
+ Assert(Status == STATUS_SUCCESS);
+
+ return Status;
+}
diff --git a/src/VBox/Additions/3D/win/VBoxWddmUmHlp/Makefile.kmk b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/Makefile.kmk
new file mode 100644
index 00000000..80f6a078
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/Makefile.kmk
@@ -0,0 +1,54 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox WDDM user mode driver
+#
+
+#
+# Copyright (C) 2018-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+LIBRARIES += VBoxWddmUmHlp
+LIBRARIES.amd64 += VBoxWddmUmHlp-x86
+
+#
+# VBoxWddmUmHlp - logger and other helpers for user mode driver
+#
+VBoxWddmUmHlp_TEMPLATE = NewerVccVBoxGuestR3Dll
+VBoxWddmUmHlp_INST = $(INST_ADDITIONS_LIB)
+VBoxWddmUmHlp_DEFS = VBOX_WITH_WDDM
+VBoxWddmUmHlp_INCS = \
+ $(PATH_ROOT)/src/VBox/Additions/WINNT/Graphics/Video/disp/wddm/shared \
+ $(VBOX_GRAPHICS_INCS)
+VBoxWddmUmHlp_SOURCES = \
+ D3DKMT.cpp \
+ VBoxMpLogger.cpp
+
+#
+# 64-bit version for 32-bit build.
+#
+VBoxWddmUmHlp-x86_EXTENDS = VBoxWddmUmHlp
+VBoxWddmUmHlp-x86_BLD_TRG_ARCH = x86
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/3D/win/VBoxWddmUmHlp/UmHlpInternal.h b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/UmHlpInternal.h
new file mode 100644
index 00000000..5a7bafdb
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/UmHlpInternal.h
@@ -0,0 +1,36 @@
+/* $Id: UmHlpInternal.h $ */
+/** @file
+ * VBox WDDM User Mode Driver Helpers
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_3D_win_VBoxWddmUmHlp_UmHlpInternal_h
+#define GA_INCLUDED_SRC_3D_win_VBoxWddmUmHlp_UmHlpInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxWddmUmHlp.h"
+
+#endif /* !GA_INCLUDED_SRC_3D_win_VBoxWddmUmHlp_UmHlpInternal_h */
diff --git a/src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxMpLogger.cpp b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxMpLogger.cpp
new file mode 100644
index 00000000..702e1558
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxMpLogger.cpp
@@ -0,0 +1,167 @@
+/* $Id: VBoxMpLogger.cpp $ */
+/** @file
+ * VBox WDDM Display logger implementation
+ *
+ * We're unable to use standard r3 vbgl-based backdoor logging API because
+ * win8 Metro apps can not do CreateFile/Read/Write by default. This is why
+ * we use miniport escape functionality to issue backdoor log string to the
+ * miniport and submit it to host via standard r0 backdoor logging api
+ * accordingly
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define IPRT_NO_CRT_FOR_3RD_PARTY /* To get malloc and free wrappers in IPRT_NO_CRT mode. Doesn't link with IPRT in non-no-CRT mode. */
+#include "UmHlpInternal.h"
+
+#include <../../../common/wddm/VBoxMPIf.h>
+#include <stdlib.h>
+#ifdef IPRT_NO_CRT
+# include <iprt/process.h>
+# include <iprt/string.h>
+#else
+# include <stdio.h>
+#endif
+#include <VBox/VBoxGuestLib.h>
+
+
+static void VBoxDispMpLoggerLogN(const char *pchString, size_t cchString)
+{
+ D3DKMTFUNCTIONS const *d3dkmt = D3DKMTFunctions();
+ if (d3dkmt->pfnD3DKMTEscape == NULL)
+ return;
+
+ D3DKMT_HANDLE hAdapter;
+ NTSTATUS Status = vboxDispKmtOpenAdapter(&hAdapter);
+ Assert(Status == STATUS_SUCCESS);
+ if (Status == 0)
+ {
+ uint32_t cchString2 = (uint32_t)RT_MIN(cchString, _64K - 1U);
+ uint32_t cbCmd = RT_UOFFSETOF_DYN(VBOXDISPIFESCAPE_DBGPRINT, aStringBuf[cchString2 + 1]);
+ PVBOXDISPIFESCAPE_DBGPRINT pCmd = (PVBOXDISPIFESCAPE_DBGPRINT)malloc(cbCmd);
+ Assert(pCmd);
+ if (pCmd)
+ {
+ pCmd->EscapeHdr.escapeCode = VBOXESC_DBGPRINT;
+ pCmd->EscapeHdr.u32CmdSpecific = 0;
+ memcpy(pCmd->aStringBuf, pchString, cchString2);
+ pCmd->aStringBuf[cchString2] = '\0';
+
+ D3DKMT_ESCAPE EscapeData;
+ memset(&EscapeData, 0, sizeof(EscapeData));
+ EscapeData.hAdapter = hAdapter;
+ // EscapeData.hDevice = NULL;
+ EscapeData.Type = D3DKMT_ESCAPE_DRIVERPRIVATE;
+ // EscapeData.Flags.HardwareAccess = 0;
+ EscapeData.pPrivateDriverData = pCmd;
+ EscapeData.PrivateDriverDataSize = cbCmd;
+ // EscapeData.hContext = NULL;
+
+ Status = d3dkmt->pfnD3DKMTEscape(&EscapeData);
+ Assert(Status == STATUS_SUCCESS);
+
+ free(pCmd);
+ }
+
+ Status = vboxDispKmtCloseAdapter(hAdapter);
+ Assert(Status == STATUS_SUCCESS);
+ }
+}
+
+
+DECLCALLBACK(void) VBoxDispMpLoggerLog(const char *pszString)
+{
+ VBoxDispMpLoggerLogN(pszString, strlen(pszString));
+}
+
+
+DECLCALLBACK(void) VBoxDispMpLoggerLogF(const char *pszFormat, ...)
+{
+ /** @todo would make a whole lot more sense to just allocate
+ * VBOXDISPIFESCAPE_DBGPRINT here and printf into it's buffer than
+ * double buffering it like this */
+ char szBuffer[4096];
+ va_list va;
+ va_start(va, pszFormat);
+#ifdef IPRT_NO_CRT
+ RTStrPrintfV(szBuffer, sizeof(szBuffer), pszFormat, va);
+#else
+ _vsnprintf(szBuffer, sizeof(szBuffer), pszFormat, va);
+ szBuffer[sizeof(szBuffer) - 1] = '\0'; /* Don't trust the _vsnprintf function terminate the string! */
+#endif
+ va_end(va);
+
+ VBoxDispMpLoggerLog(szBuffer);
+}
+
+
+/* Interface used for backdoor logging. In no-CRT mode we will drag in IPRT
+ logging and it will be used on assertion in the no-CRT and IPRT code. */
+VBGLR3DECL(int) VbglR3WriteLog(const char *pch, size_t cch)
+{
+ VBoxDispMpLoggerLogN(pch, cch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Prefix the output string with exe name and pid/tid.
+ */
+#ifndef IPRT_NO_CRT
+static const char *vboxUmLogGetExeName(void)
+{
+ static int s_fModuleNameInited = 0;
+ static char s_szModuleName[MAX_PATH];
+
+ if (!s_fModuleNameInited)
+ {
+ const DWORD cchName = GetModuleFileNameA(NULL, s_szModuleName, RT_ELEMENTS(s_szModuleName));
+ if (cchName == 0)
+ return "<no module>";
+ s_fModuleNameInited = 1;
+ }
+ return &s_szModuleName[0];
+}
+#endif
+
+DECLCALLBACK(void) VBoxWddmUmLog(const char *pszString)
+{
+ /** @todo Allocate VBOXDISPIFESCAPE_DBGPRINT here and format right into it
+ * instead? That would be a lot more flexible and a little faster. */
+ char szBuffer[4096];
+#ifdef IPRT_NO_CRT
+ /** @todo use RTProcShortName instead of RTProcExecutablePath? Will avoid
+ * chopping off log text if the executable path is too long. */
+ RTStrPrintf(szBuffer, sizeof(szBuffer), "['%s' 0x%lx.0x%lx]: %s",
+ RTProcExecutablePath() /* should've been initialized by nocrt-startup-dll-win.cpp already */,
+ GetCurrentProcessId(), GetCurrentThreadId(), pszString);
+#else
+ int cch = _snprintf(szBuffer, sizeof(szBuffer), "['%s' 0x%lx.0x%lx]: %s",
+ vboxUmLogGetExeName(), GetCurrentProcessId(), GetCurrentThreadId(), pszString);
+ AssertReturnVoid(cch > 0); /* unlikely that we'll have string encoding problems, but just in case. */
+ szBuffer[sizeof(szBuffer) - 1] = '\0'; /* the function doesn't necessarily terminate the buffer on overflow. */
+#endif
+
+ VBoxDispMpLoggerLog(szBuffer);
+}
+
diff --git a/src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxWddmUmHlp.h b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxWddmUmHlp.h
new file mode 100644
index 00000000..ffc10c79
--- /dev/null
+++ b/src/VBox/Additions/3D/win/VBoxWddmUmHlp/VBoxWddmUmHlp.h
@@ -0,0 +1,116 @@
+/* $Id: VBoxWddmUmHlp.h $ */
+/** @file
+ * VBox WDDM User Mode Driver Helpers
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_3D_win_VBoxWddmUmHlp_VBoxWddmUmHlp_h
+#define GA_INCLUDED_SRC_3D_win_VBoxWddmUmHlp_VBoxWddmUmHlp_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/win/d3d9.h>
+#include <d3dumddi.h>
+#include <iprt/win/d3dkmthk.h>
+
+#include <iprt/asm.h>
+#include <iprt/cdefs.h>
+
+/* Do not require IPRT library. */
+/** @todo r=bird: It is *NOT* okay to redefine Assert* (or Log*) macros! It
+ * causes confusing as the code no longer behaves in the way one expect. Thus,
+ * it is strictly forbidden. */
+#ifndef IPRT_NO_CRT
+# undef Assert
+# undef AssertReturnVoid
+# ifdef RT_STRICT
+# define Assert(_e) (void)( (!!(_e)) || (ASMBreakpoint(), 0) )
+# define AssertReturnVoid(a_Expr) do { if (RT_LIKELY(a_Expr)) {} else { ASMBreakpoint(); return; } } while (0)
+# else
+# define Assert(_e) (void)( 0 )
+# define AssertReturnVoid(a_Expr) do { if (RT_LIKELY(a_Expr)) {} else return; } while (0)
+# endif
+#endif
+
+/* Do not require ntstatus.h.
+ * D3DKMT functions return NTSTATUS, but the driver code uses it only as a success/failure indicator.
+ * Therefore define the success and a failure status here.
+ */
+#ifndef STATUS_SUCCESS
+#define STATUS_SUCCESS 0
+#endif
+#ifndef STATUS_NOT_SUPPORTED
+#define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BBL)
+#endif
+
+RT_C_DECLS_BEGIN
+
+typedef struct VBOXWDDMDLLPROC
+{
+ char const *pszName;
+ FARPROC *ppfn;
+} VBOXWDDMDLLPROC;
+
+typedef struct D3DKMTFUNCTIONS
+{
+ PFND3DKMT_OPENADAPTERFROMHDC pfnD3DKMTOpenAdapterFromHdc;
+ PFND3DKMT_OPENADAPTERFROMDEVICENAME pfnD3DKMTOpenAdapterFromDeviceName;
+ PFND3DKMT_CLOSEADAPTER pfnD3DKMTCloseAdapter;
+ PFND3DKMT_QUERYADAPTERINFO pfnD3DKMTQueryAdapterInfo;
+ PFND3DKMT_ESCAPE pfnD3DKMTEscape;
+ PFND3DKMT_CREATEDEVICE pfnD3DKMTCreateDevice;
+ PFND3DKMT_DESTROYDEVICE pfnD3DKMTDestroyDevice;
+ PFND3DKMT_CREATECONTEXT pfnD3DKMTCreateContext;
+ PFND3DKMT_DESTROYCONTEXT pfnD3DKMTDestroyContext;
+ PFND3DKMT_CREATEALLOCATION pfnD3DKMTCreateAllocation;
+ PFND3DKMT_DESTROYALLOCATION pfnD3DKMTDestroyAllocation;
+ PFND3DKMT_RENDER pfnD3DKMTRender;
+ PFND3DKMT_PRESENT pfnD3DKMTPresent;
+ PFND3DKMT_GETSHAREDPRIMARYHANDLE pfnD3DKMTGetSharedPrimaryHandle;
+ PFND3DKMT_QUERYRESOURCEINFO pfnD3DKMTQueryResourceInfo;
+ PFND3DKMT_OPENRESOURCE pfnD3DKMTOpenResource;
+
+ /* Win 8+ */
+ PFND3DKMT_ENUMADAPTERS pfnD3DKMTEnumAdapters;
+ PFND3DKMT_OPENADAPTERFROMLUID pfnD3DKMTOpenAdapterFromLuid;
+} D3DKMTFUNCTIONS;
+
+DECLCALLBACK(HMODULE) VBoxWddmLoadSystemDll(const char *pszName);
+DECLCALLBACK(void) VBoxWddmLoadAdresses(HMODULE hmod, VBOXWDDMDLLPROC *paProcs);
+
+DECLCALLBACK(int) D3DKMTLoad(void);
+DECLCALLBACK(D3DKMTFUNCTIONS const *) D3DKMTFunctions(void);
+
+DECLCALLBACK(void) VBoxDispMpLoggerLogF(const char *pszFormat, ...);
+DECLCALLBACK(void) VBoxWddmUmLog(const char *pszString);
+
+/** @todo Rename to VBoxWddm* */
+NTSTATUS vboxDispKmtOpenAdapter2(D3DKMT_HANDLE *phAdapter, LUID *pLuid);
+NTSTATUS vboxDispKmtOpenAdapter(D3DKMT_HANDLE *phAdapter);
+NTSTATUS vboxDispKmtCloseAdapter(D3DKMT_HANDLE hAdapter);
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_3D_win_VBoxWddmUmHlp_VBoxWddmUmHlp_h */
diff --git a/src/VBox/Additions/3D/win/include/VBoxGaDriver.h b/src/VBox/Additions/3D/win/include/VBoxGaDriver.h
new file mode 100644
index 00000000..0f964999
--- /dev/null
+++ b/src/VBox/Additions/3D/win/include/VBoxGaDriver.h
@@ -0,0 +1,115 @@
+/* $Id: VBoxGaDriver.h $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Gallium driver interface.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_3D_WIN_VBoxGaDriver_h
+#define GA_INCLUDED_3D_WIN_VBoxGaDriver_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBoxGaHWInfo.h>
+#include <VBoxGaTypes.h>
+
+#include <iprt/win/windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct WDDMGalliumDriverEnv
+{
+ /* Size of the structure. */
+ DWORD cb;
+ const VBOXGAHWINFO *pHWInfo;
+ /* The environment context pointer to use in the following callbacks. */
+ void *pvEnv;
+ /* The callbacks to use by the driver. */
+ DECLCALLBACKMEMBER(uint32_t, pfnContextCreate,(void *pvEnv,
+ boolean extended,
+ boolean vgpu10));
+ DECLCALLBACKMEMBER(void, pfnContextDestroy,(void *pvEnv,
+ uint32_t u32Cid));
+ DECLCALLBACKMEMBER(int, pfnSurfaceDefine,(void *pvEnv,
+ GASURFCREATE *pCreateParms,
+ GASURFSIZE *paSizes,
+ uint32_t cSizes,
+ uint32_t *pu32Sid));
+ DECLCALLBACKMEMBER(void, pfnSurfaceDestroy,(void *pvEnv,
+ uint32_t u32Sid));
+ DECLCALLBACKMEMBER(int, pfnRender,(void *pvEnv,
+ uint32_t u32Cid,
+ void *pvCommands,
+ uint32_t cbCommands,
+ GAFENCEQUERY *pFenceQuery));
+ DECLCALLBACKMEMBER(void, pfnFenceUnref,(void *pvEnv,
+ uint32_t u32FenceHandle));
+ DECLCALLBACKMEMBER(int, pfnFenceQuery,(void *pvEnv,
+ uint32_t u32FenceHandle,
+ GAFENCEQUERY *pFenceQuery));
+ DECLCALLBACKMEMBER(int, pfnFenceWait,(void *pvEnv,
+ uint32_t u32FenceHandle,
+ uint32_t u32TimeoutUS));
+ DECLCALLBACKMEMBER(int, pfnRegionCreate,(void *pvEnv,
+ uint32_t u32RegionSize,
+ uint32_t *pu32GmrId,
+ void **ppvMap));
+ DECLCALLBACKMEMBER(void, pfnRegionDestroy,(void *pvEnv,
+ uint32_t u32GmrId,
+ void *pvMap));
+ /* VGPU10 */
+ DECLCALLBACKMEMBER(int, pfnGBSurfaceDefine,(void *pvEnv,
+ SVGAGBSURFCREATE *pCreateParms));
+} WDDMGalliumDriverEnv;
+
+struct pipe_context;
+struct pipe_screen;
+struct pipe_resource;
+
+typedef struct pipe_screen * WINAPI FNGaDrvScreenCreate(const WDDMGalliumDriverEnv *pEnv);
+typedef FNGaDrvScreenCreate *PFNGaDrvScreenCreate;
+
+typedef void WINAPI FNGaDrvScreenDestroy(struct pipe_screen *s);
+typedef FNGaDrvScreenDestroy *PFNGaDrvScreenDestroy;
+
+typedef const WDDMGalliumDriverEnv * WINAPI FNGaDrvGetWDDMEnv(struct pipe_screen *pScreen);
+typedef FNGaDrvGetWDDMEnv *PFNGaDrvGetWDDMEnv;
+
+typedef uint32_t WINAPI FNGaDrvGetContextId(struct pipe_context *pPipeContext);
+typedef FNGaDrvGetContextId *PFNGaDrvGetContextId;
+
+typedef uint32_t WINAPI FNGaDrvGetSurfaceId(struct pipe_screen *pScreen, struct pipe_resource *pResource);
+typedef FNGaDrvGetSurfaceId *PFNGaDrvGetSurfaceId;
+
+typedef void WINAPI FNGaDrvContextFlush(struct pipe_context *pPipeContext);
+typedef FNGaDrvContextFlush *PFNGaDrvContextFlush;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_3D_WIN_VBoxGaDriver_h */
+
diff --git a/src/VBox/Additions/3D/win/include/VBoxGaHWInfo.h b/src/VBox/Additions/3D/win/include/VBoxGaHWInfo.h
new file mode 100644
index 00000000..54a38eda
--- /dev/null
+++ b/src/VBox/Additions/3D/win/include/VBoxGaHWInfo.h
@@ -0,0 +1,64 @@
+/* $Id: VBoxGaHWInfo.h $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Gallium driver interface.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_3D_WIN_VBoxGaHWInfo_h
+#define GA_INCLUDED_3D_WIN_VBoxGaHWInfo_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/assert.h>
+
+#include <VBoxGaHwSVGA.h>
+
+/* Gallium virtual hardware supported by the miniport. */
+#define VBOX_GA_HW_TYPE_UNKNOWN 0
+#define VBOX_GA_HW_TYPE_VMSVGA 1
+
+/*
+ * VBOXGAHWINFO contains information about the virtual hardware, which is passed
+ * to the user mode Gallium driver. The driver can not query the info at the initialization time,
+ * therefore we send the complete info to the driver.
+ *
+ * VBOXGAHWINFO struct goes both to 32 and 64 bit user mode binaries, take care of alignment.
+ */
+#pragma pack(1)
+typedef struct VBOXGAHWINFO
+{
+ uint32_t u32HwType; /* VBOX_GA_HW_TYPE_* */
+ uint32_t u32Reserved;
+ union
+ {
+ VBOXGAHWINFOSVGA svga;
+ uint8_t au8Raw[65536];
+ } u;
+} VBOXGAHWINFO;
+#pragma pack()
+
+AssertCompile(RT_SIZEOFMEMB(VBOXGAHWINFO, u) <= RT_SIZEOFMEMB(VBOXGAHWINFO, u.au8Raw));
+
+#endif /* !GA_INCLUDED_3D_WIN_VBoxGaHWInfo_h */
diff --git a/src/VBox/Additions/3D/win/include/VBoxGaHwSVGA.h b/src/VBox/Additions/3D/win/include/VBoxGaHwSVGA.h
new file mode 100644
index 00000000..6c145eb0
--- /dev/null
+++ b/src/VBox/Additions/3D/win/include/VBoxGaHwSVGA.h
@@ -0,0 +1,66 @@
+/* $Id: VBoxGaHwSVGA.h $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Gallium driver interface.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_3D_WIN_VBoxGaHwSVGA_h
+#define GA_INCLUDED_3D_WIN_VBoxGaHwSVGA_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+
+/*
+ * VBOXGAHWINFOSVGA contains information about the virtual hardware, which is neede dy the user mode
+ * Gallium driver. The driver can not query the info at the initialization time, therefore
+ * we send the complete info to the driver.
+ *
+ * Since both FIFO and SVGA_REG_ are expanded over time, we reserve some space.
+ * The Gallium user mode driver can figure out which part of au32Regs and au32Fifo
+ * is valid from the raw data.
+ *
+ * VBOXGAHWINFOSVGA struct goes both to 32 and 64 bit user mode binaries, take care of alignment.
+ */
+#pragma pack(1)
+typedef struct VBOXGAHWINFOSVGA
+{
+ uint32_t cbInfoSVGA;
+
+ /* Copy of SVGA_REG_*, up to 256, currently 58 are used. */
+ uint32_t au32Regs[256];
+
+ /* Copy of FIFO registers, up to 1024, currently 290 are used. */
+ uint32_t au32Fifo[1024];
+
+ /* Currently SVGA has 260 caps, 512 should be ok for near future.
+ * This is a copy of SVGA3D_DEVCAP_* values returned by the host.
+ * Only valid if SVGA_CAP_GBOBJECTS is set in SVGA_REG_CAPABILITIES.
+ */
+ uint32_t au32Caps[512];
+} VBOXGAHWINFOSVGA;
+#pragma pack()
+
+#endif /* !GA_INCLUDED_3D_WIN_VBoxGaHwSVGA_h */
diff --git a/src/VBox/Additions/3D/win/include/VBoxGaNine.h b/src/VBox/Additions/3D/win/include/VBoxGaNine.h
new file mode 100644
index 00000000..cb896c5e
--- /dev/null
+++ b/src/VBox/Additions/3D/win/include/VBoxGaNine.h
@@ -0,0 +1,58 @@
+/* $Id: VBoxGaNine.h $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Gallium driver interface for WDDM user mode driver.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_3D_WIN_VBoxGaNine_h
+#define GA_INCLUDED_3D_WIN_VBoxGaNine_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/win/d3d9.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pipe_screen;
+struct pipe_resource;
+struct pipe_context;
+typedef struct ID3DAdapter9 ID3DAdapter9;
+
+typedef HRESULT WINAPI FNGaNineD3DAdapter9Create(struct pipe_screen *s, ID3DAdapter9 **ppOut);
+typedef FNGaNineD3DAdapter9Create *PFNGaNineD3DAdapter9Create;
+
+typedef struct pipe_resource * WINAPI FNGaNinePipeResourceFromSurface(IUnknown *pSurface);
+typedef FNGaNinePipeResourceFromSurface *PFNGaNinePipeResourceFromSurface;
+
+typedef struct pipe_context * WINAPI FNGaNinePipeContextFromDevice(IDirect3DDevice9 *pDevice);
+typedef FNGaNinePipeContextFromDevice *PFNGaNinePipeContextFromDevice;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_3D_WIN_VBoxGaNine_h */
diff --git a/src/VBox/Additions/3D/win/include/VBoxGaTypes.h b/src/VBox/Additions/3D/win/include/VBoxGaTypes.h
new file mode 100644
index 00000000..cfd379a1
--- /dev/null
+++ b/src/VBox/Additions/3D/win/include/VBoxGaTypes.h
@@ -0,0 +1,114 @@
+/* $Id: VBoxGaTypes.h $ */
+/** @file
+ * VirtualBox Windows Guest Mesa3D - Gallium driver interface.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_3D_WIN_VBoxGaTypes_h
+#define GA_INCLUDED_3D_WIN_VBoxGaTypes_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+
+#pragma pack(1) /* VMSVGA structures are '__packed'. */
+#include <svga3d_caps.h>
+#include <svga3d_reg.h>
+#pragma pack()
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GA_MAX_SURFACE_FACES 6
+#define GA_MAX_MIP_LEVELS 24
+
+typedef struct GASURFCREATE
+{
+ uint32_t flags; /* SVGA3dSurfaceFlags */
+ uint32_t format; /* SVGA3dSurfaceFormat */
+ uint32_t usage; /* SVGA_SURFACE_USAGE_* */
+ uint32_t mip_levels[GA_MAX_SURFACE_FACES];
+} GASURFCREATE;
+
+typedef struct GASURFSIZE
+{
+ uint32_t cWidth;
+ uint32_t cHeight;
+ uint32_t cDepth;
+ uint32_t u32Reserved;
+} GASURFSIZE;
+
+#define GA_FENCE_STATUS_NULL 0 /* Fence not found */
+#define GA_FENCE_STATUS_IDLE 1
+#define GA_FENCE_STATUS_SUBMITTED 2
+#define GA_FENCE_STATUS_SIGNALED 3
+
+typedef struct GAFENCEQUERY
+{
+ /* IN: The miniport's handle of the fence.
+ * Assigned by the miniport. Not DXGK fence id!
+ */
+ uint32_t u32FenceHandle;
+
+ /* OUT: The miniport's sequence number associated with the command buffer.
+ */
+ uint32_t u32SubmittedSeqNo;
+
+ /* OUT: The miniport's sequence number associated with the last command buffer completed on host.
+ */
+ uint32_t u32ProcessedSeqNo;
+
+ /* OUT: GA_FENCE_STATUS_*. */
+ uint32_t u32FenceStatus;
+} GAFENCEQUERY;
+
+typedef struct SVGAGBSURFCREATE
+{
+ /* Surface data. */
+ struct
+ {
+ SVGA3dSurfaceAllFlags flags;
+ SVGA3dSurfaceFormat format;
+ unsigned usage;
+ SVGA3dSize size;
+ uint32_t numFaces;
+ uint32_t numMipLevels;
+ unsigned sampleCount;
+ SVGA3dMSPattern multisamplePattern;
+ SVGA3dMSQualityLevel qualityLevel;
+ } s;
+ uint32_t gmrid; /* In/Out: Backing GMR. */
+ uint32_t cbGB; /* Out: Size of backing memory. */
+ uint64_t u64UserAddress; /* Out: R3 mapping of the backing memory. */
+ uint32_t u32Sid; /* Out: Surface id. */
+} SVGAGBSURFCREATE, *PSVGAGBSURFCREATE;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_3D_WIN_VBoxGaTypes_h */
+
diff --git a/src/VBox/Additions/Makefile.kmk b/src/VBox/Additions/Makefile.kmk
new file mode 100644
index 00000000..a0a90098
--- /dev/null
+++ b/src/VBox/Additions/Makefile.kmk
@@ -0,0 +1,405 @@
+# $Id: Makefile.kmk $
+## @file
+# Top-level makefile for the VirtualBox Guest Additions.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Globals
+#
+VBOX_PATH_ADDITIONS_SRC := $(PATH_SUB_CURRENT)
+
+#
+# Cross building of the additions is generally done by remote building
+# by means of smbfs, cifs, VBOX_ONLY_ADDITIONS=1 and setting KBUILD_TARGET
+# and KBUILD_TARGET_ARCH to the desired target and architecture.
+#
+# Limited support for cross building the windows additions using wine
+# is provided. There are a couple of issues with the approach (lack of
+# signing, no VC++ 8 support, ++) that makes it unsuitable for releases.
+#
+#
+# Note! VBOX_WITH_ADDITIONS is checked for by our parent makefile.
+#
+# Note! VBOX_WITH_X11_ADDITIONS is set in Config.kmk
+#
+# Note! The additions build box will set the VBOX_WITH_ADDITIONS_ISO.win.x86
+# variables before invoking us from the root makefile.
+#
+# ==> All we have to worry about is what to do on the target we're on.
+#
+VBOX_WITH_ADDITIONS_ISO.$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH) = 1
+
+# Include sub-makefiles.
+include $(PATH_SUB_CURRENT)/common/Makefile.kmk
+
+ifdef VBOX_WITH_X11_ADDITIONS
+ include $(PATH_SUB_CURRENT)/x11/Makefile.kmk
+endif
+
+ifeq ($(KBUILD_TARGET),freebsd)
+ include $(PATH_SUB_CURRENT)/freebsd/Makefile.kmk
+endif
+ifeq ($(KBUILD_TARGET),linux)
+ include $(PATH_SUB_CURRENT)/linux/Makefile.kmk
+endif
+ifeq ($(KBUILD_TARGET),os2)
+ include $(PATH_SUB_CURRENT)/os2/Makefile.kmk
+endif
+ifeq ($(KBUILD_TARGET),solaris)
+ include $(PATH_SUB_CURRENT)/solaris/Makefile.kmk
+endif
+ifeq ($(KBUILD_TARGET),win)
+ include $(PATH_SUB_CURRENT)/3D/win/VBoxWddmUmHlp/Makefile.kmk
+ ifdef VBOX_WITH_MESA3D
+ include $(PATH_SUB_CURRENT)/3D/Makefile.kmk
+ endif
+ include $(PATH_SUB_CURRENT)/WINNT/Makefile.kmk
+endif
+ifeq ($(KBUILD_TARGET),darwin)
+ include $(PATH_SUB_CURRENT)/darwin/Makefile.kmk
+endif
+ifeq ($(KBUILD_TARGET),haiku)
+ include $(PATH_SUB_CURRENT)/haiku/Makefile.kmk
+endif
+
+ifeq ($(KBUILD_TARGET),linux)
+
+ INSTALLS += LnxAddIso-scripts
+ LnxAddIso-scripts_INST = $(INST_ADDITIONS)
+ LnxAddIso-scripts_MODE = a+rx,u+w
+ LnxAddIso-scripts_SOURCES = \
+ ../Installer/linux/runasroot.sh \
+ linux/installer/autorun.sh
+
+endif # KBUILD_TARGET == linux
+ifeq ($(KBUILD_TARGET),win)
+ #
+ # Inf2Cat requires all the files referenced in the .inf file
+ # to be present in the directory, so we have to do this from here,
+ # since VBoxGuest.sys is being built from the common sources.
+ #
+ INSTALLS += VBoxGuest-inf
+ VBoxGuest-inf_INST = $(INST_ADDITIONS)
+ VBoxGuest-inf_MODE = a+r,u+w
+ VBoxGuest-inf_SOURCES = \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf
+ VBoxGuest-inf_SOURCES.x86 = \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxGuestEarlyNT.inf
+ VBoxGuest-inf_CLEAN = $(VBoxGuest-inf_SOURCES)
+ VBoxGuest-inf_CLEAN.x86 = $(VBoxGuest-inf_SOURCES.x86)
+ VBoxGuest-inf_BLDDIRS = $(PATH_TARGET)/VBoxGuestCat.dir
+ VBoxGuest-inf_BLDDIRS.x86 = $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir
+
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf: $(PATH_SUB_CURRENT)/common/VBoxGuest/win/VBoxGuest.inf $(MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,VBoxGuest-inf,$@,$<)
+ $(call VBOX_EDIT_INF_FN,$<,$@)
+
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxGuestEarlyNT.inf: $(PATH_SUB_CURRENT)/common/VBoxGuest/win/VBoxGuestEarlyNT.inf $(MAKEFILE_CURRENT) | $$(dir $$@)
+ $(call MSG_GENERATE,VBoxGuestEarlyNT-inf,$@,$<)
+ $(call VBOX_EDIT_INF_FN,$<,$@)
+
+ if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS)
+ VBoxGuest-inf_SOURCES += \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.cat \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.cat=>VBoxGuest-PreW10.cat \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxControl.exe \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxTray.exe
+ VBoxGuest-inf_SOURCES.x86 += \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxGuestEarlyNT.cat
+
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxGuest.sys: $$(VBoxGuest_1_TARGET) | $$(dir $$@)
+ $(INSTALL) -m 644 $< $(@D)
+
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxControl.exe \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxControl.exe: $$(VBoxControl_1_TARGET) | $$(dir $$@)
+ $(INSTALL) -m 755 $< $(@D)
+
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxTray.exe \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxTray.exe: $$(VBoxTray_1_TARGET) | $$(dir $$@)
+ $(INSTALL) -m 755 $< $(@D)
+
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.cat: \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.inf \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxGuest.sys \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxControl.exe \
+ $(PATH_TARGET)/VBoxGuestCat.dir/VBoxTray.exe
+ $(call MSG_TOOL,Inf2Cat,VBoxGuest-inf,$@,$<)
+ $(call VBOX_MAKE_CAT_FN, $(@D),$@)
+
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxGuestEarlyNT.cat: \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxGuestEarlyNT.inf \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxGuest.sys \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxControl.exe \
+ $(PATH_TARGET)/VBoxGuestEarlyNTCat.dir/VBoxTray.exe
+ $(call MSG_TOOL,Inf2Cat,VBoxGuestEarlyNT-inf,$@,$<)
+ $(call VBOX_MAKE_CAT_FN, $(@D),$@)
+ endif # signing
+endif # KBUILD_TARGET == win
+
+# The packing target rule, but only if we're on the local build box.
+# (VBOX_WITHOUT_ADDITIONS_ISO is used by the additions build box, see the root makefile.)
+ifndef VBOX_WITHOUT_ADDITIONS_ISO
+ PACKING += $(VBOX_PATH_ADDITIONS_ISO)/VBoxGuestAdditions.iso
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+#
+# File per-OS/arch file specs for the additions iso (alphabetical order).
+#
+# We test for the VBOX_WITH_ADDITIONS_ISO.os.arch so that we don't have to
+# do the $(if )'ing down where the GUESTADDITIONS_FILESPEC.os.arch down
+# in the dependency list and RTIsoMaker command.
+#
+
+# Darwin / Mac OS X
+## @todo Create .pkg files not run files. The build server shall create a combined packaged, goverend by
+# the VBOX_WITH_COMBINED_DARWIN_GUEST_PACKAGE macro.
+ifdef VBOX_WITH_ADDITIONS_ISO.darwin.x86
+ VBOX_PATH_ADDITIONS.darwin.x86 = $(PATH_OUT_BASE)/darwin.x86/$(KBUILD_TYPE)/dist/additions
+ GUESTADDITIONS_FILESPEC.darwin.x86 = \
+ VBoxDarwinAdditionsLegacy.pkg=$(VBOX_PATH_ADDITIONS.darwin.x86)/VBoxGuestAdditions.pkg
+ ifndef VBOX_WITH_ADDITIONS_ISO.darwin.amd64
+ GUESTADDITIONS_FILESPEC.darwin.x86 += \
+ VBoxDarwinAdditionsUninstall.tool=$(VBOX_PATH_ADDITIONS.darwin.x86)/VBoxDarwinAdditionsUninstall.tool
+ endif
+endif
+
+ifdef VBOX_WITH_ADDITIONS_ISO.darwin.amd64
+ VBOX_PATH_ADDITIONS.darwin.amd64 = $(PATH_OUT_BASE)/darwin.amd64/$(KBUILD_TYPE)/dist/additions
+ GUESTADDITIONS_FILESPEC.darwin.amd64 = \
+ VBoxDarwinAdditions.pkg=$(VBOX_PATH_ADDITIONS.darwin.amd64)/VBoxGuestAdditions.pkg
+ ifndef VBOX_WITH_ADDITIONS_ISO.darwin.x86
+ GUESTADDITIONS_FILESPEC.darwin.amd64 += \
+ VBoxDarwinAdditionsUninstall.tool=$(VBOX_PATH_ADDITIONS.darwin.amd64)/VBoxDarwinAdditionsUninstall.tool
+ endif
+endif
+
+# FreeBSD
+ifdef VBOX_WITH_ADDITIONS_ISO.freebsd.amd64
+ VBOX_PATH_ADDITIONS.freebsd.amd64 = $(PATH_OUT_BASE)/freebsd.amd64/$(KBUILD_TYPE)/bin/additions
+ GUESTADDITIONS_FILESPEC.freebsd.amd64 = \
+ VBoxFreeBSDAdditions-amd64.tbz=$(VBOX_PATH_ADDITIONS.freebsd.amd64)/VBoxFreeBSDAdditions.tbz
+endif
+ifdef VBOX_WITH_ADDITIONS_ISO.freebsd.x86
+ VBOX_PATH_ADDITIONS.freebsd.x86 = $(PATH_OUT_BASE)/freebsd.x86/$(KBUILD_TYPE)/bin/additions
+ GUESTADDITIONS_FILESPEC.freebsd.x86 = \
+ VBoxFreeBSDAdditions-x86.tbz=$(VBOX_PATH_ADDITIONS.freebsd.x86)/VBoxFreeBSDAdditions.tbz
+endif
+
+# GNU/Linux
+ifdef VBOX_WITH_ADDITIONS_ISO.linux.amd64
+ VBOX_PATH_ADDITIONS.linux.amd64 = $(PATH_OUT_BASE)/linux.amd64/$(KBUILD_TYPE)/bin/additions
+ ifdef VBOX_WITH_COMBINED_LINUX_GUEST_PACKAGE
+ VBOX_LNX_ADD_AMD64_RUN_PKG = VBoxLinuxAdditions.run
+ else
+ VBOX_LNX_ADD_AMD64_RUN_PKG = VBoxLinuxAdditions-amd64.run
+ endif
+ GUESTADDITIONS_FILESPEC.linux.amd64 = \
+ $(VBOX_LNX_ADD_AMD64_RUN_PKG)=$(VBOX_PATH_ADDITIONS.linux.amd64)/VBoxLinuxAdditions.run
+endif
+ifdef VBOX_WITH_ADDITIONS_ISO.linux.x86
+ VBOX_PATH_ADDITIONS.linux.x86 = $(PATH_OUT_BASE)/linux.x86/$(KBUILD_TYPE)/bin/additions
+ ## @todo 64-bit additions: rename this package, update docs (?) and tests (?). create wrapper? create gnome/kde autorun app (xplatform) ?
+ ifdef VBOX_WITH_COMBINED_LINUX_GUEST_PACKAGE
+ VBOX_LNX_ADD_X86_RUN_PKG = VBoxLinuxAdditions.run
+ else
+ VBOX_LNX_ADD_X86_RUN_PKG = VBoxLinuxAdditions-x86.run
+ endif
+ GUESTADDITIONS_FILESPEC.linux.x86 = \
+ $(VBOX_LNX_ADD_X86_RUN_PKG)=$(VBOX_PATH_ADDITIONS.linux.x86)/VBoxLinuxAdditions.run \
+ runasroot.sh=$(VBOX_PATH_ADDITIONS.linux.x86)/runasroot.sh \
+ autorun.sh=$(VBOX_PATH_ADDITIONS.linux.x86)/autorun.sh
+endif
+
+# IBM OS/2
+ifdef VBOX_WITH_ADDITIONS_ISO.os2.x86
+ VBOX_PATH_ADDITIONS.os2.x86 = $(PATH_OUT_BASE)/os2.x86/$(KBUILD_TYPE)/bin/additions
+ GUESTADDITIONS_FILESPEC.os2.x86 = \
+ OS2/VBoxGuest.sys=$(VBOX_PATH_ADDITIONS.os2.x86)/VBoxGuest.sys \
+ OS2/VBoxSF.ifs=$(VBOX_PATH_ADDITIONS.os2.x86)/VBoxSF.ifs \
+ OS2/VBoxService.exe=$(VBOX_PATH_ADDITIONS.os2.x86)/VBoxService.exe \
+ OS2/VBoxControl.exe=$(VBOX_PATH_ADDITIONS.os2.x86)/VBoxControl.exe \
+ OS2/VBoxReplaceDll.exe=$(VBOX_PATH_ADDITIONS.os2.x86)/VBoxReplaceDll.exe \
+ OS2/VBoxOs2AdditionsInstall.exe=$(VBOX_PATH_ADDITIONS.os2.x86)/VBoxOs2AdditionsInstall.exe \
+ OS2/libc06.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/libc06.dll \
+ OS2/libc061.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/libc061.dll \
+ OS2/libc062.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/libc062.dll \
+ OS2/libc063.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/libc063.dll \
+ OS2/libc064.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/libc064.dll \
+ OS2/libc065.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/libc065.dll \
+ OS2/libc066.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/libc066.dll \
+ OS2/readme.txt=$(VBOX_PATH_ADDITIONS.os2.x86)/readme.txt \
+ \
+ OS2/gengradd.dll=$(VBOX_PATH_ADDITIONS.os2.x86)/gengradd.dll \
+ OS2/VBoxMouse.sys=$(VBOX_PATH_ADDITIONS.os2.x86)/vboxmouse.sys
+endif
+
+# Oracle Solaris.
+ifdef VBOX_WITH_ADDITIONS_ISO.solaris.amd64
+ VBOX_PATH_ADDITIONS.solaris.amd64 = $(PATH_OUT_BASE)/solaris.amd64/$(KBUILD_TYPE)/bin/additions
+ GUESTADDITIONS_FILESPEC.solaris.amd64 = \
+ VBoxSolarisAdditions-amd64.pkg=$(VBOX_PATH_ADDITIONS.solaris.amd64)/VBoxSolarisAdditions.pkg
+endif
+ifdef VBOX_WITH_ADDITIONS_ISO.solaris.x86
+ VBOX_PATH_ADDITIONS.solaris.x86 = $(PATH_OUT_BASE)/solaris.x86/$(KBUILD_TYPE)/bin/additions
+ GUESTADDITIONS_FILESPEC.solaris.x86 = \
+ VBoxSolarisAdditions-x86.pkg=$(VBOX_PATH_ADDITIONS.solaris.x86)/VBoxSolarisAdditions.pkg
+endif
+ifdef VBOX_WITH_COMBINED_SOLARIS_GUEST_PACKAGE
+ # Build combined 32bit and 64bit solaris additions, not just a single arch.
+ # This assumes that the 32bit build directory contains the combined additions
+ # for 32bit and 64bit solaris. This just modifies variables set above.
+ GUESTADDITIONS_FILESPEC.solaris.x86 = \
+ VBoxSolarisAdditions.pkg=$(VBOX_PATH_ADDITIONS.solaris.x86)/VBoxSolarisAdditions.pkg
+ GUESTADDITIONS_FILESPEC.solaris.amd64 =
+endif
+
+# Microsoft Windows.
+ifdef VBOX_WITH_ADDITIONS_ISO.win.amd64
+ VBOX_PATH_ADDITIONS.win.amd64 = $(PATH_OUT_BASE)/win.amd64/$(KBUILD_TYPE)/bin/additions
+ VBOX_PATH_ADDITIONS.win = $(VBOX_PATH_ADDITIONS.win.amd64)
+ GUESTADDITIONS_FILESPEC.win.amd64 = \
+ VBoxWindowsAdditions-amd64.exe=$(VBOX_PATH_ADDITIONS.win.amd64)/VBoxWindowsAdditions-amd64.exe
+ ifndef VBOX_WITH_ADDITIONS_ISO.win.x86
+ GUESTADDITIONS_FILESPEC.win.amd64 += \
+ cert/VBoxCertUtil.exe=$(VBOX_PATH_ADDITIONS.win.amd64)/VBoxCertUtil.exe
+ endif
+endif
+
+ifdef VBOX_WITH_ADDITIONS_ISO.win.x86
+ VBOX_PATH_ADDITIONS.win.x86 = $(PATH_OUT_BASE)/win.x86/$(KBUILD_TYPE)/bin/additions
+ VBOX_PATH_ADDITIONS.win = $(VBOX_PATH_ADDITIONS.win.x86)
+ GUESTADDITIONS_FILESPEC.win.x86 = \
+ VBoxWindowsAdditions-x86.exe=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxWindowsAdditions-x86.exe \
+ VBoxWindowsAdditions.exe=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxWindowsAdditions.exe \
+ AUTORUN.INF=$(VBOX_PATH_ADDITIONS_SRC)/WINNT/Installer/ISO/AUTORUN.INF \
+ cert/VBoxCertUtil.exe=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxCertUtil.exe \
+ NT3x/Readme.txt=$(VBOX_PATH_ADDITIONS_SRC)/WINNT/Installer/ISO/NT3xReadme.txt \
+ NT3x/VBoxGuest.sys=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxGuest.sys \
+ NT3x/VBoxMouseNT.sys=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxMouseNT.sys \
+ NT3x/VBoxService.exe=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxService.exe \
+ NT3x/VBoxControl.exe=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxControl.exe \
+ NT3x/VBoxAddInstallNt3x.exe=$(VBOX_PATH_ADDITIONS.win.x86)/VBoxAddInstallNt3x.exe
+endif # win.x86
+
+if defined(VBOX_WITH_ADDITIONS_ISO.win.amd64) || defined(VBOX_WITH_ADDITIONS_ISO.win.x86)
+ # Note! This probably only work reliably when packing is also done on a windows host!
+ ifndef VBOX_SIGNING_MODE
+ GUESTADDITIONS_FILESPEC.win =
+ else if !defined(VBOX_CERTIFICATE_SHA2_SUBJECT_NAME) && !$(intersects win all 1,$(VBOX_WITH_CORP_CODE_SIGNING))
+ GUESTADDITIONS_FILESPEC.win = \
+ cert/vbox.cer=$(VBOX_PATH_ADDITIONS.win)/vbox.cer \
+ cert/vbox-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-root.cer
+ ifdef VBOX_TSA_URL_ARGS
+ GUESTADDITIONS_FILESPEC.win += cert/vbox-timestamp-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-timestamp-root.cer
+ endif
+ else
+ GUESTADDITIONS_FILESPEC.win = \
+ cert/vbox-sha1.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha1.cer \
+ cert/vbox-sha1-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha1-root.cer \
+ cert/vbox-sha256-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha256-root.cer \
+ cert/vbox-sha256.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha256.cer
+ ifdef VBOX_TSA_URL_ARGS
+ GUESTADDITIONS_FILESPEC.win += cert/vbox-sha1-timestamp-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha1-timestamp-root.cer
+ endif
+ ifdef VBOX_TSA_SHA2_URL_ARGS
+ GUESTADDITIONS_FILESPEC.win += cert/vbox-sha256-timestamp-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha256-timestamp-root.cer
+ endif
+ if $(intersects win_planb,$(VBOX_WITH_CORP_CODE_SIGNING))
+ GUESTADDITIONS_FILESPEC.win += \
+ cert/vbox-sha256-r3.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha256-r3.cer \
+ cert/vbox-sha256-r3-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha256-r3-root.cer \
+ cert/vbox-sha256-r3-timestamp-root.cer=$(VBOX_PATH_ADDITIONS.win)/vbox-sha256-r3-timestamp-root.cer
+ endif
+ endif
+ GUESTADDITIONS_FILESPEC.win += windows11-bypass.reg=$(VBOX_PATH_ADDITIONS_SRC)/WINNT/tools/windows11-bypass.reg
+endif
+
+# haiku
+ifdef VBOX_WITH_ADDITIONS_ISO.haiku.x86
+ VBOX_PATH_ADDITIONS.haiku.x86 = $(PATH_OUT_BASE)/haiku.x86/$(KBUILD_TYPE)/bin/additions
+ # or bfs?
+ GUESTADDITIONS_FILESPEC.haiku.x86 = \
+ VBoxHaikuAdditions-x86.run=$(VBOX_PATH_ADDITIONS.haiku.x86)/VBoxHaikuAdditions-x86.run
+endif
+
+# For the iso rule.
+GUESTADDITIONS_FILESPEC_ALL = \
+ $(GUESTADDITIONS_FILESPEC.win) \
+ $(GUESTADDITIONS_FILESPEC.win.x86) \
+ $(GUESTADDITIONS_FILESPEC.win.amd64) \
+ $(GUESTADDITIONS_FILESPEC.solaris.x86) \
+ $(GUESTADDITIONS_FILESPEC.solaris.amd64) \
+ $(GUESTADDITIONS_FILESPEC.os2.x86) \
+ $(GUESTADDITIONS_FILESPEC.linux.x86) \
+ $(GUESTADDITIONS_FILESPEC.linux.amd64) \
+ $(GUESTADDITIONS_FILESPEC.freebsd.x86) \
+ $(GUESTADDITIONS_FILESPEC.freebsd.amd64) \
+ $(GUESTADDITIONS_FILESPEC.haiku.x86) \
+ $(GUESTADDITIONS_FILESPEC.darwin.x86) \
+ $(GUESTADDITIONS_FILESPEC.darwin.amd64)
+
+#
+# Build the Guest Additions ISO image.
+#
+ifndef VBOX_WITHOUT_ADDITIONS_ISO
+ $(VBOX_PATH_ADDITIONS_ISO)/VBoxGuestAdditions.iso: \
+ $(filter-out %=deleteme=,$(subst =,=deleteme= , $(GUESTADDITIONS_FILESPEC_ALL))) \
+ $(VBOX_SVN_REV_KMK) \
+ $(VBOX_PATH_ADDITIONS_SRC)/Makefile.kmk \
+ | $(if-expr defined(VBOX_USE_RTISOMAKER),$(VBOX_RTISOMAKER),)
+ $(call MSG_TOOL,RTIsoMaker,,$@)
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(VBOX_RTISOMAKER) \
+ --output $@ \
+ --iso-level 3 \
+ --rock-ridge \
+ --joliet \
+ --rational-attribs \
+ --random-order-verification 2048 \
+ $(addprefix /,$(GUESTADDITIONS_FILESPEC_ALL)) \
+ $(foreach spec, $(filter %.run %.sh %.tool,$(GUESTADDITIONS_FILESPEC_ALL)) \
+ , --chmod a+x:/$(substr $(spec), 1, $(expr $(pos =,$(spec)) - 1))) \
+ --volume-id="VBOXADDITIONS_$(VBOX_VERSION_STRING_RAW)_$(VBOX_SVN_REV)" \
+ --name-setup=joliet \
+ --volume-id="VBox_GAs_$(VBOX_VERSION_STRING_RAW)"
+
+ $(VBOX_PATH_ADDITIONS_ISO)/VBoxGuestAdditions.zip: $(VBOX_PATH_ADDITIONS_ISO)/VBoxGuestAdditions.iso
+ $(call MSG_L1,Zipping image $@)
+ $(QUIET)$(RM) -f $@
+ $(QUIET)$(REDIRECT) -C $(VBOX_PATH_ADDITIONS_ISO) -- $(VBOX_ZIP) -9 $@ $(notdir $^)
+
+ # Alias for creating the iso.
+ .PHONY: additions-iso
+ additions-iso: $(VBOX_PATH_ADDITIONS_ISO)/VBoxGuestAdditions.iso
+
+endif
+
diff --git a/src/VBox/Additions/common/Makefile.kmk b/src/VBox/Additions/common/Makefile.kmk
new file mode 100644
index 00000000..c1b38073
--- /dev/null
+++ b/src/VBox/Additions/common/Makefile.kmk
@@ -0,0 +1,39 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the common addition code.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Include sub-makefile.
+include $(PATH_SUB_CURRENT)/VBoxControl/Makefile.kmk
+include $(PATH_SUB_CURRENT)/VBoxGuest/Makefile.kmk
+include $(PATH_SUB_CURRENT)/VBoxService/Makefile.kmk
+ifdef VBOX_WITH_PAM
+ include $(PATH_SUB_CURRENT)/pam/Makefile.kmk
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/common/VBoxControl/Makefile.kmk b/src/VBox/Additions/common/VBoxControl/Makefile.kmk
new file mode 100644
index 00000000..432ca1c0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxControl/Makefile.kmk
@@ -0,0 +1,61 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Guest Additions Command Line Management Interface.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Include sub-makefile(s).
+include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+
+#
+# VBoxControl
+#
+PROGRAMS += VBoxControl
+VBoxControl_TEMPLATE = VBoxGuestR3Exe
+if "$(KBUILD_TARGET)" == "win" && defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS) # (See the main Windows Additions makefile.)
+ VBoxControl_INSTTYPE = none
+ VBoxControl_DEBUG_INSTTYPE = both
+endif
+VBoxControl_DEFS += \
+ $(if $(VBOX_WITH_HGCM),VBOX_WITH_HGCM,) \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_WITH_SHARED_FOLDERS),VBOX_WITH_SHARED_FOLDERS,)
+VBoxControl_DEFS.win += \
+ $(if $(VBOX_WITH_DPC_LATENCY_CHECKER),VBOX_WITH_DPC_LATENCY_CHECKER,)
+VBoxControl_SDKS = VBoxZlibStatic
+VBoxControl_SOURCES = \
+ VBoxControl.cpp
+VBoxControl_SOURCES.win = \
+ VBoxControl.rc
+VBoxControl_LDFLAGS.darwin = -framework IOKit
+VBoxControl_LIBS.netbsd = crypt
+VBoxControl_USES.win += vboximportchecker
+VBoxControl_VBOX_IMPORT_CHECKER.win.x86 = nt31
+VBoxControl_VBOX_IMPORT_CHECKER.win.amd64 = xp64
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp b/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp
new file mode 100644
index 00000000..6646e9d2
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.cpp
@@ -0,0 +1,2209 @@
+/* $Id: VBoxControl.cpp $ */
+/** @file
+ * VBoxControl - Guest Additions Command Line Management Interface.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/alloca.h>
+#include <iprt/cpp/autores.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/zip.h>
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <VBox/VBoxGuestLib.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+# include <VBox/shflsvc.h>
+# ifdef RT_OS_OS2
+# define OS2EMX_PLAIN_CHAR
+# define INCL_ERRORS
+# define INCL_DOSFILEMGR
+# include <os2emx.h>
+# endif
+#endif
+#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
+# include <VBox/VBoxGuest.h>
+# include "../VBoxGuest/lib/VBoxGuestR3LibInternal.h" /* HACK ALERT! Using vbglR3DoIOCtl directly!! */
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The program name (derived from argv[0]). */
+char const *g_pszProgName = "";
+/** The current verbosity level. */
+int g_cVerbosity = 0;
+
+
+/** @name Displays the program usage message.
+ * @{
+ */
+
+/**
+ * Helper function that does indentation.
+ *
+ * @param pszLine Text.
+ * @param pszName Program name.
+ * @param pszCommand Command/option syntax.
+ */
+static void doUsage(char const *pszLine, char const *pszName = "", char const *pszCommand = "")
+{
+ /* Allow for up to 15 characters command name length (VBoxControl.exe) with
+ * perfect column alignment. Beyond that there's at least one space between
+ * the command if there are command line parameters. */
+ RTPrintf("%s %-*s%s%s\n",
+ pszName,
+ *pszLine ? 35 - strlen(pszName) : 1, pszCommand,
+ *pszLine ? " " : "", pszLine);
+}
+
+/** Enumerate the different parts of the usage we might want to print out */
+enum VBoxControlUsage
+{
+#ifdef RT_OS_WINDOWS
+ GET_VIDEO_ACCEL,
+ SET_VIDEO_ACCEL,
+ VIDEO_FLAGS,
+ LIST_CUST_MODES,
+ ADD_CUST_MODE,
+ REMOVE_CUST_MODE,
+ SET_VIDEO_MODE,
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+ GUEST_PROP,
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+ GUEST_SHAREDFOLDERS,
+#endif
+#if !defined(VBOX_CONTROL_TEST)
+ WRITE_CORE_DUMP,
+#endif
+ WRITE_LOG,
+ TAKE_SNAPSHOT,
+ SAVE_STATE,
+ SUSPEND,
+ POWER_OFF,
+ VERSION,
+ HELP,
+ USAGE_ALL = UINT32_MAX
+};
+
+static RTEXITCODE usage(enum VBoxControlUsage eWhich = USAGE_ALL)
+{
+ RTPrintf("Usage:\n\n");
+ doUsage("print version number and exit", g_pszProgName, "[-V|--version]");
+ doUsage("suppress the logo", g_pszProgName, "--nologo ...");
+ RTPrintf("\n");
+
+ /* Exclude the Windows bits from the test version. Anyone who needs to
+ test them can fix this. */
+#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST)
+ if (eWhich == GET_VIDEO_ACCEL || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "getvideoacceleration");
+ if (eWhich == SET_VIDEO_ACCEL || eWhich == USAGE_ALL)
+ doUsage("<on|off>", g_pszProgName, "setvideoacceleration");
+ if (eWhich == VIDEO_FLAGS || eWhich == USAGE_ALL)
+ doUsage("<get|set|clear|delete> [hex mask]", g_pszProgName, "videoflags");
+ if (eWhich == LIST_CUST_MODES || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "listcustommodes");
+ if (eWhich == ADD_CUST_MODE || eWhich == USAGE_ALL)
+ doUsage("<width> <height> <bpp>", g_pszProgName, "addcustommode");
+ if (eWhich == REMOVE_CUST_MODE || eWhich == USAGE_ALL)
+ doUsage("<width> <height> <bpp>", g_pszProgName, "removecustommode");
+ if (eWhich == SET_VIDEO_MODE || eWhich == USAGE_ALL)
+ doUsage("<width> <height> <bpp> <screen>", g_pszProgName, "setvideomode");
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (eWhich == GUEST_PROP || eWhich == USAGE_ALL)
+ {
+ doUsage("get <property> [--verbose]", g_pszProgName, "guestproperty");
+ doUsage("set <property> [<value> [--flags <flags>]]", g_pszProgName, "guestproperty");
+ doUsage("delete|unset <property>", g_pszProgName, "guestproperty");
+ doUsage("enumerate [--patterns <patterns>]", g_pszProgName, "guestproperty");
+ doUsage("wait <patterns>", g_pszProgName, "guestproperty");
+ doUsage("[--timestamp <last timestamp>]");
+ doUsage("[--timeout <timeout in ms>");
+ }
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+ if (eWhich == GUEST_SHAREDFOLDERS || eWhich == USAGE_ALL)
+ {
+ doUsage("list [--automount]", g_pszProgName, "sharedfolder");
+# ifdef RT_OS_OS2
+ doUsage("use <drive> <folder>", g_pszProgName, "sharedfolder");
+ doUsage("unuse <drive>", g_pszProgName, "sharedfolder");
+# endif
+ }
+#endif
+
+#if !defined(VBOX_CONTROL_TEST)
+ if (eWhich == WRITE_CORE_DUMP || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "writecoredump");
+#endif
+ if (eWhich == WRITE_LOG || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "writelog [-n|--no-newline] [--] <msg>");
+ if (eWhich == TAKE_SNAPSHOT || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "takesnapshot");
+ if (eWhich == SAVE_STATE || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "savestate");
+ if (eWhich == SUSPEND || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "suspend");
+ if (eWhich == POWER_OFF || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "poweroff");
+ if (eWhich == HELP || eWhich == USAGE_ALL)
+ doUsage("[command]", g_pszProgName, "help");
+ if (eWhich == VERSION || eWhich == USAGE_ALL)
+ doUsage("", g_pszProgName, "version");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+/** @} */
+
+
+/**
+ * Implementation of the '--version' option.
+ *
+ * @returns RTEXITCODE_SUCCESS
+ */
+static RTEXITCODE printVersion(void)
+{
+ RTPrintf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Displays an error message.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pszFormat The message text. No newline.
+ * @param ... Format arguments.
+ */
+static RTEXITCODE VBoxControlError(const char *pszFormat, ...)
+{
+ /** @todo prefix with current command. */
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Displays a getopt error.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param ch The RTGetOpt return value.
+ * @param pValueUnion The RTGetOpt return data.
+ */
+static RTEXITCODE VBoxCtrlGetOptError(int ch, PCRTGETOPTUNION pValueUnion)
+{
+ /** @todo prefix with current command. */
+ return RTGetOptPrintError(ch, pValueUnion);
+}
+
+
+/**
+ * Displays an syntax error message.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pszFormat The message text. No newline.
+ * @param ... Format arguments.
+ */
+static RTEXITCODE VBoxControlSyntaxError(const char *pszFormat, ...)
+{
+ /** @todo prefix with current command. */
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_SYNTAX;
+}
+
+#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST)
+
+decltype(ChangeDisplaySettingsExA) *g_pfnChangeDisplaySettingsExA;
+decltype(ChangeDisplaySettings) *g_pfnChangeDisplaySettingsA;
+decltype(EnumDisplaySettingsA) *g_pfnEnumDisplaySettingsA;
+
+static unsigned nextAdjacentRectXP(RECTL const *paRects, unsigned cRects, unsigned iRect)
+{
+ for (unsigned i = 0; i < cRects; i++)
+ if (paRects[iRect].right == paRects[i].left)
+ return i;
+ return ~0U;
+}
+
+static unsigned nextAdjacentRectXN(RECTL const *paRects, unsigned cRects, unsigned iRect)
+{
+ for (unsigned i = 0; i < cRects; i++)
+ if (paRects[iRect].left == paRects[i].right)
+ return i;
+ return ~0U;
+}
+
+static unsigned nextAdjacentRectYP(RECTL const *paRects, unsigned cRects, unsigned iRect)
+{
+ for (unsigned i = 0; i < cRects; i++)
+ if (paRects[iRect].bottom == paRects[i].top)
+ return i;
+ return ~0U;
+}
+
+unsigned nextAdjacentRectYN(RECTL const *paRects, unsigned cRects, unsigned iRect)
+{
+ for (unsigned i = 0; i < cRects; i++)
+ if (paRects[iRect].top == paRects[i].bottom)
+ return i;
+ return ~0U;
+}
+
+void resizeRect(RECTL *paRects, unsigned cRects, unsigned iPrimary, unsigned iResized, int NewWidth, int NewHeight)
+{
+ RECTL *paNewRects = (RECTL *)alloca(sizeof (RECTL) * cRects);
+ memcpy (paNewRects, paRects, sizeof(RECTL) * cRects);
+ paNewRects[iResized].right += NewWidth - (paNewRects[iResized].right - paNewRects[iResized].left);
+ paNewRects[iResized].bottom += NewHeight - (paNewRects[iResized].bottom - paNewRects[iResized].top);
+
+ /* Verify all pairs of originally adjacent rectangles for all 4 directions.
+ * If the pair has a "good" delta (that is the first rectangle intersects the second)
+ * at a direction and the second rectangle is not primary one (which can not be moved),
+ * move the second rectangle to make it adjacent to the first one.
+ */
+
+ /* X positive. */
+ unsigned iRect;
+ for (iRect = 0; iRect < cRects; iRect++)
+ {
+ /* Find the next adjacent original rect in x positive direction. */
+ unsigned iNextRect = nextAdjacentRectXP (paRects, cRects, iRect);
+ Log(("next %d -> %d\n", iRect, iNextRect));
+
+ if (iNextRect == ~0 || iNextRect == iPrimary)
+ {
+ continue;
+ }
+
+ /* Check whether there is an X intersection between these adjacent rects in the new rectangles
+ * and fix the intersection if delta is "good".
+ */
+ int delta = paNewRects[iRect].right - paNewRects[iNextRect].left;
+
+ if (delta > 0)
+ {
+ Log(("XP intersection right %d left %d, diff %d\n",
+ paNewRects[iRect].right, paNewRects[iNextRect].left,
+ delta));
+
+ paNewRects[iNextRect].left += delta;
+ paNewRects[iNextRect].right += delta;
+ }
+ }
+
+ /* X negative. */
+ for (iRect = 0; iRect < cRects; iRect++)
+ {
+ /* Find the next adjacent original rect in x negative direction. */
+ unsigned iNextRect = nextAdjacentRectXN (paRects, cRects, iRect);
+ Log(("next %d -> %d\n", iRect, iNextRect));
+
+ if (iNextRect == ~0 || iNextRect == iPrimary)
+ {
+ continue;
+ }
+
+ /* Check whether there is an X intersection between these adjacent rects in the new rectangles
+ * and fix the intersection if delta is "good".
+ */
+ int delta = paNewRects[iRect].left - paNewRects[iNextRect].right;
+
+ if (delta < 0)
+ {
+ Log(("XN intersection left %d right %d, diff %d\n",
+ paNewRects[iRect].left, paNewRects[iNextRect].right,
+ delta));
+
+ paNewRects[iNextRect].left += delta;
+ paNewRects[iNextRect].right += delta;
+ }
+ }
+
+ /* Y positive (in the computer sense, top->down). */
+ for (iRect = 0; iRect < cRects; iRect++)
+ {
+ /* Find the next adjacent original rect in y positive direction. */
+ unsigned iNextRect = nextAdjacentRectYP (paRects, cRects, iRect);
+ Log(("next %d -> %d\n", iRect, iNextRect));
+
+ if (iNextRect == ~0 || iNextRect == iPrimary)
+ {
+ continue;
+ }
+
+ /* Check whether there is an Y intersection between these adjacent rects in the new rectangles
+ * and fix the intersection if delta is "good".
+ */
+ int delta = paNewRects[iRect].bottom - paNewRects[iNextRect].top;
+
+ if (delta > 0)
+ {
+ Log(("YP intersection bottom %d top %d, diff %d\n",
+ paNewRects[iRect].bottom, paNewRects[iNextRect].top,
+ delta));
+
+ paNewRects[iNextRect].top += delta;
+ paNewRects[iNextRect].bottom += delta;
+ }
+ }
+
+ /* Y negative (in the computer sense, down->top). */
+ for (iRect = 0; iRect < cRects; iRect++)
+ {
+ /* Find the next adjacent original rect in x negative direction. */
+ unsigned iNextRect = nextAdjacentRectYN (paRects, cRects, iRect);
+ Log(("next %d -> %d\n", iRect, iNextRect));
+
+ if (iNextRect == ~0 || iNextRect == iPrimary)
+ {
+ continue;
+ }
+
+ /* Check whether there is an Y intersection between these adjacent rects in the new rectangles
+ * and fix the intersection if delta is "good".
+ */
+ int delta = paNewRects[iRect].top - paNewRects[iNextRect].bottom;
+
+ if (delta < 0)
+ {
+ Log(("YN intersection top %d bottom %d, diff %d\n",
+ paNewRects[iRect].top, paNewRects[iNextRect].bottom,
+ delta));
+
+ paNewRects[iNextRect].top += delta;
+ paNewRects[iNextRect].bottom += delta;
+ }
+ }
+
+ memcpy (paRects, paNewRects, sizeof (RECTL) * cRects);
+ return;
+}
+
+/* Returns TRUE to try again. */
+static BOOL ResizeDisplayDevice(ULONG Id, DWORD Width, DWORD Height, DWORD BitsPerPixel)
+{
+ BOOL fModeReset = (Width == 0 && Height == 0 && BitsPerPixel == 0);
+
+ DISPLAY_DEVICE DisplayDevice;
+ RT_ZERO(DisplayDevice);
+ DisplayDevice.cb = sizeof(DisplayDevice);
+
+ /* Find out how many display devices the system has */
+ DWORD NumDevices = 0;
+ DWORD i = 0;
+ while (EnumDisplayDevices(NULL, i, &DisplayDevice, 0))
+ {
+ Log(("[%d] %s\n", i, DisplayDevice.DeviceName));
+
+ if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
+ {
+ Log(("Found primary device. err %d\n", GetLastError()));
+ NumDevices++;
+ }
+ else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
+ {
+
+ Log(("Found secondary device. err %d\n", GetLastError()));
+ NumDevices++;
+ }
+
+ RT_ZERO(DisplayDevice);
+ DisplayDevice.cb = sizeof(DisplayDevice);
+ i++;
+ }
+
+ Log(("Found total %d devices. err %d\n", NumDevices, GetLastError()));
+
+ if (NumDevices == 0 || Id >= NumDevices)
+ {
+ Log(("Requested identifier %d is invalid. err %d\n", Id, GetLastError()));
+ return FALSE;
+ }
+
+ DISPLAY_DEVICE *paDisplayDevices = (DISPLAY_DEVICE *)alloca(sizeof (DISPLAY_DEVICE) * NumDevices);
+ DEVMODE *paDeviceModes = (DEVMODE *)alloca(sizeof (DEVMODE) * NumDevices);
+ RECTL *paRects = (RECTL *)alloca(sizeof (RECTL) * NumDevices);
+
+ /* Fetch information about current devices and modes. */
+ DWORD DevNum = 0;
+ DWORD DevPrimaryNum = 0;
+
+ RT_ZERO(DisplayDevice);
+ DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
+
+ i = 0;
+ while (EnumDisplayDevices (NULL, i, &DisplayDevice, 0))
+ {
+ Log(("[%d(%d)] %s\n", i, DevNum, DisplayDevice.DeviceName));
+
+ BOOL fFetchDevice = FALSE;
+
+ if (DisplayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE)
+ {
+ Log(("Found primary device. err %d\n", GetLastError()));
+ DevPrimaryNum = DevNum;
+ fFetchDevice = TRUE;
+ }
+ else if (!(DisplayDevice.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER))
+ {
+
+ Log(("Found secondary device. err %d\n", GetLastError()));
+ fFetchDevice = TRUE;
+ }
+
+ if (fFetchDevice)
+ {
+ if (DevNum >= NumDevices)
+ {
+ Log(("%d >= %d\n", NumDevices, DevNum));
+ return FALSE;
+ }
+
+ paDisplayDevices[DevNum] = DisplayDevice;
+
+ RT_BZERO(&paDeviceModes[DevNum], sizeof(DEVMODE));
+ paDeviceModes[DevNum].dmSize = sizeof(DEVMODE);
+ if (!g_pfnEnumDisplaySettingsA((LPSTR)DisplayDevice.DeviceName, ENUM_REGISTRY_SETTINGS, &paDeviceModes[DevNum]))
+ {
+ Log(("EnumDisplaySettings err %d\n", GetLastError()));
+ return FALSE;
+ }
+
+ Log(("%dx%d at %d,%d\n",
+ paDeviceModes[DevNum].dmPelsWidth,
+ paDeviceModes[DevNum].dmPelsHeight,
+ paDeviceModes[DevNum].dmPosition.x,
+ paDeviceModes[DevNum].dmPosition.y));
+
+ paRects[DevNum].left = paDeviceModes[DevNum].dmPosition.x;
+ paRects[DevNum].top = paDeviceModes[DevNum].dmPosition.y;
+ paRects[DevNum].right = paDeviceModes[DevNum].dmPosition.x + paDeviceModes[DevNum].dmPelsWidth;
+ paRects[DevNum].bottom = paDeviceModes[DevNum].dmPosition.y + paDeviceModes[DevNum].dmPelsHeight;
+ DevNum++;
+ }
+
+ RT_ZERO(DisplayDevice);
+ DisplayDevice.cb = sizeof(DISPLAY_DEVICE);
+ i++;
+ }
+
+ if (Width == 0)
+ Width = paRects[Id].right - paRects[Id].left;
+
+ if (Height == 0)
+ Height = paRects[Id].bottom - paRects[Id].top;
+
+ /* Check whether a mode reset or a change is requested. */
+ if ( !fModeReset
+ && paRects[Id].right - paRects[Id].left == (LONG)Width
+ && paRects[Id].bottom - paRects[Id].top == (LONG)Height
+ && paDeviceModes[Id].dmBitsPerPel == BitsPerPixel)
+ {
+ Log(("VBoxDisplayThread : already at desired resolution.\n"));
+ return FALSE;
+ }
+
+ resizeRect(paRects, NumDevices, DevPrimaryNum, Id, Width, Height);
+#ifdef LOG_ENABLED
+ for (i = 0; i < NumDevices; i++)
+ Log(("[%d]: %d,%d %dx%d\n",
+ i, paRects[i].left, paRects[i].top,
+ paRects[i].right - paRects[i].left,
+ paRects[i].bottom - paRects[i].top));
+#endif /* Log */
+
+ /* Without this, Windows will not ask the miniport for its
+ * mode table but uses an internal cache instead.
+ */
+ DEVMODE tempDevMode;
+ RT_ZERO(tempDevMode);
+ tempDevMode.dmSize = sizeof(DEVMODE);
+ g_pfnEnumDisplaySettingsA(NULL, 0xffffff, &tempDevMode);
+
+ /* Assign the new rectangles to displays. */
+ for (i = 0; i < NumDevices; i++)
+ {
+ paDeviceModes[i].dmPosition.x = paRects[i].left;
+ paDeviceModes[i].dmPosition.y = paRects[i].top;
+ paDeviceModes[i].dmPelsWidth = paRects[i].right - paRects[i].left;
+ paDeviceModes[i].dmPelsHeight = paRects[i].bottom - paRects[i].top;
+
+ paDeviceModes[i].dmFields = DM_POSITION | DM_PELSHEIGHT | DM_PELSWIDTH;
+
+ if ( i == Id
+ && BitsPerPixel != 0)
+ {
+ paDeviceModes[i].dmFields |= DM_BITSPERPEL;
+ paDeviceModes[i].dmBitsPerPel = BitsPerPixel;
+ }
+ Log(("calling pfnChangeDisplaySettingsEx %p\n", RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsExA)));
+ g_pfnChangeDisplaySettingsExA((LPSTR)paDisplayDevices[i].DeviceName,
+ &paDeviceModes[i], NULL, CDS_NORESET | CDS_UPDATEREGISTRY, NULL);
+ Log(("ChangeDisplaySettingsEx position err %d\n", GetLastError()));
+ }
+
+ /* A second call to ChangeDisplaySettings updates the monitor. */
+ LONG status = g_pfnChangeDisplaySettingsA(NULL, 0);
+ Log(("ChangeDisplaySettings update status %d\n", status));
+ if (status == DISP_CHANGE_SUCCESSFUL || status == DISP_CHANGE_BADMODE)
+ {
+ /* Successfully set new video mode or our driver can not set the requested mode. Stop trying. */
+ return FALSE;
+ }
+
+ /* Retry the request. */
+ return TRUE;
+}
+
+static DECLCALLBACK(RTEXITCODE) handleSetVideoMode(int argc, char *argv[])
+{
+ if (argc != 3 && argc != 4)
+ {
+ usage(SET_VIDEO_MODE);
+ return RTEXITCODE_FAILURE;
+ }
+
+ DWORD xres = RTStrToUInt32(argv[0]);
+ DWORD yres = RTStrToUInt32(argv[1]);
+ DWORD bpp = RTStrToUInt32(argv[2]);
+ DWORD scr = 0;
+ if (argc == 4)
+ scr = RTStrToUInt32(argv[3]);
+
+ HMODULE hmodUser = GetModuleHandle("user32.dll");
+ if (hmodUser)
+ {
+ /* ChangeDisplaySettingsExA was probably added in W2K, whereas ChangeDisplaySettingsA
+ and EnumDisplaySettingsA was added in NT 3.51. */
+ g_pfnChangeDisplaySettingsExA = (decltype(g_pfnChangeDisplaySettingsExA))GetProcAddress(hmodUser, "ChangeDisplaySettingsExA");
+ g_pfnChangeDisplaySettingsA = (decltype(g_pfnChangeDisplaySettingsA)) GetProcAddress(hmodUser, "ChangeDisplaySettingsA");
+ g_pfnEnumDisplaySettingsA = (decltype(g_pfnEnumDisplaySettingsA)) GetProcAddress(hmodUser, "EnumDisplaySettingsA");
+
+ Log(("VBoxService: g_pfnChangeDisplaySettingsExA=%p g_pfnChangeDisplaySettingsA=%p g_pfnEnumDisplaySettingsA=%p\n",
+ RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsExA), RT_CB_LOG_CAST(g_pfnChangeDisplaySettingsA),
+ RT_CB_LOG_CAST(g_pfnEnumDisplaySettingsA)));
+
+ if ( g_pfnChangeDisplaySettingsExA
+ && g_pfnChangeDisplaySettingsA
+ && g_pfnEnumDisplaySettingsA)
+ {
+ /* The screen index is 0 based in the ResizeDisplayDevice call. */
+ scr = scr > 0 ? scr - 1 : 0;
+
+ /* Horizontal resolution must be a multiple of 8, round down. */
+ xres &= ~0x7;
+
+ RTPrintf("Setting resolution of display %d to %dx%dx%d ...", scr, xres, yres, bpp);
+ ResizeDisplayDevice(scr, xres, yres, bpp);
+ RTPrintf("done.\n");
+ }
+ else
+ VBoxControlError("Error retrieving API for display change!");
+ }
+ else
+ VBoxControlError("Error retrieving handle to user32.dll!");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+static int checkVBoxVideoKey(HKEY hkeyVideo)
+{
+ RTUTF16 wszValue[128];
+ DWORD cbValue = sizeof(wszValue);
+ DWORD dwKeyType;
+ LONG status = RegQueryValueExW(hkeyVideo, L"Device Description", NULL, &dwKeyType, (LPBYTE)wszValue, &cbValue);
+ if (status == ERROR_SUCCESS)
+ {
+ /* WDDM has additional chars after "Adapter" */
+ static char s_szDeviceDescription[] = "VirtualBox Graphics Adapter";
+ wszValue[sizeof(s_szDeviceDescription) - 1] = '\0';
+ if (RTUtf16ICmpAscii(wszValue, s_szDeviceDescription) == 0)
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+static HKEY getVideoKey(bool writable)
+{
+ HKEY hkeyDeviceMap = 0;
+ LONG status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\VIDEO", 0, KEY_READ, &hkeyDeviceMap);
+ if (status != ERROR_SUCCESS || !hkeyDeviceMap)
+ {
+ VBoxControlError("Error opening video device map registry key!\n");
+ return 0;
+ }
+
+ HKEY hkeyVideo = 0;
+ ULONG iDevice;
+ DWORD dwKeyType;
+
+ /*
+ * Scan all '\Device\VideoX' REG_SZ keys to find VBox video driver entry.
+ * 'ObjectNumberList' REG_BINARY is an array of 32 bit device indexes (X).
+ */
+
+ /* Get the 'ObjectNumberList' */
+ ULONG cDevices = 0;
+ DWORD adwObjectNumberList[256];
+ DWORD cbValue = sizeof(adwObjectNumberList);
+ status = RegQueryValueExA(hkeyDeviceMap, "ObjectNumberList", NULL, &dwKeyType, (LPBYTE)&adwObjectNumberList[0], &cbValue);
+
+ if ( status == ERROR_SUCCESS
+ && dwKeyType == REG_BINARY)
+ cDevices = cbValue / sizeof(DWORD);
+ else
+ {
+ /* The list might not exists. Use 'MaxObjectNumber' REG_DWORD and build a list. */
+ DWORD dwMaxObjectNumber = 0;
+ cbValue = sizeof(dwMaxObjectNumber);
+ status = RegQueryValueExA(hkeyDeviceMap, "MaxObjectNumber", NULL, &dwKeyType, (LPBYTE)&dwMaxObjectNumber, &cbValue);
+ if ( status == ERROR_SUCCESS
+ && dwKeyType == REG_DWORD)
+ {
+ /* 'MaxObjectNumber' is inclusive. */
+ cDevices = RT_MIN(dwMaxObjectNumber + 1, RT_ELEMENTS(adwObjectNumberList));
+ for (iDevice = 0; iDevice < cDevices; iDevice++)
+ adwObjectNumberList[iDevice] = iDevice;
+ }
+ }
+
+ if (cDevices == 0)
+ {
+ /* Always try '\Device\Video0' as the old code did. Enum can be used in this case in principle. */
+ adwObjectNumberList[0] = 0;
+ cDevices = 1;
+ }
+
+ /* Scan device entries */
+ for (iDevice = 0; iDevice < cDevices; iDevice++)
+ {
+ RTUTF16 wszValueName[64];
+ RTUtf16Printf(wszValueName, RT_ELEMENTS(wszValueName), "\\Device\\Video%u", adwObjectNumberList[iDevice]);
+
+ RTUTF16 wszVideoLocation[256];
+ cbValue = sizeof(wszVideoLocation);
+ status = RegQueryValueExW(hkeyDeviceMap, wszValueName, NULL, &dwKeyType, (LPBYTE)&wszVideoLocation[0], &cbValue);
+
+ /* This value starts with '\REGISTRY\Machine' */
+ if ( status == ERROR_SUCCESS
+ && dwKeyType == REG_SZ
+ && RTUtf16NICmpAscii(wszVideoLocation, RT_STR_TUPLE("\\REGISTRY\\Machine")) == 0)
+ {
+ status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, &wszVideoLocation[18], 0,
+ KEY_READ | (writable ? KEY_WRITE : 0), &hkeyVideo);
+ if (status == ERROR_SUCCESS)
+ {
+ int rc = checkVBoxVideoKey(hkeyVideo);
+ if (RT_SUCCESS(rc))
+ {
+ /* Found, return hkeyVideo to the caller. */
+ break;
+ }
+
+ RegCloseKey(hkeyVideo);
+ hkeyVideo = 0;
+ }
+ }
+ }
+
+ if (hkeyVideo == 0)
+ {
+ VBoxControlError("Error opening video registry key!\n");
+ }
+
+ RegCloseKey(hkeyDeviceMap);
+ return hkeyVideo;
+}
+
+static DECLCALLBACK(RTEXITCODE) handleGetVideoAcceleration(int argc, char *argv[])
+{
+ RT_NOREF2(argc, argv);
+ ULONG status;
+ HKEY hkeyVideo = getVideoKey(false);
+
+ if (hkeyVideo)
+ {
+ /* query the actual value */
+ DWORD fAcceleration = 1;
+ DWORD cbValue = sizeof(fAcceleration);
+ DWORD dwKeyType;
+ status = RegQueryValueExA(hkeyVideo, "EnableVideoAccel", NULL, &dwKeyType, (LPBYTE)&fAcceleration, &cbValue);
+ if (status != ERROR_SUCCESS)
+ RTPrintf("Video acceleration: default\n");
+ else
+ RTPrintf("Video acceleration: %s\n", fAcceleration ? "on" : "off");
+ RegCloseKey(hkeyVideo);
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+static DECLCALLBACK(RTEXITCODE) handleSetVideoAcceleration(int argc, char *argv[])
+{
+ ULONG status;
+ HKEY hkeyVideo;
+
+ /* must have exactly one argument: the new offset */
+ if ( (argc != 1)
+ || ( RTStrICmp(argv[0], "on")
+ && RTStrICmp(argv[0], "off")))
+ {
+ usage(SET_VIDEO_ACCEL);
+ return RTEXITCODE_FAILURE;
+ }
+
+ hkeyVideo = getVideoKey(true);
+
+ if (hkeyVideo)
+ {
+ int fAccel = 0;
+ if (RTStrICmp(argv[0], "on") == 0)
+ fAccel = 1;
+ /* set a new value */
+ status = RegSetValueExA(hkeyVideo, "EnableVideoAccel", 0, REG_DWORD, (LPBYTE)&fAccel, sizeof(fAccel));
+ if (status != ERROR_SUCCESS)
+ {
+ VBoxControlError("Error %d writing video acceleration status!\n", status);
+ }
+ RegCloseKey(hkeyVideo);
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+static DECLCALLBACK(RTEXITCODE) videoFlagsGet(void)
+{
+ HKEY hkeyVideo = getVideoKey(false);
+
+ if (hkeyVideo)
+ {
+ DWORD dwFlags = 0;
+ DWORD cbValue = sizeof(dwFlags);
+ DWORD dwKeyType;
+ ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &cbValue);
+ if (status != ERROR_SUCCESS)
+ RTPrintf("Video flags: default\n");
+ else
+ RTPrintf("Video flags: 0x%08X\n", dwFlags);
+ RegCloseKey(hkeyVideo);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ return RTEXITCODE_FAILURE;
+}
+
+static DECLCALLBACK(RTEXITCODE) videoFlagsDelete(void)
+{
+ HKEY hkeyVideo = getVideoKey(true);
+
+ if (hkeyVideo)
+ {
+ ULONG status = RegDeleteValueA(hkeyVideo, "VBoxVideoFlags");
+ if (status != ERROR_SUCCESS)
+ VBoxControlError("Error %d deleting video flags.\n", status);
+ RegCloseKey(hkeyVideo);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ return RTEXITCODE_FAILURE;
+}
+
+static DECLCALLBACK(RTEXITCODE) videoFlagsModify(bool fSet, int argc, char *argv[])
+{
+ if (argc != 1)
+ {
+ VBoxControlError("Mask required.\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ uint32_t u32Mask = 0;
+ int rc = RTStrToUInt32Full(argv[0], 16, &u32Mask);
+ if (RT_FAILURE(rc))
+ {
+ VBoxControlError("Invalid video flags mask.\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE exitCode = RTEXITCODE_SUCCESS;
+
+ HKEY hkeyVideo = getVideoKey(true);
+ if (hkeyVideo)
+ {
+ DWORD dwFlags = 0;
+ DWORD cbValue = sizeof(dwFlags);
+ DWORD dwKeyType;
+ ULONG status = RegQueryValueExA(hkeyVideo, "VBoxVideoFlags", NULL, &dwKeyType, (LPBYTE)&dwFlags, &cbValue);
+ if (status != ERROR_SUCCESS)
+ dwFlags = 0;
+
+ dwFlags = fSet ? dwFlags | u32Mask : dwFlags & ~u32Mask;
+
+ status = RegSetValueExA(hkeyVideo, "VBoxVideoFlags", 0, REG_DWORD, (LPBYTE)&dwFlags, sizeof(dwFlags));
+ if (status != ERROR_SUCCESS)
+ {
+ VBoxControlError("Error %d writing video flags.\n", status);
+ exitCode = RTEXITCODE_FAILURE;
+ }
+
+ RegCloseKey(hkeyVideo);
+ }
+ else
+ {
+ exitCode = RTEXITCODE_FAILURE;
+ }
+
+ return exitCode;
+}
+
+static DECLCALLBACK(RTEXITCODE) handleVideoFlags(int argc, char *argv[])
+{
+ /* Must have a keyword and optional value (32 bit hex string). */
+ if (argc != 1 && argc != 2)
+ {
+ VBoxControlError("Invalid number of arguments.\n");
+ usage(VIDEO_FLAGS);
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE exitCode = RTEXITCODE_SUCCESS;
+
+ if (RTStrICmp(argv[0], "get") == 0)
+ {
+ exitCode = videoFlagsGet();
+ }
+ else if (RTStrICmp(argv[0], "delete") == 0)
+ {
+ exitCode = videoFlagsDelete();
+ }
+ else if (RTStrICmp(argv[0], "set") == 0)
+ {
+ exitCode = videoFlagsModify(true, argc - 1, &argv[1]);
+ }
+ else if (RTStrICmp(argv[0], "clear") == 0)
+ {
+ exitCode = videoFlagsModify(false, argc - 1, &argv[1]);
+ }
+ else
+ {
+ VBoxControlError("Invalid command.\n");
+ exitCode = RTEXITCODE_FAILURE;
+ }
+
+ if (exitCode != RTEXITCODE_SUCCESS)
+ {
+ usage(VIDEO_FLAGS);
+ }
+
+ return exitCode;
+}
+
+#define MAX_CUSTOM_MODES 128
+
+/* the table of custom modes */
+struct
+{
+ DWORD xres;
+ DWORD yres;
+ DWORD bpp;
+} customModes[MAX_CUSTOM_MODES] = {{0}};
+
+void getCustomModes(HKEY hkeyVideo)
+{
+ ULONG status;
+ int curMode = 0;
+
+ /* null out the table */
+ RT_ZERO(customModes);
+
+ do
+ {
+ char valueName[20];
+ DWORD xres, yres, bpp = 0;
+ DWORD dwType;
+ DWORD dwLen = sizeof(DWORD);
+
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", curMode);
+ status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&xres, &dwLen);
+ if (status != ERROR_SUCCESS)
+ break;
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", curMode);
+ status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&yres, &dwLen);
+ if (status != ERROR_SUCCESS)
+ break;
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", curMode);
+ status = RegQueryValueExA(hkeyVideo, valueName, NULL, &dwType, (LPBYTE)&bpp, &dwLen);
+ if (status != ERROR_SUCCESS)
+ break;
+
+ /* check if the mode is OK */
+ if ( (xres > (1 << 16))
+ || (yres > (1 << 16))
+ || ( (bpp != 16)
+ && (bpp != 24)
+ && (bpp != 32)))
+ break;
+
+ /* add mode to table */
+ customModes[curMode].xres = xres;
+ customModes[curMode].yres = yres;
+ customModes[curMode].bpp = bpp;
+
+ ++curMode;
+
+ if (curMode >= MAX_CUSTOM_MODES)
+ break;
+ } while(1);
+}
+
+void writeCustomModes(HKEY hkeyVideo)
+{
+ ULONG status;
+ int tableIndex = 0;
+ int modeIndex = 0;
+
+ /* first remove all values */
+ for (int i = 0; i < MAX_CUSTOM_MODES; i++)
+ {
+ char valueName[20];
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", i);
+ RegDeleteValueA(hkeyVideo, valueName);
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", i);
+ RegDeleteValueA(hkeyVideo, valueName);
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", i);
+ RegDeleteValueA(hkeyVideo, valueName);
+ }
+
+ do
+ {
+ if (tableIndex >= MAX_CUSTOM_MODES)
+ break;
+
+ /* is the table entry present? */
+ if ( (!customModes[tableIndex].xres)
+ || (!customModes[tableIndex].yres)
+ || (!customModes[tableIndex].bpp))
+ {
+ tableIndex++;
+ continue;
+ }
+
+ RTPrintf("writing mode %d (%dx%dx%d)\n", modeIndex, customModes[tableIndex].xres, customModes[tableIndex].yres, customModes[tableIndex].bpp);
+ char valueName[20];
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dWidth", modeIndex);
+ status = RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].xres,
+ sizeof(customModes[tableIndex].xres));
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dHeight", modeIndex);
+ RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].yres,
+ sizeof(customModes[tableIndex].yres));
+ RTStrPrintf(valueName, sizeof(valueName), "CustomMode%dBPP", modeIndex);
+ RegSetValueExA(hkeyVideo, valueName, 0, REG_DWORD, (LPBYTE)&customModes[tableIndex].bpp,
+ sizeof(customModes[tableIndex].bpp));
+
+ modeIndex++;
+ tableIndex++;
+
+ } while(1);
+
+}
+
+static DECLCALLBACK(RTEXITCODE) handleListCustomModes(int argc, char *argv[])
+{
+ RT_NOREF1(argv);
+ if (argc != 0)
+ {
+ usage(LIST_CUST_MODES);
+ return RTEXITCODE_FAILURE;
+ }
+
+ HKEY hkeyVideo = getVideoKey(false);
+
+ if (hkeyVideo)
+ {
+ getCustomModes(hkeyVideo);
+ for (int i = 0; i < (sizeof(customModes) / sizeof(customModes[0])); i++)
+ {
+ if ( !customModes[i].xres
+ || !customModes[i].yres
+ || !customModes[i].bpp)
+ continue;
+
+ RTPrintf("Mode: %d x %d x %d\n",
+ customModes[i].xres, customModes[i].yres, customModes[i].bpp);
+ }
+ RegCloseKey(hkeyVideo);
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+static DECLCALLBACK(RTEXITCODE) handleAddCustomMode(int argc, char *argv[])
+{
+ if (argc != 3)
+ {
+ usage(ADD_CUST_MODE);
+ return RTEXITCODE_FAILURE;
+ }
+
+ DWORD xres = RTStrToUInt32(argv[0]);
+ DWORD yres = RTStrToUInt32(argv[1]);
+ DWORD bpp = RTStrToUInt32(argv[2]);
+
+ /** @todo better check including xres mod 8 = 0! */
+ if ( (xres > (1 << 16))
+ || (yres > (1 << 16))
+ || ( (bpp != 16)
+ && (bpp != 24)
+ && (bpp != 32)))
+ {
+ VBoxControlError("invalid mode specified!\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ HKEY hkeyVideo = getVideoKey(true);
+
+ if (hkeyVideo)
+ {
+ int i;
+ int fModeExists = 0;
+ getCustomModes(hkeyVideo);
+ for (i = 0; i < MAX_CUSTOM_MODES; i++)
+ {
+ /* mode exists? */
+ if ( customModes[i].xres == xres
+ && customModes[i].yres == yres
+ && customModes[i].bpp == bpp
+ )
+ {
+ fModeExists = 1;
+ }
+ }
+ if (!fModeExists)
+ {
+ for (i = 0; i < MAX_CUSTOM_MODES; i++)
+ {
+ /* item free? */
+ if (!customModes[i].xres)
+ {
+ customModes[i].xres = xres;
+ customModes[i].yres = yres;
+ customModes[i].bpp = bpp;
+ break;
+ }
+ }
+ writeCustomModes(hkeyVideo);
+ }
+ RegCloseKey(hkeyVideo);
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+static DECLCALLBACK(RTEXITCODE) handleRemoveCustomMode(int argc, char *argv[])
+{
+ if (argc != 3)
+ {
+ usage(REMOVE_CUST_MODE);
+ return RTEXITCODE_FAILURE;
+ }
+
+ DWORD xres = RTStrToUInt32(argv[0]);
+ DWORD yres = RTStrToUInt32(argv[1]);
+ DWORD bpp = RTStrToUInt32(argv[2]);
+
+ HKEY hkeyVideo = getVideoKey(true);
+
+ if (hkeyVideo)
+ {
+ getCustomModes(hkeyVideo);
+ for (int i = 0; i < MAX_CUSTOM_MODES; i++)
+ {
+ /* correct item? */
+ if ( (customModes[i].xres == xres)
+ && (customModes[i].yres == yres)
+ && (customModes[i].bpp == bpp))
+ {
+ RTPrintf("found mode at index %d\n", i);
+ RT_ZERO(customModes[i]);
+ break;
+ }
+ }
+ writeCustomModes(hkeyVideo);
+ RegCloseKey(hkeyVideo);
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+#endif /* RT_OS_WINDOWS */
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * Retrieves a value from the guest property store.
+ * This is accessed through the "VBoxGuestPropSvc" HGCM service.
+ *
+ * @returns Command exit code.
+ * @note see the command line API description for parameters
+ */
+static RTEXITCODE getGuestProperty(int argc, char **argv)
+{
+ bool fVerbose = false;
+ if ( argc == 2
+ && ( strcmp(argv[1], "-verbose") == 0
+ || strcmp(argv[1], "--verbose") == 0)
+ )
+ fVerbose = true;
+ else if (argc != 1)
+ {
+ usage(GUEST_PROP);
+ return RTEXITCODE_FAILURE;
+ }
+
+ uint32_t u32ClientId = 0;
+ int rc = VINF_SUCCESS;
+
+ rc = VbglR3GuestPropConnect(&u32ClientId);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);
+
+ /*
+ * Here we actually retrieve the value from the host.
+ */
+ const char *pszName = argv[0];
+ char *pszValue = NULL;
+ uint64_t u64Timestamp = 0;
+ 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 + 1024;
+ if (RT_SUCCESS(rc))
+ {
+ /* 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. */
+ bool fFinished = false;
+ for (unsigned i = 0; i < 10 && !fFinished; ++i)
+ {
+ void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (NULL == pvTmpBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ VBoxControlError("Out of memory\n");
+ }
+ else
+ {
+ pvBuf = pvTmpBuf;
+ rc = VbglR3GuestPropRead(u32ClientId, pszName, pvBuf, cbBuf,
+ &pszValue, &u64Timestamp, &pszFlags,
+ &cbBuf);
+ }
+ if (VERR_BUFFER_OVERFLOW == rc)
+ /* Leave a bit of extra space to be safe */
+ cbBuf += 1024;
+ else
+ fFinished = true;
+ }
+ if (VERR_TOO_MUCH_DATA == rc)
+ VBoxControlError("Temporarily unable to retrieve the property\n");
+ else if (RT_FAILURE(rc) && rc != VERR_NOT_FOUND)
+ VBoxControlError("Failed to retrieve the property value, error %Rrc\n", rc);
+ }
+
+ /*
+ * And display it on the guest console.
+ */
+ if (VERR_NOT_FOUND == rc)
+ RTPrintf("No value set!\n");
+ else if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Value: %s\n", pszValue);
+ if (fVerbose)
+ {
+ RTPrintf("Timestamp: %lld ns\n", u64Timestamp);
+ RTPrintf("Flags: %s\n", pszFlags);
+ }
+ }
+
+ if (u32ClientId != 0)
+ VbglR3GuestPropDisconnect(u32ClientId);
+ RTMemFree(pvBuf);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Writes a value to the guest property store.
+ * This is accessed through the "VBoxGuestPropSvc" HGCM service.
+ *
+ * @returns Command exit code.
+ * @note see the command line API description for parameters
+ */
+static RTEXITCODE setGuestProperty(int argc, char *argv[])
+{
+ /*
+ * Check the syntax. We can deduce the correct syntax from the number of
+ * arguments.
+ */
+ bool fUsageOK = true;
+ const char *pszName = NULL;
+ const char *pszValue = NULL;
+ const char *pszFlags = NULL;
+ if (2 == argc)
+ {
+ pszValue = argv[1];
+ }
+ else if (3 == argc)
+ fUsageOK = false;
+ else if (4 == argc)
+ {
+ pszValue = argv[1];
+ if ( strcmp(argv[2], "-flags") != 0
+ && strcmp(argv[2], "--flags") != 0)
+ fUsageOK = false;
+ pszFlags = argv[3];
+ }
+ else if (argc != 1)
+ fUsageOK = false;
+ if (!fUsageOK)
+ {
+ usage(GUEST_PROP);
+ return RTEXITCODE_FAILURE;
+ }
+ /* This is always needed. */
+ pszName = argv[0];
+
+ /*
+ * Do the actual setting.
+ */
+ uint32_t u32ClientId = 0;
+ int rc = VINF_SUCCESS;
+ rc = VbglR3GuestPropConnect(&u32ClientId);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);
+ else
+ {
+ if (pszFlags != NULL)
+ rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, pszFlags);
+ else
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Failed to store the property value, error %Rrc\n", rc);
+ }
+
+ if (u32ClientId != 0)
+ VbglR3GuestPropDisconnect(u32ClientId);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Deletes a guest property from the guest property store.
+ * This is accessed through the "VBoxGuestPropSvc" HGCM service.
+ *
+ * @returns Command exit code.
+ * @note see the command line API description for parameters
+ */
+static RTEXITCODE deleteGuestProperty(int argc, char *argv[])
+{
+ /*
+ * Check the syntax. We can deduce the correct syntax from the number of
+ * arguments.
+ */
+ bool fUsageOK = true;
+ const char *pszName = NULL;
+ if (argc < 1)
+ fUsageOK = false;
+ if (!fUsageOK)
+ {
+ usage(GUEST_PROP);
+ return RTEXITCODE_FAILURE;
+ }
+ /* This is always needed. */
+ pszName = argv[0];
+
+ /*
+ * Do the actual setting.
+ */
+ uint32_t u32ClientId = 0;
+ int rc = VbglR3GuestPropConnect(&u32ClientId);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);
+ else
+ {
+ rc = VbglR3GuestPropDelete(u32ClientId, pszName);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Failed to delete the property value, error %Rrc\n", rc);
+ }
+
+ if (u32ClientId != 0)
+ VbglR3GuestPropDisconnect(u32ClientId);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Enumerates the properties in the guest property store.
+ * This is accessed through the "VBoxGuestPropSvc" HGCM service.
+ *
+ * @returns Command exit code.
+ * @note see the command line API description for parameters
+ */
+static RTEXITCODE enumGuestProperty(int argc, char *argv[])
+{
+ /*
+ * Check the syntax. We can deduce the correct syntax from the number of
+ * arguments.
+ */
+ char const * const *papszPatterns = NULL;
+ uint32_t cPatterns = 0;
+ if ( argc > 1
+ && ( strcmp(argv[0], "-patterns") == 0
+ || strcmp(argv[0], "--patterns") == 0))
+ {
+ papszPatterns = (char const * const *)&argv[1];
+ cPatterns = argc - 1;
+ }
+ else if (argc != 0)
+ {
+ usage(GUEST_PROP);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Do the actual enumeration.
+ */
+ uint32_t u32ClientId = 0;
+ int rc = VbglR3GuestPropConnect(&u32ClientId);
+ if (RT_SUCCESS(rc))
+ {
+ PVBGLR3GUESTPROPENUM pHandle;
+ const char *pszName, *pszValue, *pszFlags;
+ uint64_t u64Timestamp;
+
+ rc = VbglR3GuestPropEnum(u32ClientId, papszPatterns, cPatterns, &pHandle,
+ &pszName, &pszValue, &u64Timestamp, &pszFlags);
+ if (RT_SUCCESS(rc))
+ {
+ while (RT_SUCCESS(rc) && pszName)
+ {
+ RTPrintf("Name: %s, value: %s, timestamp: %lld, flags: %s\n",
+ pszName, pszValue ? pszValue : "", u64Timestamp, pszFlags);
+
+ rc = VbglR3GuestPropEnumNext(pHandle, &pszName, &pszValue, &u64Timestamp, &pszFlags);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Error while enumerating guest properties: %Rrc\n", rc);
+ }
+
+ VbglR3GuestPropEnumFree(pHandle);
+ }
+ else if (VERR_NOT_FOUND == rc)
+ RTPrintf("No properties found.\n");
+ else
+ VBoxControlError("Failed to enumerate the guest properties! Error: %Rrc\n", rc);
+ VbglR3GuestPropDisconnect(u32ClientId);
+ }
+ else
+ VBoxControlError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Waits for notifications of changes to guest properties.
+ * This is accessed through the "VBoxGuestPropSvc" HGCM service.
+ *
+ * @returns Command exit code.
+ * @note see the command line API description for parameters
+ */
+static RTEXITCODE waitGuestProperty(int argc, char **argv)
+{
+ /*
+ * Handle arguments
+ */
+ const char *pszPatterns = NULL;
+ uint64_t u64TimestampIn = 0;
+ uint32_t u32Timeout = RT_INDEFINITE_WAIT;
+ bool fUsageOK = true;
+ if (argc < 1)
+ fUsageOK = false;
+ pszPatterns = argv[0];
+ for (int i = 1; fUsageOK && i < argc; ++i)
+ {
+ if ( strcmp(argv[i], "-timeout") == 0
+ || strcmp(argv[i], "--timeout") == 0)
+ {
+ if ( i + 1 >= argc
+ || RTStrToUInt32Full(argv[i + 1], 10, &u32Timeout)
+ != VINF_SUCCESS
+ )
+ fUsageOK = false;
+ else
+ ++i;
+ }
+ else if ( strcmp(argv[i], "-timestamp") == 0
+ || strcmp(argv[i], "--timestamp") == 0)
+ {
+ if ( i + 1 >= argc
+ || RTStrToUInt64Full(argv[i + 1], 10, &u64TimestampIn)
+ != VINF_SUCCESS
+ )
+ fUsageOK = false;
+ else
+ ++i;
+ }
+ else
+ fUsageOK = false;
+ }
+ if (!fUsageOK)
+ {
+ usage(GUEST_PROP);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Connect to the service
+ */
+ uint32_t u32ClientId = 0;
+ int rc = VbglR3GuestPropConnect(&u32ClientId);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Failed to connect to the guest property service, error %Rrc\n", rc);
+
+ /*
+ * Retrieve the notification from the host
+ */
+ char *pszName = NULL;
+ char *pszValue = NULL;
+ uint64_t u64TimestampOut = 0;
+ char *pszFlags = NULL;
+ bool fWasDeleted = false;
+ /* 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_NAME_LEN + 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 iTry = 0; ; iTry++)
+ {
+ pvBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvBuf != NULL)
+ {
+ rc = VbglR3GuestPropWait(u32ClientId, pszPatterns, pvBuf, cbBuf,
+ u64TimestampIn, u32Timeout,
+ &pszName, &pszValue, &u64TimestampOut,
+ &pszFlags, &cbBuf, &fWasDeleted);
+ if (rc == VERR_BUFFER_OVERFLOW && iTry < 10)
+ {
+ cbBuf += _1K; /* Add a bit of extra space to be on the safe side. */
+ continue;
+ }
+ if (rc == VERR_TOO_MUCH_DATA)
+ VBoxControlError("Temporarily unable to get a notification\n");
+ else if (rc == VERR_INTERRUPTED)
+ VBoxControlError("The request timed out or was interrupted\n");
+ else if (RT_FAILURE(rc) && rc != VERR_NOT_FOUND)
+ VBoxControlError("Failed to get a notification, error %Rrc\n", rc);
+ }
+ else
+ {
+ VBoxControlError("Out of memory\n");
+ rc = VERR_NO_MEMORY;
+ }
+ break;
+ }
+
+ /*
+ * And display it on the guest console.
+ */
+ if (VERR_NOT_FOUND == rc)
+ RTPrintf("No value set!\n");
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ RTPrintf("Internal error: unable to determine the size of the data!\n");
+ else if (RT_SUCCESS(rc))
+ {
+ if (fWasDeleted)
+ {
+ RTPrintf("Property %s was deleted\n", pszName);
+ }
+ else
+ {
+ RTPrintf("Name: %s\n", pszName);
+ RTPrintf("Value: %s\n", pszValue);
+ RTPrintf("Timestamp: %lld ns\n", u64TimestampOut);
+ RTPrintf("Flags: %s\n", pszFlags);
+ }
+ }
+
+ if (u32ClientId != 0)
+ VbglR3GuestPropDisconnect(u32ClientId);
+ RTMemFree(pvBuf);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Access the guest property store through the "VBoxGuestPropSvc" HGCM
+ * service.
+ *
+ * @returns 0 on success, 1 on failure
+ * @note see the command line API description for parameters
+ */
+static DECLCALLBACK(RTEXITCODE) handleGuestProperty(int argc, char *argv[])
+{
+ if (argc == 0)
+ {
+ usage(GUEST_PROP);
+ return RTEXITCODE_FAILURE;
+ }
+ if (!strcmp(argv[0], "get"))
+ return getGuestProperty(argc - 1, argv + 1);
+ if (!strcmp(argv[0], "set"))
+ return setGuestProperty(argc - 1, argv + 1);
+ if (!strcmp(argv[0], "delete") || !strcmp(argv[0], "unset"))
+ return deleteGuestProperty(argc - 1, argv + 1);
+ if (!strcmp(argv[0], "enumerate"))
+ return enumGuestProperty(argc - 1, argv + 1);
+ if (!strcmp(argv[0], "wait"))
+ return waitGuestProperty(argc - 1, argv + 1);
+ /* unknown cmd */
+ usage(GUEST_PROP);
+ return RTEXITCODE_FAILURE;
+}
+
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+
+/**
+ * Lists the Shared Folders provided by the host.
+ */
+static RTEXITCODE sharedFolder_list(int argc, char **argv)
+{
+ bool fUsageOK = true;
+ bool fOnlyShowAutoMount = false;
+ if (argc == 1)
+ {
+ if (!strcmp(argv[0], "--automount"))
+ fOnlyShowAutoMount = true;
+ else
+ fUsageOK = false;
+ }
+ else if (argc > 1)
+ fUsageOK = false;
+ if (!fUsageOK)
+ {
+ usage(GUEST_SHAREDFOLDERS);
+ return RTEXITCODE_SYNTAX;
+ }
+
+ uint32_t u32ClientId;
+ int rc = VbglR3SharedFolderConnect(&u32ClientId);
+ if (RT_FAILURE(rc))
+ VBoxControlError("Failed to connect to the shared folder service, error %Rrc\n", rc);
+ else
+ {
+ PVBGLR3SHAREDFOLDERMAPPING paMappings;
+ uint32_t cMappings;
+ rc = VbglR3SharedFolderGetMappings(u32ClientId, fOnlyShowAutoMount, &paMappings, &cMappings);
+ if (RT_SUCCESS(rc))
+ {
+ if (fOnlyShowAutoMount)
+ RTPrintf("Auto-mounted Shared Folder mappings (%u):\n\n", cMappings);
+ else
+ RTPrintf("Shared Folder mappings (%u):\n\n", cMappings);
+
+ for (uint32_t i = 0; i < cMappings; i++)
+ {
+ char *pszName;
+ char *pszMntPt;
+ uint64_t fFlags;
+ uint32_t uRootIdVer;
+ rc = VbglR3SharedFolderQueryFolderInfo(u32ClientId, paMappings[i].u32Root, 0,
+ &pszName, &pszMntPt, &fFlags, &uRootIdVer);
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("%02u - %s [idRoot=%u", i + 1, pszName, paMappings[i].u32Root);
+ if (fFlags & SHFL_MIF_WRITABLE)
+ RTPrintf(" writable");
+ else
+ RTPrintf(" readonly");
+ if (fFlags & SHFL_MIF_AUTO_MOUNT)
+ RTPrintf(" auto-mount");
+ if (fFlags & SHFL_MIF_SYMLINK_CREATION)
+ RTPrintf(" create-symlink");
+ if (fFlags & SHFL_MIF_HOST_ICASE)
+ RTPrintf(" host-icase");
+ if (fFlags & SHFL_MIF_GUEST_ICASE)
+ RTPrintf(" guest-icase");
+ if (*pszMntPt)
+ RTPrintf(" mnt-pt=%s", pszMntPt);
+ RTPrintf("]");
+# ifdef RT_OS_OS2
+ /* Show drive letters: */
+ const char *pszOn = " on";
+ for (char chDrive = 'A'; chDrive <= 'Z'; chDrive++)
+ {
+ char szDrive[4] = { chDrive, ':', '\0', '\0' };
+ union
+ {
+ FSQBUFFER2 FsQueryBuf;
+ char achPadding[512];
+ } uBuf;
+ RT_ZERO(uBuf);
+ ULONG cbBuf = sizeof(uBuf) - 2;
+ APIRET rcOs2 = DosQueryFSAttach(szDrive, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf);
+ if (rcOs2 == NO_ERROR)
+ {
+ const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1];
+ if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV
+ && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0)
+ {
+ const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1];
+ if (RTStrICmp(pszMountedName, pszName) == 0)
+ {
+ const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* safe */
+ if (*pszTag != '\0')
+ RTPrintf("%s %s (%s)", pszOn, szDrive, pszTag);
+ else
+ RTPrintf("%s %s", pszOn, szDrive);
+ pszOn = ",";
+ }
+ }
+ }
+ }
+# endif
+ RTPrintf("\n");
+
+ RTStrFree(pszName);
+ RTStrFree(pszMntPt);
+ }
+ else
+ VBoxControlError("Error while getting the shared folder name for root node = %u, rc = %Rrc\n",
+ paMappings[i].u32Root, rc);
+ }
+ if (!cMappings)
+ RTPrintf("No Shared Folders available.\n");
+ VbglR3SharedFolderFreeMappings(paMappings);
+ }
+ else
+ VBoxControlError("Error while getting the shared folder mappings, rc = %Rrc\n", rc);
+ VbglR3SharedFolderDisconnect(u32ClientId);
+ }
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+# ifdef RT_OS_OS2
+/**
+ * Attaches a shared folder to a drive letter.
+ */
+static RTEXITCODE sharedFolder_use(int argc, char **argv)
+{
+ /*
+ * Takes a drive letter and a share name as arguments.
+ */
+ if (argc != 2)
+ return VBoxControlSyntaxError("sharedfolder use: expected a drive letter and a shared folder name\n");
+
+ const char *pszDrive = argv[0];
+ if (!RT_C_IS_ALPHA(pszDrive[0]) || pszDrive[1] != ':' || pszDrive[2] != '\0')
+ return VBoxControlSyntaxError("sharedfolder use: not a drive letter: %s\n", pszDrive);
+
+ static const char s_szTag[] = "VBoxControl";
+ char szzNameAndTag[256];
+ const char *pszName = argv[1];
+ size_t cchName = strlen(pszName);
+ if (cchName < 1)
+ return VBoxControlSyntaxError("sharedfolder use: shared folder name cannot be empty!\n");
+ if (cchName + 1 + sizeof(s_szTag) >= sizeof(szzNameAndTag))
+ return VBoxControlSyntaxError("sharedfolder use: shared folder name is too long! (%s)\n", pszName);
+
+ /*
+ * Do the attaching.
+ */
+ memcpy(szzNameAndTag, pszName, cchName);
+ szzNameAndTag[cchName] = '\0';
+ memcpy(&szzNameAndTag[cchName + 1], s_szTag, sizeof(s_szTag));
+
+ APIRET rcOs2 = DosFSAttach(pszDrive, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(s_szTag), FS_ATTACH);
+ if (rcOs2 == NO_ERROR)
+ return RTEXITCODE_SUCCESS;
+ if (rcOs2 == ERROR_INVALID_FSD_NAME)
+ return VBoxControlError("Shared folders IFS not installed?\n");
+ return VBoxControlError("DosFSAttach/FS_ATTACH failed to attach '%s' to '%s': %u\n", pszName, pszDrive, rcOs2);
+}
+
+/**
+ * Detaches a shared folder from a drive letter.
+ */
+static RTEXITCODE sharedFolder_unuse(int argc, char **argv)
+{
+ /*
+ * Only takes a drive letter as argument.
+ */
+ if (argc != 1)
+ return VBoxControlSyntaxError("sharedfolder unuse: expected drive letter\n");
+ const char *pszDrive = argv[0];
+ if (!RT_C_IS_ALPHA(pszDrive[0]) || pszDrive[1] != ':' || pszDrive[2] != '\0')
+ return VBoxControlSyntaxError("sharedfolder unuse: not a drive letter: %s\n", pszDrive);
+
+ /*
+ * Do the detaching.
+ */
+ APIRET rcOs2 = DosFSAttach(pszDrive, "VBOXSF", NULL, 0, FS_DETACH);
+ if (rcOs2 == NO_ERROR)
+ return RTEXITCODE_SUCCESS;
+ return VBoxControlError("DosFSAttach/FS_DETACH failed on '%s': %u\n", pszDrive, rcOs2);
+}
+
+# endif /* RT_OS_OS2 */
+
+
+/**
+ * Handles Shared Folders control.
+ *
+ * @returns 0 on success, 1 on failure
+ * @note see the command line API description for parameters
+ * (r=bird: yeah, right. The API description contains nil about params)
+ */
+static DECLCALLBACK(RTEXITCODE) handleSharedFolder(int argc, char *argv[])
+{
+ if (argc == 0)
+ {
+ usage(GUEST_SHAREDFOLDERS);
+ return RTEXITCODE_FAILURE;
+ }
+ if (!strcmp(argv[0], "list"))
+ return sharedFolder_list(argc - 1, argv + 1);
+# ifdef RT_OS_OS2
+ if (!strcmp(argv[0], "use"))
+ return sharedFolder_use(argc - 1, argv + 1);
+ if (!strcmp(argv[0], "unuse"))
+ return sharedFolder_unuse(argc - 1, argv + 1);
+# endif
+
+ usage(GUEST_SHAREDFOLDERS);
+ return RTEXITCODE_FAILURE;
+}
+
+#endif
+#if !defined(VBOX_CONTROL_TEST)
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: writecoredump}
+ */
+static DECLCALLBACK(RTEXITCODE) handleWriteCoreDump(int argc, char *argv[])
+{
+ RT_NOREF2(argc, argv);
+ int rc = VbglR3WriteCoreDump();
+ if (RT_SUCCESS(rc))
+ {
+ RTPrintf("Guest core dump successful.\n");
+ return RTEXITCODE_SUCCESS;
+ }
+ else
+ {
+ VBoxControlError("Error while taking guest core dump. rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+}
+
+#endif
+#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help}
+ */
+static DECLCALLBACK(RTEXITCODE) handleDpc(int argc, char *argv[])
+{
+ RT_NOREF(argc, argv);
+ int rc = VERR_NOT_IMPLEMENTED;
+# ifndef VBOX_CONTROL_TEST
+ for (int i = 0; i < 30; i++)
+ {
+ VBGLREQHDR Req;
+ VBGLREQHDR_INIT(&Req, DPC_LATENCY_CHECKER);
+ rc = vbglR3DoIOCtl(VBGL_IOCTL_DPC_LATENCY_CHECKER, &Req, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ RTPrintf("%d\n", i);
+ else
+ break;
+ }
+# endif
+ if (RT_FAILURE(rc))
+ return VBoxControlError("Error. rc=%Rrc\n", rc);
+ RTPrintf("Samples collection completed.\n");
+ return RTEXITCODE_SUCCESS;
+}
+#endif /* VBOX_WITH_DPC_LATENCY_CHECKER */
+
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: writelog}
+ */
+static DECLCALLBACK(RTEXITCODE) handleWriteLog(int argc, char *argv[])
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--no-newline", 'n', RTGETOPT_REQ_NOTHING },
+ };
+ bool fNoNewline = false;
+
+ RTGETOPTSTATE GetOptState;
+ int rc = RTGetOptInit(&GetOptState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ 0 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_SUCCESS(rc))
+ {
+ RTGETOPTUNION ValueUnion;
+ int ch;
+ while ((ch = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
+ {
+ switch (ch)
+ {
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ size_t cch = strlen(ValueUnion.psz);
+ if ( fNoNewline
+ || (cch > 0 && ValueUnion.psz[cch - 1] == '\n') )
+ rc = VbglR3WriteLog(ValueUnion.psz, cch);
+ else
+ {
+ char *pszDup = (char *)RTMemDupEx(ValueUnion.psz, cch, 2);
+ if (RT_SUCCESS(rc))
+ {
+ pszDup[cch++] = '\n';
+ pszDup[cch] = '\0';
+ rc = VbglR3WriteLog(pszDup, cch);
+ RTMemFree(pszDup);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ if (RT_FAILURE(rc))
+ return VBoxControlError("VbglR3WriteLog: %Rrc", rc);
+ break;
+ }
+
+ case 'n':
+ fNoNewline = true;
+ break;
+
+ case 'h': return usage(WRITE_LOG);
+ case 'V': return printVersion();
+ default:
+ return VBoxCtrlGetOptError(ch, &ValueUnion);
+ }
+ }
+ }
+ else
+ return VBoxControlError("RTGetOptInit: %Rrc", rc);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: takesnapshot}
+ */
+static DECLCALLBACK(RTEXITCODE) handleTakeSnapshot(int argc, char *argv[])
+{
+ RT_NOREF2(argc, argv); //VbglR3VmTakeSnapshot(argv[0], argv[1]);
+ return VBoxControlError("not implemented");
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: savestate}
+ */
+static DECLCALLBACK(RTEXITCODE) handleSaveState(int argc, char *argv[])
+{
+ RT_NOREF2(argc, argv); //VbglR3VmSaveState();
+ return VBoxControlError("not implemented");
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: suspend|pause}
+ */
+static DECLCALLBACK(RTEXITCODE) handleSuspend(int argc, char *argv[])
+{
+ RT_NOREF2(argc, argv); //VbglR3VmSuspend();
+ return VBoxControlError("not implemented");
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: poweroff|powerdown}
+ */
+static DECLCALLBACK(RTEXITCODE) handlePowerOff(int argc, char *argv[])
+{
+ RT_NOREF2(argc, argv); //VbglR3VmPowerOff();
+ return VBoxControlError("not implemented");
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: version}
+ */
+static DECLCALLBACK(RTEXITCODE) handleVersion(int argc, char *argv[])
+{
+ RT_NOREF1(argv);
+ if (argc)
+ return VBoxControlSyntaxError("getversion does not take any arguments");
+ return printVersion();
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: help}
+ */
+static DECLCALLBACK(RTEXITCODE) handleHelp(int argc, char *argv[])
+{
+ RT_NOREF2(argc, argv); /* ignore arguments for now. */
+ usage();
+ return RTEXITCODE_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: ls}
+ */
+static DECLCALLBACK(RTEXITCODE) handleLs(int argc, char *argv[])
+{
+ return RTFsCmdLs(argc + 1, argv - 1);
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: tar}
+ */
+static DECLCALLBACK(RTEXITCODE) handleTar(int argc, char *argv[])
+{
+ return RTZipTarCmd(argc + 1, argv - 1);
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: tar}
+ */
+static DECLCALLBACK(RTEXITCODE) handleGzip(int argc, char *argv[])
+{
+ return RTZipGzipCmd(argc + 1, argv - 1);
+}
+
+/**
+ * @callback_method_impl{FNVBOXCTRLCMDHANDLER, Command: unzip}
+ */
+static DECLCALLBACK(RTEXITCODE) handleUnzip(int argc, char *argv[])
+{
+ return RTZipUnzipCmd(argc + 1, argv - 1);
+}
+
+
+/** command handler type */
+typedef DECLCALLBACKTYPE(RTEXITCODE, FNVBOXCTRLCMDHANDLER,(int argc, char *argv[]));
+typedef FNVBOXCTRLCMDHANDLER *PFNVBOXCTRLCMDHANDLER;
+
+/** The table of all registered command handlers. */
+struct COMMANDHANDLER
+{
+ const char *pszCommand;
+ PFNVBOXCTRLCMDHANDLER pfnHandler;
+ bool fNeedDevice;
+} g_aCommandHandlers[] =
+{
+#if defined(RT_OS_WINDOWS) && !defined(VBOX_CONTROL_TEST)
+ { "getvideoacceleration", handleGetVideoAcceleration, true },
+ { "setvideoacceleration", handleSetVideoAcceleration, true },
+ { "videoflags", handleVideoFlags, true },
+ { "listcustommodes", handleListCustomModes, true },
+ { "addcustommode", handleAddCustomMode, true },
+ { "removecustommode", handleRemoveCustomMode, true },
+ { "setvideomode", handleSetVideoMode, true },
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+ { "guestproperty", handleGuestProperty, true },
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+ { "sharedfolder", handleSharedFolder, true },
+#endif
+#if !defined(VBOX_CONTROL_TEST)
+ { "writecoredump", handleWriteCoreDump, true },
+#endif
+#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
+ { "dpc", handleDpc, true },
+#endif
+ { "writelog", handleWriteLog, true },
+ { "takesnapshot", handleTakeSnapshot, true },
+ { "savestate", handleSaveState, true },
+ { "suspend", handleSuspend, true },
+ { "pause", handleSuspend, true },
+ { "poweroff", handlePowerOff, true },
+ { "powerdown", handlePowerOff, true },
+ { "getversion", handleVersion, false },
+ { "version", handleVersion, false },
+ { "help", handleHelp, false },
+ /* Hany tricks that doesn't cost much space: */
+ { "gzip", handleGzip, false },
+ { "ls", handleLs, false },
+ { "tar", handleTar, false },
+ { "unzip", handleUnzip, false },
+};
+
+/** Main function */
+int main(int argc, char **argv)
+{
+ /** The application's global return code */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ /** An IPRT return code for local use */
+ int rrc = VINF_SUCCESS;
+ /** The index of the command line argument we are currently processing */
+ int iArg = 1;
+ /** Should we show the logo text? */
+ bool fShowLogo = true;
+ /** Should we print the usage after the logo? For the -help switch. */
+ bool fDoHelp = false;
+ /** Will we be executing a command or just printing information? */
+ bool fOnlyInfo = false;
+
+ rrc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rrc))
+ return RTMsgInitFailure(rrc);
+
+ /*
+ * Start by handling command line switches
+ */
+ /** @todo RTGetOpt conversion of the whole file. */
+ bool done = false; /**< Are we finished with handling switches? */
+ while (!done && (iArg < argc))
+ {
+ if ( !strcmp(argv[iArg], "-V")
+ || !strcmp(argv[iArg], "-v")
+ || !strcmp(argv[iArg], "--version")
+ || !strcmp(argv[iArg], "-version")
+ )
+ {
+ /* Print version number, and do nothing else. */
+ printVersion();
+ fOnlyInfo = true;
+ fShowLogo = false;
+ done = true;
+ }
+ else if ( !strcmp(argv[iArg], "-nologo")
+ || !strcmp(argv[iArg], "--nologo"))
+ fShowLogo = false;
+ else if ( !strcmp(argv[iArg], "-help")
+ || !strcmp(argv[iArg], "--help"))
+ {
+ fOnlyInfo = true;
+ fDoHelp = true;
+ done = true;
+ }
+ else
+ /* We have found an argument which isn't a switch. Exit to the
+ * command processing bit. */
+ done = true;
+ if (!done)
+ ++iArg;
+ }
+
+ /*
+ * Find the application name, show our logo if the user hasn't suppressed it,
+ * and show the usage if the user asked us to
+ */
+ g_pszProgName = RTPathFilename(argv[0]);
+ if (fShowLogo)
+ RTPrintf(VBOX_PRODUCT " Guest Additions Command Line Management Interface Version "
+ VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ if (fDoHelp)
+ usage();
+
+ /*
+ * Now look for an actual command in the argument list and handle it.
+ */
+ if (!fOnlyInfo && rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (argc > iArg)
+ {
+ /*
+ * Try locate the command and execute it, complain if not found.
+ */
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(g_aCommandHandlers); i++)
+ if (!strcmp(argv[iArg], g_aCommandHandlers[i].pszCommand))
+ {
+ if (g_aCommandHandlers[i].fNeedDevice)
+ {
+ rrc = VbglR3Init();
+ if (RT_FAILURE(rrc))
+ {
+ VBoxControlError("Could not contact the host system. Make sure that you are running this\n"
+ "application inside a VirtualBox guest system, and that you have sufficient\n"
+ "user permissions.\n");
+ rcExit = RTEXITCODE_FAILURE;
+ }
+ }
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = g_aCommandHandlers[i].pfnHandler(argc - iArg - 1, argv + iArg + 1);
+ break;
+ }
+ if (i >= RT_ELEMENTS(g_aCommandHandlers))
+ {
+ usage();
+ rcExit = RTEXITCODE_SYNTAX;
+ }
+ }
+ else
+ {
+ /* The user didn't specify a command. */
+ usage();
+ rcExit = RTEXITCODE_SYNTAX;
+ }
+ }
+
+ /*
+ * And exit, returning the status
+ */
+ return rcExit;
+}
+
diff --git a/src/VBox/Additions/common/VBoxControl/VBoxControl.rc b/src/VBox/Additions/common/VBoxControl/VBoxControl.rc
new file mode 100644
index 00000000..9a454882
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxControl/VBoxControl.rc
@@ -0,0 +1,61 @@
+/* $Id: VBoxControl.rc $ */
+/** @file
+ * VBoxControl - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Guest Additions Utility\0"
+ VALUE "InternalName", "VBoxControl\0"
+ VALUE "OriginalFilename", "VBoxControl.exe\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk
new file mode 100644
index 00000000..bee6c0a0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxControl/testcase/Makefile.kmk
@@ -0,0 +1,48 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VBoxControl testcases.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS)
+
+ #
+ # Dummy CLI testcase.
+ #
+ PROGRAMS += tstVBoxControl
+ tstVBoxControl_TEMPLATE = VBoxR3TstExe
+ tstVBoxControl_DEFS = VBOX_CONTROL_TEST
+ tstVBoxControl_SOURCES = tstVBoxControl.cpp ../VBoxControl.cpp
+ tstVBoxControl_LIBS = $(LIB_RUNTIME)
+ tstVBoxControl_DEFS += \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS VBOX_WITH_HGCM,)
+
+endif # VBOX_WITH_TESTCASES
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp b/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp
new file mode 100644
index 00000000..6211813e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxControl/testcase/tstVBoxControl.cpp
@@ -0,0 +1,226 @@
+/* $Id: tstVBoxControl.cpp $ */
+/** @file
+ * VBoxControl - Guest Additions Command Line Management Interface, test case
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cpp/autores.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <VBox/VBoxGuestLib.h>
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+#endif
+
+VBGLR3DECL(int) VbglR3Init(void)
+{
+ RTPrintf("Initialising guest library...\n");
+ return VINF_SUCCESS;
+}
+
+VBGLR3DECL(int) VbglR3GuestPropConnect(HGCMCLIENTID *pidClient)
+{
+ AssertPtrReturn(pidClient, VERR_INVALID_POINTER);
+ RTPrintf("Connect to guest property service...\n");
+ *pidClient = 1;
+ return VINF_SUCCESS;
+}
+
+VBGLR3DECL(int) VbglR3GuestPropDisconnect(HGCMCLIENTID idClient)
+{
+ RTPrintf("Disconnect client %d from guest property service...\n", idClient);
+ return VINF_SUCCESS;
+}
+
+VBGLR3DECL(int) VbglR3GuestPropWrite(HGCMCLIENTID idClient,
+ const char *pszName,
+ const char *pszValue,
+ const char *pszFlags)
+{
+ RTPrintf("Called SET_PROP, client %d, name %s, value %s, flags %s...\n",
+ idClient, pszName, pszValue, pszFlags);
+ return VINF_SUCCESS;
+}
+
+VBGLR3DECL(int) VbglR3GuestPropWriteValue(HGCMCLIENTID idClient,
+ const char *pszName,
+ const char *pszValue)
+{
+ RTPrintf("Called SET_PROP_VALUE, client %d, name %s, value %s...\n",
+ idClient, pszName, pszValue);
+ return VINF_SUCCESS;
+}
+
+#ifdef VBOX_WITH_GUEST_PROPS
+VBGLR3DECL(int) VbglR3GuestPropRead(HGCMCLIENTID idClient,
+ const char *pszName,
+ void *pvBuf,
+ uint32_t cbBuf,
+ char **ppszValue,
+ uint64_t *pu64Timestamp,
+ char **ppszFlags,
+ uint32_t *pcbBufActual)
+{
+ RT_NOREF2(pvBuf, cbBuf);
+ RTPrintf("Called GET_PROP, client %d, name %s...\n",
+ idClient, pszName);
+ static char szValue[] = "Value";
+ static char szFlags[] = "TRANSIENT";
+ if (ppszValue)
+ *ppszValue = szValue;
+ if (pu64Timestamp)
+ *pu64Timestamp = 12345;
+ if (ppszFlags)
+ *ppszFlags = szFlags;
+ if (pcbBufActual)
+ *pcbBufActual = 256;
+ return VINF_SUCCESS;
+}
+
+VBGLR3DECL(int) VbglR3GuestPropDelete(HGCMCLIENTID idClient,
+ const char *pszName)
+{
+ RTPrintf("Called DEL_PROP, client %d, name %s...\n",
+ idClient, pszName);
+ return VINF_SUCCESS;
+}
+
+struct VBGLR3GUESTPROPENUM
+{
+ uint32_t u32;
+};
+
+VBGLR3DECL(int) VbglR3GuestPropEnum(HGCMCLIENTID idClient,
+ char const * const *ppaszPatterns,
+ uint32_t cPatterns,
+ PVBGLR3GUESTPROPENUM *ppHandle,
+ char const **ppszName,
+ char const **ppszValue,
+ uint64_t *pu64Timestamp,
+ char const **ppszFlags)
+{
+ RT_NOREF2(ppaszPatterns, cPatterns);
+ RTPrintf("Called ENUM_PROPS, client %d...\n", idClient);
+ AssertPtrReturn(ppHandle, VERR_INVALID_POINTER);
+ static VBGLR3GUESTPROPENUM Handle = { 0 };
+ static char szName[] = "Name";
+ static char szValue[] = "Value";
+ static char szFlags[] = "TRANSIENT";
+ *ppHandle = &Handle;
+ if (ppszName)
+ *ppszName = szName;
+ if (ppszValue)
+ *ppszValue = szValue;
+ if (pu64Timestamp)
+ *pu64Timestamp = 12345;
+ if (ppszFlags)
+ *ppszFlags = szFlags;
+ return VINF_SUCCESS;
+}
+
+VBGLR3DECL(int) VbglR3GuestPropEnumNext(PVBGLR3GUESTPROPENUM pHandle,
+ char const **ppszName,
+ char const **ppszValue,
+ uint64_t *pu64Timestamp,
+ char const **ppszFlags)
+{
+ RT_NOREF1(pHandle);
+ RTPrintf("Called enumerate next...\n");
+ AssertReturn(RT_VALID_PTR(ppszName) || RT_VALID_PTR(ppszValue) || RT_VALID_PTR(ppszFlags),
+ VERR_INVALID_POINTER);
+ if (ppszName)
+ *ppszName = NULL;
+ if (ppszValue)
+ *ppszValue = NULL;
+ if (pu64Timestamp)
+ *pu64Timestamp = 0;
+ if (ppszFlags)
+ *ppszFlags = NULL;
+ return VINF_SUCCESS;
+}
+
+VBGLR3DECL(void) VbglR3GuestPropEnumFree(PVBGLR3GUESTPROPENUM pHandle)
+{
+ RT_NOREF1(pHandle);
+ RTPrintf("Called enumerate free...\n");
+}
+
+VBGLR3DECL(int) VbglR3GuestPropWait(HGCMCLIENTID idClient,
+ const char *pszPatterns,
+ void *pvBuf,
+ uint32_t cbBuf,
+ uint64_t u64Timestamp,
+ uint32_t u32Timeout,
+ char ** ppszName,
+ char **ppszValue,
+ uint64_t *pu64Timestamp,
+ char **ppszFlags,
+ uint32_t *pcbBufActual,
+ bool *pfWasDeleted)
+{
+ RT_NOREF2(pvBuf, cbBuf);
+ if (u32Timeout == RT_INDEFINITE_WAIT)
+ RTPrintf("Called GET_NOTIFICATION, client %d, patterns %s, timestamp %llu,\n"
+ " timeout RT_INDEFINITE_WAIT...\n",
+ idClient, pszPatterns, u64Timestamp);
+ else
+ RTPrintf("Called GET_NOTIFICATION, client %d, patterns %s, timestamp %llu,\n"
+ " timeout %u...\n",
+ idClient, pszPatterns, u64Timestamp, u32Timeout);
+ static char szName[] = "Name";
+ static char szValue[] = "Value";
+ static char szFlags[] = "TRANSIENT";
+ if (ppszName)
+ *ppszName = szName;
+ if (ppszValue)
+ *ppszValue = szValue;
+ if (pu64Timestamp)
+ *pu64Timestamp = 12345;
+ if (ppszFlags)
+ *ppszFlags = szFlags;
+ if (pcbBufActual)
+ *pcbBufActual = 256;
+ if (pfWasDeleted)
+ *pfWasDeleted = false;
+ return VINF_SUCCESS;
+}
+
+#endif
+
+VBGLR3DECL(int) VbglR3WriteLog(const char *pch, size_t cch)
+{
+ NOREF(pch); NOREF(cch);
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/.scm-settings b/src/VBox/Additions/common/VBoxGuest/.scm-settings
new file mode 100644
index 00000000..70bef493
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/.scm-settings
@@ -0,0 +1,65 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for VBoxGuest
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# Like the host drivers, this is dual licensed.
+--license-ose-dual
+
+/VBoxGuest-solaris.conf: --treat-as .sh
+
+/netbsd/vboxguest.ioconf: --treat-as .sh --no-convert-tabs --no-convert-eol
+
+# a stub for a file that is normally autogenerated
+/netbsd/locators.h: --external-copyright --no-fix-header-guards
+
+# And the R0 library is MIT to make two-way code exchange with the Linux kernel
+# easier.
+/lib/VBoxGuestR0LibCrOgl.cpp: --license-mit
+/lib/VBoxGuestR0LibGenericRequest.cpp: --license-mit
+/lib/VBoxGuestR0LibHGCM.cpp: --license-mit
+/lib/VBoxGuestR0LibHGCMInternal.cpp: --license-mit
+/lib/VBoxGuestR0LibIdc-os2.cpp: --license-mit
+/lib/VBoxGuestR0LibIdc-solaris.cpp: --license-mit
+/lib/VBoxGuestR0LibIdc-unix.cpp: --license-mit
+/lib/VBoxGuestR0LibIdc-win.cpp: --license-mit
+/lib/VBoxGuestR0LibIdc.cpp: --license-mit
+/lib/VBoxGuestR0LibInit.cpp: --license-mit
+/lib/VBoxGuestR0LibInternal.h: --license-mit
+/lib/VBoxGuestR0LibMouse.cpp: --license-mit
+/lib/VBoxGuestR0LibPhysHeap.cpp: --license-mit
+/lib/VBoxGuestR0LibSharedFolders.c: --license-mit
+/lib/VBoxGuestR0LibVMMDev.cpp: --license-mit
+/lib/VbglR0CanUsePhysPageList.cpp: --license-mit
+
diff --git a/src/VBox/Additions/common/VBoxGuest/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/Makefile.kmk
new file mode 100644
index 00000000..f619d04a
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/Makefile.kmk
@@ -0,0 +1,287 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for the Cross Platform Guest Additions Driver.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Include sub-makefile.
+include $(PATH_SUB_CURRENT)/lib/Makefile.kmk
+
+
+if defined(VBOX_WITH_ADDITION_DRIVERS) && "$(intersects $(KBUILD_TARGET), darwin freebsd haiku netbsd os2 solaris win)" != ""
+ #
+ # VBoxGuest - The Guest Additions Driver.
+ #
+ SYSMODS += VBoxGuest
+ VBoxGuest_TEMPLATE = VBoxGuestR0Drv
+ VBoxGuest_NAME.freebsd = vboxguest
+ VBoxGuest_NAME.haiku = vboxguest
+ VBoxGuest_NAME.netbsd = vboxguest
+ VBoxGuest_NAME.solaris = vboxguest
+ VBoxGuest_INST.darwin = $(INST_ADDITIONS)VBoxGuest.kext/Contents/MacOS/
+ if defined(VBOX_SIGNING_MODE) && defined(VBOX_SIGN_ADDITIONS) # See Additions/WINNT/Makefile.kmk?
+ VBoxGuest_INSTTYPE.win = none
+ VBoxGuest_DEBUG_INSTTYPE.win = both
+ endif
+ VBoxGuest_DEFS.haiku = VBOX_SVN_REV=$(VBOX_SVN_REV) _KERNEL_MODE=1
+ VBoxGuest_DEFS.solaris = VBOX_SVN_REV=$(VBOX_SVN_REV)
+ VBoxGuest_DEFS.win = VBOX_GUESTDRV_WITH_RELEASE_LOGGER
+ VBoxGuest_DEFS.win.x86 = TARGET_NT4 TARGET_NT3 RT_WITHOUT_NOCRT_WRAPPERS
+ VBoxGuest_DEFS.darwin = VBOX_GUESTDRV_WITH_RELEASE_LOGGER
+ ifeq ($(KBUILD_TYPE),release)
+ # Allow stopping/removing the driver without a reboot
+ # in debug mode; this is very useful for testing the shutdown stuff!
+ VBoxGuest_DEFS.win += VBOX_REBOOT_ON_UNINSTALL
+ endif
+ ifdef VBOX_WITH_GUEST_BUGCHECK_DETECTION
+ VBoxGuest_DEFS.win += VBOX_WITH_GUEST_BUGCHECK_DETECTION
+ endif
+ #VBoxGuest_DEFS.win += LOG_ENABLED LOG_TO_BACKDOOR
+ VBoxGuest_DEFS.win += \
+ $(if $(VBOX_WITH_DPC_LATENCY_CHECKER),VBOX_WITH_DPC_LATENCY_CHECKER,)
+ VBoxGuest_DEPS.solaris += $(VBOX_SVN_REV_KMK)
+ VBoxGuest_DEPS.haiku += $(VBOX_SVN_REV_HEADER)
+ VBoxGuest_DEPS.freebsd += $(VBOX_SVN_REV_HEADER)
+ VBoxGuest_DEPS.netbsd += $(VBOX_SVN_REV_HEADER)
+ VBoxGuest_DEPS.darwin += $(VBOX_SVN_REV_HEADER)
+ VBoxGuest_DEFS = VBGL_VBOXGUEST VBOX_WITH_HGCM
+ VBoxGuest_INCS = .
+ VBoxGuest_INCS.freebsd = $(VBoxGuest_0_OUTDIR) $(PATH_STAGE)/gen-sys-hdrs
+ VBoxGuest_INCS.netbsd = $(VBoxGuest_0_OUTDIR) netbsd
+ ifeq ($(KBUILD_HOST),solaris)
+ VBoxGuest_LDFLAGS.solaris += -N misc/ctf
+ else
+ VBoxGuest_SOURCES.solaris = solaris/deps.asm
+ VBoxGuest_solaris/deps.asm_ASFLAGS = -f bin -g null
+ endif
+ ifneq ($(KBUILD_TARGET),os2)
+ ifeq ($(KBUILD_TARGET),win)
+ VBoxGuest_LDFLAGS.x86 = -Entry:DriverEntry@8
+ VBoxGuest_LDFLAGS.amd64 = -Entry:DriverEntry
+ ifeq ($(KBUILD_TARGET_ARCH),x86)
+ VBoxGuest_SDKS = ReorderCompilerIncs $(VBOX_WINDDK_GST_NT4)
+ VBoxGuest_LIBS = \
+ $(VBOX_LIB_VBGL_R0BASE) \
+ $(VBOX_LIB_IPRT_GUEST_R0) \
+ $(PATH_SDK_$(VBOX_WINDDK_GST_NT4)_LIB)/exsup.lib \
+ $(PATH_SDK_$(VBOX_WINDDK_GST_NT4)_LIB)/int64.lib \
+ $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/ntoskrnl.lib \
+ $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/hal.lib
+ ifneq ($(VBOX_VCC_CC_GUARD_CF),)
+ VBoxGuest_LIBS += \
+ $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/BufferOverflowK.lib
+ endif
+ else
+ VBoxGuest_LIBS = \
+ $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/ntoskrnl.lib \
+ $(PATH_SDK_$(VBOX_WINDDK_GST)_LIB)/hal.lib
+ endif
+ VBoxGuest_USES.win += vboximportchecker
+ VBoxGuest_VBOX_IMPORT_CHECKER.win.x86 = nt31/r0
+ VBoxGuest_VBOX_IMPORT_CHECKER.win.amd64 = xp64/r0
+ endif # win
+ ifn1of ($(KBUILD_TARGET), linux freebsd netbsd solaris haiku)
+ VBoxGuest_SOURCES = VBoxGuest-$(KBUILD_TARGET).cpp
+ else
+ VBoxGuest_SOURCES = VBoxGuest-$(KBUILD_TARGET).c
+ VBoxGuest_$(KBUILD_TARGET).c_DEPS = $(VBOX_SVN_REV_HEADER)
+ ifeq ($(KBUILD_TARGET),freebsd)
+ VBoxGuest-$(KBUILD_TARGET).c_CFLAGS = -Wno-sign-compare # /usr/src/sys/sys/vmmeter.h: In function 'vm_paging_needed'
+ endif
+ endif
+ VBoxGuest_SOURCES += \
+ VBoxGuest.cpp
+ VBoxGuest_SOURCES.win += \
+ win/VBoxGuest.rc
+ VBoxGuest_SOURCES.win.x86 += \
+ ../../../Runtime/common/string/strcmp.asm \
+ ../../../Runtime/common/string/strchr.asm \
+ ../../../Runtime/r0drv/nt/nt3fakes-r0drv-nt.cpp \
+ ../../../Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm
+ VBoxGuest_LIBS += \
+ $(VBOX_LIB_VBGL_R0BASE) \
+ $(VBOX_LIB_IPRT_GUEST_R0)
+ VBoxGuest_ORDERDEPS.freebsd = \
+ $(PATH_STAGE)/gen-sys-hdrs/pci_if.h \
+ $(PATH_STAGE)/gen-sys-hdrs/bus_if.h \
+ $(PATH_STAGE)/gen-sys-hdrs/device_if.h
+ ifeq ($(KBUILD_TARGET),haiku)
+ # Haiku drivers cannot export symbols for other drivers, but modules can.
+ # Therefore vboxguest is a module containing the ring-0 guest lib, and vboxdev/vboxsf
+ # use this module to access the guest lib
+ SYSMODS += VBoxDev
+ VBoxDev_TEMPLATE = VBoxGuestR0Drv
+ VBoxDev_NAME = vboxdev
+ VBoxDev_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) _KERNEL_MODE=1 VBGL_VBOXGUEST VBOX_WITH_HGCM IN_RING0
+ VBoxDev_SOURCES = VBoxDev-haiku.c VBoxGuest-haiku-stubs.c
+ endif
+ else # OS/2:
+ # The library order is crucial, so a bit of trickery is necessary.
+ # A library is used to make sure that VBoxGuestA-os2.asm is first in the link. (temporary hack?)
+ VBoxGuest_SOURCES = \
+ VBoxGuestA-os2.asm
+ ifdef VBOX_USE_WATCOM_FOR_OS2
+ VBoxGuest_LIBS = \
+ $(VBoxGuestLibOs2Hack_1_TARGET) \
+ $(VBOX_LIB_VBGL_R0BASE) \
+ $(VBOX_LIB_IPRT_GUEST_R0) \
+ $(PATH_IGCC)/lib/libend.lib
+ else
+ VBoxGuest_SOURCES += \
+ VBoxGuest-os2.def
+ #VBoxGuest_LDFLAGS = -s -t -v
+ VBoxGuest_LIBS = \
+ $(VBoxGuestLibOs2Hack_1_TARGET) \
+ $(VBOX_LIB_VBGL_R0BASE) \
+ $(VBOX_LIB_IPRT_GUEST_R0) \
+ $(VBOX_GCC_LIBGCC) \
+ end
+ endif
+ ## When debugging init with kDrvTest:
+ #VBoxGuest_NAME = VBoxGst
+
+ # See above.
+ LIBRARIES += VBoxGuestLibOs2Hack
+ VBoxGuestLibOs2Hack_TEMPLATE = VBoxGuestR0DrvLib
+ VBoxGuestLibOs2Hack_INSTTYPE = none
+ VBoxGuestLibOs2Hack_DEFS = $(VBoxGuest_DEFS)
+ VBoxGuestLibOs2Hack_INCS = \
+ . \
+ $(PATH_ROOT)/src/VBox/Runtime/include # for the os2ddk
+ VBoxGuestLibOs2Hack_SOURCES = \
+ VBoxGuest-os2.cpp \
+ VBoxGuest.cpp
+ endif # OS/2
+
+ VBoxGuest.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV)
+endif # enabled
+
+
+if defined(VBOX_WITH_ADDITION_DRIVERS) && "$(KBUILD_TARGET)" == "darwin"
+ # Files necessary to make a darwin kernel extension bundle.
+ INSTALLS += VBoxGuest.kext
+ VBoxGuest.kext_INST = $(INST_ADDITIONS)/VBoxGuest.kext/Contents/
+ VBoxGuest.kext_SOURCES = $(VBoxGuest.kext_0_OUTDIR)/Contents/Info.plist
+ VBoxGuest.kext_CLEAN = $(VBoxGuest.kext_0_OUTDIR)/Contents/Info.plist
+ VBoxGuest.kext_BLDDIRS = $(VBoxGuest.kext_0_OUTDIR)/Contents/
+
+ $$(VBoxGuest.kext_0_OUTDIR)/Contents/Info.plist: \
+ $(PATH_SUB_CURRENT)/darwin/Info.plist \
+ $(VBOX_VERSION_MK) | $$(dir $$@)
+ $(call MSG_GENERATE,VBoxGuest,$@,$<)
+ $(QUIET)$(RM) -f $@
+ $(QUIET)$(SED) \
+ -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \
+ -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \
+ -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \
+ -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+g' \
+ -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \
+ -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \
+ -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \
+ --output $@ \
+ $<
+
+ $(evalcall2 VBOX_TEST_SIGN_KEXT,VBoxGuest)
+endif # darwin
+
+
+ifeq ($(KBUILD_TARGET),linux)
+ #
+ # Install the source files and script(s).
+ #
+ include $(PATH_SUB_CURRENT)/linux/files_vboxguest
+ # sources and stuff.
+ INSTALLS += vboxguest-src
+ vboxguest-src_INST = $(INST_ADDITIONS)src/vboxguest/
+ vboxguest-src_MODE = a+r,u+w
+ vboxguest-src_SOURCES = $(subst ",,$(FILES_VBOXGUEST_NOBIN))
+
+ INSTALLS += vboxguest-scripts
+ vboxguest-scripts_INST = $(INST_ADDITIONS)src/
+ vboxguest-scripts_MODE = a+rx,u+w
+ vboxguest-scripts_SOURCES = ../../../HostDrivers/linux/build_in_tmp
+
+ # scripts.
+ INSTALLS += vboxguest-sh
+ vboxguest-sh_INST = $(INST_ADDITIONS)src/vboxguest/
+ vboxguest-sh_MODE = a+rx,u+w
+ vboxguest-sh_SOURCES = $(subst ",,$(FILES_VBOXGUEST_BIN))
+
+ #
+ # Build test for the Guest Additions kernel module (kmk check).
+ #
+ $(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,vboxguest-src,,save_symvers)
+endif # Linux
+
+ifeq ($(KBUILD_TARGET),freebsd)
+ #
+ # Install the source files and script(s).
+ #
+ include $(PATH_SUB_CURRENT)/freebsd/files_vboxguest
+ # sources and stuff.
+ INSTALLS += vboxguest-src
+ vboxguest-src_INST = $(INST_ADDITIONS)src/vboxguest/
+ vboxguest-src_MODE = a+r,u+w
+ vboxguest-src_SOURCES = $(subst ",,$(FILES_VBOXGUEST_NOBIN))
+
+endif # FreeBSD
+
+ifeq ($(KBUILD_TARGET),win)
+ #
+ # VBoxGuestInst - The installer.
+ #
+ PROGRAMS.win.x86 += VBoxGuestInstNT
+ VBoxGuestInstNT_TEMPLATE = VBoxGuestR3Exe
+ ifndef VBOX_WITH_NOCRT_STATIC
+ VBoxGuestInstNT_LDFLAGS := -Include:_vcc100_kernel32_fakes_asm # Temporary kludge to deal with some link order issue.
+ endif
+ VBoxGuestInstNT_INCS = ../../WINNT/include
+ VBoxGuestInstNT_SOURCES = win/VBoxGuestInst.cpp
+ VBoxGuestInstNT_USES = vboximportchecker
+ VBoxGuestInstNT_VBOX_IMPORT_CHECKER.win.x86 = nt31
+endif
+
+
+#
+# Helper script.
+#
+INSTALLS.solaris += VBoxGuestLoad
+VBoxGuestLoad_TEMPLATE = VBoxGuestR0Drv
+VBoxGuestLoad_EXEC_SOURCES = solaris/load.sh
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c b/src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c
new file mode 100644
index 00000000..1fb8d1f0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxDev-haiku.c
@@ -0,0 +1,446 @@
+/* $Id: VBoxDev-haiku.c $ */
+/** @file
+ * VBoxGuest kernel driver, Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <OS.h>
+#include <Drivers.h>
+#include <KernelExport.h>
+#include <PCI.h>
+
+#include "VBoxGuest-haiku.h"
+#include "VBoxGuestInternal.h"
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/process.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+
+#define DRIVER_NAME "vboxdev"
+#define DEVICE_NAME "misc/vboxguest"
+#define MODULE_NAME "generic/vboxguest"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+int32 api_version = B_CUR_DRIVER_API_VERSION;
+
+
+/**
+ * Driver open hook.
+ *
+ * @param name The name of the device as returned by publish_devices.
+ * @param flags Open flags.
+ * @param cookie Where to store the session pointer.
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuOpen(const char *name, uint32 flags, void **cookie)
+{
+ int rc;
+ PVBOXGUESTSESSION pSession;
+
+ LogFlow((DRIVER_NAME ":vgdrvHaikuOpen\n"));
+
+ /*
+ * Create a new session.
+ */
+ rc = VGDrvCommonCreateUserSession(&g_DevExt, VMMDEV_REQUESTOR_USERMODE, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ Log((DRIVER_NAME ":vgdrvHaikuOpen success: g_DevExt=%p pSession=%p rc=%d pid=%d\n",&g_DevExt, pSession, rc,(int)RTProcSelf()));
+ ASMAtomicIncU32(&cUsers);
+ *cookie = pSession;
+ return B_OK;
+ }
+
+ LogRel((DRIVER_NAME ":vgdrvHaikuOpen: failed. rc=%d\n", rc));
+ return RTErrConvertToErrno(rc);
+}
+
+
+/**
+ * Driver close hook.
+ * @param cookie The session.
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuClose(void *cookie)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie;
+ Log(("vgdrvHaikuClose: pSession=%p\n", pSession));
+
+ /** @todo r=ramshankar: should we really be using the session spinlock here? */
+ RTSpinlockAcquire(g_DevExt.SessionSpinlock);
+
+ /** @todo we don't know if it belongs to this session!! */
+ if (sState.selectSync)
+ {
+ //dprintf(DRIVER_NAME "close: unblocking select %p %x\n", sState.selectSync, sState.selectEvent);
+ notify_select_event(sState.selectSync, sState.selectEvent);
+ sState.selectEvent = (uint8_t)0;
+ sState.selectRef = (uint32_t)0;
+ sState.selectSync = (void *)NULL;
+ }
+
+ RTSpinlockRelease(g_DevExt.SessionSpinlock);
+ return B_OK;
+}
+
+
+/**
+ * Driver free hook.
+ * @param cookie The session.
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuFree(void *cookie)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie;
+ Log(("vgdrvHaikuFree: pSession=%p\n", pSession));
+
+ /*
+ * Close the session if it's still hanging on to the device...
+ */
+ if (RT_VALID_PTR(pSession))
+ {
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ ASMAtomicDecU32(&cUsers);
+ }
+ else
+ Log(("vgdrvHaikuFree: si_drv1=%p!\n", pSession));
+ return B_OK;
+}
+
+
+/**
+ * Driver IOCtl entry.
+ * @param cookie The session.
+ * @param op The operation to perform.
+ * @param data The data associated with the operation.
+ * @param len Size of the data in bytes.
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuIOCtl(void *cookie, uint32 op, void *data, size_t len)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie;
+ int rc;
+ Log(("vgdrvHaikuIOCtl: cookie=%p op=0x%08x data=%p len=%lu)\n", cookie, op, data, len));
+
+ /*
+ * Validate the input.
+ */
+ if (RT_UNLIKELY(!RT_VALID_PTR(pSession)))
+ return EINVAL;
+
+ /*
+ * Validate the request wrapper.
+ */
+#if 0
+ if (IOCPARM_LEN(ulCmd) != sizeof(VBGLBIGREQ))
+ {
+ Log((DRIVER_NAME ": vgdrvHaikuIOCtl: bad request %lu size=%lu expected=%d\n", ulCmd, IOCPARM_LEN(ulCmd),
+ sizeof(VBGLBIGREQ)));
+ return ENOTTY;
+ }
+#endif
+
+ if (RT_UNLIKELY(len > _1M * 16))
+ {
+ dprintf(DRIVER_NAME ": vgdrvHaikuIOCtl: bad size %#x; pArg=%p Cmd=%lu.\n", (unsigned)len, data, op);
+ return EINVAL;
+ }
+
+ /*
+ * Read the request.
+ */
+ void *pvBuf = NULL;
+ if (RT_LIKELY(len > 0))
+ {
+ pvBuf = RTMemTmpAlloc(len);
+ if (RT_UNLIKELY(!pvBuf))
+ {
+ LogRel((DRIVER_NAME ":vgdrvHaikuIOCtl: RTMemTmpAlloc failed to alloc %d bytes.\n", len));
+ return ENOMEM;
+ }
+
+ /** @todo r=ramshankar: replace with RTR0MemUserCopyFrom() */
+ rc = user_memcpy(pvBuf, data, len);
+ if (RT_UNLIKELY(rc < 0))
+ {
+ RTMemTmpFree(pvBuf);
+ LogRel((DRIVER_NAME ":vgdrvHaikuIOCtl: user_memcpy failed; pvBuf=%p data=%p op=%d. rc=%d\n", pvBuf, data, op, rc));
+ return EFAULT;
+ }
+ if (RT_UNLIKELY(!RT_VALID_PTR(pvBuf)))
+ {
+ RTMemTmpFree(pvBuf);
+ LogRel((DRIVER_NAME ":vgdrvHaikuIOCtl: pvBuf invalid pointer %p\n", pvBuf));
+ return EINVAL;
+ }
+ }
+ Log(("vgdrvHaikuIOCtl: pSession=%p pid=%d.\n", pSession,(int)RTProcSelf()));
+
+ /*
+ * Process the IOCtl.
+ */
+ size_t cbDataReturned;
+ rc = VGDrvCommonIoCtl(op, &g_DevExt, pSession, pvBuf, len, &cbDataReturned);
+ if (RT_SUCCESS(rc))
+ {
+ rc = 0;
+ if (RT_UNLIKELY(cbDataReturned > len))
+ {
+ Log(("vgdrvHaikuIOCtl: too much output data %d expected %d\n", cbDataReturned, len));
+ cbDataReturned = len;
+ }
+ if (cbDataReturned > 0)
+ {
+ rc = user_memcpy(data, pvBuf, cbDataReturned);
+ if (RT_UNLIKELY(rc < 0))
+ {
+ Log(("vgdrvHaikuIOCtl: user_memcpy failed; pvBuf=%p pArg=%p Cmd=%lu. rc=%d\n", pvBuf, data, op, rc));
+ rc = EFAULT;
+ }
+ }
+ }
+ else
+ {
+ Log(("vgdrvHaikuIOCtl: VGDrvCommonIoCtl failed. rc=%d\n", rc));
+ rc = EFAULT;
+ }
+ RTMemTmpFree(pvBuf);
+ return rc;
+}
+
+
+/**
+ * Driver select hook.
+ *
+ * @param cookie The session.
+ * @param event The event.
+ * @param ref ???
+ * @param sync ???
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuSelect(void *cookie, uint8 event, uint32 ref, selectsync *sync)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie;
+ status_t err = B_OK;
+
+ switch (event)
+ {
+ case B_SELECT_READ:
+ break;
+ default:
+ return EINVAL;
+ }
+
+ RTSpinlockAcquire(g_DevExt.SessionSpinlock);
+
+ uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+ if (pSession->u32MousePosChangedSeq != u32CurSeq)
+ {
+ pSession->u32MousePosChangedSeq = u32CurSeq;
+ notify_select_event(sync, event);
+ }
+ else if (sState.selectSync == NULL)
+ {
+ sState.selectEvent = (uint8_t)event;
+ sState.selectRef = (uint32_t)ref;
+ sState.selectSync = (void *)sync;
+ }
+ else
+ err = B_WOULD_BLOCK;
+
+ RTSpinlockRelease(g_DevExt.SessionSpinlock);
+
+ return err;
+}
+
+
+/**
+ * Driver deselect hook.
+ * @param cookie The session.
+ * @param event The event.
+ * @param sync ???
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuDeselect(void *cookie, uint8 event, selectsync *sync)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie;
+ status_t err = B_OK;
+ //dprintf(DRIVER_NAME "deselect(,%d,%p)\n", event, sync);
+
+ RTSpinlockAcquire(g_DevExt.SessionSpinlock);
+
+ if (sState.selectSync == sync)
+ {
+ //dprintf(DRIVER_NAME "deselect: dropping: %p %x\n", sState.selectSync, sState.selectEvent);
+ sState.selectEvent = (uint8_t)0;
+ sState.selectRef = (uint32_t)0;
+ sState.selectSync = NULL;
+ }
+ else
+ err = B_OK;
+
+ RTSpinlockRelease(g_DevExt.SessionSpinlock);
+ return err;
+}
+
+
+/**
+ * Driver write hook.
+ * @param cookie The session.
+ * @param position The offset.
+ * @param data Pointer to the data.
+ * @param numBytes Where to store the number of bytes written.
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuWrite(void *cookie, off_t position, const void *data, size_t *numBytes)
+{
+ *numBytes = 0;
+ return B_OK;
+}
+
+
+/**
+ * Driver read hook.
+ * @param cookie The session.
+ * @param position The offset.
+ * @param data Pointer to the data.
+ * @param numBytes Where to store the number of bytes read.
+ *
+ * @return Haiku status code.
+ */
+static status_t vgdrvHaikuRead(void *cookie, off_t position, void *data, size_t *numBytes)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)cookie;
+
+ if (*numBytes == 0)
+ return B_OK;
+
+ uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+ if (pSession->u32MousePosChangedSeq != u32CurSeq)
+ {
+ pSession->u32MousePosChangedSeq = u32CurSeq;
+ *numBytes = 1;
+ return B_OK;
+ }
+
+ *numBytes = 0;
+ return B_OK;
+}
+
+
+
+status_t init_hardware()
+{
+ return get_module(MODULE_NAME, (module_info **)&g_VBoxGuest);
+}
+
+status_t init_driver()
+{
+ return B_OK;
+}
+
+device_hooks *find_device(const char *name)
+{
+ static device_hooks s_vgdrvHaikuDeviceHooks =
+ {
+ vgdrvHaikuOpen,
+ vgdrvHaikuClose,
+ vgdrvHaikuFree,
+ vgdrvHaikuIOCtl,
+ vgdrvHaikuRead,
+ vgdrvHaikuWrite,
+ vgdrvHaikuSelect,
+ vgdrvHaikuDeselect,
+ };
+ return &s_vgdrvHaikuDeviceHooks;
+}
+
+const char **publish_devices()
+{
+ static const char *s_papszDevices[] = { DEVICE_NAME, NULL };
+ return s_papszDevices;
+}
+
+void uninit_driver()
+{
+ put_module(MODULE_NAME);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp
new file mode 100644
index 00000000..1ee85362
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-darwin.cpp
@@ -0,0 +1,1393 @@
+/* $Id: VBoxGuest-darwin.cpp $ */
+/** @file
+ * VBoxGuest - Darwin Specifics.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_VGDRV
+/*
+ * Deal with conflicts first.
+ * PVM - BSD mess, that FreeBSD has correct a long time ago.
+ * iprt/types.h before sys/param.h - prevents UINT32_C and friends.
+ */
+#include <iprt/types.h>
+#include <sys/param.h>
+#undef PVM
+
+#include <IOKit/IOLib.h> /* Assert as function */
+
+#include <VBox/version.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/power.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/spinlock.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <mach/kmod.h>
+#include <miscfs/devfs/devfs.h>
+#include <sys/conf.h>
+#include <sys/errno.h>
+#include <sys/ioccom.h>
+#include <sys/malloc.h>
+#include <sys/proc.h>
+#include <sys/kauth.h>
+#define _OS_OSUNSERIALIZE_H /* HACK ALERT! Block importing OSUnserialized.h as it causes compilation trouble with
+ newer clang versions and the 10.15 SDK, and we really don't need it. Sample error:
+ libkern/c++/OSUnserialize.h:72:2: error: use of OSPtr outside of a return type [-Werror,-Wossharedptr-misuse] */
+#include <IOKit/IOService.h>
+#include <IOKit/IOUserClient.h>
+#include <IOKit/pwr_mgt/RootDomain.h>
+#include <IOKit/pci/IOPCIDevice.h>
+#include <IOKit/IOBufferMemoryDescriptor.h>
+#include <IOKit/IOFilterInterruptEventSource.h>
+#include "VBoxGuestInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The system device node name. */
+#define DEVICE_NAME_SYS "vboxguest"
+/** The user device node name. */
+#define DEVICE_NAME_USR "vboxguestu"
+
+
+/** @name For debugging/whatever, now permanent.
+ * @{ */
+#define VBOX_PROC_SELFNAME_LEN 31
+#define VBOX_RETRIEVE_CUR_PROC_NAME(a_Name) char a_Name[VBOX_PROC_SELFNAME_LEN + 1]; \
+ proc_selfname(a_Name, VBOX_PROC_SELFNAME_LEN)
+/** @} */
+
+#ifndef minor
+/* The inlined C++ function version minor() takes the wrong parameter
+ type, uint32_t instead of dev_t. This kludge works around that. */
+# define minor(x) minor((uint32_t)(x))
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+static kern_return_t vgdrvDarwinStart(struct kmod_info *pKModInfo, void *pvData);
+static kern_return_t vgdrvDarwinStop(struct kmod_info *pKModInfo, void *pvData);
+static int vgdrvDarwinCharDevRemove(void);
+
+static int vgdrvDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess);
+static int vgdrvDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess);
+static int vgdrvDarwinIOCtlSlow(PVBOXGUESTSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess);
+static int vgdrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess);
+
+static int vgdrvDarwinErr2DarwinErr(int rc);
+
+static IOReturn vgdrvDarwinSleepHandler(void *pvTarget, void *pvRefCon, UInt32 uMessageType, IOService *pProvider, void *pvMessageArgument, vm_size_t argSize);
+RT_C_DECLS_END
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * The service class for handling the VMMDev PCI device.
+ *
+ * Instantiated when the module is loaded (and on PCI hotplugging?).
+ */
+class org_virtualbox_VBoxGuest : public IOService
+{
+ OSDeclareDefaultStructors(org_virtualbox_VBoxGuest);
+
+private:
+ IOPCIDevice *m_pIOPCIDevice;
+ IOMemoryMap *m_pMap;
+ IOFilterInterruptEventSource *m_pInterruptSrc;
+
+ bool setupVmmDevInterrupts(IOService *pProvider);
+ bool disableVmmDevInterrupts(void);
+ bool isVmmDev(IOPCIDevice *pIOPCIDevice);
+
+protected:
+ /** Non-NULL if interrupts are registered. Probably same as getProvider(). */
+ IOService *m_pInterruptProvider;
+
+public:
+ virtual bool init(OSDictionary *pDictionary = 0);
+ virtual void free(void);
+ virtual IOService *probe(IOService *pProvider, SInt32 *pi32Score);
+ virtual bool start(IOService *pProvider);
+ virtual void stop(IOService *pProvider);
+ virtual bool terminate(IOOptionBits fOptions);
+ static void vgdrvDarwinIrqHandler(OSObject *pTarget, void *pvRefCon, IOService *pNub, int iSrc);
+};
+
+OSDefineMetaClassAndStructors(org_virtualbox_VBoxGuest, IOService);
+
+
+/**
+ * An attempt at getting that clientDied() notification.
+ * I don't think it'll work as I cannot figure out where/what creates the correct
+ * port right.
+ *
+ * Instantiated when userland does IOServiceOpen().
+ */
+class org_virtualbox_VBoxGuestClient : public IOUserClient
+{
+ OSDeclareDefaultStructors(org_virtualbox_VBoxGuestClient);
+
+private:
+ /** Guard against the parent class growing and us using outdated headers. */
+ uint8_t m_abSafetyPadding[256];
+
+ PVBOXGUESTSESSION m_pSession; /**< The session. */
+ task_t m_Task; /**< The client task. */
+ org_virtualbox_VBoxGuest *m_pProvider; /**< The service provider. */
+
+public:
+ virtual bool initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type);
+ virtual bool start(IOService *pProvider);
+ static void sessionClose(RTPROCESS Process);
+ virtual IOReturn clientClose(void);
+ virtual IOReturn clientDied(void);
+ virtual bool terminate(IOOptionBits fOptions = 0);
+ virtual bool finalize(IOOptionBits fOptions);
+ virtual void stop(IOService *pProvider);
+
+ RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT();
+};
+
+OSDefineMetaClassAndStructors(org_virtualbox_VBoxGuestClient, IOUserClient);
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Declare the module stuff.
+ */
+RT_C_DECLS_BEGIN
+extern kern_return_t _start(struct kmod_info *pKModInfo, void *pvData);
+extern kern_return_t _stop(struct kmod_info *pKModInfo, void *pvData);
+
+KMOD_EXPLICIT_DECL(VBoxGuest, VBOX_VERSION_STRING, _start, _stop)
+DECL_HIDDEN_DATA(kmod_start_func_t *) _realmain = vgdrvDarwinStart;
+DECL_HIDDEN_DATA(kmod_stop_func_t *) _antimain = vgdrvDarwinStop;
+DECL_HIDDEN_DATA(int) _kext_apple_cc = __APPLE_CC__;
+RT_C_DECLS_END
+
+
+/**
+ * Device extention & session data association structure.
+ */
+static VBOXGUESTDEVEXT g_DevExt;
+
+/**
+ * The character device switch table for the driver.
+ */
+static struct cdevsw g_DevCW =
+{
+ /*.d_open = */ vgdrvDarwinOpen,
+ /*.d_close = */ vgdrvDarwinClose,
+ /*.d_read = */ eno_rdwrt,
+ /*.d_write = */ eno_rdwrt,
+ /*.d_ioctl = */ vgdrvDarwinIOCtl,
+ /*.d_stop = */ eno_stop,
+ /*.d_reset = */ eno_reset,
+ /*.d_ttys = */ NULL,
+ /*.d_select = */ eno_select,
+ /*.d_mmap = */ eno_mmap,
+ /*.d_strategy = */ eno_strat,
+ /*.d_getc = */ (void *)(uintptr_t)&enodev, //eno_getc,
+ /*.d_putc = */ (void *)(uintptr_t)&enodev, //eno_putc,
+ /*.d_type = */ 0
+};
+
+/** Major device number. */
+static int g_iMajorDeviceNo = -1;
+/** Registered devfs device handle. */
+static void *g_hDevFsDeviceSys = NULL;
+/** Registered devfs device handle for the user device. */
+static void *g_hDevFsDeviceUsr = NULL; /**< @todo 4 later */
+
+/** Spinlock protecting g_apSessionHashTab. */
+static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK;
+/** Hash table */
+static PVBOXGUESTSESSION g_apSessionHashTab[19];
+/** Calculates the index into g_apSessionHashTab.*/
+#define SESSION_HASH(pid) ((pid) % RT_ELEMENTS(g_apSessionHashTab))
+/** The number of open sessions. */
+static int32_t volatile g_cSessions = 0;
+/** Makes sure there is only one org_virtualbox_VBoxGuest instance. */
+static bool volatile g_fInstantiated = 0;
+/** The notifier handle for the sleep callback handler. */
+static IONotifier *g_pSleepNotifier = NULL;
+
+
+/**
+ * Start the kernel module.
+ */
+static kern_return_t vgdrvDarwinStart(struct kmod_info *pKModInfo, void *pvData)
+{
+ RT_NOREF(pKModInfo, pvData);
+#ifdef DEBUG
+ printf("vgdrvDarwinStart\n");
+#endif
+#if 0
+ gIOKitDebug |= 0x001 //kIOLogAttach
+ | 0x002 //kIOLogProbe
+ | 0x004 //kIOLogStart
+ | 0x008 //kIOLogRegister
+ | 0x010 //kIOLogMatch
+ | 0x020 //kIOLogConfig
+ ;
+#endif
+
+ /*
+ * Initialize IPRT.
+ */
+ int rc = RTR0Init(0);
+ if (RT_SUCCESS(rc))
+ {
+ Log(("VBoxGuest: driver loaded\n"));
+ return KMOD_RETURN_SUCCESS;
+ }
+
+ RTLogBackdoorPrintf("VBoxGuest: RTR0Init failed with rc=%Rrc\n", rc);
+ printf("VBoxGuest: RTR0Init failed with rc=%d\n", rc);
+ return KMOD_RETURN_FAILURE;
+}
+
+
+/**
+ * Stop the kernel module.
+ */
+static kern_return_t vgdrvDarwinStop(struct kmod_info *pKModInfo, void *pvData)
+{
+ RT_NOREF(pKModInfo, pvData);
+
+ /** @todo we need to check for VBoxSF clients? */
+
+ RTLogBackdoorPrintf("VBoxGuest: calling RTR0TermForced ...\n");
+ RTR0TermForced();
+
+ RTLogBackdoorPrintf("VBoxGuest: vgdrvDarwinStop returns.\n");
+ printf("VBoxGuest: driver unloaded\n");
+ return KMOD_RETURN_SUCCESS;
+}
+
+
+/**
+ * Register VBoxGuest char device
+ */
+static int vgdrvDarwinCharDevInit(void)
+{
+ int rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestDarwin");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Registering ourselves as a character device.
+ */
+ g_iMajorDeviceNo = cdevsw_add(-1, &g_DevCW);
+ if (g_iMajorDeviceNo >= 0)
+ {
+ /** @todo limit /dev/vboxguest access. */
+ g_hDevFsDeviceSys = devfs_make_node(makedev((uint32_t)g_iMajorDeviceNo, 0), DEVFS_CHAR,
+ UID_ROOT, GID_WHEEL, 0666, DEVICE_NAME_SYS);
+ if (g_hDevFsDeviceSys != NULL)
+ {
+ /*
+ * And a all-user device.
+ */
+ g_hDevFsDeviceUsr = devfs_make_node(makedev((uint32_t)g_iMajorDeviceNo, 1), DEVFS_CHAR,
+ UID_ROOT, GID_WHEEL, 0666, DEVICE_NAME_USR);
+ if (g_hDevFsDeviceUsr != NULL)
+ {
+ /*
+ * Register a sleep/wakeup notification callback.
+ */
+ g_pSleepNotifier = registerPrioritySleepWakeInterest(&vgdrvDarwinSleepHandler, &g_DevExt, NULL);
+ if (g_pSleepNotifier != NULL)
+ return KMOD_RETURN_SUCCESS;
+ }
+ }
+ }
+ vgdrvDarwinCharDevRemove();
+ }
+ return KMOD_RETURN_FAILURE;
+}
+
+
+/**
+ * Unregister VBoxGuest char devices and associated session spinlock.
+ */
+static int vgdrvDarwinCharDevRemove(void)
+{
+ if (g_pSleepNotifier)
+ {
+ g_pSleepNotifier->remove();
+ g_pSleepNotifier = NULL;
+ }
+
+ if (g_hDevFsDeviceSys)
+ {
+ devfs_remove(g_hDevFsDeviceSys);
+ g_hDevFsDeviceSys = NULL;
+ }
+
+ if (g_hDevFsDeviceUsr)
+ {
+ devfs_remove(g_hDevFsDeviceUsr);
+ g_hDevFsDeviceUsr = NULL;
+ }
+
+ if (g_iMajorDeviceNo != -1)
+ {
+ int rc2 = cdevsw_remove(g_iMajorDeviceNo, &g_DevCW);
+ Assert(rc2 == g_iMajorDeviceNo); NOREF(rc2);
+ g_iMajorDeviceNo = -1;
+ }
+
+ if (g_Spinlock != NIL_RTSPINLOCK)
+ {
+ int rc2 = RTSpinlockDestroy(g_Spinlock); AssertRC(rc2);
+ g_Spinlock = NIL_RTSPINLOCK;
+ }
+
+ return KMOD_RETURN_SUCCESS;
+}
+
+
+/**
+ * Device open. Called on open /dev/vboxguest and (later) /dev/vboxguestu.
+ *
+ * @param Dev The device number.
+ * @param fFlags ???.
+ * @param fDevType ???.
+ * @param pProcess The process issuing this request.
+ */
+static int vgdrvDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess)
+{
+ RT_NOREF(fFlags, fDevType);
+
+ /*
+ * Only two minor devices numbers are allowed.
+ */
+ if (minor(Dev) != 0 && minor(Dev) != 1)
+ return EACCES;
+
+ /*
+ * The process issuing the request must be the current process.
+ */
+ RTPROCESS Process = RTProcSelf();
+ if ((int)Process != proc_pid(pProcess))
+ return EIO;
+
+ /*
+ * Find the session created by org_virtualbox_VBoxGuestClient, fail
+ * if no such session, and mark it as opened. We set the uid & gid
+ * here too, since that is more straight forward at this point.
+ */
+ const bool fUnrestricted = minor(Dev) == 0;
+ int rc = VINF_SUCCESS;
+ PVBOXGUESTSESSION pSession = NULL;
+ kauth_cred_t pCred = kauth_cred_proc_ref(pProcess);
+ if (pCred)
+ {
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
+ RTUID Uid = kauth_cred_getruid(pCred);
+ RTGID Gid = kauth_cred_getrgid(pCred);
+#else
+ RTUID Uid = pCred->cr_ruid;
+ RTGID Gid = pCred->cr_rgid;
+#endif
+ unsigned iHash = SESSION_HASH(Process);
+ RTSpinlockAcquire(g_Spinlock);
+
+ pSession = g_apSessionHashTab[iHash];
+ while (pSession && pSession->Process != Process)
+ pSession = pSession->pNextHash;
+ if (pSession)
+ {
+ if (!pSession->fOpened)
+ {
+ pSession->fOpened = true;
+ pSession->fUserSession = !fUnrestricted;
+ pSession->fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
+ if (Uid == 0)
+ pSession->fRequestor |= VMMDEV_REQUESTOR_USR_ROOT;
+ else
+ pSession->fRequestor |= VMMDEV_REQUESTOR_USR_USER;
+ if (Gid == 0)
+ pSession->fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL;
+ if (!fUnrestricted)
+ pSession->fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE;
+ pSession->fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW; /** @todo see if we can figure out console relationship of pProc. */
+ }
+ else
+ rc = VERR_ALREADY_LOADED;
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+
+ RTSpinlockRelease(g_Spinlock);
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
+ kauth_cred_unref(&pCred);
+#else /* 10.4 */
+ /* The 10.4u SDK headers and 10.4.11 kernel source have inconsistent definitions
+ of kauth_cred_unref(), so use the other (now deprecated) API for releasing it. */
+ kauth_cred_rele(pCred);
+#endif /* 10.4 */
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ Log(("vgdrvDarwinOpen: g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, proc_pid(pProcess)));
+ return vgdrvDarwinErr2DarwinErr(rc);
+}
+
+
+/**
+ * Close device.
+ */
+static int vgdrvDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess)
+{
+ RT_NOREF(Dev, fFlags, fDevType, pProcess);
+ Log(("vgdrvDarwinClose: pid=%d\n", (int)RTProcSelf()));
+ Assert(proc_pid(pProcess) == (int)RTProcSelf());
+
+ /*
+ * Hand the session closing to org_virtualbox_VBoxGuestClient.
+ */
+ org_virtualbox_VBoxGuestClient::sessionClose(RTProcSelf());
+ return 0;
+}
+
+
+/**
+ * Device I/O Control entry point.
+ *
+ * @returns Darwin for slow IOCtls and VBox status code for the fast ones.
+ * @param Dev The device number (major+minor).
+ * @param iCmd The IOCtl command.
+ * @param pData Pointer to the request data.
+ * @param fFlags Flag saying we're a character device (like we didn't know already).
+ * @param pProcess The process issuing this request.
+ */
+static int vgdrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess)
+{
+ RT_NOREF(Dev, fFlags);
+ const bool fUnrestricted = minor(Dev) == 0;
+ const RTPROCESS Process = (RTPROCESS)proc_pid(pProcess);
+ const unsigned iHash = SESSION_HASH(Process);
+ PVBOXGUESTSESSION pSession;
+
+ /*
+ * Find the session.
+ */
+ RTSpinlockAcquire(g_Spinlock);
+ pSession = g_apSessionHashTab[iHash];
+ while (pSession && (pSession->Process != Process || pSession->fUserSession == fUnrestricted || !pSession->fOpened))
+ pSession = pSession->pNextHash;
+
+ //if (RT_LIKELY(pSession))
+ // supdrvSessionRetain(pSession);
+
+ RTSpinlockRelease(g_Spinlock);
+ if (!pSession)
+ {
+ Log(("VBoxDrvDarwinIOCtl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d iCmd=%#lx\n",
+ (int)Process, iCmd));
+ return EINVAL;
+ }
+
+ /*
+ * Deal with the high-speed IOCtl.
+ */
+ int rc;
+ if (VBGL_IOCTL_IS_FAST(iCmd))
+ rc = VGDrvCommonIoCtlFast(iCmd, &g_DevExt, pSession);
+ else
+ rc = vgdrvDarwinIOCtlSlow(pSession, iCmd, pData, pProcess);
+
+ //supdrvSessionRelease(pSession);
+ return rc;
+}
+
+
+/**
+ * Worker for VBoxDrvDarwinIOCtl that takes the slow IOCtl functions.
+ *
+ * @returns Darwin errno.
+ *
+ * @param pSession The session.
+ * @param iCmd The IOCtl command.
+ * @param pData Pointer to the request data.
+ * @param pProcess The calling process.
+ */
+static int vgdrvDarwinIOCtlSlow(PVBOXGUESTSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess)
+{
+ RT_NOREF(pProcess);
+ LogFlow(("vgdrvDarwinIOCtlSlow: pSession=%p iCmd=%p pData=%p pProcess=%p\n", pSession, iCmd, pData, pProcess));
+
+
+ /*
+ * Buffered or unbuffered?
+ */
+ PVBGLREQHDR pHdr;
+ user_addr_t pUser = 0;
+ void *pvPageBuf = NULL;
+ uint32_t cbReq = IOCPARM_LEN(iCmd);
+ if ((IOC_DIRMASK & iCmd) == IOC_INOUT)
+ {
+ pHdr = (PVBGLREQHDR)pData;
+ if (RT_UNLIKELY(cbReq < sizeof(*pHdr)))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: cbReq=%#x < %#x; iCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), iCmd));
+ return EINVAL;
+ }
+ if (RT_UNLIKELY(pHdr->uVersion != VBGLREQHDR_VERSION))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: bad uVersion=%#x; iCmd=%#lx\n", pHdr->uVersion, iCmd));
+ return EINVAL;
+ }
+ if (RT_UNLIKELY( RT_MAX(pHdr->cbIn, pHdr->cbOut) != cbReq
+ || pHdr->cbIn < sizeof(*pHdr)
+ || (pHdr->cbOut < sizeof(*pHdr) && pHdr->cbOut != 0)))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: max(%#x,%#x) != %#x; iCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, iCmd));
+ return EINVAL;
+ }
+ }
+ else if ((IOC_DIRMASK & iCmd) == IOC_VOID && !cbReq)
+ {
+ /*
+ * Get the header and figure out how much we're gonna have to read.
+ */
+ VBGLREQHDR Hdr;
+ pUser = (user_addr_t)*(void **)pData;
+ int rc = copyin(pUser, &Hdr, sizeof(Hdr));
+ if (RT_UNLIKELY(rc))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: copyin(%llx,Hdr,) -> %#x; iCmd=%#lx\n", (unsigned long long)pUser, rc, iCmd));
+ return rc;
+ }
+ if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: bad uVersion=%#x; iCmd=%#lx\n", Hdr.uVersion, iCmd));
+ return EINVAL;
+ }
+ cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut);
+ if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr)
+ || (Hdr.cbOut < sizeof(Hdr) && Hdr.cbOut != 0)
+ || cbReq > _1M*16))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: max(%#x,%#x); iCmd=%#lx\n", Hdr.cbIn, Hdr.cbOut, iCmd));
+ return EINVAL;
+ }
+
+ /*
+ * Allocate buffer and copy in the data.
+ */
+ pHdr = (PVBGLREQHDR)RTMemTmpAlloc(cbReq);
+ if (!pHdr)
+ pvPageBuf = pHdr = (PVBGLREQHDR)IOMallocAligned(RT_ALIGN_Z(cbReq, PAGE_SIZE), 8);
+ if (RT_UNLIKELY(!pHdr))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: failed to allocate buffer of %d bytes; iCmd=%#lx\n", cbReq, iCmd));
+ return ENOMEM;
+ }
+ rc = copyin(pUser, pHdr, Hdr.cbIn);
+ if (RT_UNLIKELY(rc))
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: copyin(%llx,%p,%#x) -> %#x; iCmd=%#lx\n",
+ (unsigned long long)pUser, pHdr, Hdr.cbIn, rc, iCmd));
+ if (pvPageBuf)
+ IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE));
+ else
+ RTMemTmpFree(pHdr);
+ return rc;
+ }
+ if (Hdr.cbIn < cbReq)
+ RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbReq - Hdr.cbIn);
+ }
+ else
+ {
+ Log(("vgdrvDarwinIOCtlSlow: huh? cbReq=%#x iCmd=%#lx\n", cbReq, iCmd));
+ return EINVAL;
+ }
+
+ /*
+ * Process the IOCtl.
+ */
+ int rc = VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, cbReq);
+ if (RT_LIKELY(!rc))
+ {
+ /*
+ * If not buffered, copy back the buffer before returning.
+ */
+ if (pUser)
+ {
+ uint32_t cbOut = pHdr->cbOut;
+ if (cbOut > cbReq)
+ {
+ LogRel(("vgdrvDarwinIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, iCmd));
+ cbOut = cbReq;
+ }
+ rc = copyout(pHdr, pUser, cbOut);
+ if (RT_UNLIKELY(rc))
+ LogRel(("vgdrvDarwinIOCtlSlow: copyout(%p,%llx,%#x) -> %d; uCmd=%#lx!\n",
+ pHdr, (unsigned long long)pUser, cbOut, rc, iCmd));
+
+ /* cleanup */
+ if (pvPageBuf)
+ IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE));
+ else
+ RTMemTmpFree(pHdr);
+ }
+ }
+ else
+ {
+ /*
+ * The request failed, just clean up.
+ */
+ if (pUser)
+ {
+ if (pvPageBuf)
+ IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE));
+ else
+ RTMemTmpFree(pHdr);
+ }
+
+ Log(("vgdrvDarwinIOCtlSlow: pid=%d iCmd=%lx pData=%p failed, rc=%d\n", proc_pid(pProcess), iCmd, (void *)pData, rc));
+ rc = EINVAL;
+ }
+
+ Log2(("vgdrvDarwinIOCtlSlow: returns %d\n", rc));
+ return rc;
+}
+
+
+/**
+ * @note This code is duplicated on other platforms with variations, so please
+ * keep them all up to date when making changes!
+ */
+int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq)
+{
+ /*
+ * Simple request validation (common code does the rest).
+ */
+ int rc;
+ if ( RT_VALID_PTR(pReqHdr)
+ && cbReq >= sizeof(*pReqHdr))
+ {
+ /*
+ * All requests except the connect one requires a valid session.
+ */
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession;
+ if (pSession)
+ {
+ if ( RT_VALID_PTR(pSession)
+ && pSession->pDevExt == &g_DevExt)
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else if (uReq == VBGL_IOCTL_IDC_CONNECT)
+ {
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ if (RT_FAILURE(rc))
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ }
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+ return rc;
+}
+
+
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+ NOREF(pDevExt);
+}
+
+
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+/**
+ * Callback for blah blah blah.
+ *
+ * @todo move to IPRT.
+ */
+static IOReturn vgdrvDarwinSleepHandler(void *pvTarget, void *pvRefCon, UInt32 uMessageType,
+ IOService *pProvider, void *pvMsgArg, vm_size_t cbMsgArg)
+{
+ RT_NOREF(pvTarget, pProvider, pvMsgArg, cbMsgArg);
+ LogFlow(("VBoxGuest: Got sleep/wake notice. Message type was %x\n", uMessageType));
+
+ if (uMessageType == kIOMessageSystemWillSleep)
+ RTPowerSignalEvent(RTPOWEREVENT_SUSPEND);
+ else if (uMessageType == kIOMessageSystemHasPoweredOn)
+ RTPowerSignalEvent(RTPOWEREVENT_RESUME);
+
+ acknowledgeSleepWakeNotification(pvRefCon);
+
+ return 0;
+}
+
+
+/**
+ * Converts an IPRT error code to a darwin error code.
+ *
+ * @returns corresponding darwin error code.
+ * @param rc IPRT status code.
+ */
+static int vgdrvDarwinErr2DarwinErr(int rc)
+{
+ switch (rc)
+ {
+ case VINF_SUCCESS: return 0;
+ case VERR_GENERAL_FAILURE: return EACCES;
+ case VERR_INVALID_PARAMETER: return EINVAL;
+ case VERR_INVALID_MAGIC: return EILSEQ;
+ case VERR_INVALID_HANDLE: return ENXIO;
+ case VERR_INVALID_POINTER: return EFAULT;
+ case VERR_LOCK_FAILED: return ENOLCK;
+ case VERR_ALREADY_LOADED: return EEXIST;
+ case VERR_PERMISSION_DENIED: return EPERM;
+ case VERR_VERSION_MISMATCH: return ENOSYS;
+ }
+
+ return EPERM;
+}
+
+
+/*
+ *
+ * org_virtualbox_VBoxGuest
+ *
+ * - IOService diff resync -
+ * - IOService diff resync -
+ * - IOService diff resync -
+ *
+ */
+
+
+/**
+ * Initialize the object.
+ */
+bool org_virtualbox_VBoxGuest::init(OSDictionary *pDictionary)
+{
+ LogFlow(("IOService::init([%p], %p)\n", this, pDictionary));
+ if (IOService::init(pDictionary))
+ {
+ /* init members. */
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Free the object.
+ */
+void org_virtualbox_VBoxGuest::free(void)
+{
+ RTLogBackdoorPrintf("IOService::free([%p])\n", this); /* might go sideways if we use LogFlow() here. weird. */
+ IOService::free();
+}
+
+
+/**
+ * Check if it's ok to start this service.
+ * It's always ok by us, so it's up to IOService to decide really.
+ */
+IOService *org_virtualbox_VBoxGuest::probe(IOService *pProvider, SInt32 *pi32Score)
+{
+ LogFlow(("IOService::probe([%p])\n", this));
+ IOService *pRet = IOService::probe(pProvider, pi32Score);
+ LogFlow(("IOService::probe([%p]) returns %p *pi32Score=%d\n", this, pRet, pi32Score ? *pi32Score : -1));
+ return pRet;
+}
+
+
+/**
+ * Start this service.
+ */
+bool org_virtualbox_VBoxGuest::start(IOService *pProvider)
+{
+ LogFlow(("IOService::start([%p])\n", this));
+
+ /*
+ * Low level initialization / device initialization should be performed only once.
+ */
+ if (ASMAtomicCmpXchgBool(&g_fInstantiated, true, false))
+ {
+ /*
+ * Make sure it's a PCI device.
+ */
+ m_pIOPCIDevice = OSDynamicCast(IOPCIDevice, pProvider);
+ if (m_pIOPCIDevice)
+ {
+ /*
+ * Call parent.
+ */
+ if (IOService::start(pProvider))
+ {
+ /*
+ * Is it the VMM device?
+ */
+ if (isVmmDev(m_pIOPCIDevice))
+ {
+ /*
+ * Enable I/O port and memory regions on the device.
+ */
+ m_pIOPCIDevice->setMemoryEnable(true);
+ m_pIOPCIDevice->setIOEnable(true);
+
+ /*
+ * Region #0: I/O ports. Mandatory.
+ */
+ IOMemoryDescriptor *pMem = m_pIOPCIDevice->getDeviceMemoryWithIndex(0);
+ if (pMem)
+ {
+ IOPhysicalAddress IOPortBasePhys = pMem->getPhysicalAddress();
+ if ((IOPortBasePhys >> 16) == 0)
+ {
+ RTIOPORT IOPortBase = (RTIOPORT)IOPortBasePhys;
+ void *pvMMIOBase = NULL;
+ uint32_t cbMMIO = 0;
+
+ /*
+ * Region #1: Shared Memory. Technically optional.
+ */
+ m_pMap = m_pIOPCIDevice->mapDeviceMemoryWithIndex(1);
+ if (m_pMap)
+ {
+ pvMMIOBase = (void *)m_pMap->getVirtualAddress();
+ cbMMIO = (uint32_t)m_pMap->getLength();
+ }
+
+ /*
+ * Initialize the device extension.
+ */
+ int rc = VGDrvCommonInitDevExt(&g_DevExt, IOPortBase, pvMMIOBase, cbMMIO,
+ ARCH_BITS == 64 ? VBOXOSTYPE_MacOS_x64 : VBOXOSTYPE_MacOS, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Register the device nodes and enable interrupts.
+ */
+ rc = vgdrvDarwinCharDevInit();
+ if (rc == KMOD_RETURN_SUCCESS)
+ {
+ if (setupVmmDevInterrupts(pProvider))
+ {
+ /*
+ * Read host configuration.
+ */
+ VGDrvCommonProcessOptionsFromHost(&g_DevExt);
+
+ /*
+ * Just register the service and we're done!
+ */
+ registerService();
+
+ LogRel(("VBoxGuest: IOService started\n"));
+ return true;
+ }
+
+ LogRel(("VBoxGuest: Failed to set up interrupts\n"));
+ vgdrvDarwinCharDevRemove();
+ }
+ else
+ LogRel(("VBoxGuest: Failed to initialize character devices (rc=%#x).\n", rc));
+
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ }
+ else
+ LogRel(("VBoxGuest: Failed to initialize common code (rc=%Rrc).\n", rc));
+
+ if (m_pMap)
+ {
+ m_pMap->release();
+ m_pMap = NULL;
+ }
+ }
+ else
+ LogRel(("VBoxGuest: Bad I/O port address: %#RX64\n", (uint64_t)IOPortBasePhys));
+ }
+ else
+ LogRel(("VBoxGuest: The device missing is the I/O port range (#0).\n"));
+ }
+ else
+ LogRel(("VBoxGuest: Not the VMMDev (%#x:%#x).\n",
+ m_pIOPCIDevice->configRead16(kIOPCIConfigVendorID), m_pIOPCIDevice->configRead16(kIOPCIConfigDeviceID)));
+
+ IOService::stop(pProvider);
+ }
+ }
+ else
+ LogRel(("VBoxGuest: Provider is not an instance of IOPCIDevice.\n"));
+
+ ASMAtomicXchgBool(&g_fInstantiated, false);
+ }
+ return false;
+}
+
+
+/**
+ * Stop this service.
+ */
+void org_virtualbox_VBoxGuest::stop(IOService *pProvider)
+{
+#ifdef LOG_ENABLED
+ RTLogBackdoorPrintf("org_virtualbox_VBoxGuest::stop([%p], %p)\n", this, pProvider); /* Being cautious here, no Log(). */
+#endif
+ AssertReturnVoid(ASMAtomicReadBool(&g_fInstantiated));
+
+ /* Low level termination should be performed only once */
+ if (!disableVmmDevInterrupts())
+ printf("VBoxGuest: unable to unregister interrupt handler\n");
+
+ vgdrvDarwinCharDevRemove();
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+
+ if (m_pMap)
+ {
+ m_pMap->release();
+ m_pMap = NULL;
+ }
+
+ IOService::stop(pProvider);
+
+ ASMAtomicWriteBool(&g_fInstantiated, false);
+
+ printf("VBoxGuest: IOService stopped\n");
+ RTLogBackdoorPrintf("org_virtualbox_VBoxGuest::stop: returning\n"); /* Being cautious here, no Log(). */
+}
+
+
+/**
+ * Termination request.
+ *
+ * @return true if we're ok with shutting down now, false if we're not.
+ * @param fOptions Flags.
+ */
+bool org_virtualbox_VBoxGuest::terminate(IOOptionBits fOptions)
+{
+#ifdef LOG_ENABLED
+ RTLogBackdoorPrintf("org_virtualbox_VBoxGuest::terminate: reference_count=%d g_cSessions=%d (fOptions=%#x)\n",
+ KMOD_INFO_NAME.reference_count, ASMAtomicUoReadS32(&g_cSessions), fOptions); /* Being cautious here, no Log(). */
+#endif
+
+ bool fRc;
+ if ( KMOD_INFO_NAME.reference_count != 0
+ || ASMAtomicUoReadS32(&g_cSessions))
+ fRc = false;
+ else
+ fRc = IOService::terminate(fOptions);
+
+#ifdef LOG_ENABLED
+ RTLogBackdoorPrintf("org_virtualbox_SupDrv::terminate: returns %d\n", fRc); /* Being cautious here, no Log(). */
+#endif
+ return fRc;
+}
+
+
+/**
+ * Implementes a IOInterruptHandler, called by provider when an interrupt occurs.
+ */
+/*static*/ void org_virtualbox_VBoxGuest::vgdrvDarwinIrqHandler(OSObject *pTarget, void *pvRefCon, IOService *pNub, int iSrc)
+{
+#ifdef LOG_ENABLED
+ RTLogBackdoorPrintf("vgdrvDarwinIrqHandler: %p %p %p %d\n", pTarget, pvRefCon, pNub, iSrc);
+#endif
+ RT_NOREF(pTarget, pvRefCon, pNub, iSrc);
+
+ VGDrvCommonISR(&g_DevExt);
+ /* There is in fact no way of indicating that this is our interrupt, other
+ than making the device lower it. So, the return code is ignored. */
+}
+
+
+/**
+ * Sets up and enables interrupts on the device.
+ *
+ * Interrupts are handled directly, no messing around with workloops. The
+ * rational here is is that the main job of our interrupt handler is waking up
+ * other threads currently sitting in HGCM calls, i.e. little more effort than
+ * waking up the workloop thread.
+ *
+ * @returns success indicator. Failures are fully logged.
+ */
+bool org_virtualbox_VBoxGuest::setupVmmDevInterrupts(IOService *pProvider)
+{
+ AssertReturn(pProvider, false);
+
+ if (m_pInterruptProvider != pProvider)
+ {
+ pProvider->retain();
+ if (m_pInterruptProvider)
+ m_pInterruptProvider->release();
+ m_pInterruptProvider = pProvider;
+ }
+
+ IOReturn rc = pProvider->registerInterrupt(0 /*intIndex*/, this, vgdrvDarwinIrqHandler, this);
+ if (rc == kIOReturnSuccess)
+ {
+ rc = pProvider->enableInterrupt(0 /*intIndex*/);
+ if (rc == kIOReturnSuccess)
+ return true;
+
+ LogRel(("VBoxGuest: Failed to enable interrupt: %#x\n", rc));
+ m_pInterruptProvider->unregisterInterrupt(0 /*intIndex*/);
+ }
+ else
+ LogRel(("VBoxGuest: Failed to register interrupt: %#x\n", rc));
+ return false;
+}
+
+
+/**
+ * Counterpart to setupVmmDevInterrupts().
+ */
+bool org_virtualbox_VBoxGuest::disableVmmDevInterrupts(void)
+{
+ if (m_pInterruptProvider)
+ {
+ IOReturn rc = m_pInterruptProvider->disableInterrupt(0 /*intIndex*/);
+ AssertMsg(rc == kIOReturnSuccess, ("%#x\n", rc));
+ rc = m_pInterruptProvider->unregisterInterrupt(0 /*intIndex*/);
+ AssertMsg(rc == kIOReturnSuccess, ("%#x\n", rc));
+ RT_NOREF_PV(rc);
+
+ m_pInterruptProvider->release();
+ m_pInterruptProvider = NULL;
+ }
+
+ return true;
+}
+
+
+/**
+ * Checks if it's the VMM device.
+ *
+ * @returns true if it is, false if it isn't.
+ * @param pIOPCIDevice The PCI device we think might be the VMM device.
+ */
+bool org_virtualbox_VBoxGuest::isVmmDev(IOPCIDevice *pIOPCIDevice)
+{
+ if (pIOPCIDevice)
+ {
+ uint16_t idVendor = m_pIOPCIDevice->configRead16(kIOPCIConfigVendorID);
+ if (idVendor == VMMDEV_VENDORID)
+ {
+ uint16_t idDevice = m_pIOPCIDevice->configRead16(kIOPCIConfigDeviceID);
+ if (idDevice == VMMDEV_DEVICEID)
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+/*
+ *
+ * org_virtualbox_VBoxGuestClient
+ *
+ */
+
+
+/**
+ * Initializer called when the client opens the service.
+ */
+bool org_virtualbox_VBoxGuestClient::initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type)
+{
+ LogFlow(("org_virtualbox_VBoxGuestClient::initWithTask([%p], %#x, %p, %#x) (cur pid=%d proc=%p)\n",
+ this, OwningTask, pvSecurityId, u32Type, RTProcSelf(), RTR0ProcHandleSelf()));
+ AssertMsg((RTR0PROCESS)OwningTask == RTR0ProcHandleSelf(), ("%p %p\n", OwningTask, RTR0ProcHandleSelf()));
+
+ if (!OwningTask)
+ return false;
+
+ if (u32Type != VBOXGUEST_DARWIN_IOSERVICE_COOKIE)
+ {
+ VBOX_RETRIEVE_CUR_PROC_NAME(szProcName);
+ LogRelMax(10, ("org_virtualbox_VBoxGuestClient::initWithTask: Bad cookie %#x (%s)\n", u32Type, szProcName));
+ return false;
+ }
+
+ if (IOUserClient::initWithTask(OwningTask, pvSecurityId , u32Type))
+ {
+ /*
+ * In theory we have to call task_reference() to make sure that the task is
+ * valid during the lifetime of this object. The pointer is only used to check
+ * for the context this object is called in though and never dereferenced
+ * or passed to anything which might, so we just skip this step.
+ */
+ m_Task = OwningTask;
+ m_pSession = NULL;
+ m_pProvider = NULL;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Start the client service.
+ */
+bool org_virtualbox_VBoxGuestClient::start(IOService *pProvider)
+{
+ LogFlow(("org_virtualbox_VBoxGuestClient::start([%p], %p) (cur pid=%d proc=%p)\n",
+ this, pProvider, RTProcSelf(), RTR0ProcHandleSelf() ));
+ AssertMsgReturn((RTR0PROCESS)m_Task == RTR0ProcHandleSelf(),
+ ("%p %p\n", m_Task, RTR0ProcHandleSelf()),
+ false);
+
+ if (IOUserClient::start(pProvider))
+ {
+ m_pProvider = OSDynamicCast(org_virtualbox_VBoxGuest, pProvider);
+ if (m_pProvider)
+ {
+ Assert(!m_pSession);
+
+ /*
+ * Create a new session.
+ * Note! We complete the requestor stuff in the open method.
+ */
+ int rc = VGDrvCommonCreateUserSession(&g_DevExt, VMMDEV_REQUESTOR_USERMODE, &m_pSession);
+ if (RT_SUCCESS(rc))
+ {
+ m_pSession->fOpened = false;
+ /* The Uid, Gid and fUnrestricted fields are set on open. */
+
+ /*
+ * Insert it into the hash table, checking that there isn't
+ * already one for this process first. (One session per proc!)
+ */
+ unsigned iHash = SESSION_HASH(m_pSession->Process);
+ RTSpinlockAcquire(g_Spinlock);
+
+ PVBOXGUESTSESSION pCur = g_apSessionHashTab[iHash];
+ while (pCur && pCur->Process != m_pSession->Process)
+ pCur = pCur->pNextHash;
+ if (!pCur)
+ {
+ m_pSession->pNextHash = g_apSessionHashTab[iHash];
+ g_apSessionHashTab[iHash] = m_pSession;
+ m_pSession->pvVBoxGuestClient = this;
+ ASMAtomicIncS32(&g_cSessions);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_ALREADY_LOADED;
+
+ RTSpinlockRelease(g_Spinlock);
+ if (RT_SUCCESS(rc))
+ {
+ Log(("org_virtualbox_VBoxGuestClient::start: created session %p for pid %d\n", m_pSession, (int)RTProcSelf()));
+ return true;
+ }
+
+ LogFlow(("org_virtualbox_VBoxGuestClient::start: already got a session for this process (%p)\n", pCur));
+ VGDrvCommonCloseSession(&g_DevExt, m_pSession); //supdrvSessionRelease(m_pSession);
+ }
+
+ m_pSession = NULL;
+ LogFlow(("org_virtualbox_VBoxGuestClient::start: rc=%Rrc from supdrvCreateSession\n", rc));
+ }
+ else
+ LogFlow(("org_virtualbox_VBoxGuestClient::start: %p isn't org_virtualbox_VBoxGuest\n", pProvider));
+ }
+ return false;
+}
+
+
+/**
+ * Common worker for clientClose and VBoxDrvDarwinClose.
+ */
+/* static */ void org_virtualbox_VBoxGuestClient::sessionClose(RTPROCESS Process)
+{
+ /*
+ * Find the session and remove it from the hash table.
+ *
+ * Note! Only one session per process. (Both start() and
+ * vgdrvDarwinOpen makes sure this is so.)
+ */
+ const unsigned iHash = SESSION_HASH(Process);
+ RTSpinlockAcquire(g_Spinlock);
+ PVBOXGUESTSESSION pSession = g_apSessionHashTab[iHash];
+ if (pSession)
+ {
+ if (pSession->Process == Process)
+ {
+ g_apSessionHashTab[iHash] = pSession->pNextHash;
+ pSession->pNextHash = NULL;
+ ASMAtomicDecS32(&g_cSessions);
+ }
+ else
+ {
+ PVBOXGUESTSESSION pPrev = pSession;
+ pSession = pSession->pNextHash;
+ while (pSession)
+ {
+ if (pSession->Process == Process)
+ {
+ pPrev->pNextHash = pSession->pNextHash;
+ pSession->pNextHash = NULL;
+ ASMAtomicDecS32(&g_cSessions);
+ break;
+ }
+
+ /* next */
+ pPrev = pSession;
+ pSession = pSession->pNextHash;
+ }
+ }
+ }
+ RTSpinlockRelease(g_Spinlock);
+ if (!pSession)
+ {
+ Log(("VBoxGuestClient::sessionClose: pSession == NULL, pid=%d; freed already?\n", (int)Process));
+ return;
+ }
+
+ /*
+ * Remove it from the client object.
+ */
+ org_virtualbox_VBoxGuestClient *pThis = (org_virtualbox_VBoxGuestClient *)pSession->pvVBoxGuestClient;
+ pSession->pvVBoxGuestClient = NULL;
+ if (pThis)
+ {
+ Assert(pThis->m_pSession == pSession);
+ pThis->m_pSession = NULL;
+ }
+
+ /*
+ * Close the session.
+ */
+ VGDrvCommonCloseSession(&g_DevExt, pSession); // supdrvSessionRelease(m_pSession);
+}
+
+
+/**
+ * Client exits normally.
+ */
+IOReturn org_virtualbox_VBoxGuestClient::clientClose(void)
+{
+ LogFlow(("org_virtualbox_VBoxGuestClient::clientClose([%p]) (cur pid=%d proc=%p)\n", this, RTProcSelf(), RTR0ProcHandleSelf()));
+ AssertMsg((RTR0PROCESS)m_Task == RTR0ProcHandleSelf(), ("%p %p\n", m_Task, RTR0ProcHandleSelf()));
+
+ /*
+ * Clean up the session if it's still around.
+ *
+ * We cannot rely 100% on close, and in the case of a dead client
+ * we'll end up hanging inside vm_map_remove() if we postpone it.
+ */
+ if (m_pSession)
+ {
+ sessionClose(RTProcSelf());
+ Assert(!m_pSession);
+ }
+
+ m_pProvider = NULL;
+ terminate();
+
+ return kIOReturnSuccess;
+}
+
+
+/**
+ * The client exits abnormally / forgets to do cleanups. (logging)
+ */
+IOReturn org_virtualbox_VBoxGuestClient::clientDied(void)
+{
+ LogFlow(("IOService::clientDied([%p]) m_Task=%p R0Process=%p Process=%d\n", this, m_Task, RTR0ProcHandleSelf(), RTProcSelf()));
+
+ /* IOUserClient::clientDied() calls clientClose, so we'll just do the work there. */
+ return IOUserClient::clientDied();
+}
+
+
+/**
+ * Terminate the service (initiate the destruction). (logging)
+ */
+bool org_virtualbox_VBoxGuestClient::terminate(IOOptionBits fOptions)
+{
+ LogFlow(("IOService::terminate([%p], %#x)\n", this, fOptions));
+ return IOUserClient::terminate(fOptions);
+}
+
+
+/**
+ * The final stage of the client service destruction. (logging)
+ */
+bool org_virtualbox_VBoxGuestClient::finalize(IOOptionBits fOptions)
+{
+ LogFlow(("IOService::finalize([%p], %#x)\n", this, fOptions));
+ return IOUserClient::finalize(fOptions);
+}
+
+
+/**
+ * Stop the client service. (logging)
+ */
+void org_virtualbox_VBoxGuestClient::stop(IOService *pProvider)
+{
+ LogFlow(("IOService::stop([%p])\n", this));
+ IOUserClient::stop(pProvider);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c
new file mode 100644
index 00000000..99228388
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c
@@ -0,0 +1,799 @@
+/* $Id: VBoxGuest-freebsd.c $ */
+/** @file
+ * VirtualBox Guest Additions Driver for FreeBSD.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/** @todo r=bird: This must merge with SUPDrv-freebsd.c before long. The two
+ * source files should only differ on prefixes and the extra bits wrt to the
+ * pci device. I.e. it should be diffable so that fixes to one can easily be
+ * applied to the other. */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/param.h>
+#undef PVM
+#include <sys/types.h>
+#include <sys/module.h>
+#include <sys/systm.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/fcntl.h>
+#include <sys/conf.h>
+#include <sys/uio.h>
+#include <sys/bus.h>
+#include <sys/poll.h>
+#include <sys/selinfo.h>
+#include <sys/queue.h>
+#include <sys/lock.h>
+#include <sys/lockmgr.h>
+#include <sys/malloc.h>
+#include <sys/file.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcireg.h>
+
+#include "VBoxGuestInternal.h"
+#include <VBox/version.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The module name. */
+#define DEVICE_NAME "vboxguest"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct VBoxGuestDeviceState
+{
+ /** Resource ID of the I/O port */
+ int iIOPortResId;
+ /** Pointer to the I/O port resource. */
+ struct resource *pIOPortRes;
+ /** Start address of the IO Port. */
+ uint16_t uIOPortBase;
+ /** Resource ID of the MMIO area */
+ int iVMMDevMemResId;
+ /** Pointer to the MMIO resource. */
+ struct resource *pVMMDevMemRes;
+ /** Handle of the MMIO resource. */
+ bus_space_handle_t VMMDevMemHandle;
+ /** Size of the memory area. */
+ bus_size_t VMMDevMemSize;
+ /** Mapping of the register space */
+ void *pMMIOBase;
+ /** IRQ number */
+ int iIrqResId;
+ /** IRQ resource handle. */
+ struct resource *pIrqRes;
+ /** Pointer to the IRQ handler. */
+ void *pfnIrqHandler;
+ /** VMMDev version */
+ uint32_t u32Version;
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+/*
+ * Character device file handlers.
+ */
+static d_fdopen_t vgdrvFreeBSDOpen;
+static d_close_t vgdrvFreeBSDClose;
+static d_ioctl_t vgdrvFreeBSDIOCtl;
+static int vgdrvFreeBSDIOCtlSlow(PVBOXGUESTSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd);
+static d_write_t vgdrvFreeBSDWrite;
+static d_read_t vgdrvFreeBSDRead;
+static d_poll_t vgdrvFreeBSDPoll;
+
+/*
+ * IRQ related functions.
+ */
+static void vgdrvFreeBSDRemoveIRQ(device_t pDevice, void *pvState);
+static int vgdrvFreeBSDAddIRQ(device_t pDevice, void *pvState);
+static int vgdrvFreeBSDISR(void *pvState);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static MALLOC_DEFINE(M_VBOXGUEST, "vboxguest", "VirtualBox Guest Device Driver");
+
+#ifndef D_NEEDMINOR
+# define D_NEEDMINOR 0
+#endif
+
+/*
+ * The /dev/vboxguest character device entry points.
+ */
+static struct cdevsw g_vgdrvFreeBSDChrDevSW =
+{
+ .d_version = D_VERSION,
+ .d_flags = D_TRACKCLOSE | D_NEEDMINOR,
+ .d_fdopen = vgdrvFreeBSDOpen,
+ .d_close = vgdrvFreeBSDClose,
+ .d_ioctl = vgdrvFreeBSDIOCtl,
+ .d_read = vgdrvFreeBSDRead,
+ .d_write = vgdrvFreeBSDWrite,
+ .d_poll = vgdrvFreeBSDPoll,
+ .d_name = "vboxguest"
+};
+
+/** Device extention & session data association structure. */
+static VBOXGUESTDEVEXT g_DevExt;
+
+/** List of cloned device. Managed by the kernel. */
+static struct clonedevs *g_pvgdrvFreeBSDClones;
+/** The dev_clone event handler tag. */
+static eventhandler_tag g_vgdrvFreeBSDEHTag;
+/** Reference counter */
+static volatile uint32_t cUsers;
+/** selinfo structure used for polling. */
+static struct selinfo g_SelInfo;
+
+/**
+ * DEVFS event handler.
+ */
+static void vgdrvFreeBSDClone(void *pvArg, struct ucred *pCred, char *pszName, int cchName, struct cdev **ppDev)
+{
+ int iUnit;
+ int rc;
+
+ Log(("vgdrvFreeBSDClone: pszName=%s ppDev=%p\n", pszName, ppDev));
+
+ /*
+ * One device node per user, si_drv1 points to the session.
+ * /dev/vboxguest<N> where N = {0...255}.
+ */
+ if (!ppDev)
+ return;
+ if (strcmp(pszName, "vboxguest") == 0)
+ iUnit = -1;
+ else if (dev_stdclone(pszName, NULL, "vboxguest", &iUnit) != 1)
+ return;
+ if (iUnit >= 256)
+ {
+ Log(("vgdrvFreeBSDClone: iUnit=%d >= 256 - rejected\n", iUnit));
+ return;
+ }
+
+ Log(("vgdrvFreeBSDClone: pszName=%s iUnit=%d\n", pszName, iUnit));
+
+ rc = clone_create(&g_pvgdrvFreeBSDClones, &g_vgdrvFreeBSDChrDevSW, &iUnit, ppDev, 0);
+ Log(("vgdrvFreeBSDClone: clone_create -> %d; iUnit=%d\n", rc, iUnit));
+ if (rc)
+ {
+ *ppDev = make_dev(&g_vgdrvFreeBSDChrDevSW,
+ iUnit,
+ UID_ROOT,
+ GID_WHEEL,
+ 0664,
+ "vboxguest%d", iUnit);
+ if (*ppDev)
+ {
+ dev_ref(*ppDev);
+ (*ppDev)->si_flags |= SI_CHEAPCLONE;
+ Log(("vgdrvFreeBSDClone: Created *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n",
+ *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2));
+ (*ppDev)->si_drv1 = (*ppDev)->si_drv2 = NULL;
+ }
+ else
+ Log(("vgdrvFreeBSDClone: make_dev iUnit=%d failed\n", iUnit));
+ }
+ else
+ Log(("vgdrvFreeBSDClone: Existing *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n",
+ *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2));
+}
+
+/**
+ * File open handler
+ *
+ */
+#if __FreeBSD_version >= 700000
+static int vgdrvFreeBSDOpen(struct cdev *pDev, int fOpen, struct thread *pTd, struct file *pFd)
+#else
+static int vgdrvFreeBSDOpen(struct cdev *pDev, int fOpen, struct thread *pTd)
+#endif
+{
+ int rc;
+ PVBOXGUESTSESSION pSession;
+ uint32_t fRequestor;
+ struct ucred *pCred = curthread->td_ucred;
+ if (!pCred)
+ pCred = curproc->p_ucred;
+
+ LogFlow(("vgdrvFreeBSDOpen:\n"));
+
+ /*
+ * Try grab it (we don't grab the giant, remember).
+ */
+ if (!ASMAtomicCmpXchgPtr(&pDev->si_drv1, (void *)0x42, NULL))
+ return EBUSY;
+
+ /*
+ * Create a new session.
+ */
+ fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
+ if (pCred && pCred->cr_uid == 0)
+ fRequestor |= VMMDEV_REQUESTOR_USR_ROOT;
+ else
+ fRequestor |= VMMDEV_REQUESTOR_USR_USER;
+ if (pCred && groupmember(0, pCred))
+ fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL;
+ fRequestor |= VMMDEV_REQUESTOR_NO_USER_DEVICE; /** @todo implement /dev/vboxuser
+ if (!fUnrestricted)
+ fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE; */
+ fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW; /** @todo see if we can figure out console relationship of pProc. */
+ rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ if (ASMAtomicCmpXchgPtr(&pDev->si_drv1, pSession, (void *)0x42))
+ {
+ Log(("vgdrvFreeBSDOpen: success - g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, (int)RTProcSelf()));
+ ASMAtomicIncU32(&cUsers);
+ return 0;
+ }
+
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ }
+
+ LogRel(("vgdrvFreeBSDOpen: failed. rc=%d\n", rc));
+ return RTErrConvertToErrno(rc);
+}
+
+/**
+ * File close handler
+ *
+ */
+static int vgdrvFreeBSDClose(struct cdev *pDev, int fFile, int DevType, struct thread *pTd)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pDev->si_drv1;
+ Log(("vgdrvFreeBSDClose: fFile=%#x pSession=%p\n", fFile, pSession));
+
+ /*
+ * Close the session if it's still hanging on to the device...
+ */
+ if (RT_VALID_PTR(pSession))
+ {
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ if (!ASMAtomicCmpXchgPtr(&pDev->si_drv1, NULL, pSession))
+ Log(("vgdrvFreeBSDClose: si_drv1=%p expected %p!\n", pDev->si_drv1, pSession));
+ ASMAtomicDecU32(&cUsers);
+ /* Don't use destroy_dev here because it may sleep resulting in a hanging user process. */
+ destroy_dev_sched(pDev);
+ }
+ else
+ Log(("vgdrvFreeBSDClose: si_drv1=%p!\n", pSession));
+ return 0;
+}
+
+
+/**
+ * I/O control request.
+ *
+ * @returns depends...
+ * @param pDev The device.
+ * @param ulCmd The command.
+ * @param pvData Pointer to the data.
+ * @param fFile The file descriptor flags.
+ * @param pTd The calling thread.
+ */
+static int vgdrvFreeBSDIOCtl(struct cdev *pDev, u_long ulCmd, caddr_t pvData, int fFile, struct thread *pTd)
+{
+ PVBOXGUESTSESSION pSession;
+ devfs_get_cdevpriv((void **)&pSession);
+
+ /*
+ * Deal with the fast ioctl path first.
+ */
+ if (VBGL_IOCTL_IS_FAST(ulCmd))
+ return VGDrvCommonIoCtlFast(ulCmd, &g_DevExt, pSession);
+
+ return vgdrvFreeBSDIOCtlSlow(pSession, ulCmd, pvData, pTd);
+}
+
+
+/**
+ * Deal with the 'slow' I/O control requests.
+ *
+ * @returns 0 on success, appropriate errno on failure.
+ * @param pSession The session.
+ * @param ulCmd The command.
+ * @param pvData The request data.
+ * @param pTd The calling thread.
+ */
+static int vgdrvFreeBSDIOCtlSlow(PVBOXGUESTSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd)
+{
+ PVBGLREQHDR pHdr;
+ uint32_t cbReq = IOCPARM_LEN(ulCmd);
+ void *pvUser = NULL;
+
+ /*
+ * Buffered request?
+ */
+ if ((IOC_DIRMASK & ulCmd) == IOC_INOUT)
+ {
+ pHdr = (PVBGLREQHDR)pvData;
+ if (RT_UNLIKELY(cbReq < sizeof(*pHdr)))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: cbReq=%#x < %#x; ulCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), ulCmd));
+ return EINVAL;
+ }
+ if (RT_UNLIKELY(pHdr->uVersion != VBGLREQHDR_VERSION))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: bad uVersion=%#x; ulCmd=%#lx\n", pHdr->uVersion, ulCmd));
+ return EINVAL;
+ }
+ if (RT_UNLIKELY( RT_MAX(pHdr->cbIn, pHdr->cbOut) != cbReq
+ || pHdr->cbIn < sizeof(*pHdr)
+ || (pHdr->cbOut < sizeof(*pHdr) && pHdr->cbOut != 0)))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: max(%#x,%#x) != %#x; ulCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, ulCmd));
+ return EINVAL;
+ }
+ }
+ /*
+ * Big unbuffered request?
+ */
+ else if ((IOC_DIRMASK & ulCmd) == IOC_VOID && !cbReq)
+ {
+ /*
+ * Read the header, validate it and figure out how much that needs to be buffered.
+ */
+ VBGLREQHDR Hdr;
+ pvUser = *(void **)pvData;
+ int rc = copyin(pvUser, &Hdr, sizeof(Hdr));
+ if (RT_UNLIKELY(rc))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: copyin(%p,Hdr,) -> %#x; ulCmd=%#lx\n", pvUser, rc, ulCmd));
+ return rc;
+ }
+ if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: bad uVersion=%#x; ulCmd=%#lx\n", Hdr.uVersion, ulCmd));
+ return EINVAL;
+ }
+ cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut);
+ if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr)
+ || (Hdr.cbOut < sizeof(Hdr) && Hdr.cbOut != 0)
+ || cbReq > _1M*16))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: max(%#x,%#x); ulCmd=%#lx\n", Hdr.cbIn, Hdr.cbOut, ulCmd));
+ return EINVAL;
+ }
+
+ /*
+ * Allocate buffer and copy in the data.
+ */
+ pHdr = (PVBGLREQHDR)RTMemTmpAlloc(cbReq);
+ if (RT_UNLIKELY(!pHdr))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: failed to allocate buffer of %d bytes; ulCmd=%#lx\n", cbReq, ulCmd));
+ return ENOMEM;
+ }
+ rc = copyin(pvUser, pHdr, Hdr.cbIn);
+ if (RT_UNLIKELY(rc))
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: copyin(%p,%p,%#x) -> %#x; ulCmd=%#lx\n",
+ pvUser, pHdr, Hdr.cbIn, rc, ulCmd));
+ RTMemTmpFree(pHdr);
+ return rc;
+ }
+ if (Hdr.cbIn < cbReq)
+ RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbReq - Hdr.cbIn);
+ }
+ else
+ {
+ Log(("vgdrvFreeBSDIOCtlSlow: huh? cbReq=%#x ulCmd=%#lx\n", cbReq, ulCmd));
+ return EINVAL;
+ }
+
+ /*
+ * Process the IOCtl.
+ */
+ int rc = VGDrvCommonIoCtl(ulCmd, &g_DevExt, pSession, pHdr, cbReq);
+ if (RT_LIKELY(!rc))
+ {
+ /*
+ * If unbuffered, copy back the result before returning.
+ */
+ if (pvUser)
+ {
+ uint32_t cbOut = pHdr->cbOut;
+ if (cbOut > cbReq)
+ {
+ LogRel(("vgdrvFreeBSDIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, ulCmd));
+ cbOut = cbReq;
+ }
+ rc = copyout(pHdr, pvUser, cbOut);
+ if (RT_UNLIKELY(rc))
+ LogRel(("vgdrvFreeBSDIOCtlSlow: copyout(%p,%p,%#x) -> %d; uCmd=%#lx!\n", pHdr, pvUser, cbOut, rc, ulCmd));
+
+ Log(("vgdrvFreeBSDIOCtlSlow: returns %d / %d ulCmd=%lx\n", 0, pHdr->rc, ulCmd));
+
+ /* cleanup */
+ RTMemTmpFree(pHdr);
+ }
+ }
+ else
+ {
+ /*
+ * The request failed, just clean up.
+ */
+ if (pvUser)
+ RTMemTmpFree(pHdr);
+
+ Log(("vgdrvFreeBSDIOCtlSlow: ulCmd=%lx pData=%p failed, rc=%d\n", ulCmd, pvData, rc));
+ rc = EINVAL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @note This code is duplicated on other platforms with variations, so please
+ * keep them all up to date when making changes!
+ */
+int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq)
+{
+ /*
+ * Simple request validation (common code does the rest).
+ */
+ int rc;
+ if ( RT_VALID_PTR(pReqHdr)
+ && cbReq >= sizeof(*pReqHdr))
+ {
+ /*
+ * All requests except the connect one requires a valid session.
+ */
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession;
+ if (pSession)
+ {
+ if ( RT_VALID_PTR(pSession)
+ && pSession->pDevExt == &g_DevExt)
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else if (uReq == VBGL_IOCTL_IDC_CONNECT)
+ {
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ if (RT_FAILURE(rc))
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ }
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+ return rc;
+}
+
+
+static int vgdrvFreeBSDPoll(struct cdev *pDev, int fEvents, struct thread *td)
+{
+ int fEventsProcessed;
+
+ LogFlow(("vgdrvFreeBSDPoll: fEvents=%d\n", fEvents));
+
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pDev->si_drv1;
+ if (RT_UNLIKELY(!RT_VALID_PTR(pSession))) {
+ Log(("vgdrvFreeBSDPoll: no state data for %s\n", devtoname(pDev)));
+ return (fEvents & (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
+ }
+
+ uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+ if (pSession->u32MousePosChangedSeq != u32CurSeq)
+ {
+ fEventsProcessed = fEvents & (POLLIN | POLLRDNORM);
+ pSession->u32MousePosChangedSeq = u32CurSeq;
+ }
+ else
+ {
+ fEventsProcessed = 0;
+
+ selrecord(td, &g_SelInfo);
+ }
+
+ return fEventsProcessed;
+}
+
+static int vgdrvFreeBSDWrite(struct cdev *pDev, struct uio *pUio, int fIo)
+{
+ return 0;
+}
+
+static int vgdrvFreeBSDRead(struct cdev *pDev, struct uio *pUio, int fIo)
+{
+ return 0;
+}
+
+static int vgdrvFreeBSDDetach(device_t pDevice)
+{
+ struct VBoxGuestDeviceState *pState = device_get_softc(pDevice);
+
+ if (cUsers > 0)
+ return EBUSY;
+
+ /*
+ * Reverse what we did in vgdrvFreeBSDAttach.
+ */
+ if (g_vgdrvFreeBSDEHTag != NULL)
+ EVENTHANDLER_DEREGISTER(dev_clone, g_vgdrvFreeBSDEHTag);
+
+ clone_cleanup(&g_pvgdrvFreeBSDClones);
+
+ vgdrvFreeBSDRemoveIRQ(pDevice, pState);
+
+ if (pState->pVMMDevMemRes)
+ bus_release_resource(pDevice, SYS_RES_MEMORY, pState->iVMMDevMemResId, pState->pVMMDevMemRes);
+ if (pState->pIOPortRes)
+ bus_release_resource(pDevice, SYS_RES_IOPORT, pState->iIOPortResId, pState->pIOPortRes);
+
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+
+ RTR0Term();
+
+ return 0;
+}
+
+
+/**
+ * Interrupt service routine.
+ *
+ * @returns Whether the interrupt was from VMMDev.
+ * @param pvState Opaque pointer to the device state.
+ */
+static int vgdrvFreeBSDISR(void *pvState)
+{
+ LogFlow(("vgdrvFreeBSDISR: pvState=%p\n", pvState));
+
+ bool fOurIRQ = VGDrvCommonISR(&g_DevExt);
+
+ return fOurIRQ ? 0 : 1;
+}
+
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+ LogFlow(("VGDrvNativeISRMousePollEvent:\n"));
+
+ /*
+ * Wake up poll waiters.
+ */
+ selwakeup(&g_SelInfo);
+}
+
+
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+/**
+ * Sets IRQ for VMMDev.
+ *
+ * @returns FreeBSD error code.
+ * @param pDevice Pointer to the device info structure.
+ * @param pvState Pointer to the state info structure.
+ */
+static int vgdrvFreeBSDAddIRQ(device_t pDevice, void *pvState)
+{
+ int iResId = 0;
+ int rc = 0;
+ struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState;
+
+ pState->pIrqRes = bus_alloc_resource_any(pDevice, SYS_RES_IRQ, &iResId, RF_SHAREABLE | RF_ACTIVE);
+
+#if __FreeBSD_version >= 700000
+ rc = bus_setup_intr(pDevice, pState->pIrqRes, INTR_TYPE_BIO | INTR_MPSAFE, NULL, (driver_intr_t *)vgdrvFreeBSDISR, pState,
+ &pState->pfnIrqHandler);
+#else
+ rc = bus_setup_intr(pDevice, pState->pIrqRes, INTR_TYPE_BIO, (driver_intr_t *)vgdrvFreeBSDISR, pState, &pState->pfnIrqHandler);
+#endif
+
+ if (rc)
+ {
+ pState->pfnIrqHandler = NULL;
+ return VERR_DEV_IO_ERROR;
+ }
+
+ pState->iIrqResId = iResId;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Removes IRQ for VMMDev.
+ *
+ * @param pDevice Pointer to the device info structure.
+ * @param pvState Opaque pointer to the state info structure.
+ */
+static void vgdrvFreeBSDRemoveIRQ(device_t pDevice, void *pvState)
+{
+ struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState;
+
+ if (pState->pIrqRes)
+ {
+ bus_teardown_intr(pDevice, pState->pIrqRes, pState->pfnIrqHandler);
+ bus_release_resource(pDevice, SYS_RES_IRQ, 0, pState->pIrqRes);
+ }
+}
+
+static int vgdrvFreeBSDAttach(device_t pDevice)
+{
+ int rc;
+ int iResId;
+ struct VBoxGuestDeviceState *pState;
+
+ cUsers = 0;
+
+ /*
+ * Initialize IPRT R0 driver, which internally calls OS-specific r0 init.
+ */
+ rc = RTR0Init(0);
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("RTR0Init failed.\n"));
+ return ENXIO;
+ }
+
+ pState = device_get_softc(pDevice);
+
+ /*
+ * Allocate I/O port resource.
+ */
+ iResId = PCIR_BAR(0);
+ pState->pIOPortRes = bus_alloc_resource_any(pDevice, SYS_RES_IOPORT, &iResId, RF_ACTIVE);
+ pState->uIOPortBase = rman_get_start(pState->pIOPortRes);
+ pState->iIOPortResId = iResId;
+ if (pState->uIOPortBase)
+ {
+ /*
+ * Map the MMIO region.
+ */
+ iResId = PCIR_BAR(1);
+ pState->pVMMDevMemRes = bus_alloc_resource_any(pDevice, SYS_RES_MEMORY, &iResId, RF_ACTIVE);
+ pState->VMMDevMemHandle = rman_get_bushandle(pState->pVMMDevMemRes);
+ pState->VMMDevMemSize = rman_get_size(pState->pVMMDevMemRes);
+
+ pState->pMMIOBase = rman_get_virtual(pState->pVMMDevMemRes);
+ pState->iVMMDevMemResId = iResId;
+ if (pState->pMMIOBase)
+ {
+ /*
+ * Call the common device extension initializer.
+ */
+ rc = VGDrvCommonInitDevExt(&g_DevExt, pState->uIOPortBase,
+ pState->pMMIOBase, pState->VMMDevMemSize,
+#if ARCH_BITS == 64
+ VBOXOSTYPE_FreeBSD_x64,
+#else
+ VBOXOSTYPE_FreeBSD,
+#endif
+ VMMDEV_EVENT_MOUSE_POSITION_CHANGED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add IRQ of VMMDev.
+ */
+ rc = vgdrvFreeBSDAddIRQ(pDevice, pState);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read host configuration.
+ */
+ VGDrvCommonProcessOptionsFromHost(&g_DevExt);
+
+ /*
+ * Configure device cloning.
+ */
+ clone_setup(&g_pvgdrvFreeBSDClones);
+ g_vgdrvFreeBSDEHTag = EVENTHANDLER_REGISTER(dev_clone, vgdrvFreeBSDClone, 0, 1000);
+ if (g_vgdrvFreeBSDEHTag)
+ {
+ printf(DEVICE_NAME ": loaded successfully\n");
+ return 0;
+ }
+
+ printf(DEVICE_NAME ": EVENTHANDLER_REGISTER(dev_clone,,,) failed\n");
+ clone_cleanup(&g_pvgdrvFreeBSDClones);
+ vgdrvFreeBSDRemoveIRQ(pDevice, pState);
+ }
+ else
+ printf((DEVICE_NAME ": VGDrvCommonInitDevExt failed.\n"));
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ }
+ else
+ printf((DEVICE_NAME ": vgdrvFreeBSDAddIRQ failed.\n"));
+ }
+ else
+ printf((DEVICE_NAME ": MMIO region setup failed.\n"));
+ }
+ else
+ printf((DEVICE_NAME ": IOport setup failed.\n"));
+
+ RTR0Term();
+ return ENXIO;
+}
+
+static int vgdrvFreeBSDProbe(device_t pDevice)
+{
+ if ((pci_get_vendor(pDevice) == VMMDEV_VENDORID) && (pci_get_device(pDevice) == VMMDEV_DEVICEID))
+ return 0;
+
+ return ENXIO;
+}
+
+static device_method_t vgdrvFreeBSDMethods[] =
+{
+ /* Device interface. */
+ DEVMETHOD(device_probe, vgdrvFreeBSDProbe),
+ DEVMETHOD(device_attach, vgdrvFreeBSDAttach),
+ DEVMETHOD(device_detach, vgdrvFreeBSDDetach),
+ {0,0}
+};
+
+static driver_t vgdrvFreeBSDDriver =
+{
+ DEVICE_NAME,
+ vgdrvFreeBSDMethods,
+ sizeof(struct VBoxGuestDeviceState),
+};
+
+static devclass_t vgdrvFreeBSDClass;
+
+DRIVER_MODULE(vboxguest, pci, vgdrvFreeBSDDriver, vgdrvFreeBSDClass, 0, 0);
+MODULE_VERSION(vboxguest, 1);
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c
new file mode 100644
index 00000000..8981a5e9
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c
@@ -0,0 +1,471 @@
+/* $Id: VBoxGuest-haiku-stubs.c $ */
+/** @file
+ * VBoxGuest kernel module, Haiku Guest Additions, stubs.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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.
+ */
+
+
+/*
+ * This file provides stubs for calling VBox runtime functions through the vboxguest module.
+ * It should be linked into any driver or module that uses the VBox runtime, except vboxguest
+ * itself (which contains the actual library and therefore doesn't need stubs to call it).
+ */
+
+#include "VBoxGuest-haiku.h"
+#include "VBoxGuestInternal.h"
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/process.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+#include <iprt/mp.h>
+#include <iprt/power.h>
+#include <iprt/thread.h>
+
+// >>> file('/tmp/stubs.c', 'w').writelines([re.sub(r'^(?P<returntype>[^(]+) \(\*_(?P<functionname>[A-Za-z0-9_]+)\)\((?P<params>[^)]+)\);', lambda m: '%s %s(%s)\n{\n %sg_VBoxGuest->_%s(%s);\n}\n' % (m.group(1), m.group(2), m.group(3), ('return ' if m.group(1) != 'void' else ''), m.group(2), (', '.join(a.split(' ')[-1].replace('*', '') for a in m.group(3).split(',')) if m.group(3) != 'void' else '')), f) for f in functions])
+
+struct vboxguest_module_info *g_VBoxGuest;
+
+RTDECL(size_t) RTLogBackdoorPrintf(const char *pszFormat, ...)
+{
+ va_list args;
+ size_t cb;
+
+ va_start(args, pszFormat);
+ cb = g_VBoxGuest->_RTLogBackdoorPrintf(pszFormat, args);
+ va_end(args);
+
+ return cb;
+}
+RTDECL(size_t) RTLogBackdoorPrintfV(const char *pszFormat, va_list args)
+{
+ return g_VBoxGuest->_RTLogBackdoorPrintfV(pszFormat, args);
+}
+RTDECL(int) RTLogSetDefaultInstanceThread(PRTLOGGER pLogger, uintptr_t uKey)
+{
+ return g_VBoxGuest->_RTLogSetDefaultInstanceThread(pLogger, uKey);
+}
+RTDECL(int) RTMemAllocExTag(size_t cb, size_t cbAlignment, uint32_t fFlags, const char *pszTag, void **ppv)
+{
+ return g_VBoxGuest->_RTMemAllocExTag(cb, cbAlignment, fFlags, pszTag, ppv);
+}
+RTR0DECL(void*) RTMemContAlloc(PRTCCPHYS pPhys, size_t cb)
+{
+ return g_VBoxGuest->_RTMemContAlloc(pPhys, cb);
+}
+RTR0DECL(void) RTMemContFree(void *pv, size_t cb)
+{
+ g_VBoxGuest->_RTMemContFree(pv, cb);
+}
+RTDECL(void) RTMemFreeEx(void *pv, size_t cb)
+{
+ g_VBoxGuest->_RTMemFreeEx(pv, cb);
+}
+RTDECL(bool) RTMpIsCpuPossible(RTCPUID idCpu)
+{
+ return g_VBoxGuest->_RTMpIsCpuPossible(idCpu);
+}
+RTDECL(int) RTMpNotificationDeregister(PFNRTMPNOTIFICATION pfnCallback, void *pvUser)
+{
+ return g_VBoxGuest->_RTMpNotificationDeregister(pfnCallback, pvUser);
+}
+RTDECL(int) RTMpNotificationRegister(PFNRTMPNOTIFICATION pfnCallback, void *pvUser)
+{
+ return g_VBoxGuest->_RTMpNotificationRegister(pfnCallback, pvUser);
+}
+RTDECL(int) RTMpOnAll(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2)
+{
+ return g_VBoxGuest->_RTMpOnAll(pfnWorker, pvUser1, pvUser2);
+}
+RTDECL(int) RTMpOnOthers(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2)
+{
+ return g_VBoxGuest->_RTMpOnOthers(pfnWorker, pvUser1, pvUser2);
+}
+RTDECL(int) RTMpOnSpecific(RTCPUID idCpu, PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2)
+{
+ return g_VBoxGuest->_RTMpOnSpecific(idCpu, pfnWorker, pvUser1, pvUser2);
+}
+RTDECL(int) RTPowerNotificationDeregister(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser)
+{
+ return g_VBoxGuest->_RTPowerNotificationDeregister(pfnCallback, pvUser);
+}
+RTDECL(int) RTPowerNotificationRegister(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser)
+{
+ return g_VBoxGuest->_RTPowerNotificationRegister(pfnCallback, pvUser);
+}
+RTDECL(int) RTPowerSignalEvent(RTPOWEREVENT enmEvent)
+{
+ return g_VBoxGuest->_RTPowerSignalEvent(enmEvent);
+}
+RTR0DECL(void) RTR0AssertPanicSystem(void)
+{
+ g_VBoxGuest->_RTR0AssertPanicSystem();
+}
+RTR0DECL(int) RTR0Init(unsigned fReserved)
+{
+ return g_VBoxGuest->_RTR0Init(fReserved);
+}
+RTR0DECL(void*) RTR0MemObjAddress(RTR0MEMOBJ MemObj)
+{
+ return g_VBoxGuest->_RTR0MemObjAddress(MemObj);
+}
+RTR0DECL(RTR3PTR) RTR0MemObjAddressR3(RTR0MEMOBJ MemObj)
+{
+ return g_VBoxGuest->_RTR0MemObjAddressR3(MemObj);
+}
+RTR0DECL(int) RTR0MemObjAllocContTag(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjAllocContTag(pMemObj, cb, fExecutable, pszTag);
+}
+RTR0DECL(int) RTR0MemObjAllocLowTag(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjAllocLowTag(pMemObj, cb, fExecutable, pszTag);
+}
+RTR0DECL(int) RTR0MemObjAllocPageTag(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjAllocPageTag(pMemObj, cb, fExecutable, pszTag);
+}
+RTR0DECL(int) RTR0MemObjAllocPhysExTag(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, size_t uAlignment, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjAllocPhysExTag(pMemObj, cb, PhysHighest, uAlignment, pszTag);
+}
+RTR0DECL(int) RTR0MemObjAllocPhysNCTag(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjAllocPhysNCTag(pMemObj, cb, PhysHighest, pszTag);
+}
+RTR0DECL(int) RTR0MemObjAllocPhysTag(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjAllocPhysTag(pMemObj, cb, PhysHighest, pszTag);
+}
+RTR0DECL(int) RTR0MemObjEnterPhysTag(PRTR0MEMOBJ pMemObj, RTHCPHYS Phys, size_t cb, uint32_t uCachePolicy, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjEnterPhysTag(pMemObj, Phys, cb, uCachePolicy, pszTag);
+}
+RTR0DECL(int) RTR0MemObjFree(RTR0MEMOBJ MemObj, bool fFreeMappings)
+{
+ return g_VBoxGuest->_RTR0MemObjFree(MemObj, fFreeMappings);
+}
+RTR0DECL(RTHCPHYS) RTR0MemObjGetPagePhysAddr(RTR0MEMOBJ MemObj, size_t iPage)
+{
+ return g_VBoxGuest->_RTR0MemObjGetPagePhysAddr(MemObj, iPage);
+}
+RTR0DECL(bool) RTR0MemObjIsMapping(RTR0MEMOBJ MemObj)
+{
+ return g_VBoxGuest->_RTR0MemObjIsMapping(MemObj);
+}
+RTR0DECL(int) RTR0MemObjLockKernelTag(PRTR0MEMOBJ pMemObj, void *pv, size_t cb, uint32_t fAccess, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjLockKernelTag(pMemObj, pv, cb, fAccess, pszTag);
+}
+RTR0DECL(int) RTR0MemObjLockUserTag(PRTR0MEMOBJ pMemObj, RTR3PTR R3Ptr, size_t cb, uint32_t fAccess, RTR0PROCESS R0Process, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjLockUserTag(pMemObj, R3Ptr, cb, fAccess, R0Process, pszTag);
+}
+RTR0DECL(int) RTR0MemObjMapKernelExTag(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed, size_t uAlignment, unsigned fProt, size_t offSub, size_t cbSub, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjMapKernelExTag(pMemObj, MemObjToMap, pvFixed, uAlignment, fProt, offSub, cbSub, pszTag);
+}
+RTR0DECL(int) RTR0MemObjMapKernelTag(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed, size_t uAlignment, unsigned fProt, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjMapKernelTag(pMemObj, MemObjToMap, pvFixed, uAlignment, fProt, pszTag);
+}
+RTR0DECL(int) RTR0MemObjMapUserTag(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, RTR3PTR R3PtrFixed, size_t uAlignment, unsigned fProt, RTR0PROCESS R0Process, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjMapUserTag(pMemObj, MemObjToMap, R3PtrFixed, uAlignment, fProt, R0Process, pszTag);
+}
+RTR0DECL(int) RTR0MemObjProtect(RTR0MEMOBJ hMemObj, size_t offSub, size_t cbSub, uint32_t fProt)
+{
+ return g_VBoxGuest->_RTR0MemObjProtect(hMemObj, offSub, cbSub, fProt);
+}
+RTR0DECL(int) RTR0MemObjReserveKernelTag(PRTR0MEMOBJ pMemObj, void *pvFixed, size_t cb, size_t uAlignment, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjReserveKernelTag(pMemObj, pvFixed, cb, uAlignment, pszTag);
+}
+RTR0DECL(int) RTR0MemObjReserveUserTag(PRTR0MEMOBJ pMemObj, RTR3PTR R3PtrFixed, size_t cb, size_t uAlignment, RTR0PROCESS R0Process, const char *pszTag)
+{
+ return g_VBoxGuest->_RTR0MemObjReserveUserTag(pMemObj, R3PtrFixed, cb, uAlignment, R0Process, pszTag);
+}
+RTR0DECL(size_t) RTR0MemObjSize(RTR0MEMOBJ MemObj)
+{
+ return g_VBoxGuest->_RTR0MemObjSize(MemObj);
+}
+RTR0DECL(RTR0PROCESS) RTR0ProcHandleSelf(void)
+{
+ return g_VBoxGuest->_RTR0ProcHandleSelf();
+}
+RTR0DECL(void) RTR0Term(void)
+{
+ g_VBoxGuest->_RTR0Term();
+}
+RTR0DECL(void) RTR0TermForced(void)
+{
+ g_VBoxGuest->_RTR0TermForced();
+}
+RTDECL(RTPROCESS) RTProcSelf(void)
+{
+ return g_VBoxGuest->_RTProcSelf();
+}
+RTDECL(uint32_t) RTSemEventGetResolution(void)
+{
+ return g_VBoxGuest->_RTSemEventGetResolution();
+}
+RTDECL(uint32_t) RTSemEventMultiGetResolution(void)
+{
+ return g_VBoxGuest->_RTSemEventMultiGetResolution();
+}
+RTDECL(int) RTSemEventMultiWaitEx(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout)
+{
+ return g_VBoxGuest->_RTSemEventMultiWaitEx(hEventMultiSem, fFlags, uTimeout);
+}
+RTDECL(int) RTSemEventMultiWaitExDebug(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout, RTHCUINTPTR uId, RT_SRC_POS_DECL)
+{
+ return g_VBoxGuest->_RTSemEventMultiWaitExDebug(hEventMultiSem, fFlags, uTimeout, uId, pszFile, iLine, pszFunction);
+}
+RTDECL(int) RTSemEventWaitEx(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout)
+{
+ return g_VBoxGuest->_RTSemEventWaitEx(hEventSem, fFlags, uTimeout);
+}
+RTDECL(int) RTSemEventWaitExDebug(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout, RTHCUINTPTR uId, RT_SRC_POS_DECL)
+{
+ return g_VBoxGuest->_RTSemEventWaitExDebug(hEventSem, fFlags, uTimeout, uId, pszFile, iLine, pszFunction);
+}
+RTDECL(bool) RTThreadIsInInterrupt(RTTHREAD hThread)
+{
+ return g_VBoxGuest->_RTThreadIsInInterrupt(hThread);
+}
+RTDECL(void) RTThreadPreemptDisable(PRTTHREADPREEMPTSTATE pState)
+{
+ g_VBoxGuest->_RTThreadPreemptDisable(pState);
+}
+RTDECL(bool) RTThreadPreemptIsEnabled(RTTHREAD hThread)
+{
+ return g_VBoxGuest->_RTThreadPreemptIsEnabled(hThread);
+}
+RTDECL(bool) RTThreadPreemptIsPending(RTTHREAD hThread)
+{
+ return g_VBoxGuest->_RTThreadPreemptIsPending(hThread);
+}
+RTDECL(bool) RTThreadPreemptIsPendingTrusty(void)
+{
+ return g_VBoxGuest->_RTThreadPreemptIsPendingTrusty();
+}
+RTDECL(bool) RTThreadPreemptIsPossible(void)
+{
+ return g_VBoxGuest->_RTThreadPreemptIsPossible();
+}
+RTDECL(void) RTThreadPreemptRestore(PRTTHREADPREEMPTSTATE pState)
+{
+ g_VBoxGuest->_RTThreadPreemptRestore(pState);
+}
+RTDECL(uint32_t) RTTimerGetSystemGranularity(void)
+{
+ return g_VBoxGuest->_RTTimerGetSystemGranularity();
+}
+RTDECL(int) RTTimerReleaseSystemGranularity(uint32_t u32Granted)
+{
+ return g_VBoxGuest->_RTTimerReleaseSystemGranularity(u32Granted);
+}
+RTDECL(int) RTTimerRequestSystemGranularity(uint32_t u32Request, uint32_t *pu32Granted)
+{
+ return g_VBoxGuest->_RTTimerRequestSystemGranularity(u32Request, pu32Granted);
+}
+RTDECL(void) RTSpinlockAcquire(RTSPINLOCK Spinlock)
+{
+ g_VBoxGuest->_RTSpinlockAcquire(Spinlock);
+}
+RTDECL(void) RTSpinlockRelease(RTSPINLOCK Spinlock)
+{
+ g_VBoxGuest->_RTSpinlockRelease(Spinlock);
+}
+RTDECL(void*) RTMemTmpAllocTag(size_t cb, const char *pszTag)
+{
+ return g_VBoxGuest->_RTMemTmpAllocTag(cb, pszTag);
+}
+RTDECL(void) RTMemTmpFree(void *pv)
+{
+ g_VBoxGuest->_RTMemTmpFree(pv);
+}
+RTDECL(PRTLOGGER) RTLogDefaultInstance(void)
+{
+ return g_VBoxGuest->_RTLogDefaultInstance();
+}
+RTDECL(PRTLOGGER) RTLogDefaultInstanceEx(uint32_t fFlagsAndGroup)
+{
+ return g_VBoxGuest->_RTLogDefaultInstanceEx(fFlagsAndGroup);
+}
+RTDECL(PRTLOGGER) RTLogRelGetDefaultInstance(void)
+{
+ return g_VBoxGuest->_RTLogRelGetDefaultInstance();
+}
+RTDECL(PRTLOGGER) RTLogRelGetDefaultInstance(uint32_t fFlags, uint32_t iGroup)
+{
+ return g_VBoxGuest->_RTLogRelGetDefaultInstanceEx(fFlags, iGroup);
+}
+RTDECL(int) RTErrConvertToErrno(int iErr)
+{
+ return g_VBoxGuest->_RTErrConvertToErrno(iErr);
+}
+int VGDrvCommonIoCtl(unsigned iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, void *pvData, size_t cbData, size_t *pcbDataReturned)
+{
+ return g_VBoxGuest->_VGDrvCommonIoCtl(iFunction, pDevExt, pSession, pvData, cbData, pcbDataReturned);
+}
+int VGDrvCommonCreateUserSession(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession)
+{
+ return g_VBoxGuest->_VGDrvCommonCreateUserSession(pDevExt, fRequestor, ppSession);
+}
+void VGDrvCommonCloseSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession)
+{
+ g_VBoxGuest->_VGDrvCommonCloseSession(pDevExt, pSession);
+}
+void* VBoxGuestIDCOpen(uint32_t *pu32Version)
+{
+ return g_VBoxGuest->_VBoxGuestIDCOpen(pu32Version);
+}
+int VBoxGuestIDCClose(void *pvSession)
+{
+ return g_VBoxGuest->_VBoxGuestIDCClose(pvSession);
+}
+int VBoxGuestIDCCall(void *pvSession, unsigned iCmd, void *pvData, size_t cbData, size_t *pcbDataReturned)
+{
+ return g_VBoxGuest->_VBoxGuestIDCCall(pvSession, iCmd, pvData, cbData, pcbDataReturned);
+}
+RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ g_VBoxGuest->_RTAssertMsg1Weak(pszExpr, uLine, pszFile, pszFunction);
+}
+RTDECL(void) RTAssertMsg2Weak(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2WeakV(pszFormat, va);
+ va_end(va);
+}
+RTDECL(void) RTAssertMsg2WeakV(const char *pszFormat, va_list va)
+{
+ g_VBoxGuest->_RTAssertMsg2WeakV(pszFormat, va);
+}
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return g_VBoxGuest->_RTAssertShouldPanic();
+}
+RTDECL(int) RTSemFastMutexCreate(PRTSEMFASTMUTEX phFastMtx)
+{
+ return g_VBoxGuest->_RTSemFastMutexCreate(phFastMtx);
+}
+RTDECL(int) RTSemFastMutexDestroy(RTSEMFASTMUTEX hFastMtx)
+{
+ return g_VBoxGuest->_RTSemFastMutexDestroy(hFastMtx);
+}
+RTDECL(int) RTSemFastMutexRelease(RTSEMFASTMUTEX hFastMtx)
+{
+ return g_VBoxGuest->_RTSemFastMutexRelease(hFastMtx);
+}
+RTDECL(int) RTSemFastMutexRequest(RTSEMFASTMUTEX hFastMtx)
+{
+ return g_VBoxGuest->_RTSemFastMutexRequest(hFastMtx);
+}
+RTDECL(int) RTSemMutexCreate(PRTSEMMUTEX phFastMtx)
+{
+ return g_VBoxGuest->_RTSemMutexCreate(phFastMtx);
+}
+RTDECL(int) RTSemMutexDestroy(RTSEMMUTEX hFastMtx)
+{
+ return g_VBoxGuest->_RTSemMutexDestroy(hFastMtx);
+}
+RTDECL(int) RTSemMutexRelease(RTSEMMUTEX hFastMtx)
+{
+ return g_VBoxGuest->_RTSemMutexRelease(hFastMtx);
+}
+RTDECL(int) RTSemMutexRequest(RTSEMMUTEX hFastMtx, RTMSINTERVAL cMillies)
+{
+ return g_VBoxGuest->_RTSemMutexRequest(hFastMtx, cMillies);
+}
+int RTHeapSimpleRelocate(RTHEAPSIMPLE hHeap, uintptr_t offDelta)
+{
+ return g_VBoxGuest->_RTHeapSimpleRelocate(hHeap, offDelta);
+}
+int RTHeapOffsetInit(PRTHEAPOFFSET phHeap, void *pvMemory, size_t cbMemory)
+{
+ return g_VBoxGuest->_RTHeapOffsetInit(phHeap, pvMemory, cbMemory);
+}
+int RTHeapSimpleInit(PRTHEAPSIMPLE pHeap, void *pvMemory, size_t cbMemory)
+{
+ return g_VBoxGuest->_RTHeapSimpleInit(pHeap, pvMemory, cbMemory);
+}
+void* RTHeapOffsetAlloc(RTHEAPOFFSET hHeap, size_t cb, size_t cbAlignment)
+{
+ return g_VBoxGuest->_RTHeapOffsetAlloc(hHeap, cb, cbAlignment);
+}
+void* RTHeapSimpleAlloc(RTHEAPSIMPLE Heap, size_t cb, size_t cbAlignment)
+{
+ return g_VBoxGuest->_RTHeapSimpleAlloc(Heap, cb, cbAlignment);
+}
+void RTHeapOffsetFree(RTHEAPOFFSET hHeap, void *pv)
+{
+ g_VBoxGuest->_RTHeapOffsetFree(hHeap, pv);
+}
+void RTHeapSimpleFree(RTHEAPSIMPLE Heap, void *pv)
+{
+ g_VBoxGuest->_RTHeapSimpleFree(Heap, pv);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c
new file mode 100644
index 00000000..2a872032
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.c
@@ -0,0 +1,588 @@
+/* $Id: VBoxGuest-haiku.c $ */
+/** @file
+ * VBoxGuest kernel module, Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IN_VBOXGUEST
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <OS.h>
+#include <Drivers.h>
+#include <KernelExport.h>
+#include <PCI.h>
+
+#include "VBoxGuest-haiku.h"
+#include "VBoxGuestInternal.h"
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/process.h>
+#include <iprt/mem.h>
+#include <iprt/memobj.h>
+#include <iprt/asm.h>
+#include <iprt/timer.h>
+#include <iprt/heap.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MODULE_NAME VBOXGUEST_MODULE_NAME
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+/*
+ * IRQ related functions.
+ */
+static void vgdrvHaikuRemoveIRQ(void *pvState);
+static int vgdrvHaikuAddIRQ(void *pvState);
+static int32 vgdrvHaikuISR(void *pvState);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static status_t std_ops(int32 op, ...);
+
+static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK;
+
+int32 api_version = B_CUR_DRIVER_API_VERSION;
+
+/** List of cloned device. Managed by the kernel. */
+//static struct clonedevs *g_pvgdrvHaikuClones;
+/** The dev_clone event handler tag. */
+//static eventhandler_tag g_vgdrvHaikuEHTag;
+/** selinfo structure used for polling. */
+//static struct selinfo g_SelInfo;
+/** PCI Bus Manager Module */
+static pci_module_info *gPCI;
+
+static struct vboxguest_module_info g_VBoxGuest =
+{
+ {
+ MODULE_NAME,
+ 0,
+ std_ops
+ },
+ { 0 },
+ { 0 },
+ 0,
+ RTLogBackdoorPrintf,
+ RTLogBackdoorPrintfV,
+ RTLogSetDefaultInstanceThread,
+ RTMemAllocExTag,
+ RTMemContAlloc,
+ RTMemContFree,
+ RTMemFreeEx,
+ RTMpIsCpuPossible,
+ RTMpNotificationDeregister,
+ RTMpNotificationRegister,
+ RTMpOnAll,
+ RTMpOnOthers,
+ RTMpOnSpecific,
+ RTPowerNotificationDeregister,
+ RTPowerNotificationRegister,
+ RTPowerSignalEvent,
+ RTR0AssertPanicSystem,
+ RTR0Init,
+ RTR0MemObjAddress,
+ RTR0MemObjAddressR3,
+ RTR0MemObjAllocContTag,
+ RTR0MemObjAllocLowTag,
+ RTR0MemObjAllocPageTag,
+ RTR0MemObjAllocPhysExTag,
+ RTR0MemObjAllocPhysNCTag,
+ RTR0MemObjAllocPhysTag,
+ RTR0MemObjEnterPhysTag,
+ RTR0MemObjFree,
+ RTR0MemObjGetPagePhysAddr,
+ RTR0MemObjIsMapping,
+ RTR0MemObjLockKernelTag,
+ RTR0MemObjLockUserTag,
+ RTR0MemObjMapKernelExTag,
+ RTR0MemObjMapKernelTag,
+ RTR0MemObjMapUserTag,
+ RTR0MemObjProtect,
+ RTR0MemObjReserveKernelTag,
+ RTR0MemObjReserveUserTag,
+ RTR0MemObjSize,
+ RTR0ProcHandleSelf,
+ RTR0Term,
+ RTR0TermForced,
+ RTProcSelf,
+ RTSemEventGetResolution,
+ RTSemEventMultiGetResolution,
+ RTSemEventMultiWaitEx,
+ RTSemEventMultiWaitExDebug,
+ RTSemEventWaitEx,
+ RTSemEventWaitExDebug,
+ RTThreadIsInInterrupt,
+ RTThreadPreemptDisable,
+ RTThreadPreemptIsEnabled,
+ RTThreadPreemptIsPending,
+ RTThreadPreemptIsPendingTrusty,
+ RTThreadPreemptIsPossible,
+ RTThreadPreemptRestore,
+ RTTimerGetSystemGranularity,
+ RTTimerReleaseSystemGranularity,
+ RTTimerRequestSystemGranularity,
+ RTSpinlockAcquire,
+ RTSpinlockRelease,
+ RTMemTmpAllocTag,
+ RTMemTmpFree,
+ RTLogDefaultInstance,
+ RTLogDefaultInstanceEx,
+ RTLogRelGetDefaultInstance,
+ RTLogRelGetDefaultInstanceEx,
+ RTErrConvertToErrno,
+ VGDrvCommonIoCtl,
+ VGDrvCommonCreateUserSession,
+ VGDrvCommonCloseSession,
+ VBoxGuestIDCOpen,
+ VBoxGuestIDCClose,
+ VBoxGuestIDCCall,
+ RTAssertMsg1Weak,
+ RTAssertMsg2Weak,
+ RTAssertMsg2WeakV,
+ RTAssertShouldPanic,
+ RTSemFastMutexCreate,
+ RTSemFastMutexDestroy,
+ RTSemFastMutexRelease,
+ RTSemFastMutexRequest,
+ RTSemMutexCreate,
+ RTSemMutexDestroy,
+ RTSemMutexRelease,
+ RTSemMutexRequest,
+ RTHeapSimpleRelocate,
+ RTHeapOffsetInit,
+ RTHeapSimpleInit,
+ RTHeapOffsetAlloc,
+ RTHeapSimpleAlloc,
+ RTHeapOffsetFree,
+ RTHeapSimpleFree
+};
+
+#if 0
+/**
+ * DEVFS event handler.
+ */
+static void vgdrvHaikuClone(void *pvArg, struct ucred *pCred, char *pszName, int cchName, struct cdev **ppDev)
+{
+ int iUnit;
+ int rc;
+
+ Log(("vgdrvHaikuClone: pszName=%s ppDev=%p\n", pszName, ppDev));
+
+ /*
+ * One device node per user, si_drv1 points to the session.
+ * /dev/vboxguest<N> where N = {0...255}.
+ */
+ if (!ppDev)
+ return;
+ if (strcmp(pszName, "vboxguest") == 0)
+ iUnit = -1;
+ else if (dev_stdclone(pszName, NULL, "vboxguest", &iUnit) != 1)
+ return;
+ if (iUnit >= 256)
+ {
+ Log(("vgdrvHaikuClone: iUnit=%d >= 256 - rejected\n", iUnit));
+ return;
+ }
+
+ Log(("vgdrvHaikuClone: pszName=%s iUnit=%d\n", pszName, iUnit));
+
+ rc = clone_create(&g_pvgdrvHaikuClones, &g_vgdrvHaikuDeviceHooks, &iUnit, ppDev, 0);
+ Log(("vgdrvHaikuClone: clone_create -> %d; iUnit=%d\n", rc, iUnit));
+ if (rc)
+ {
+ *ppDev = make_dev(&g_vgdrvHaikuDeviceHooks,
+ iUnit,
+ UID_ROOT,
+ GID_WHEEL,
+ 0644,
+ "vboxguest%d", iUnit);
+ if (*ppDev)
+ {
+ dev_ref(*ppDev);
+ (*ppDev)->si_flags |= SI_CHEAPCLONE;
+ Log(("vgdrvHaikuClone: Created *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n",
+ *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2));
+ (*ppDev)->si_drv1 = (*ppDev)->si_drv2 = NULL;
+ }
+ else
+ Log(("vgdrvHaikuClone: make_dev iUnit=%d failed\n", iUnit));
+ }
+ else
+ Log(("vgdrvHaikuClone: Existing *ppDev=%p iUnit=%d si_drv1=%p si_drv2=%p\n",
+ *ppDev, iUnit, (*ppDev)->si_drv1, (*ppDev)->si_drv2));
+}
+#endif
+
+
+static status_t vgdrvHaikuDetach(void)
+{
+ struct VBoxGuestDeviceState *pState = &sState;
+
+ if (cUsers > 0)
+ return EBUSY;
+
+ /*
+ * Reverse what we did in vgdrvHaikuAttach.
+ */
+ vgdrvHaikuRemoveIRQ(pState);
+
+ if (pState->iVMMDevMemAreaId)
+ delete_area(pState->iVMMDevMemAreaId);
+
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+
+#ifdef DO_LOG
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+ RTLogSetDefaultInstance(NULL);
+// RTLogDestroy(RTLogSetDefaultInstance(NULL));
+#endif
+
+ RTSpinlockDestroy(g_Spinlock);
+ g_Spinlock = NIL_RTSPINLOCK;
+
+ RTR0Term();
+ return B_OK;
+}
+
+
+/**
+ * Interrupt service routine.
+ *
+ * @returns Whether the interrupt was from VMMDev.
+ * @param pvState Opaque pointer to the device state.
+ */
+static int32 vgdrvHaikuISR(void *pvState)
+{
+ LogFlow((MODULE_NAME ":vgdrvHaikuISR pvState=%p\n", pvState));
+
+ bool fOurIRQ = VGDrvCommonISR(&g_DevExt);
+ if (fOurIRQ)
+ return B_HANDLED_INTERRUPT;
+ return B_UNHANDLED_INTERRUPT;
+}
+
+
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+ LogFlow(("VGDrvNativeISRMousePollEvent:\n"));
+
+ status_t err = B_OK;
+ //dprintf(MODULE_NAME ": isr mouse\n");
+
+ /*
+ * Wake up poll waiters.
+ */
+ //selwakeup(&g_SelInfo);
+ //XXX:notify_select_event();
+ RTSpinlockAcquire(g_Spinlock);
+
+ if (sState.selectSync)
+ {
+ //dprintf(MODULE_NAME ": isr mouse: notify\n");
+ notify_select_event(sState.selectSync, sState.selectEvent);
+ sState.selectEvent = (uint8_t)0;
+ sState.selectRef = (uint32_t)0;
+ sState.selectSync = NULL;
+ }
+ else
+ err = B_ERROR;
+
+ RTSpinlockRelease(g_Spinlock);
+}
+
+
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+/**
+ * Sets IRQ for VMMDev.
+ *
+ * @returns Haiku error code.
+ * @param pvState Pointer to the state info structure.
+ */
+static int vgdrvHaikuAddIRQ(void *pvState)
+{
+ status_t err;
+ struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState;
+
+ AssertReturn(pState, VERR_INVALID_PARAMETER);
+
+ err = install_io_interrupt_handler(pState->iIrqResId, vgdrvHaikuISR, pState, 0);
+ if (err == B_OK)
+ return VINF_SUCCESS;
+ return VERR_DEV_IO_ERROR;
+}
+
+
+/**
+ * Removes IRQ for VMMDev.
+ *
+ * @param pvState Opaque pointer to the state info structure.
+ */
+static void vgdrvHaikuRemoveIRQ(void *pvState)
+{
+ struct VBoxGuestDeviceState *pState = (struct VBoxGuestDeviceState *)pvState;
+ AssertPtr(pState);
+
+ remove_io_interrupt_handler(pState->iIrqResId, vgdrvHaikuISR, pState);
+}
+
+
+static status_t vgdrvHaikuAttach(const pci_info *pDevice)
+{
+ status_t status;
+ int rc;
+ int iResId;
+ struct VBoxGuestDeviceState *pState = &sState;
+ static const char *const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ PRTLOGGER pRelLogger;
+
+ AssertReturn(pDevice, B_BAD_VALUE);
+
+ cUsers = 0;
+
+ /*
+ * Initialize IPRT R0 driver, which internally calls OS-specific r0 init.
+ */
+ rc = RTR0Init(0);
+ if (RT_FAILURE(rc))
+ {
+ dprintf(MODULE_NAME ": RTR0Init failed: %d\n", rc);
+ return ENXIO;
+ }
+
+ rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "vgdrvHaiku");
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("vgdrvHaikuAttach: RTSpinlock create failed. rc=%Rrc\n", rc));
+ return ENXIO;
+ }
+
+#ifdef DO_LOG
+ /*
+ * Create the release log.
+ * (We do that here instead of common code because we want to log
+ * early failures using the LogRel macro.)
+ */
+ rc = RTLogCreate(&pRelLogger, 0 | RTLOGFLAGS_PREFIX_THREAD /* fFlags */, "all",
+ "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups,
+ RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER | RTLOGDEST_USER, NULL);
+ dprintf(MODULE_NAME ": RTLogCreate: %d\n", rc);
+ if (RT_SUCCESS(rc))
+ {
+ //RTLogGroupSettings(pRelLogger, g_szLogGrp);
+ //RTLogFlags(pRelLogger, g_szLogFlags);
+ //RTLogDestinations(pRelLogger, "/var/log/vboxguest.log");
+ RTLogRelSetDefaultInstance(pRelLogger);
+ RTLogSetDefaultInstance(pRelLogger); //XXX
+ }
+#endif
+
+ /*
+ * Allocate I/O port resource.
+ */
+ pState->uIOPortBase = pDevice->u.h0.base_registers[0];
+ /** @todo check flags for IO? */
+ if (pState->uIOPortBase)
+ {
+ /*
+ * Map the MMIO region.
+ */
+ uint32 phys = pDevice->u.h0.base_registers[1];
+ /** @todo Check flags for mem? */
+ pState->VMMDevMemSize = pDevice->u.h0.base_register_sizes[1];
+ pState->iVMMDevMemAreaId = map_physical_memory("VirtualBox Guest MMIO", phys, pState->VMMDevMemSize,
+ B_ANY_KERNEL_BLOCK_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
+ &pState->pMMIOBase);
+ if (pState->iVMMDevMemAreaId > 0 && pState->pMMIOBase)
+ {
+ /*
+ * Call the common device extension initializer.
+ */
+ rc = VGDrvCommonInitDevExt(&g_DevExt, pState->uIOPortBase, pState->pMMIOBase, pState->VMMDevMemSize,
+#if ARCH_BITS == 64
+ VBOXOSTYPE_Haiku_x64,
+#else
+ VBOXOSTYPE_Haiku,
+#endif
+ VMMDEV_EVENT_MOUSE_POSITION_CHANGED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add IRQ of VMMDev.
+ */
+ pState->iIrqResId = pDevice->u.h0.interrupt_line;
+ rc = vgdrvHaikuAddIRQ(pState);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read host configuration.
+ */
+ VGDrvCommonProcessOptionsFromHost(&g_DevExt);
+
+ LogRel((MODULE_NAME ": loaded successfully\n"));
+ return B_OK;
+ }
+
+ LogRel((MODULE_NAME ": VGDrvCommonInitDevExt failed.\n"));
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ }
+ else
+ LogRel((MODULE_NAME ": vgdrvHaikuAddIRQ failed.\n"));
+ }
+ else
+ LogRel((MODULE_NAME ": MMIO region setup failed.\n"));
+ }
+ else
+ LogRel((MODULE_NAME ": IOport setup failed.\n"));
+
+ RTR0Term();
+ return ENXIO;
+}
+
+
+static status_t vgdrvHaikuProbe(pci_info *pDevice)
+{
+ if ( pDevice->vendor_id == VMMDEV_VENDORID
+ && pDevice->device_id == VMMDEV_DEVICEID)
+ return B_OK;
+
+ return ENXIO;
+}
+
+
+status_t init_module(void)
+{
+ status_t err = B_ENTRY_NOT_FOUND;
+ pci_info info;
+ int ix = 0;
+
+ err = get_module(B_PCI_MODULE_NAME, (module_info **)&gPCI);
+ if (err != B_OK)
+ return err;
+
+ while ((*gPCI->get_nth_pci_info)(ix++, &info) == B_OK)
+ {
+ if (vgdrvHaikuProbe(&info) == 0)
+ {
+ /* We found it */
+ err = vgdrvHaikuAttach(&info);
+ return err;
+ }
+ }
+
+ return B_ENTRY_NOT_FOUND;
+}
+
+
+void uninit_module(void)
+{
+ vgdrvHaikuDetach();
+ put_module(B_PCI_MODULE_NAME);
+}
+
+
+static status_t std_ops(int32 op, ...)
+{
+ switch (op)
+ {
+ case B_MODULE_INIT:
+ return init_module();
+
+ case B_MODULE_UNINIT:
+ {
+ uninit_module();
+ return B_OK;
+ }
+
+ default:
+ return B_ERROR;
+ }
+}
+
+
+_EXPORT module_info *modules[] =
+{
+ (module_info *)&g_VBoxGuest,
+ NULL
+};
+
+/* Common code that depend on g_DevExt. */
+#include "VBoxGuestIDC-unix.c.h"
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h
new file mode 100644
index 00000000..a5fa58d6
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku.h
@@ -0,0 +1,248 @@
+/* $Id: VBoxGuest-haiku.h $ */
+/** @file
+ * VBoxGuest kernel module, Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_common_VBoxGuest_VBoxGuest_haiku_h
+#define GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuest_haiku_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <OS.h>
+#include <Drivers.h>
+#include <drivers/module.h>
+
+#include "VBoxGuestInternal.h"
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/process.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+#include <iprt/mp.h>
+#include <iprt/power.h>
+#include <iprt/thread.h>
+
+/** The module name. */
+#define VBOXGUEST_MODULE_NAME "generic/vboxguest"
+
+struct VBoxGuestDeviceState
+{
+ /** Resource ID of the I/O port */
+ int iIOPortResId;
+ /** Pointer to the I/O port resource. */
+// struct resource *pIOPortRes;
+ /** Start address of the IO Port. */
+ uint16_t uIOPortBase;
+ /** Resource ID of the MMIO area */
+ area_id iVMMDevMemAreaId;
+ /** Pointer to the MMIO resource. */
+// struct resource *pVMMDevMemRes;
+ /** Handle of the MMIO resource. */
+// bus_space_handle_t VMMDevMemHandle;
+ /** Size of the memory area. */
+ size_t VMMDevMemSize;
+ /** Mapping of the register space */
+ void *pMMIOBase;
+ /** IRQ number */
+ int iIrqResId;
+ /** IRQ resource handle. */
+// struct resource *pIrqRes;
+ /** Pointer to the IRQ handler. */
+// void *pfnIrqHandler;
+ /** VMMDev version */
+ uint32_t u32Version;
+
+ /** The (only) select data we wait on. */
+ //XXX: should leave in pSession ?
+ uint8_t selectEvent;
+ uint32_t selectRef;
+ void *selectSync;
+};
+
+struct vboxguest_module_info
+{
+ module_info module;
+
+ VBOXGUESTDEVEXT devExt;
+ struct VBoxGuestDeviceState _sState;
+ volatile uint32_t _cUsers;
+
+ size_t(*_RTLogBackdoorPrintf)(const char *pszFormat, ...);
+ size_t(*_RTLogBackdoorPrintfV)(const char *pszFormat, va_list args);
+ int (*_RTLogSetDefaultInstanceThread)(PRTLOGGER pLogger, uintptr_t uKey);
+ int (*_RTMemAllocExTag)(size_t cb, size_t cbAlignment, uint32_t fFlags, const char *pszTag, void **ppv);
+ void* (*_RTMemContAlloc)(PRTCCPHYS pPhys, size_t cb);
+ void (*_RTMemContFree)(void *pv, size_t cb);
+ void (*_RTMemFreeEx)(void *pv, size_t cb);
+ bool (*_RTMpIsCpuPossible)(RTCPUID idCpu);
+ int (*_RTMpNotificationDeregister)(PFNRTMPNOTIFICATION pfnCallback, void *pvUser);
+ int (*_RTMpNotificationRegister)(PFNRTMPNOTIFICATION pfnCallback, void *pvUser);
+ int (*_RTMpOnAll)(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2);
+ int (*_RTMpOnOthers)(PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2);
+ int (*_RTMpOnSpecific)(RTCPUID idCpu, PFNRTMPWORKER pfnWorker, void *pvUser1, void *pvUser2);
+ int (*_RTPowerNotificationDeregister)(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser);
+ int (*_RTPowerNotificationRegister)(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser);
+ int (*_RTPowerSignalEvent)(RTPOWEREVENT enmEvent);
+ void (*_RTR0AssertPanicSystem)(void);
+ int (*_RTR0Init)(unsigned fReserved);
+ void* (*_RTR0MemObjAddress)(RTR0MEMOBJ MemObj);
+ RTR3PTR(*_RTR0MemObjAddressR3)(RTR0MEMOBJ MemObj);
+ int (*_RTR0MemObjAllocContTag)(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag);
+ int (*_RTR0MemObjAllocLowTag)(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag);
+ int (*_RTR0MemObjAllocPageTag)(PRTR0MEMOBJ pMemObj, size_t cb, bool fExecutable, const char *pszTag);
+ int (*_RTR0MemObjAllocPhysExTag)(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, size_t uAlignment, const char *pszTag);
+ int (*_RTR0MemObjAllocPhysNCTag)(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag);
+ int (*_RTR0MemObjAllocPhysTag)(PRTR0MEMOBJ pMemObj, size_t cb, RTHCPHYS PhysHighest, const char *pszTag);
+ int (*_RTR0MemObjEnterPhysTag)(PRTR0MEMOBJ pMemObj, RTHCPHYS Phys, size_t cb, uint32_t uCachePolicy, const char *pszTag);
+ int (*_RTR0MemObjFree)(RTR0MEMOBJ MemObj, bool fFreeMappings);
+ RTHCPHYS(*_RTR0MemObjGetPagePhysAddr)(RTR0MEMOBJ MemObj, size_t iPage);
+ bool (*_RTR0MemObjIsMapping)(RTR0MEMOBJ MemObj);
+ int (*_RTR0MemObjLockKernelTag)(PRTR0MEMOBJ pMemObj, void *pv, size_t cb, uint32_t fAccess, const char *pszTag);
+ int (*_RTR0MemObjLockUserTag)(PRTR0MEMOBJ pMemObj, RTR3PTR R3Ptr, size_t cb, uint32_t fAccess,
+ RTR0PROCESS R0Process, const char *pszTag);
+ int (*_RTR0MemObjMapKernelExTag)(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed, size_t uAlignment,
+ unsigned fProt, size_t offSub, size_t cbSub, const char *pszTag);
+ int (*_RTR0MemObjMapKernelTag)(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, void *pvFixed,
+ size_t uAlignment, unsigned fProt, const char *pszTag);
+ int (*_RTR0MemObjMapUserTag)(PRTR0MEMOBJ pMemObj, RTR0MEMOBJ MemObjToMap, RTR3PTR R3PtrFixed,
+ size_t uAlignment, unsigned fProt, RTR0PROCESS R0Process, const char *pszTag);
+ int (*_RTR0MemObjProtect)(RTR0MEMOBJ hMemObj, size_t offSub, size_t cbSub, uint32_t fProt);
+ int (*_RTR0MemObjReserveKernelTag)(PRTR0MEMOBJ pMemObj, void *pvFixed, size_t cb, size_t uAlignment, const char *pszTag);
+ int (*_RTR0MemObjReserveUserTag)(PRTR0MEMOBJ pMemObj, RTR3PTR R3PtrFixed, size_t cb, size_t uAlignment,
+ RTR0PROCESS R0Process, const char *pszTag);
+ size_t(*_RTR0MemObjSize)(RTR0MEMOBJ MemObj);
+ RTR0PROCESS(*_RTR0ProcHandleSelf)(void);
+ void (*_RTR0Term)(void);
+ void (*_RTR0TermForced)(void);
+ RTPROCESS(*_RTProcSelf)(void);
+ uint32_t(*_RTSemEventGetResolution)(void);
+ uint32_t(*_RTSemEventMultiGetResolution)(void);
+ int (*_RTSemEventMultiWaitEx)(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout);
+ int (*_RTSemEventMultiWaitExDebug)(RTSEMEVENTMULTI hEventMultiSem, uint32_t fFlags, uint64_t uTimeout,
+ RTHCUINTPTR uId, RT_SRC_POS_DECL);
+ int (*_RTSemEventWaitEx)(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout);
+ int (*_RTSemEventWaitExDebug)(RTSEMEVENT hEventSem, uint32_t fFlags, uint64_t uTimeout,
+ RTHCUINTPTR uId, RT_SRC_POS_DECL);
+ bool (*_RTThreadIsInInterrupt)(RTTHREAD hThread);
+ void (*_RTThreadPreemptDisable)(PRTTHREADPREEMPTSTATE pState);
+ bool (*_RTThreadPreemptIsEnabled)(RTTHREAD hThread);
+ bool (*_RTThreadPreemptIsPending)(RTTHREAD hThread);
+ bool (*_RTThreadPreemptIsPendingTrusty)(void);
+ bool (*_RTThreadPreemptIsPossible)(void);
+ void (*_RTThreadPreemptRestore)(PRTTHREADPREEMPTSTATE pState);
+ uint32_t(*_RTTimerGetSystemGranularity)(void);
+ int (*_RTTimerReleaseSystemGranularity)(uint32_t u32Granted);
+ int (*_RTTimerRequestSystemGranularity)(uint32_t u32Request, uint32_t *pu32Granted);
+ void (*_RTSpinlockAcquire)(RTSPINLOCK Spinlock);
+ void (*_RTSpinlockRelease)(RTSPINLOCK Spinlock);
+ void* (*_RTMemTmpAllocTag)(size_t cb, const char *pszTag);
+ void (*_RTMemTmpFree)(void *pv);
+ PRTLOGGER(*_RTLogDefaultInstance)(void);
+ PRTLOGGER(*_RTLogDefaultInstanceEx)(uint32_t fFlagsAndGroup);
+ PRTLOGGER(*_RTLogRelGetDefaultInstance)(void);
+ PRTLOGGER(*_RTLogRelGetDefaultInstanceEx)(uint32_t fFlagsAndGroup);
+ int (*_RTErrConvertToErrno)(int iErr);
+ int (*_VGDrvCommonIoCtl)(unsigned iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ void *pvData, size_t cbData, size_t *pcbDataReturned);
+ int (*_VGDrvCommonCreateUserSession)(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession);
+ void (*_VGDrvCommonCloseSession)(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession);
+ void* (*_VBoxGuestIDCOpen)(uint32_t *pu32Version);
+ int (*_VBoxGuestIDCClose)(void *pvSession);
+ int (*_VBoxGuestIDCCall)(void *pvSession, unsigned iCmd, void *pvData, size_t cbData, size_t *pcbDataReturned);
+ void (*_RTAssertMsg1Weak)(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction);
+ void (*_RTAssertMsg2Weak)(const char *pszFormat, ...);
+ void (*_RTAssertMsg2WeakV)(const char *pszFormat, va_list va);
+ bool (*_RTAssertShouldPanic)(void);
+ int (*_RTSemFastMutexCreate)(PRTSEMFASTMUTEX phFastMtx);
+ int (*_RTSemFastMutexDestroy)(RTSEMFASTMUTEX hFastMtx);
+ int (*_RTSemFastMutexRelease)(RTSEMFASTMUTEX hFastMtx);
+ int (*_RTSemFastMutexRequest)(RTSEMFASTMUTEX hFastMtx);
+ int (*_RTSemMutexCreate)(PRTSEMMUTEX phFastMtx);
+ int (*_RTSemMutexDestroy)(RTSEMMUTEX hFastMtx);
+ int (*_RTSemMutexRelease)(RTSEMMUTEX hFastMtx);
+ int (*_RTSemMutexRequest)(RTSEMMUTEX hFastMtx, RTMSINTERVAL cMillies);
+ int (*_RTHeapSimpleRelocate)(RTHEAPSIMPLE hHeap, uintptr_t offDelta);
+ int (*_RTHeapOffsetInit)(PRTHEAPOFFSET phHeap, void *pvMemory, size_t cbMemory);
+ int (*_RTHeapSimpleInit)(PRTHEAPSIMPLE pHeap, void *pvMemory, size_t cbMemory);
+ void* (*_RTHeapOffsetAlloc)(RTHEAPOFFSET hHeap, size_t cb, size_t cbAlignment);
+ void* (*_RTHeapSimpleAlloc)(RTHEAPSIMPLE Heap, size_t cb, size_t cbAlignment);
+ void (*_RTHeapOffsetFree)(RTHEAPOFFSET hHeap, void *pv);
+ void (*_RTHeapSimpleFree)(RTHEAPSIMPLE Heap, void *pv);
+};
+
+
+#ifdef IN_VBOXGUEST
+#define g_DevExt (g_VBoxGuest.devExt)
+#define cUsers (g_VBoxGuest._cUsers)
+#define sState (g_VBoxGuest._sState)
+#else
+#define g_DevExt (g_VBoxGuest->devExt)
+#define cUsers (g_VBoxGuest->_cUsers)
+#define sState (g_VBoxGuest->_sState)
+extern struct vboxguest_module_info *g_VBoxGuest;
+#endif
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuest_haiku_h */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c
new file mode 100644
index 00000000..538a0bc4
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c
@@ -0,0 +1,1470 @@
+/* $Id: VBoxGuest-linux.c $ */
+/** @file
+ * VBoxGuest - Linux specifics.
+ *
+ * Note. Unfortunately, the difference between this and SUPDrv-linux.c is
+ * a little bit too big to be helpful.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SUP_DRV
+
+#include "the-linux-kernel.h"
+
+#if RTLNX_VER_MIN(2,6,15)
+# define VBOXGUEST_WITH_INPUT_DRIVER
+#endif
+
+#if RTLNX_VER_MIN(4,15,0)
+# define CONST_4_15 const
+#else
+# define CONST_4_15
+#endif
+
+#include "VBoxGuestInternal.h"
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+# include <linux/input.h>
+#endif
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#if RTLNX_VER_MIN(2,6,28)
+# include <linux/tty.h>
+#endif
+#include <VBox/version.h>
+#include "revision-generated.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/mp.h>
+#include <iprt/process.h>
+#include <iprt/spinlock.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The device name. */
+#define DEVICE_NAME "vboxguest"
+/** The device name for the device node open to everyone. */
+#define DEVICE_NAME_USER "vboxuser"
+/** The name of the PCI driver */
+#define DRIVER_NAME DEVICE_NAME
+
+
+/* 2.4.x compatibility macros that may or may not be defined. */
+#ifndef IRQ_RETVAL
+# define irqreturn_t void
+# define IRQ_RETVAL(n)
+#endif
+
+/* uidgid.h was introduced in 3.5.0. */
+#if RTLNX_VER_MAX(3,5,0)
+# define kgid_t gid_t
+# define kuid_t uid_t
+#endif
+
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+/** The name of input pointing device for mouse integration. */
+# define VBOX_INPUT_DEVICE_NAME "VirtualBox mouse integration"
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void vgdrvLinuxTermPci(struct pci_dev *pPciDev);
+static int vgdrvLinuxProbePci(struct pci_dev *pPciDev, const struct pci_device_id *id);
+static int __init vgdrvLinuxModInit(void);
+static void __exit vgdrvLinuxModExit(void);
+static int vgdrvLinuxOpen(struct inode *pInode, struct file *pFilp);
+static int vgdrvLinuxRelease(struct inode *pInode, struct file *pFilp);
+#ifdef HAVE_UNLOCKED_IOCTL
+static long vgdrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg);
+#else
+static int vgdrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg);
+#endif
+static int vgdrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PVBOXGUESTSESSION pSession);
+static int vgdrvLinuxFAsync(int fd, struct file *pFile, int fOn);
+static unsigned int vgdrvLinuxPoll(struct file *pFile, poll_table *pPt);
+static ssize_t vgdrvLinuxRead(struct file *pFile, char *pbBuf, size_t cbRead, loff_t *poff);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Device extention & session data association structure.
+ */
+static VBOXGUESTDEVEXT g_DevExt;
+/** The PCI device. */
+static struct pci_dev *g_pPciDev = NULL;
+/** The base of the I/O port range. */
+static RTIOPORT g_IOPortBase;
+/** The base of the MMIO range. */
+static RTHCPHYS g_MMIOPhysAddr = NIL_RTHCPHYS;
+/** The size of the MMIO range as seen by PCI. */
+static uint32_t g_cbMMIO;
+/** The pointer to the mapping of the MMIO range. */
+static void *g_pvMMIOBase;
+/** Wait queue used by polling. */
+static wait_queue_head_t g_PollEventQueue;
+/** Asynchronous notification stuff. */
+static struct fasync_struct *g_pFAsyncQueue;
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+/** Pre-allocated mouse status VMMDev requests for use in the IRQ handler. */
+static VMMDevReqMouseStatusEx *g_pMouseStatusReqEx;
+#endif
+#if RTLNX_VER_MIN(2,6,0)
+/** Whether we've create the logger or not. */
+static volatile bool g_fLoggerCreated;
+/** Release logger group settings. */
+static char g_szLogGrp[128];
+/** Release logger flags settings. */
+static char g_szLogFlags[128];
+/** Release logger destination settings. */
+static char g_szLogDst[128];
+# if 0
+/** Debug logger group settings. */
+static char g_szDbgLogGrp[128];
+/** Debug logger flags settings. */
+static char g_szDbgLogFlags[128];
+/** Debug logger destination settings. */
+static char g_szDbgLogDst[128];
+# endif
+#endif
+
+/** The input device handle */
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+static struct input_dev *g_pInputDevice = NULL;
+#endif
+
+/** The file_operations structure. */
+static struct file_operations g_FileOps =
+{
+ owner: THIS_MODULE,
+ open: vgdrvLinuxOpen,
+ release: vgdrvLinuxRelease,
+#ifdef HAVE_UNLOCKED_IOCTL
+ unlocked_ioctl: vgdrvLinuxIOCtl,
+#else
+ ioctl: vgdrvLinuxIOCtl,
+#endif
+ fasync: vgdrvLinuxFAsync,
+ read: vgdrvLinuxRead,
+ poll: vgdrvLinuxPoll,
+ llseek: no_llseek,
+};
+
+/** The miscdevice structure. */
+static struct miscdevice g_MiscDevice =
+{
+ minor: MISC_DYNAMIC_MINOR,
+ name: DEVICE_NAME,
+ fops: &g_FileOps,
+};
+
+/** The file_operations structure for the user device.
+ * @remarks For the time being we'll be using the same implementation as
+ * /dev/vboxguest here. */
+static struct file_operations g_FileOpsUser =
+{
+ owner: THIS_MODULE,
+ open: vgdrvLinuxOpen,
+ release: vgdrvLinuxRelease,
+#ifdef HAVE_UNLOCKED_IOCTL
+ unlocked_ioctl: vgdrvLinuxIOCtl,
+#else
+ ioctl: vgdrvLinuxIOCtl,
+#endif
+};
+
+/** The miscdevice structure for the user device. */
+static struct miscdevice g_MiscDeviceUser =
+{
+ minor: MISC_DYNAMIC_MINOR,
+ name: DEVICE_NAME_USER,
+ fops: &g_FileOpsUser,
+};
+
+
+/** PCI hotplug structure. */
+static const struct pci_device_id g_VBoxGuestPciId[] =
+{
+ {
+ vendor: VMMDEV_VENDORID,
+ device: VMMDEV_DEVICEID
+ },
+ {
+ /* empty entry */
+ }
+};
+
+MODULE_DEVICE_TABLE(pci, g_VBoxGuestPciId);
+
+/** Structure for registering the PCI driver. */
+static struct pci_driver g_PciDriver =
+{
+ name: DRIVER_NAME,
+ id_table: g_VBoxGuestPciId,
+ probe: vgdrvLinuxProbePci,
+ remove: vgdrvLinuxTermPci
+};
+
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+/** Kernel IDC session to ourselves for use with the mouse events. */
+static PVBOXGUESTSESSION g_pKernelSession = NULL;
+#endif
+
+
+
+/**
+ * Converts a VBox status code to a linux error code.
+ *
+ * @returns corresponding negative linux error code.
+ * @param rc supdrv error code (SUPDRV_ERR_* defines).
+ */
+static int vgdrvLinuxConvertToNegErrno(int rc)
+{
+ if ( rc > -1000
+ && rc < 1000)
+ return -RTErrConvertToErrno(rc);
+ switch (rc)
+ {
+ case VERR_HGCM_SERVICE_NOT_FOUND: return -ESRCH;
+ case VINF_HGCM_CLIENT_REJECTED: return 0;
+ case VERR_HGCM_INVALID_CMD_ADDRESS: return -EFAULT;
+ case VINF_HGCM_ASYNC_EXECUTE: return 0;
+ case VERR_HGCM_INTERNAL: return -EPROTO;
+ case VERR_HGCM_INVALID_CLIENT_ID: return -EINVAL;
+ case VINF_HGCM_SAVE_STATE: return 0;
+ /* No reason to return this to a guest */
+ // case VERR_HGCM_SERVICE_EXISTS: return -EEXIST;
+ default:
+ AssertMsgFailed(("Unhandled error code %Rrc\n", rc));
+ return -EPROTO;
+ }
+}
+
+
+/**
+ * Does the PCI detection and init of the device.
+ *
+ * @returns 0 on success, negated errno on failure.
+ */
+static int vgdrvLinuxProbePci(struct pci_dev *pPciDev, const struct pci_device_id *id)
+{
+ int rc;
+
+ NOREF(id);
+ AssertReturn(!g_pPciDev, -EINVAL);
+ rc = pci_enable_device(pPciDev);
+ if (rc >= 0)
+ {
+ /* I/O Ports are mandatory, the MMIO bit is not. */
+ g_IOPortBase = pci_resource_start(pPciDev, 0);
+ if (g_IOPortBase != 0)
+ {
+ /*
+ * Map the register address space.
+ */
+ g_MMIOPhysAddr = pci_resource_start(pPciDev, 1);
+ g_cbMMIO = pci_resource_len(pPciDev, 1);
+ if (request_mem_region(g_MMIOPhysAddr, g_cbMMIO, DEVICE_NAME) != NULL)
+ {
+ g_pvMMIOBase = ioremap(g_MMIOPhysAddr, g_cbMMIO);
+ if (g_pvMMIOBase)
+ {
+ /** @todo why aren't we requesting ownership of the I/O ports as well? */
+ g_pPciDev = pPciDev;
+ return 0;
+ }
+
+ /* failure cleanup path */
+ LogRel((DEVICE_NAME ": ioremap failed; MMIO Addr=%RHp cb=%#x\n", g_MMIOPhysAddr, g_cbMMIO));
+ rc = -ENOMEM;
+ release_mem_region(g_MMIOPhysAddr, g_cbMMIO);
+ }
+ else
+ {
+ LogRel((DEVICE_NAME ": failed to obtain adapter memory\n"));
+ rc = -EBUSY;
+ }
+ g_MMIOPhysAddr = NIL_RTHCPHYS;
+ g_cbMMIO = 0;
+ g_IOPortBase = 0;
+ }
+ else
+ {
+ LogRel((DEVICE_NAME ": did not find expected hardware resources\n"));
+ rc = -ENXIO;
+ }
+ pci_disable_device(pPciDev);
+ }
+ else
+ LogRel((DEVICE_NAME ": could not enable device: %d\n", rc));
+ return rc;
+}
+
+
+/**
+ * Clean up the usage of the PCI device.
+ */
+static void vgdrvLinuxTermPci(struct pci_dev *pPciDev)
+{
+ g_pPciDev = NULL;
+ if (pPciDev)
+ {
+ iounmap(g_pvMMIOBase);
+ g_pvMMIOBase = NULL;
+
+ release_mem_region(g_MMIOPhysAddr, g_cbMMIO);
+ g_MMIOPhysAddr = NIL_RTHCPHYS;
+ g_cbMMIO = 0;
+
+ pci_disable_device(pPciDev);
+ }
+}
+
+
+/**
+ * Interrupt service routine.
+ *
+ * @returns In 2.4 it returns void.
+ * In 2.6 we indicate whether we've handled the IRQ or not.
+ *
+ * @param iIrq The IRQ number.
+ * @param pvDevId The device ID, a pointer to g_DevExt.
+ * @param pRegs Register set. Removed in 2.6.19.
+ */
+#if RTLNX_VER_MIN(2,6,19) && !defined(DOXYGEN_RUNNING)
+static irqreturn_t vgdrvLinuxISR(int iIrq, void *pvDevId)
+#else
+static irqreturn_t vgdrvLinuxISR(int iIrq, void *pvDevId, struct pt_regs *pRegs)
+#endif
+{
+ bool fTaken = VGDrvCommonISR(&g_DevExt);
+ return IRQ_RETVAL(fTaken);
+}
+
+
+/**
+ * Registers the ISR and initializes the poll wait queue.
+ */
+static int __init vgdrvLinuxInitISR(void)
+{
+ int rc;
+
+ init_waitqueue_head(&g_PollEventQueue);
+ rc = request_irq(g_pPciDev->irq,
+ vgdrvLinuxISR,
+#if RTLNX_VER_MIN(2,6,20)
+ IRQF_SHARED,
+#else
+ SA_SHIRQ,
+#endif
+ DEVICE_NAME,
+ &g_DevExt);
+ if (rc)
+ {
+ LogRel((DEVICE_NAME ": could not request IRQ %d: err=%d\n", g_pPciDev->irq, rc));
+ return rc;
+ }
+ return 0;
+}
+
+
+/**
+ * Deregisters the ISR.
+ */
+static void vgdrvLinuxTermISR(void)
+{
+ free_irq(g_pPciDev->irq, &g_DevExt);
+}
+
+
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+/**
+ * Check if extended mouse pointer state request protocol is currently used by driver.
+ *
+ * @returns True if extended protocol is used, False otherwise.
+ */
+static bool vgdrvLinuxUsesMouseStatusEx(void)
+{
+ return g_pMouseStatusReqEx->Core.header.requestType == VMMDevReq_GetMouseStatusEx;
+}
+
+/**
+ * Reports the mouse integration status to the host.
+ *
+ * Calls the kernel IOCtl to report mouse status to the host on behalf of
+ * our kernel session.
+ *
+ * @param fStatus The mouse status to report.
+ */
+static int vgdrvLinuxSetMouseStatus(uint32_t fStatus)
+{
+ int rc;
+ VBGLIOCSETMOUSESTATUS Req;
+ VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS);
+ Req.u.In.fStatus = fStatus;
+ rc = VGDrvCommonIoCtl(VBGL_IOCTL_SET_MOUSE_STATUS, &g_DevExt, g_pKernelSession, &Req.Hdr, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ rc = Req.Hdr.rc;
+ return rc;
+}
+
+
+/**
+ * Called when the input device is first opened.
+ *
+ * Sets up absolute mouse reporting.
+ */
+static int vboxguestOpenInputDevice(struct input_dev *pDev)
+{
+ int rc = vgdrvLinuxSetMouseStatus( VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
+ | VMMDEV_MOUSE_NEW_PROTOCOL
+ | (vgdrvLinuxUsesMouseStatusEx() ? VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL : 0));
+ if (RT_FAILURE(rc))
+ return ENODEV;
+ NOREF(pDev);
+ return 0;
+}
+
+
+/**
+ * Called if all open handles to the input device are closed.
+ *
+ * Disables absolute reporting.
+ */
+static void vboxguestCloseInputDevice(struct input_dev *pDev)
+{
+ NOREF(pDev);
+ vgdrvLinuxSetMouseStatus(0);
+}
+
+
+/**
+ * Free corresponding mouse status request structure.
+ */
+static void vgdrvLinuxFreeMouseStatusReq(void)
+{
+ VbglR0GRFree(&g_pMouseStatusReqEx->Core.header);
+ g_pMouseStatusReqEx = NULL;
+}
+
+/**
+ * Creates the kernel input device.
+ */
+static int __init vgdrvLinuxCreateInputDevice(void)
+{
+ /* Try to allocate legacy request data first, and check if host supports extended protocol. */
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&g_pMouseStatusReqEx, sizeof(VMMDevReqMouseStatus), VMMDevReq_GetMouseStatus);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check if host supports extended mouse state reporting. */
+ g_pMouseStatusReqEx->Core.mouseFeatures = 0;
+ rc = VbglR0GRPerform(&g_pMouseStatusReqEx->Core.header);
+ if (RT_SUCCESS(rc))
+ {
+ if (g_pMouseStatusReqEx->Core.mouseFeatures & VMMDEV_MOUSE_HOST_USES_FULL_STATE_PROTOCOL)
+ {
+ VMMDevReqMouseStatusEx *pReqEx = NULL;
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReqEx, sizeof(*pReqEx), VMMDevReq_GetMouseStatusEx);
+ if (RT_SUCCESS(rc))
+ {
+ /* De-allocate legacy request data, */
+ VbglR0GRFree(&g_pMouseStatusReqEx->Core.header);
+ /* ..and switch to extended requests. */
+ g_pMouseStatusReqEx = pReqEx;
+ LogRel(("Host supports full mouse state reporting, switching to extended mouse integration protocol\n"));
+ }
+ else
+ LogRel(("Host supports full mouse state reporting, but feature cannot be initialized, switching to legacy mouse integration protocol\n"));
+ }
+ else
+ LogRel(("Host does not support full mouse state reporting, switching to legacy mouse integration protocol\n"));
+ }
+ else
+ LogRel(("Unable to get host mouse capabilities, switching to legacy mouse integration protocol\n"));
+ }
+ else
+ rc = -ENOMEM;
+
+ if (RT_SUCCESS(rc))
+ {
+ g_pInputDevice = input_allocate_device();
+ if (g_pInputDevice)
+ {
+ g_pInputDevice->name = VBOX_INPUT_DEVICE_NAME;
+ g_pInputDevice->id.bustype = BUS_PCI;
+ g_pInputDevice->id.vendor = VMMDEV_VENDORID;
+ g_pInputDevice->id.product = VMMDEV_DEVICEID;
+ g_pInputDevice->id.version = VBOX_SHORT_VERSION;
+ g_pInputDevice->open = vboxguestOpenInputDevice;
+ g_pInputDevice->close = vboxguestCloseInputDevice;
+# if RTLNX_VER_MAX(2,6,22)
+ g_pInputDevice->cdev.dev = &g_pPciDev->dev;
+# else
+ g_pInputDevice->dev.parent = &g_pPciDev->dev;
+# endif
+ /* Set up input device capabilities. */
+ ASMBitSet(g_pInputDevice->evbit, EV_ABS);
+ ASMBitSet(g_pInputDevice->evbit, EV_KEY);
+# ifdef EV_SYN
+ ASMBitSet(g_pInputDevice->evbit, EV_SYN);
+# endif
+ ASMBitSet(g_pInputDevice->absbit, ABS_X);
+ ASMBitSet(g_pInputDevice->absbit, ABS_Y);
+
+ input_set_abs_params(g_pInputDevice, ABS_X, VMMDEV_MOUSE_RANGE_MIN, VMMDEV_MOUSE_RANGE_MAX, 0, 0);
+ input_set_abs_params(g_pInputDevice, ABS_Y, VMMDEV_MOUSE_RANGE_MIN, VMMDEV_MOUSE_RANGE_MAX, 0, 0);
+
+ ASMBitSet(g_pInputDevice->keybit, BTN_MOUSE);
+
+ /* Report extra capabilities to input layer if extended mouse state protocol
+ * will be used in communication with host. */
+ if (vgdrvLinuxUsesMouseStatusEx())
+ {
+ ASMBitSet(g_pInputDevice->evbit, EV_REL);
+ ASMBitSet(g_pInputDevice->evbit, EV_KEY);
+
+ ASMBitSet(g_pInputDevice->relbit, REL_WHEEL);
+ ASMBitSet(g_pInputDevice->relbit, REL_HWHEEL);
+
+ ASMBitSet(g_pInputDevice->keybit, BTN_LEFT);
+ ASMBitSet(g_pInputDevice->keybit, BTN_RIGHT);
+ ASMBitSet(g_pInputDevice->keybit, BTN_MIDDLE);
+ ASMBitSet(g_pInputDevice->keybit, BTN_SIDE);
+ ASMBitSet(g_pInputDevice->keybit, BTN_EXTRA);
+ }
+
+ rc = input_register_device(g_pInputDevice);
+ if (rc == 0)
+ return 0;
+
+ input_free_device(g_pInputDevice);
+ }
+ else
+ rc = -ENOMEM;
+
+ vgdrvLinuxFreeMouseStatusReq();
+ }
+
+ return rc;
+}
+
+
+/**
+ * Terminates the kernel input device.
+ */
+static void vgdrvLinuxTermInputDevice(void)
+{
+ /* Notify host that mouse integration is no longer available. */
+ vgdrvLinuxSetMouseStatus(0);
+
+ vgdrvLinuxFreeMouseStatusReq();
+
+ /* See documentation of input_register_device(): input_free_device()
+ * should not be called after a device has been registered. */
+ input_unregister_device(g_pInputDevice);
+}
+
+#endif /* VBOXGUEST_WITH_INPUT_DRIVER */
+
+/**
+ * Creates the device nodes.
+ *
+ * @returns 0 on success, negated errno on failure.
+ */
+static int __init vgdrvLinuxInitDeviceNodes(void)
+{
+ /*
+ * The full feature device node.
+ */
+ int rc = misc_register(&g_MiscDevice);
+ if (!rc)
+ {
+ /*
+ * The device node intended to be accessible by all users.
+ */
+ rc = misc_register(&g_MiscDeviceUser);
+ if (!rc)
+ return 0;
+ LogRel((DEVICE_NAME ": misc_register failed for %s (rc=%d)\n", DEVICE_NAME_USER, rc));
+ misc_deregister(&g_MiscDevice);
+ }
+ else
+ LogRel((DEVICE_NAME ": misc_register failed for %s (rc=%d)\n", DEVICE_NAME, rc));
+ return rc;
+}
+
+
+/**
+ * Deregisters the device nodes.
+ */
+static void vgdrvLinuxTermDeviceNodes(void)
+{
+ misc_deregister(&g_MiscDevice);
+ misc_deregister(&g_MiscDeviceUser);
+}
+
+
+/**
+ * Initialize module.
+ *
+ * @returns appropriate status code.
+ */
+static int __init vgdrvLinuxModInit(void)
+{
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ PRTLOGGER pRelLogger;
+ int rc;
+
+ /*
+ * Initialize IPRT first.
+ */
+ rc = RTR0Init(0);
+ if (RT_FAILURE(rc))
+ {
+ printk(KERN_ERR DEVICE_NAME ": RTR0Init failed, rc=%d.\n", rc);
+ return -EINVAL;
+ }
+
+ /*
+ * Create the release log.
+ * (We do that here instead of common code because we want to log
+ * early failures using the LogRel macro.)
+ */
+ rc = RTLogCreate(&pRelLogger, 0 /* fFlags */, "all",
+ "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups,
+ RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER | RTLOGDEST_USER, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#if RTLNX_VER_MIN(2,6,0)
+ RTLogGroupSettings(pRelLogger, g_szLogGrp);
+ RTLogFlags(pRelLogger, g_szLogFlags);
+ RTLogDestinations(pRelLogger, g_szLogDst);
+#endif
+ RTLogRelSetDefaultInstance(pRelLogger);
+ }
+#if RTLNX_VER_MIN(2,6,0)
+ g_fLoggerCreated = true;
+#endif
+
+ /*
+ * Locate and initialize the PCI device.
+ */
+ rc = pci_register_driver(&g_PciDriver);
+ if (rc >= 0 && g_pPciDev)
+ {
+ /*
+ * Call the common device extension initializer.
+ */
+#if RTLNX_VER_MIN(2,6,0) && defined(RT_ARCH_X86)
+ VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26;
+#elif RTLNX_VER_MIN(2,6,0) && defined(RT_ARCH_AMD64)
+ VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux26_x64;
+#elif RTLNX_VER_MIN(2,4,0) && defined(RT_ARCH_X86)
+ VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux24;
+#elif RTLNX_VER_MIN(2,4,0) && defined(RT_ARCH_AMD64)
+ VBOXOSTYPE enmOSType = VBOXOSTYPE_Linux24_x64;
+#else
+# warning "huh? which arch + version is this?"
+ VBOXOSTYPE enmOsType = VBOXOSTYPE_Linux;
+#endif
+ rc = VGDrvCommonInitDevExt(&g_DevExt,
+ g_IOPortBase,
+ g_pvMMIOBase,
+ g_cbMMIO,
+ enmOSType,
+ VMMDEV_EVENT_MOUSE_POSITION_CHANGED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Register the interrupt service routine for it now that g_DevExt can handle IRQs.
+ */
+ rc = vgdrvLinuxInitISR();
+ if (rc >= 0) /** @todo r=bird: status check differs from that inside vgdrvLinuxInitISR. */
+ {
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+ /*
+ * Create the kernel session for this driver.
+ */
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &g_pKernelSession);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create the kernel input device.
+ */
+ rc = vgdrvLinuxCreateInputDevice();
+ if (rc >= 0)
+ {
+#endif
+ /*
+ * Read host configuration.
+ */
+ VGDrvCommonProcessOptionsFromHost(&g_DevExt);
+
+ /*
+ * Finally, create the device nodes.
+ */
+ rc = vgdrvLinuxInitDeviceNodes();
+ if (rc >= 0)
+ {
+ /* some useful information for the user but don't show this on the console */
+ LogRel((DEVICE_NAME ": Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n"));
+ LogRel((DEVICE_NAME ": misc device minor %d, IRQ %d, I/O port %RTiop, MMIO at %RHp (size 0x%x)\n",
+ g_MiscDevice.minor, g_pPciDev->irq, g_IOPortBase, g_MMIOPhysAddr, g_cbMMIO));
+ printk(KERN_DEBUG DEVICE_NAME ": Successfully loaded version "
+ VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (interface " RT_XSTR(VMMDEV_VERSION) ")\n");
+ return rc;
+ }
+
+ /* bail out */
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+ vgdrvLinuxTermInputDevice();
+ }
+ else
+ {
+ LogRel((DEVICE_NAME ": vboxguestCreateInputDevice failed with rc=%Rrc\n", rc));
+ rc = RTErrConvertFromErrno(rc);
+ }
+ VGDrvCommonCloseSession(&g_DevExt, g_pKernelSession);
+ }
+#endif
+ vgdrvLinuxTermISR();
+ }
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ }
+ else
+ {
+ LogRel((DEVICE_NAME ": VGDrvCommonInitDevExt failed with rc=%Rrc\n", rc));
+ rc = RTErrConvertFromErrno(rc);
+ }
+ }
+ else
+ {
+ LogRel((DEVICE_NAME ": PCI device not found, probably running on physical hardware.\n"));
+ rc = -ENODEV;
+ }
+ pci_unregister_driver(&g_PciDriver);
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+ RTLogDestroy(RTLogSetDefaultInstance(NULL));
+ RTR0Term();
+ return rc;
+}
+
+
+/**
+ * Unload the module.
+ */
+static void __exit vgdrvLinuxModExit(void)
+{
+ /*
+ * Inverse order of init.
+ */
+ vgdrvLinuxTermDeviceNodes();
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+ vgdrvLinuxTermInputDevice();
+ VGDrvCommonCloseSession(&g_DevExt, g_pKernelSession);
+#endif
+ vgdrvLinuxTermISR();
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ pci_unregister_driver(&g_PciDriver);
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+ RTLogDestroy(RTLogSetDefaultInstance(NULL));
+ RTR0Term();
+}
+
+
+/**
+ * Get the process user ID.
+ *
+ * @returns UID.
+ */
+DECLINLINE(RTUID) vgdrvLinuxGetUid(void)
+{
+#if RTLNX_VER_MIN(2,6,29)
+# if RTLNX_VER_MIN(3,5,0)
+ return from_kuid(current_user_ns(), current->cred->uid);
+# else
+ return current->cred->uid;
+# endif
+#else
+ return current->uid;
+#endif
+}
+
+
+/**
+ * Checks if the given group number is zero or not.
+ *
+ * @returns true / false.
+ * @param gid The group to check for.
+ */
+DECLINLINE(bool) vgdrvLinuxIsGroupZero(kgid_t gid)
+{
+#if RTLNX_VER_MIN(3,5,0)
+ return from_kgid(current_user_ns(), gid);
+#else
+ return gid == 0;
+#endif
+}
+
+
+/**
+ * Searches the effective group and supplementary groups for @a gid.
+ *
+ * @returns true if member, false if not.
+ * @param gid The group to check for.
+ */
+DECLINLINE(RTGID) vgdrvLinuxIsInGroupEff(kgid_t gid)
+{
+ return in_egroup_p(gid) != 0;
+}
+
+
+/**
+ * Check if we can positively or negatively determine that the process is
+ * running under a login on the physical machine console.
+ *
+ * Havne't found a good way to figure this out for graphical sessions, so this
+ * is mostly pointless. But let us try do what we can do.
+ *
+ * @returns VMMDEV_REQUESTOR_CON_XXX.
+ */
+static uint32_t vgdrvLinuxRequestorOnConsole(void)
+{
+ uint32_t fRet = VMMDEV_REQUESTOR_CON_DONT_KNOW;
+
+#if RTLNX_VER_MIN(2,6,28) /* First with tty_kref_put(). */
+ /*
+ * Check for tty0..63, ASSUMING that these are only used for the physical console.
+ */
+ struct tty_struct *pTty = get_current_tty();
+ if (pTty)
+ {
+# if RTLNX_VER_MIN(4,2,0)
+ const char *pszName = tty_name(pTty);
+# else
+ char szBuf[64];
+ const char *pszName = tty_name(pTty, szBuf);
+# endif
+ if ( pszName
+ && pszName[0] == 't'
+ && pszName[1] == 't'
+ && pszName[2] == 'y'
+ && RT_C_IS_DIGIT(pszName[3])
+ && ( pszName[4] == '\0'
+ || ( RT_C_IS_DIGIT(pszName[4])
+ && pszName[5] == '\0'
+ && (pszName[3] - '0') * 10 + (pszName[4] - '0') <= 63)) )
+ fRet = VMMDEV_REQUESTOR_CON_YES;
+ tty_kref_put(pTty);
+ }
+#endif
+
+ return fRet;
+}
+
+
+/**
+ * Device open. Called on open /dev/vboxdrv
+ *
+ * @param pInode Pointer to inode info structure.
+ * @param pFilp Associated file pointer.
+ */
+static int vgdrvLinuxOpen(struct inode *pInode, struct file *pFilp)
+{
+ int rc;
+ PVBOXGUESTSESSION pSession;
+ uint32_t fRequestor;
+ Log((DEVICE_NAME ": pFilp=%p pid=%d/%d %s\n", pFilp, RTProcSelf(), current->pid, current->comm));
+
+ /*
+ * Figure out the requestor flags.
+ * ASSUMES that the gid of /dev/vboxuser is what we should consider the special vbox group.
+ */
+ fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
+ if (vgdrvLinuxGetUid() == 0)
+ fRequestor |= VMMDEV_REQUESTOR_USR_ROOT;
+ else
+ fRequestor |= VMMDEV_REQUESTOR_USR_USER;
+ if (MINOR(pInode->i_rdev) == g_MiscDeviceUser.minor)
+ {
+ fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE;
+ if (!vgdrvLinuxIsGroupZero(pInode->i_gid) && vgdrvLinuxIsInGroupEff(pInode->i_gid))
+ fRequestor |= VMMDEV_REQUESTOR_GRP_VBOX;
+ }
+ fRequestor |= vgdrvLinuxRequestorOnConsole();
+
+ /*
+ * Call common code to create the user session. Associate it with
+ * the file so we can access it in the other methods.
+ */
+ rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession);
+ if (RT_SUCCESS(rc))
+ pFilp->private_data = pSession;
+
+ Log(("vgdrvLinuxOpen: g_DevExt=%p pSession=%p rc=%d/%d (pid=%d/%d %s)\n",
+ &g_DevExt, pSession, rc, vgdrvLinuxConvertToNegErrno(rc), RTProcSelf(), current->pid, current->comm));
+ return vgdrvLinuxConvertToNegErrno(rc);
+}
+
+
+/**
+ * Close device.
+ *
+ * @param pInode Pointer to inode info structure.
+ * @param pFilp Associated file pointer.
+ */
+static int vgdrvLinuxRelease(struct inode *pInode, struct file *pFilp)
+{
+ Log(("vgdrvLinuxRelease: pFilp=%p pSession=%p pid=%d/%d %s\n",
+ pFilp, pFilp->private_data, RTProcSelf(), current->pid, current->comm));
+
+#if RTLNX_VER_MAX(2,6,28)
+ /* This housekeeping was needed in older kernel versions to ensure that
+ * the file pointer didn't get left on the polling queue. */
+ vgdrvLinuxFAsync(-1, pFilp, 0);
+#endif
+ VGDrvCommonCloseSession(&g_DevExt, (PVBOXGUESTSESSION)pFilp->private_data);
+ pFilp->private_data = NULL;
+ return 0;
+}
+
+
+/**
+ * Device I/O Control entry point.
+ *
+ * @param pFilp Associated file pointer.
+ * @param uCmd The function specified to ioctl().
+ * @param ulArg The argument specified to ioctl().
+ */
+#if defined(HAVE_UNLOCKED_IOCTL) || defined(DOXYGEN_RUNNING)
+static long vgdrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg)
+#else
+static int vgdrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg)
+#endif
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFilp->private_data;
+ int rc;
+#ifndef HAVE_UNLOCKED_IOCTL
+ unlock_kernel();
+#endif
+
+#if 0 /* no fast I/O controls defined atm. */
+ if (RT_LIKELY( ( uCmd == SUP_IOCTL_FAST_DO_RAW_RUN
+ || uCmd == SUP_IOCTL_FAST_DO_HM_RUN
+ || uCmd == SUP_IOCTL_FAST_DO_NOP)
+ && pSession->fUnrestricted == true))
+ rc = VGDrvCommonIoCtlFast(uCmd, ulArg, &g_DevExt, pSession);
+ else
+#endif
+ rc = vgdrvLinuxIOCtlSlow(pFilp, uCmd, ulArg, pSession);
+
+#ifndef HAVE_UNLOCKED_IOCTL
+ lock_kernel();
+#endif
+ return rc;
+}
+
+
+/**
+ * Device I/O Control entry point, slow variant.
+ *
+ * @param pFilp Associated file pointer.
+ * @param uCmd The function specified to ioctl().
+ * @param ulArg The argument specified to ioctl().
+ * @param pSession The session instance.
+ */
+static int vgdrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PVBOXGUESTSESSION pSession)
+{
+ int rc;
+ VBGLREQHDR Hdr;
+ PVBGLREQHDR pHdr;
+ uint32_t cbBuf;
+
+ Log6(("vgdrvLinuxIOCtlSlow: pFilp=%p uCmd=%#x ulArg=%p pid=%d/%d\n", pFilp, uCmd, (void *)ulArg, RTProcSelf(), current->pid));
+
+ /*
+ * Read the header.
+ */
+ if (RT_FAILURE(RTR0MemUserCopyFrom(&Hdr, ulArg, sizeof(Hdr))))
+ {
+ Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx,) failed; uCmd=%#x\n", ulArg, uCmd));
+ return -EFAULT;
+ }
+ if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION))
+ {
+ Log(("vgdrvLinuxIOCtlSlow: bad header version %#x; uCmd=%#x\n", Hdr.uVersion, uCmd));
+ return -EINVAL;
+ }
+
+ /*
+ * Buffer the request.
+ * Note! The header is revalidated by the common code.
+ */
+ cbBuf = RT_MAX(Hdr.cbIn, Hdr.cbOut);
+ if (RT_UNLIKELY(cbBuf > _1M*16))
+ {
+ Log(("vgdrvLinuxIOCtlSlow: too big cbBuf=%#x; uCmd=%#x\n", cbBuf, uCmd));
+ return -E2BIG;
+ }
+ if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr)
+ || (cbBuf != _IOC_SIZE(uCmd) && _IOC_SIZE(uCmd) != 0)))
+ {
+ Log(("vgdrvLinuxIOCtlSlow: bad ioctl cbBuf=%#x _IOC_SIZE=%#x; uCmd=%#x\n", cbBuf, _IOC_SIZE(uCmd), uCmd));
+ return -EINVAL;
+ }
+ pHdr = RTMemAlloc(cbBuf);
+ if (RT_UNLIKELY(!pHdr))
+ {
+ LogRel(("vgdrvLinuxIOCtlSlow: failed to allocate buffer of %d bytes for uCmd=%#x\n", cbBuf, uCmd));
+ return -ENOMEM;
+ }
+ if (RT_FAILURE(RTR0MemUserCopyFrom(pHdr, ulArg, Hdr.cbIn)))
+ {
+ Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx, %#x) failed; uCmd=%#x\n", ulArg, Hdr.cbIn, uCmd));
+ RTMemFree(pHdr);
+ return -EFAULT;
+ }
+ if (Hdr.cbIn < cbBuf)
+ RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbBuf - Hdr.cbIn);
+
+ /*
+ * Process the IOCtl.
+ */
+ rc = VGDrvCommonIoCtl(uCmd, &g_DevExt, pSession, pHdr, cbBuf);
+
+ /*
+ * Copy ioctl data and output buffer back to user space.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbOut = pHdr->cbOut;
+ if (RT_UNLIKELY(cbOut > cbBuf))
+ {
+ LogRel(("vgdrvLinuxIOCtlSlow: too much output! %#x > %#x; uCmd=%#x!\n", cbOut, cbBuf, uCmd));
+ cbOut = cbBuf;
+ }
+ if (RT_FAILURE(RTR0MemUserCopyTo(ulArg, pHdr, cbOut)))
+ {
+ /* this is really bad! */
+ LogRel(("vgdrvLinuxIOCtlSlow: copy_to_user(%#lx,,%#x); uCmd=%#x!\n", ulArg, cbOut, uCmd));
+ rc = -EFAULT;
+ }
+ }
+ else
+ {
+ Log(("vgdrvLinuxIOCtlSlow: pFilp=%p uCmd=%#x ulArg=%p failed, rc=%d\n", pFilp, uCmd, (void *)ulArg, rc));
+ rc = -EINVAL;
+ }
+ RTMemFree(pHdr);
+
+ Log6(("vgdrvLinuxIOCtlSlow: returns %d (pid=%d/%d)\n", rc, RTProcSelf(), current->pid));
+ return rc;
+}
+
+
+/**
+ * @note This code is duplicated on other platforms with variations, so please
+ * keep them all up to date when making changes!
+ */
+int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq)
+{
+ /*
+ * Simple request validation (common code does the rest).
+ */
+ int rc;
+ if ( RT_VALID_PTR(pReqHdr)
+ && cbReq >= sizeof(*pReqHdr))
+ {
+ /*
+ * All requests except the connect one requires a valid session.
+ */
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession;
+ if (pSession)
+ {
+ if ( RT_VALID_PTR(pSession)
+ && pSession->pDevExt == &g_DevExt)
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else if (uReq == VBGL_IOCTL_IDC_CONNECT)
+ {
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ if (RT_FAILURE(rc))
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ }
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+ return rc;
+}
+EXPORT_SYMBOL(VBoxGuestIDC);
+
+
+/**
+ * Asynchronous notification activation method.
+ *
+ * @returns 0 on success, negative errno on failure.
+ *
+ * @param fd The file descriptor.
+ * @param pFile The file structure.
+ * @param fOn On/off indicator.
+ */
+static int vgdrvLinuxFAsync(int fd, struct file *pFile, int fOn)
+{
+ return fasync_helper(fd, pFile, fOn, &g_pFAsyncQueue);
+}
+
+
+/**
+ * Poll function.
+ *
+ * This returns ready to read if the mouse pointer mode or the pointer position
+ * has changed since last call to read.
+ *
+ * @returns 0 if no changes, POLLIN | POLLRDNORM if there are unseen changes.
+ *
+ * @param pFile The file structure.
+ * @param pPt The poll table.
+ *
+ * @remarks This is probably not really used, X11 is said to use the fasync
+ * interface instead.
+ */
+static unsigned int vgdrvLinuxPoll(struct file *pFile, poll_table *pPt)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFile->private_data;
+ uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+ unsigned int fMask = pSession->u32MousePosChangedSeq != u32CurSeq
+ ? POLLIN | POLLRDNORM
+ : 0;
+ poll_wait(pFile, &g_PollEventQueue, pPt);
+ return fMask;
+}
+
+
+/**
+ * Read to go with our poll/fasync response.
+ *
+ * @returns 1 or -EINVAL.
+ *
+ * @param pFile The file structure.
+ * @param pbBuf The buffer to read into.
+ * @param cbRead The max number of bytes to read.
+ * @param poff The current file position.
+ *
+ * @remarks This is probably not really used as X11 lets the driver do its own
+ * event reading. The poll condition is therefore also cleared when we
+ * see VMMDevReq_GetMouseStatus in vgdrvIoCtl_VMMRequest.
+ */
+static ssize_t vgdrvLinuxRead(struct file *pFile, char *pbBuf, size_t cbRead, loff_t *poff)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFile->private_data;
+ uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+
+ if (*poff != 0)
+ return -EINVAL;
+
+ /*
+ * Fake a single byte read if we're not up to date with the current mouse position.
+ */
+ if ( pSession->u32MousePosChangedSeq != u32CurSeq
+ && cbRead > 0)
+ {
+ pSession->u32MousePosChangedSeq = u32CurSeq;
+ pbBuf[0] = 0;
+ return 1;
+ }
+ return 0;
+}
+
+
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+/**
+ * Get host mouse state.
+ *
+ * @returns IPRT status code.
+ * @param pfMouseFeatures Where to store host mouse capabilities.
+ * @param pX Where to store X axis coordinate.
+ * @param pY Where to store Y axis coordinate.
+ * @param pDz Where to store vertical wheel movement offset (only set if in case of VMMDevReq_GetMouseStatusEx request).
+ * @param pDw Where to store horizontal wheel movement offset (only set if in case of VMMDevReq_GetMouseStatusEx request).
+ * @param pfButtons Where to store mouse buttons state (only set if in case of VMMDevReq_GetMouseStatusEx request).
+ */
+static int vgdrvLinuxGetHostMouseState(uint32_t *pfMouseFeatures, int32_t *pX, int32_t *pY, int32_t *pDz, int32_t *pDw, uint32_t *pfButtons)
+{
+ int rc = VERR_INVALID_PARAMETER;
+
+ Assert(pfMouseFeatures);
+ Assert(pX);
+ Assert(pY);
+ Assert(pDz);
+ Assert(pDw);
+ Assert(pfButtons);
+
+ /* Initialize legacy request data. */
+ g_pMouseStatusReqEx->Core.mouseFeatures = 0;
+ g_pMouseStatusReqEx->Core.pointerXPos = 0;
+ g_pMouseStatusReqEx->Core.pointerYPos = 0;
+
+ /* Initialize extended request data if VMMDevReq_GetMouseStatusEx is used. */
+ if (vgdrvLinuxUsesMouseStatusEx())
+ {
+ g_pMouseStatusReqEx->dz = 0;
+ g_pMouseStatusReqEx->dw = 0;
+ g_pMouseStatusReqEx->fButtons = 0;
+ }
+
+ /* Get host mouse state - either lagacy or extended version. */
+ rc = VbglR0GRPerform(&g_pMouseStatusReqEx->Core.header);
+ if (RT_SUCCESS(rc))
+ {
+ *pfMouseFeatures = g_pMouseStatusReqEx->Core.mouseFeatures;
+ *pX = g_pMouseStatusReqEx->Core.pointerXPos;
+ *pY = g_pMouseStatusReqEx->Core.pointerYPos;
+
+ /* Get extended mouse state data in case of VMMDevReq_GetMouseStatusEx. */
+ if (vgdrvLinuxUsesMouseStatusEx())
+ {
+ *pDz = g_pMouseStatusReqEx->dz;
+ *pDw = g_pMouseStatusReqEx->dw;
+ *pfButtons = g_pMouseStatusReqEx->fButtons;
+ }
+ }
+
+ return rc;
+}
+#endif /* VBOXGUEST_WITH_INPUT_DRIVER */
+
+
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+ int rc;
+ uint32_t fMouseFeatures = 0;
+ int32_t x = 0;
+ int32_t y = 0;
+ int32_t dz = 0;
+ int32_t dw = 0;
+ uint32_t fButtons = 0;
+#endif
+ NOREF(pDevExt);
+
+ /*
+ * Wake up everyone that's in a poll() and post anyone that has
+ * subscribed to async notifications.
+ */
+ Log3(("VGDrvNativeISRMousePollEvent: wake_up_all\n"));
+ wake_up_all(&g_PollEventQueue);
+ Log3(("VGDrvNativeISRMousePollEvent: kill_fasync\n"));
+ kill_fasync(&g_pFAsyncQueue, SIGIO, POLL_IN);
+#ifdef VBOXGUEST_WITH_INPUT_DRIVER
+ rc = vgdrvLinuxGetHostMouseState(&fMouseFeatures, &x, &y, &dz, &dw, &fButtons);
+ if (RT_SUCCESS(rc))
+ {
+ input_report_abs(g_pInputDevice, ABS_X, x);
+ input_report_abs(g_pInputDevice, ABS_Y, y);
+
+ if ( vgdrvLinuxUsesMouseStatusEx()
+ && fMouseFeatures & VMMDEV_MOUSE_HOST_USES_FULL_STATE_PROTOCOL
+ && fMouseFeatures & VMMDEV_MOUSE_GUEST_USES_FULL_STATE_PROTOCOL)
+ {
+ /* Vertical and horizontal scroll values come as-is from GUI.
+ * Invert values here as it is done in PS/2 mouse driver, so
+ * scrolling direction will be exectly the same. */
+ input_report_rel(g_pInputDevice, REL_WHEEL, -dz);
+ input_report_rel(g_pInputDevice, REL_HWHEEL, -dw);
+
+ input_report_key(g_pInputDevice, BTN_LEFT, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_LEFT));
+ input_report_key(g_pInputDevice, BTN_RIGHT, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_RIGHT));
+ input_report_key(g_pInputDevice, BTN_MIDDLE, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_MIDDLE));
+ input_report_key(g_pInputDevice, BTN_SIDE, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_X1));
+ input_report_key(g_pInputDevice, BTN_EXTRA, RT_BOOL(fButtons & VMMDEV_MOUSE_BUTTON_X2));
+ }
+
+# ifdef EV_SYN
+ input_sync(g_pInputDevice);
+# endif
+ }
+#endif
+ Log3(("VGDrvNativeISRMousePollEvent: done\n"));
+}
+
+
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+#if RTLNX_VER_MIN(2,6,0)
+
+/** log and dbg_log parameter setter. */
+static int vgdrvLinuxParamLogGrpSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam)
+{
+ if (g_fLoggerCreated)
+ {
+ PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance();
+ if (pLogger)
+ RTLogGroupSettings(pLogger, pszValue);
+ }
+ else if (pParam->name[0] != 'd')
+ strlcpy(&g_szLogGrp[0], pszValue, sizeof(g_szLogGrp));
+
+ return 0;
+}
+
+/** log and dbg_log parameter getter. */
+static int vgdrvLinuxParamLogGrpGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam)
+{
+ PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance();
+ *pszBuf = '\0';
+ if (pLogger)
+ RTLogQueryGroupSettings(pLogger, pszBuf, _4K);
+ return strlen(pszBuf);
+}
+
+
+/** log and dbg_log_flags parameter setter. */
+static int vgdrvLinuxParamLogFlagsSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam)
+{
+ if (g_fLoggerCreated)
+ {
+ PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance();
+ if (pLogger)
+ RTLogFlags(pLogger, pszValue);
+ }
+ else if (pParam->name[0] != 'd')
+ strlcpy(&g_szLogFlags[0], pszValue, sizeof(g_szLogFlags));
+ return 0;
+}
+
+/** log and dbg_log_flags parameter getter. */
+static int vgdrvLinuxParamLogFlagsGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam)
+{
+ PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance();
+ *pszBuf = '\0';
+ if (pLogger)
+ RTLogQueryFlags(pLogger, pszBuf, _4K);
+ return strlen(pszBuf);
+}
+
+
+/** log and dbg_log_dest parameter setter. */
+static int vgdrvLinuxParamLogDstSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam)
+{
+ if (g_fLoggerCreated)
+ {
+ PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance();
+ if (pLogger)
+ RTLogDestinations(pLogger, pszValue);
+ }
+ else if (pParam->name[0] != 'd')
+ strlcpy(&g_szLogDst[0], pszValue, sizeof(g_szLogDst));
+ return 0;
+}
+
+/** log and dbg_log_dest parameter getter. */
+static int vgdrvLinuxParamLogDstGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam)
+{
+ PRTLOGGER pLogger = pParam->name[0] == 'd' ? RTLogDefaultInstance() : RTLogRelGetDefaultInstance();
+ *pszBuf = '\0';
+ if (pLogger)
+ RTLogQueryDestinations(pLogger, pszBuf, _4K);
+ return strlen(pszBuf);
+}
+
+
+/** r3_log_to_host parameter setter. */
+static int vgdrvLinuxParamR3LogToHostSet(const char *pszValue, CONST_4_15 struct kernel_param *pParam)
+{
+ g_DevExt.fLoggingEnabled = VBDrvCommonIsOptionValueTrue(pszValue);
+ return 0;
+}
+
+/** r3_log_to_host parameter getter. */
+static int vgdrvLinuxParamR3LogToHostGet(char *pszBuf, CONST_4_15 struct kernel_param *pParam)
+{
+ strcpy(pszBuf, g_DevExt.fLoggingEnabled ? "enabled" : "disabled");
+ return strlen(pszBuf);
+}
+
+
+/*
+ * Define module parameters.
+ */
+module_param_call(log, vgdrvLinuxParamLogGrpSet, vgdrvLinuxParamLogGrpGet, NULL, 0664);
+module_param_call(log_flags, vgdrvLinuxParamLogFlagsSet, vgdrvLinuxParamLogFlagsGet, NULL, 0664);
+module_param_call(log_dest, vgdrvLinuxParamLogDstSet, vgdrvLinuxParamLogDstGet, NULL, 0664);
+# ifdef LOG_ENABLED
+module_param_call(dbg_log, vgdrvLinuxParamLogGrpSet, vgdrvLinuxParamLogGrpGet, NULL, 0664);
+module_param_call(dbg_log_flags, vgdrvLinuxParamLogFlagsSet, vgdrvLinuxParamLogFlagsGet, NULL, 0664);
+module_param_call(dbg_log_dest, vgdrvLinuxParamLogDstSet, vgdrvLinuxParamLogDstGet, NULL, 0664);
+# endif
+module_param_call(r3_log_to_host, vgdrvLinuxParamR3LogToHostSet, vgdrvLinuxParamR3LogToHostGet, NULL, 0664);
+
+#endif /* 2.6.0 and later */
+
+
+module_init(vgdrvLinuxModInit);
+module_exit(vgdrvLinuxModExit);
+
+MODULE_AUTHOR(VBOX_VENDOR);
+MODULE_DESCRIPTION(VBOX_PRODUCT " Guest Additions for Linux Module");
+MODULE_LICENSE("GPL");
+#ifdef MODULE_VERSION
+MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV));
+#endif
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c
new file mode 100644
index 00000000..a7c8c028
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-netbsd.c
@@ -0,0 +1,1094 @@
+/* $Id: VBoxGuest-netbsd.c $ */
+/** @file
+ * VirtualBox Guest Additions Driver for NetBSD.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/select.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/kmem.h>
+#include <sys/module.h>
+#include <sys/device.h>
+#include <sys/bus.h>
+#include <sys/poll.h>
+#include <sys/proc.h>
+#include <sys/kauth.h>
+#include <sys/stat.h>
+#include <sys/selinfo.h>
+#include <sys/queue.h>
+#include <sys/lock.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/malloc.h>
+#include <sys/uio.h>
+#include <sys/file.h>
+#include <sys/filedesc.h>
+#include <sys/vfs_syscalls.h>
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcidevs.h>
+
+#include <dev/wscons/wsconsio.h>
+#include <dev/wscons/wsmousevar.h>
+#include <dev/wscons/tpcalibvar.h>
+
+#ifdef PVM
+# undef PVM
+#endif
+#include "VBoxGuestInternal.h"
+#include <VBox/log.h>
+#include <iprt/err.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/process.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The module name. */
+#define DEVICE_NAME "vboxguest"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct VBoxGuestDeviceState
+{
+ device_t sc_dev;
+ pci_chipset_tag_t sc_pc;
+
+ bus_space_tag_t sc_iot;
+ bus_space_handle_t sc_ioh;
+ bus_addr_t sc_iobase;
+ bus_size_t sc_iosize;
+
+ bus_space_tag_t sc_memt;
+ bus_space_handle_t sc_memh;
+
+ /** Size of the memory area. */
+ bus_size_t sc_memsize;
+
+ /** IRQ resource handle. */
+ pci_intr_handle_t ih;
+ /** Pointer to the IRQ handler. */
+ void *pfnIrqHandler;
+
+ /** Controller features, limits and status. */
+ u_int vboxguest_state;
+
+ device_t sc_wsmousedev;
+ VMMDevReqMouseStatus *sc_vmmmousereq;
+ PVBOXGUESTSESSION sc_session;
+ struct tpcalib_softc sc_tpcalib;
+} vboxguest_softc;
+
+
+struct vboxguest_fdata
+{
+ vboxguest_softc *sc;
+ PVBOXGUESTSESSION session;
+};
+
+#define VBOXGUEST_STATE_INITOK 1 << 0
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+/*
+ * Driver(9) autoconf machinery.
+ */
+static int VBoxGuestNetBSDMatch(device_t parent, cfdata_t match, void *aux);
+static void VBoxGuestNetBSDAttach(device_t parent, device_t self, void *aux);
+static void VBoxGuestNetBSDWsmAttach(vboxguest_softc *sc);
+static int VBoxGuestNetBSDDetach(device_t self, int flags);
+
+/*
+ * IRQ related functions.
+ */
+static int VBoxGuestNetBSDAddIRQ(vboxguest_softc *sc, struct pci_attach_args *pa);
+static void VBoxGuestNetBSDRemoveIRQ(vboxguest_softc *sc);
+static int VBoxGuestNetBSDISR(void *pvState);
+
+/*
+ * Character device file handlers.
+ */
+static int VBoxGuestNetBSDOpen(dev_t device, int flags, int fmt, struct lwp *process);
+static int VBoxGuestNetBSDClose(struct file *fp);
+static int VBoxGuestNetBSDIOCtl(struct file *fp, u_long cmd, void *addr);
+static int VBoxGuestNetBSDIOCtlSlow(struct vboxguest_fdata *fdata, u_long command, void *data);
+static int VBoxGuestNetBSDPoll(struct file *fp, int events);
+
+/*
+ * wsmouse(4) accessops
+ */
+static int VBoxGuestNetBSDWsmEnable(void *cookie);
+static void VBoxGuestNetBSDWsmDisable(void *cookie);
+static int VBoxGuestNetBSDWsmIOCtl(void *cookie, u_long cmd, void *data, int flag, struct lwp *l);
+
+static int VBoxGuestNetBSDSetMouseStatus(vboxguest_softc *sc, uint32_t fStatus);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+extern struct cfdriver vboxguest_cd; /* CFDRIVER_DECL */
+extern struct cfattach vboxguest_ca; /* CFATTACH_DECL */
+
+/*
+ * The /dev/vboxguest character device entry points.
+ */
+static struct cdevsw g_VBoxGuestNetBSDChrDevSW =
+{
+ .d_open = VBoxGuestNetBSDOpen,
+ .d_close = noclose,
+ .d_read = noread,
+ .d_write = nowrite,
+ .d_ioctl = noioctl,
+ .d_stop = nostop,
+ .d_tty = notty,
+ .d_poll = nopoll,
+ .d_mmap = nommap,
+ .d_kqfilter = nokqfilter,
+};
+
+static const struct fileops vboxguest_fileops = {
+ .fo_read = fbadop_read,
+ .fo_write = fbadop_write,
+ .fo_ioctl = VBoxGuestNetBSDIOCtl,
+ .fo_fcntl = fnullop_fcntl,
+ .fo_poll = VBoxGuestNetBSDPoll,
+ .fo_stat = fbadop_stat,
+ .fo_close = VBoxGuestNetBSDClose,
+ .fo_kqfilter = fnullop_kqfilter,
+ .fo_restart = fnullop_restart
+};
+
+
+const struct wsmouse_accessops vboxguest_wsm_accessops = {
+ VBoxGuestNetBSDWsmEnable,
+ VBoxGuestNetBSDWsmIOCtl,
+ VBoxGuestNetBSDWsmDisable,
+};
+
+
+/*
+ * XXX: wsmux(4) doesn't properly handle the case when two mice with
+ * absolute position events but different calibration data are being
+ * multiplexed. Without GAs the absolute events will be reported
+ * through the tablet ums(4) device with the range of 32k, but with
+ * GAs the absolute events will be reported through the VMM device
+ * (wsmouse at vboxguest) and VMM uses the range of 64k. Which one
+ * responds to the calibration ioctl depends on the order of
+ * attachment. On boot kernel attaches ums first and GAs later, so
+ * it's VMM (this driver) that gets the ioctl. After save/restore the
+ * ums will be detached and re-attached and after that it's ums that
+ * will get the ioctl, but the events (with a wider range) will still
+ * come via the VMM, confusing X, wsmoused, etc. Hack around that by
+ * forcing the range here to match the tablet's range.
+ *
+ * We force VMM range into the ums range and rely on the fact that no
+ * actual calibration is done and both devices are used in the raw
+ * mode. See tpcalib_trans call below.
+ *
+ * Cf. src/VBox/Devices/Input/UsbMouse.cpp
+ */
+#define USB_TABLET_RANGE_MIN 0
+#define USB_TABLET_RANGE_MAX 0x7fff
+
+static struct wsmouse_calibcoords vboxguest_wsm_default_calib = {
+ .minx = USB_TABLET_RANGE_MIN, // VMMDEV_MOUSE_RANGE_MIN,
+ .miny = USB_TABLET_RANGE_MIN, // VMMDEV_MOUSE_RANGE_MIN,
+ .maxx = USB_TABLET_RANGE_MAX, // VMMDEV_MOUSE_RANGE_MAX,
+ .maxy = USB_TABLET_RANGE_MAX, // VMMDEV_MOUSE_RANGE_MAX,
+ .samplelen = WSMOUSE_CALIBCOORDS_RESET,
+};
+
+/** Device extention & session data association structure. */
+static VBOXGUESTDEVEXT g_DevExt;
+
+static vboxguest_softc *g_SC;
+
+/** Reference counter */
+static volatile uint32_t cUsers;
+/** selinfo structure used for polling. */
+static struct selinfo g_SelInfo;
+
+
+CFATTACH_DECL_NEW(vboxguest, sizeof(vboxguest_softc),
+ VBoxGuestNetBSDMatch, VBoxGuestNetBSDAttach, VBoxGuestNetBSDDetach, NULL);
+
+
+static int VBoxGuestNetBSDMatch(device_t parent, cfdata_t match, void *aux)
+{
+ const struct pci_attach_args *pa = aux;
+
+ if (RT_UNLIKELY(g_SC != NULL)) /* should not happen */
+ return 0;
+
+ if ( PCI_VENDOR(pa->pa_id) == VMMDEV_VENDORID
+ && PCI_PRODUCT(pa->pa_id) == VMMDEV_DEVICEID)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static void VBoxGuestNetBSDAttach(device_t parent, device_t self, void *aux)
+{
+ int rc = VINF_SUCCESS;
+ int iResId = 0;
+ vboxguest_softc *sc;
+ struct pci_attach_args *pa = aux;
+ bus_space_tag_t iot, memt;
+ bus_space_handle_t ioh, memh;
+ bus_dma_segment_t seg;
+ int ioh_valid, memh_valid;
+
+ KASSERT(g_SC == NULL);
+
+ cUsers = 0;
+
+ aprint_normal(": VirtualBox Guest\n");
+
+ sc = device_private(self);
+ sc->sc_dev = self;
+
+ /*
+ * Initialize IPRT R0 driver, which internally calls OS-specific r0 init.
+ */
+ rc = RTR0Init(0);
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("RTR0Init failed.\n"));
+ aprint_error_dev(sc->sc_dev, "RTR0Init failed\n");
+ return;
+ }
+
+ sc->sc_pc = pa->pa_pc;
+
+ /*
+ * Allocate I/O port resource.
+ */
+ ioh_valid = (pci_mapreg_map(pa, PCI_BAR0,
+ PCI_MAPREG_TYPE_IO, 0,
+ &sc->sc_iot, &sc->sc_ioh,
+ &sc->sc_iobase, &sc->sc_iosize) == 0);
+
+ if (ioh_valid)
+ {
+
+ /*
+ * Map the MMIO region.
+ */
+ memh_valid = (pci_mapreg_map(pa, PCI_BAR1,
+ PCI_MAPREG_TYPE_MEM, BUS_SPACE_MAP_LINEAR,
+ &sc->sc_memt, &sc->sc_memh,
+ NULL, &sc->sc_memsize) == 0);
+ if (memh_valid)
+ {
+ /*
+ * Call the common device extension initializer.
+ */
+ rc = VGDrvCommonInitDevExt(&g_DevExt, sc->sc_iobase,
+ bus_space_vaddr(sc->sc_memt, sc->sc_memh),
+ sc->sc_memsize,
+#if ARCH_BITS == 64
+ VBOXOSTYPE_NetBSD_x64,
+#else
+ VBOXOSTYPE_NetBSD,
+#endif
+ VMMDEV_EVENT_MOUSE_POSITION_CHANGED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add IRQ of VMMDev.
+ */
+ rc = VBoxGuestNetBSDAddIRQ(sc, pa);
+ if (RT_SUCCESS(rc))
+ {
+ sc->vboxguest_state |= VBOXGUEST_STATE_INITOK;
+
+ /*
+ * Read host configuration.
+ */
+ VGDrvCommonProcessOptionsFromHost(&g_DevExt);
+
+ /*
+ * Attach wsmouse.
+ */
+ VBoxGuestNetBSDWsmAttach(sc);
+
+ g_SC = sc;
+ return;
+ }
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ }
+ else
+ {
+ aprint_error_dev(sc->sc_dev, "init failed\n");
+ }
+ bus_space_unmap(sc->sc_memt, sc->sc_memh, sc->sc_memsize);
+ }
+ else
+ {
+ aprint_error_dev(sc->sc_dev, "MMIO mapping failed\n");
+ }
+ bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_iosize);
+ }
+ else
+ {
+ aprint_error_dev(sc->sc_dev, "IO mapping failed\n");
+ }
+
+ RTR0Term();
+ return;
+}
+
+
+/**
+ * Sets IRQ for VMMDev.
+ *
+ * @returns NetBSD error code.
+ * @param sc Pointer to the state info structure.
+ * @param pa Pointer to the PCI attach arguments.
+ */
+static int VBoxGuestNetBSDAddIRQ(vboxguest_softc *sc, struct pci_attach_args *pa)
+{
+ int iResId = 0;
+ int rc = 0;
+ const char *intrstr;
+#if __NetBSD_Prereq__(6, 99, 39)
+ char intstrbuf[100];
+#endif
+
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ if (pci_intr_map(pa, &sc->ih))
+ {
+ aprint_error_dev(sc->sc_dev, "couldn't map interrupt.\n");
+ return VERR_DEV_IO_ERROR;
+ }
+
+ intrstr = pci_intr_string(sc->sc_pc, sc->ih
+#if __NetBSD_Prereq__(6, 99, 39)
+ , intstrbuf, sizeof(intstrbuf)
+#endif
+ );
+ aprint_normal_dev(sc->sc_dev, "interrupting at %s\n", intrstr);
+
+ sc->pfnIrqHandler = pci_intr_establish(sc->sc_pc, sc->ih, IPL_BIO, VBoxGuestNetBSDISR, sc);
+ if (sc->pfnIrqHandler == NULL)
+ {
+ aprint_error_dev(sc->sc_dev, "couldn't establish interrupt\n");
+ return VERR_DEV_IO_ERROR;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Optionally attach wsmouse(4) device as a child.
+ */
+static void VBoxGuestNetBSDWsmAttach(vboxguest_softc *sc)
+{
+ struct wsmousedev_attach_args am = { &vboxguest_wsm_accessops, sc };
+
+ PVBOXGUESTSESSION session = NULL;
+ VMMDevReqMouseStatus *req = NULL;
+ int rc;
+
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &session);
+ if (RT_FAILURE(rc))
+ goto fail;
+
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&req, sizeof(*req),
+ VMMDevReq_GetMouseStatus);
+ if (RT_FAILURE(rc))
+ goto fail;
+
+#if __NetBSD_Prereq__(9,99,88)
+ sc->sc_wsmousedev = config_found(sc->sc_dev, &am, wsmousedevprint,
+ CFARGS(.iattr = "wsmousedev"));
+#elif __NetBSD_Prereq__(9,99,82)
+ sc->sc_wsmousedev = config_found(sc->sc_dev, &am, wsmousedevprint,
+ CFARG_IATTR, "wsmousedev",
+ CFARG_EOL);
+#else
+ sc->sc_wsmousedev = config_found_ia(sc->sc_dev, "wsmousedev",
+ &am, wsmousedevprint);
+#endif
+
+ if (sc->sc_wsmousedev == NULL)
+ goto fail;
+
+ sc->sc_session = session;
+ sc->sc_vmmmousereq = req;
+
+ tpcalib_init(&sc->sc_tpcalib);
+ tpcalib_ioctl(&sc->sc_tpcalib, WSMOUSEIO_SCALIBCOORDS,
+ &vboxguest_wsm_default_calib, 0, 0);
+ return;
+
+ fail:
+ if (session != NULL)
+ VGDrvCommonCloseSession(&g_DevExt, session);
+ if (req != NULL)
+ VbglR0GRFree((VMMDevRequestHeader *)req);
+}
+
+
+static int VBoxGuestNetBSDDetach(device_t self, int flags)
+{
+ vboxguest_softc *sc;
+ sc = device_private(self);
+
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ if (cUsers > 0)
+ return EBUSY;
+
+ if ((sc->vboxguest_state & VBOXGUEST_STATE_INITOK) == 0)
+ return 0;
+
+ /*
+ * Reverse what we did in VBoxGuestNetBSDAttach.
+ */
+ if (sc->sc_vmmmousereq != NULL)
+ VbglR0GRFree((VMMDevRequestHeader *)sc->sc_vmmmousereq);
+
+ VBoxGuestNetBSDRemoveIRQ(sc);
+
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+
+ bus_space_unmap(sc->sc_memt, sc->sc_memh, sc->sc_memsize);
+ bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_iosize);
+
+ RTR0Term();
+
+ return config_detach_children(self, flags);
+}
+
+
+/**
+ * Removes IRQ for VMMDev.
+ *
+ * @param sc Opaque pointer to the state info structure.
+ */
+static void VBoxGuestNetBSDRemoveIRQ(vboxguest_softc *sc)
+{
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ if (sc->pfnIrqHandler)
+ {
+ pci_intr_disestablish(sc->sc_pc, sc->pfnIrqHandler);
+ }
+}
+
+
+/**
+ * Interrupt service routine.
+ *
+ * @returns Whether the interrupt was from VMMDev.
+ * @param pvState Opaque pointer to the device state.
+ */
+static int VBoxGuestNetBSDISR(void *pvState)
+{
+ LogFlow((DEVICE_NAME ": %s: pvState=%p\n", __func__, pvState));
+
+ bool fOurIRQ = VGDrvCommonISR(&g_DevExt);
+
+ return fOurIRQ ? 1 : 0;
+}
+
+
+/*
+ * Called by VGDrvCommonISR() if mouse position changed
+ */
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+ vboxguest_softc *sc = g_SC;
+
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ /*
+ * Wake up poll waiters.
+ */
+ selnotify(&g_SelInfo, 0, 0);
+
+ if (sc->sc_vmmmousereq != NULL) {
+ int x, y;
+ int rc;
+
+ sc->sc_vmmmousereq->mouseFeatures = 0;
+ sc->sc_vmmmousereq->pointerXPos = 0;
+ sc->sc_vmmmousereq->pointerYPos = 0;
+
+ rc = VbglR0GRPerform(&sc->sc_vmmmousereq->header);
+ if (RT_FAILURE(rc))
+ return;
+
+ /* XXX: see the comment for vboxguest_wsm_default_calib */
+ int rawx = (unsigned)sc->sc_vmmmousereq->pointerXPos >> 1;
+ int rawy = (unsigned)sc->sc_vmmmousereq->pointerYPos >> 1;
+ tpcalib_trans(&sc->sc_tpcalib, rawx, rawy, &x, &y);
+
+ wsmouse_input(sc->sc_wsmousedev,
+ 0, /* buttons */
+ x, y,
+ 0, 0, /* z, w */
+ WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
+ }
+}
+
+
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+static int VBoxGuestNetBSDSetMouseStatus(vboxguest_softc *sc, uint32_t fStatus)
+{
+ VBGLIOCSETMOUSESTATUS Req;
+ int rc;
+
+ VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS);
+ Req.u.In.fStatus = fStatus;
+ rc = VGDrvCommonIoCtl(VBGL_IOCTL_SET_MOUSE_STATUS,
+ &g_DevExt,
+ sc->sc_session,
+ &Req.Hdr, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ rc = Req.Hdr.rc;
+
+ return rc;
+}
+
+
+static int
+VBoxGuestNetBSDWsmEnable(void *cookie)
+{
+ vboxguest_softc *sc = cookie;
+ int rc;
+
+ rc = VBoxGuestNetBSDSetMouseStatus(sc, VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
+ | VMMDEV_MOUSE_NEW_PROTOCOL);
+ if (RT_FAILURE(rc))
+ return RTErrConvertToErrno(rc);
+
+ return 0;
+}
+
+
+static void
+VBoxGuestNetBSDWsmDisable(void *cookie)
+{
+ vboxguest_softc *sc = cookie;
+ VBoxGuestNetBSDSetMouseStatus(sc, 0);
+}
+
+
+static int
+VBoxGuestNetBSDWsmIOCtl(void *cookie, u_long cmd, void *data, int flag, struct lwp *l)
+{
+ vboxguest_softc *sc = cookie;
+
+ switch (cmd) {
+ case WSMOUSEIO_GTYPE:
+ *(u_int *)data = WSMOUSE_TYPE_TPANEL;
+ break;
+
+ case WSMOUSEIO_SCALIBCOORDS:
+ case WSMOUSEIO_GCALIBCOORDS:
+ return tpcalib_ioctl(&sc->sc_tpcalib, cmd, data, flag, l);
+
+ default:
+ return EPASSTHROUGH;
+ }
+ return 0;
+}
+
+
+/**
+ * File open handler
+ *
+ */
+static int VBoxGuestNetBSDOpen(dev_t device, int flags, int fmt, struct lwp *pLwp)
+{
+ vboxguest_softc *sc;
+ struct vboxguest_fdata *fdata;
+ file_t *fp;
+ int fd, error;
+
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ if ((sc = device_lookup_private(&vboxguest_cd, minor(device))) == NULL)
+ {
+ printf("device_lookup_private failed\n");
+ return (ENXIO);
+ }
+
+ if ((sc->vboxguest_state & VBOXGUEST_STATE_INITOK) == 0)
+ {
+ aprint_error_dev(sc->sc_dev, "device not configured\n");
+ return (ENXIO);
+ }
+
+ fdata = kmem_alloc(sizeof(*fdata), KM_SLEEP);
+ if (fdata != NULL)
+ {
+ fdata->sc = sc;
+
+ error = fd_allocfile(&fp, &fd);
+ if (error == 0)
+ {
+ /*
+ * Create a new session.
+ */
+ struct kauth_cred *pCred = pLwp->l_cred;
+ int fHaveCred = (pCred != NULL && pCred != NOCRED && pCred != FSCRED);
+ uint32_t fRequestor;
+ int fIsWheel;
+ int rc;
+
+ fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
+
+ /* uid */
+ if (fHaveCred && kauth_cred_geteuid(pCred) == (uid_t)0)
+ fRequestor |= VMMDEV_REQUESTOR_USR_ROOT;
+ else
+ fRequestor |= VMMDEV_REQUESTOR_USR_USER;
+
+ /* gid */
+ if (fHaveCred
+ && (kauth_cred_getegid(pCred) == (gid_t)0
+ || (kauth_cred_ismember_gid(pCred, 0, &fIsWheel) == 0
+ && fIsWheel)))
+ fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL;
+
+#if 0 /** @todo implement /dev/vboxuser */
+ if (!fUnrestricted)
+ fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE;
+#else
+ fRequestor |= VMMDEV_REQUESTOR_NO_USER_DEVICE;
+#endif
+
+ /** @todo can we find out if pLwp is on the console? */
+ fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW;
+
+ rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &fdata->session);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicIncU32(&cUsers);
+ return fd_clone(fp, fd, flags, &vboxguest_fileops, fdata);
+ }
+
+ aprint_error_dev(sc->sc_dev, "VBox session creation failed\n");
+ closef(fp); /* ??? */
+ error = RTErrConvertToErrno(rc);
+ }
+ kmem_free(fdata, sizeof(*fdata));
+ }
+ else
+ error = ENOMEM;
+ return error;
+}
+
+/**
+ * File close handler
+ *
+ */
+static int VBoxGuestNetBSDClose(struct file *fp)
+{
+ struct vboxguest_fdata *fdata = fp->f_data;
+ vboxguest_softc *sc = fdata->sc;
+
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ VGDrvCommonCloseSession(&g_DevExt, fdata->session);
+ ASMAtomicDecU32(&cUsers);
+
+ kmem_free(fdata, sizeof(*fdata));
+
+ return 0;
+}
+
+/**
+ * IOCTL handler
+ *
+ */
+static int VBoxGuestNetBSDIOCtl(struct file *fp, u_long command, void *data)
+{
+ struct vboxguest_fdata *fdata = fp->f_data;
+
+ if (VBGL_IOCTL_IS_FAST(command))
+ return VGDrvCommonIoCtlFast(command, &g_DevExt, fdata->session);
+
+ return VBoxGuestNetBSDIOCtlSlow(fdata, command, data);
+}
+
+static int VBoxGuestNetBSDIOCtlSlow(struct vboxguest_fdata *fdata, u_long command, void *data)
+{
+ vboxguest_softc *sc = fdata->sc;
+ size_t cbReq = IOCPARM_LEN(command);
+ PVBGLREQHDR pHdr = NULL;
+ void *pvUser = NULL;
+ int err, rc;
+
+ LogFlow(("%s: command=%#lx data=%p\n", __func__, command, data));
+
+ /*
+ * Buffered request?
+ */
+ if ((command & IOC_DIRMASK) == IOC_INOUT)
+ {
+ /* will be validated by VGDrvCommonIoCtl() */
+ pHdr = (PVBGLREQHDR)data;
+ }
+
+ /*
+ * Big unbuffered request? "data" is the userland pointer.
+ */
+ else if ((command & IOC_DIRMASK) == IOC_VOID && cbReq != 0)
+ {
+ /*
+ * Read the header, validate it and figure out how much that
+ * needs to be buffered.
+ */
+ VBGLREQHDR Hdr;
+
+ if (RT_UNLIKELY(cbReq < sizeof(Hdr)))
+ return ENOTTY;
+
+ pvUser = data;
+ err = copyin(pvUser, &Hdr, sizeof(Hdr));
+ if (RT_UNLIKELY(err != 0))
+ return err;
+
+ if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION))
+ return ENOTTY;
+
+ if (cbReq > 16 * _1M)
+ return EINVAL;
+
+ if (Hdr.cbOut == 0)
+ Hdr.cbOut = Hdr.cbIn;
+
+ if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) || Hdr.cbIn > cbReq
+ || Hdr.cbOut < sizeof(Hdr) || Hdr.cbOut > cbReq))
+ return EINVAL;
+
+ /*
+ * Allocate buffer and copy in the data.
+ */
+ cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut);
+
+ pHdr = (PVBGLREQHDR)RTMemTmpAlloc(cbReq);
+ if (RT_UNLIKELY(pHdr == NULL))
+ {
+ LogRel(("%s: command=%#lx data=%p: unable to allocate %zu bytes\n",
+ __func__, command, data, cbReq));
+ return ENOMEM;
+ }
+
+ err = copyin(pvUser, pHdr, Hdr.cbIn);
+ if (err != 0)
+ {
+ RTMemTmpFree(pHdr);
+ return err;
+ }
+
+ if (Hdr.cbIn < cbReq)
+ memset((uint8_t *)pHdr + Hdr.cbIn, '\0', cbReq - Hdr.cbIn);
+ }
+
+ /*
+ * Process the IOCtl.
+ */
+ rc = VGDrvCommonIoCtl(command, &g_DevExt, fdata->session, pHdr, cbReq);
+ if (RT_SUCCESS(rc))
+ {
+ err = 0;
+
+ /*
+ * If unbuffered, copy back the result before returning.
+ */
+ if (pvUser != NULL)
+ {
+ size_t cbOut = pHdr->cbOut;
+ if (cbOut > cbReq)
+ {
+ LogRel(("%s: command=%#lx data=%p: too much output: %zu > %zu\n",
+ __func__, command, data, cbOut, cbReq));
+ cbOut = cbReq;
+ }
+
+ err = copyout(pHdr, pvUser, cbOut);
+ RTMemTmpFree(pHdr);
+ }
+ }
+ else
+ {
+ LogRel(("%s: command=%#lx data=%p: error %Rrc\n",
+ __func__, command, data, rc));
+
+ if (pvUser != NULL)
+ RTMemTmpFree(pHdr);
+
+ err = RTErrConvertToErrno(rc);
+ }
+
+ return err;
+}
+
+static int VBoxGuestNetBSDPoll(struct file *fp, int events)
+{
+ struct vboxguest_fdata *fdata = fp->f_data;
+ vboxguest_softc *sc = fdata->sc;
+
+ int rc = 0;
+ int events_processed;
+
+ uint32_t u32CurSeq;
+
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+ if (fdata->session->u32MousePosChangedSeq != u32CurSeq)
+ {
+ events_processed = events & (POLLIN | POLLRDNORM);
+ fdata->session->u32MousePosChangedSeq = u32CurSeq;
+ }
+ else
+ {
+ events_processed = 0;
+
+ selrecord(curlwp, &g_SelInfo);
+ }
+
+ return events_processed;
+}
+
+
+/**
+ * @note This code is duplicated on other platforms with variations, so please
+ * keep them all up to date when making changes!
+ */
+int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq)
+{
+ /*
+ * Simple request validation (common code does the rest).
+ */
+ int rc;
+ if ( RT_VALID_PTR(pReqHdr)
+ && cbReq >= sizeof(*pReqHdr))
+ {
+ /*
+ * All requests except the connect one requires a valid session.
+ */
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession;
+ if (pSession)
+ {
+ if ( RT_VALID_PTR(pSession)
+ && pSession->pDevExt == &g_DevExt)
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else if (uReq == VBGL_IOCTL_IDC_CONNECT)
+ {
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ if (RT_FAILURE(rc))
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ }
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+ return rc;
+}
+
+
+MODULE(MODULE_CLASS_DRIVER, vboxguest, "pci");
+
+/*
+ * XXX: See netbsd/vboxguest.ioconf for the details.
+*/
+#if 0
+#include "ioconf.c"
+#else
+
+static const struct cfiattrdata wsmousedevcf_iattrdata = {
+ "wsmousedev", 1, {
+ { "mux", "0", 0 },
+ }
+};
+
+/* device vboxguest: wsmousedev */
+static const struct cfiattrdata * const vboxguest_attrs[] = { &wsmousedevcf_iattrdata, NULL };
+CFDRIVER_DECL(vboxguest, DV_DULL, vboxguest_attrs);
+
+static struct cfdriver * const cfdriver_ioconf_vboxguest[] = {
+ &vboxguest_cd, NULL
+};
+
+
+static const struct cfparent vboxguest_pspec = {
+ "pci", "pci", DVUNIT_ANY
+};
+static int vboxguest_loc[] = { -1, -1 };
+
+
+static const struct cfparent wsmousedev_pspec = {
+ "wsmousedev", "vboxguest", DVUNIT_ANY
+};
+static int wsmousedev_loc[] = { 0 };
+
+
+static struct cfdata cfdata_ioconf_vboxguest[] = {
+ /* vboxguest0 at pci? dev ? function ? */
+ {
+ .cf_name = "vboxguest",
+ .cf_atname = "vboxguest",
+ .cf_unit = 0, /* Only unit 0 is ever used */
+ .cf_fstate = FSTATE_NOTFOUND,
+ .cf_loc = vboxguest_loc,
+ .cf_flags = 0,
+ .cf_pspec = &vboxguest_pspec,
+ },
+
+ /* wsmouse* at vboxguest? */
+ { "wsmouse", "wsmouse", 0, FSTATE_STAR, wsmousedev_loc, 0, &wsmousedev_pspec },
+
+ { NULL, NULL, 0, 0, NULL, 0, NULL }
+};
+
+static struct cfattach * const vboxguest_cfattachinit[] = {
+ &vboxguest_ca, NULL
+};
+
+static const struct cfattachinit cfattach_ioconf_vboxguest[] = {
+ { "vboxguest", vboxguest_cfattachinit },
+ { NULL, NULL }
+};
+#endif
+
+
+static int
+vboxguest_modcmd(modcmd_t cmd, void *opaque)
+{
+ devmajor_t bmajor, cmajor;
+#if !__NetBSD_Prereq__(8,99,46)
+ register_t retval;
+#endif
+ int error;
+
+ LogFlow((DEVICE_NAME ": %s\n", __func__));
+
+ switch (cmd)
+ {
+ case MODULE_CMD_INIT:
+ error = config_init_component(cfdriver_ioconf_vboxguest,
+ cfattach_ioconf_vboxguest,
+ cfdata_ioconf_vboxguest);
+ if (error)
+ break;
+
+ bmajor = cmajor = NODEVMAJOR;
+ error = devsw_attach("vboxguest",
+ NULL, &bmajor,
+ &g_VBoxGuestNetBSDChrDevSW, &cmajor);
+ if (error)
+ {
+ if (error == EEXIST)
+ error = 0; /* maybe built-in ... improve eventually */
+ else
+ break;
+ }
+
+ error = do_sys_mknod(curlwp, "/dev/vboxguest",
+ 0666|S_IFCHR, makedev(cmajor, 0),
+#if !__NetBSD_Prereq__(8,99,46)
+ &retval,
+#endif
+ UIO_SYSSPACE);
+ if (error == EEXIST) {
+ error = 0;
+
+ /*
+ * Since NetBSD doesn't yet have a major reserved for
+ * vboxguest, the (first free) major we get will
+ * change when new devices are added, so an existing
+ * /dev/vboxguest may now point to some other device,
+ * creating confusion (tripped me up a few times).
+ */
+ aprint_normal("vboxguest: major %d:"
+ " check existing /dev/vboxguest\n", cmajor);
+ }
+ break;
+
+ case MODULE_CMD_FINI:
+ error = config_fini_component(cfdriver_ioconf_vboxguest,
+ cfattach_ioconf_vboxguest,
+ cfdata_ioconf_vboxguest);
+ if (error)
+ break;
+
+ devsw_detach(NULL, &g_VBoxGuestNetBSDChrDevSW);
+ break;
+
+ default:
+ return ENOTTY;
+ }
+ return error;
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp
new file mode 100644
index 00000000..10363a65
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.cpp
@@ -0,0 +1,698 @@
+/* $Id: VBoxGuest-os2.cpp $ */
+/** @file
+ * VBoxGuest - OS/2 specifics.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ * ---------------------------------------------------------------------------
+ * This code is based on:
+ *
+ * VBoxDrv - OS/2 specifics.
+ *
+ * Copyright (c) 2007-2012 knut st. osmundsen <bird-src-spam@anduin.net>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <os2ddk/bsekee.h>
+
+#include "VBoxGuestInternal.h"
+#include <VBox/version.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/log.h>
+#include <iprt/memobj.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/process.h>
+#include <iprt/spinlock.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Device extention & session data association structure.
+ */
+static VBOXGUESTDEVEXT g_DevExt;
+/** The memory object for the MMIO memory. */
+static RTR0MEMOBJ g_MemObjMMIO = NIL_RTR0MEMOBJ;
+/** The memory mapping object the MMIO memory. */
+static RTR0MEMOBJ g_MemMapMMIO = NIL_RTR0MEMOBJ;
+
+/** Spinlock protecting g_apSessionHashTab. */
+static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK;
+/** Hash table */
+static PVBOXGUESTSESSION g_apSessionHashTab[19];
+/** Calculates the index into g_apSessionHashTab.*/
+#define SESSION_HASH(sfn) ((sfn) % RT_ELEMENTS(g_apSessionHashTab))
+
+RT_C_DECLS_BEGIN
+/* Defined in VBoxGuestA-os2.asm */
+extern uint32_t g_PhysMMIOBase;
+extern uint32_t g_cbMMIO; /* 0 currently not set. */
+extern uint16_t g_IOPortBase;
+extern uint8_t g_bInterruptLine;
+extern uint8_t g_bPciBusNo;
+extern uint8_t g_bPciDevFunNo;
+extern RTFAR16 g_fpfnVBoxGuestOs2IDCService16;
+extern RTFAR16 g_fpfnVBoxGuestOs2IDCService16Asm;
+#ifdef DEBUG_READ
+/* (debugging) */
+extern uint16_t g_offLogHead;
+extern uint16_t volatile g_offLogTail;
+extern uint16_t const g_cchLogMax;
+extern char g_szLog[];
+#endif
+/* (init only:) */
+extern char g_szInitText[];
+extern uint16_t g_cchInitText;
+extern uint16_t g_cchInitTextMax;
+RT_C_DECLS_END
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int vgdrvOS2MapMemory(void);
+static VBOXOSTYPE vgdrvOS2DetectVersion(void);
+
+/* in VBoxGuestA-os2.asm */
+DECLASM(int) vgdrvOS2DevHlpSetIRQ(uint8_t bIRQ);
+
+
+/**
+ * 32-bit Ring-0 initialization.
+ *
+ * This is called from VBoxGuestA-os2.asm upon the first open call to the vboxgst$ device.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param pszArgs Pointer to the device arguments.
+ */
+DECLASM(int) vgdrvOS2Init(const char *pszArgs)
+{
+ //Log(("vgdrvOS2Init: pszArgs='%s' MMIO=0x%RX32 IOPort=0x%RX16 Int=%#x Bus=%#x Dev=%#x Fun=%d\n",
+ // pszArgs, g_PhysMMIOBase, g_IOPortBase, g_bInterruptLine, g_bPciBusNo, g_bPciDevFunNo >> 3, g_bPciDevFunNo & 7));
+
+ /*
+ * Initialize the runtime.
+ */
+ int rc = RTR0Init(0);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Process the command line.
+ */
+ bool fVerbose = true;
+ if (pszArgs)
+ {
+ char ch;
+ while ((ch = *pszArgs++) != '\0')
+ if (ch == '-' || ch == '/')
+ {
+ ch = *pszArgs++;
+ if (ch == 'Q' || ch == 'q')
+ fVerbose = false;
+ else if (ch == 'V' || ch == 'v')
+ fVerbose = true;
+ else if (ch == '\0')
+ break;
+ /*else: ignore stuff we don't know what is */
+ }
+ /* else: skip spaces and unknown stuff */
+ }
+
+ /*
+ * Map the MMIO memory if found.
+ */
+ rc = vgdrvOS2MapMemory();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize the device extension.
+ */
+ if (g_MemMapMMIO != NIL_RTR0MEMOBJ)
+ rc = VGDrvCommonInitDevExt(&g_DevExt, g_IOPortBase,
+ RTR0MemObjAddress(g_MemMapMMIO),
+ RTR0MemObjSize(g_MemMapMMIO),
+ vgdrvOS2DetectVersion(),
+ 0);
+ else
+ rc = VGDrvCommonInitDevExt(&g_DevExt, g_IOPortBase, NULL, 0, vgdrvOS2DetectVersion(), 0);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize the session hash table.
+ */
+ rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestOS2");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Configure the interrupt handler.
+ */
+ if (g_bInterruptLine)
+ {
+ rc = vgdrvOS2DevHlpSetIRQ(g_bInterruptLine);
+ if (rc)
+ {
+ Log(("vgdrvOS2DevHlpSetIRQ(%d) -> %d\n", g_bInterruptLine, rc));
+ rc = RTErrConvertFromOS2(rc);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read host configuration.
+ */
+ VGDrvCommonProcessOptionsFromHost(&g_DevExt);
+
+ /*
+ * Success
+ */
+ if (fVerbose)
+ {
+ strcpy(&g_szInitText[0],
+ "\r\n"
+ "VirtualBox Guest Additions Driver for OS/2 version " VBOX_VERSION_STRING "\r\n"
+ "Copyright (C) 2008-" VBOX_C_YEAR " " VBOX_VENDOR "\r\n");
+ g_cchInitText = strlen(&g_szInitText[0]);
+ }
+ Log(("vgdrvOS2Init: Successfully loaded\n%s", g_szInitText));
+ return VINF_SUCCESS;
+ }
+
+ g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: SetIrq failed for IRQ %#d, rc=%Rrc\n",
+ g_bInterruptLine, rc);
+ }
+ else
+ g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: RTSpinlockCreate failed, rc=%Rrc\n", rc);
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ }
+ else
+ g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: vgdrvOS2InitDevExt failed, rc=%Rrc\n", rc);
+
+ int rc2 = RTR0MemObjFree(g_MemObjMMIO, true /* fFreeMappings */); AssertRC(rc2);
+ g_MemObjMMIO = g_MemMapMMIO = NIL_RTR0MEMOBJ;
+ }
+ else
+ g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: VBoxGuestOS2MapMMIO failed, rc=%Rrc\n", rc);
+ RTR0Term();
+ }
+ else
+ g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxGuest.sys: RTR0Init failed, rc=%Rrc\n", rc);
+
+ RTLogBackdoorPrintf("vgdrvOS2Init: failed rc=%Rrc - %s", rc, &g_szInitText[0]);
+ return rc;
+}
+
+
+/**
+ * Maps the VMMDev memory.
+ *
+ * @returns VBox status code.
+ * @retval VERR_VERSION_MISMATCH The VMMDev memory didn't meet our expectations.
+ */
+static int vgdrvOS2MapMemory(void)
+{
+ const RTCCPHYS PhysMMIOBase = g_PhysMMIOBase;
+
+ /*
+ * Did we find any MMIO region (0 or NIL)?
+ */
+ if ( !PhysMMIOBase
+ || PhysMMIOBase == NIL_RTCCPHYS)
+ {
+ Assert(g_MemMapMMIO != NIL_RTR0MEMOBJ);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Create a physical memory object for it.
+ *
+ * Since we don't know the actual size (OS/2 doesn't at least), we make
+ * a qualified guess using the VMMDEV_RAM_SIZE.
+ */
+ size_t cb = RT_ALIGN_Z(VMMDEV_RAM_SIZE, PAGE_SIZE);
+ int rc = RTR0MemObjEnterPhys(&g_MemObjMMIO, PhysMMIOBase, cb, RTMEM_CACHE_POLICY_DONT_CARE);
+ if (RT_FAILURE(rc))
+ {
+ cb = _4K;
+ rc = RTR0MemObjEnterPhys(&g_MemObjMMIO, PhysMMIOBase, cb, RTMEM_CACHE_POLICY_DONT_CARE);
+ }
+ if (RT_FAILURE(rc))
+ {
+ Log(("vgdrvOS2MapMemory: RTR0MemObjEnterPhys(,%RCp,%zx) -> %Rrc\n", PhysMMIOBase, cb, rc));
+ return rc;
+ }
+
+ /*
+ * Map the object into kernel space.
+ *
+ * We want a normal mapping with normal caching, which good in two ways. First
+ * since the API doesn't have any flags indicating how the mapping should be cached.
+ * And second, because PGM doesn't necessarily respect the cache/writethru bits
+ * anyway for normal RAM.
+ */
+ rc = RTR0MemObjMapKernel(&g_MemMapMMIO, g_MemObjMMIO, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Validate the VMM memory.
+ */
+ VMMDevMemory *pVMMDev = (VMMDevMemory *)RTR0MemObjAddress(g_MemMapMMIO);
+ Assert(pVMMDev);
+ if ( pVMMDev->u32Version == VMMDEV_MEMORY_VERSION
+ && pVMMDev->u32Size >= 32 /* just for checking sanity */)
+ {
+ /*
+ * Did we hit the correct size? If not we'll have to
+ * redo the mapping using the correct size.
+ */
+ if (RT_ALIGN_32(pVMMDev->u32Size, PAGE_SIZE) == cb)
+ return VINF_SUCCESS;
+
+ Log(("vgdrvOS2MapMemory: Actual size %#RX32 (tried %#zx)\n", pVMMDev->u32Size, cb));
+ cb = RT_ALIGN_32(pVMMDev->u32Size, PAGE_SIZE);
+
+ rc = RTR0MemObjFree(g_MemObjMMIO, true); AssertRC(rc);
+ g_MemObjMMIO = g_MemMapMMIO = NIL_RTR0MEMOBJ;
+
+ rc = RTR0MemObjEnterPhys(&g_MemObjMMIO, PhysMMIOBase, cb, RTMEM_CACHE_POLICY_DONT_CARE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTR0MemObjMapKernel(&g_MemMapMMIO, g_MemObjMMIO, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ Log(("vgdrvOS2MapMemory: RTR0MemObjMapKernel [%RCp,%zx] -> %Rrc (2nd)\n", PhysMMIOBase, cb, rc));
+ }
+ else
+ Log(("vgdrvOS2MapMemory: RTR0MemObjEnterPhys(,%RCp,%zx) -> %Rrc (2nd)\n", PhysMMIOBase, cb, rc));
+ }
+ else
+ {
+ rc = VERR_VERSION_MISMATCH;
+ LogRel(("vgdrvOS2MapMemory: Bogus VMMDev memory; u32Version=%RX32 (expected %RX32) u32Size=%RX32\n",
+ pVMMDev->u32Version, VMMDEV_MEMORY_VERSION, pVMMDev->u32Size));
+ }
+ }
+ else
+ Log(("vgdrvOS2MapMemory: RTR0MemObjMapKernel [%RCp,%zx] -> %Rrc\n", PhysMMIOBase, cb, rc));
+
+ int rc2 = RTR0MemObjFree(g_MemObjMMIO, true /* fFreeMappings */); AssertRC(rc2);
+ g_MemObjMMIO = g_MemMapMMIO = NIL_RTR0MEMOBJ;
+ return rc;
+}
+
+
+/**
+ * Called fromn vgdrvOS2Init to determine which OS/2 version this is.
+ *
+ * @returns VBox OS/2 type.
+ */
+static VBOXOSTYPE vgdrvOS2DetectVersion(void)
+{
+ VBOXOSTYPE enmOSType = VBOXOSTYPE_OS2;
+
+#if 0 /** @todo dig up the version stuff from GIS later and verify that the numbers are actually decimal. */
+ unsigned uMajor, uMinor;
+ if (uMajor == 2)
+ {
+ if (uMinor >= 30 && uMinor < 40)
+ enmOSType = VBOXOSTYPE_OS2Warp3;
+ else if (uMinor >= 40 && uMinor < 45)
+ enmOSType = VBOXOSTYPE_OS2Warp4;
+ else if (uMinor >= 45 && uMinor < 50)
+ enmOSType = VBOXOSTYPE_OS2Warp45;
+ }
+#endif
+ return enmOSType;
+}
+
+
+DECLASM(int) vgdrvOS2Open(uint16_t sfn)
+{
+ int rc;
+ PVBOXGUESTSESSION pSession;
+
+ /*
+ * Create a new session.
+ */
+ uint32_t fRequestor = VMMDEV_REQUESTOR_USERMODE
+ | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN
+ | VMMDEV_REQUESTOR_USR_ROOT /* everyone is root on OS/2 */
+ | VMMDEV_REQUESTOR_GRP_WHEEL /* and their admins */
+ | VMMDEV_REQUESTOR_NO_USER_DEVICE /** @todo implement /dev/vboxuser? */
+ | VMMDEV_REQUESTOR_CON_DONT_KNOW; /** @todo check screen group/whatever of process to see if console */
+ rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ pSession->sfn = sfn;
+
+ /*
+ * Insert it into the hash table.
+ */
+ unsigned iHash = SESSION_HASH(sfn);
+ RTSpinlockAcquire(g_Spinlock);
+ pSession->pNextHash = g_apSessionHashTab[iHash];
+ g_apSessionHashTab[iHash] = pSession;
+ RTSpinlockRelease(g_Spinlock);
+ }
+
+ Log(("vgdrvOS2Open: g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, (int)RTProcSelf()));
+ return rc;
+}
+
+
+DECLASM(int) vgdrvOS2Close(uint16_t sfn)
+{
+ Log(("vgdrvOS2Close: pid=%d sfn=%d\n", (int)RTProcSelf(), sfn));
+
+ /*
+ * Remove from the hash table.
+ */
+ PVBOXGUESTSESSION pSession;
+ const RTPROCESS Process = RTProcSelf();
+ const unsigned iHash = SESSION_HASH(sfn);
+ RTSpinlockAcquire(g_Spinlock);
+
+ pSession = g_apSessionHashTab[iHash];
+ if (pSession)
+ {
+ if ( pSession->sfn == sfn
+ && pSession->Process == Process)
+ {
+ g_apSessionHashTab[iHash] = pSession->pNextHash;
+ pSession->pNextHash = NULL;
+ }
+ else
+ {
+ PVBOXGUESTSESSION pPrev = pSession;
+ pSession = pSession->pNextHash;
+ while (pSession)
+ {
+ if ( pSession->sfn == sfn
+ && pSession->Process == Process)
+ {
+ pPrev->pNextHash = pSession->pNextHash;
+ pSession->pNextHash = NULL;
+ break;
+ }
+
+ /* next */
+ pPrev = pSession;
+ pSession = pSession->pNextHash;
+ }
+ }
+ }
+ RTSpinlockRelease(g_Spinlock);
+ if (!pSession)
+ {
+ Log(("VBoxGuestIoctl: WHUT?!? pSession == NULL! This must be a mistake... pid=%d sfn=%d\n", (int)Process, sfn));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Close the session.
+ */
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ return 0;
+}
+
+
+DECLASM(int) vgdrvOS2IOCtlFast(uint16_t sfn, uint8_t iFunction, int32_t *prc)
+{
+ /*
+ * Find the session.
+ */
+ const RTPROCESS Process = RTProcSelf();
+ const unsigned iHash = SESSION_HASH(sfn);
+ PVBOXGUESTSESSION pSession;
+
+ RTSpinlockAcquire(g_Spinlock);
+ pSession = g_apSessionHashTab[iHash];
+ if (pSession && pSession->Process != Process)
+ {
+ do pSession = pSession->pNextHash;
+ while ( pSession
+ && ( pSession->sfn != sfn
+ || pSession->Process != Process));
+ }
+ RTSpinlockRelease(g_Spinlock);
+ if (RT_UNLIKELY(!pSession))
+ {
+ Log(("VBoxGuestIoctl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d\n", (int)Process));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Dispatch the fast IOCtl.
+ */
+ *prc = VGDrvCommonIoCtlFast(iFunction, &g_DevExt, pSession);
+ return 0;
+}
+
+
+/**
+ * 32-bit IDC service routine.
+ *
+ * @returns VBox status code.
+ * @param u32Session The session handle (PVBOXGUESTSESSION).
+ * @param iFunction The requested function.
+ * @param pReqHdr The input/output data buffer. The caller
+ * ensures that this cannot be swapped out, or that
+ * it's acceptable to take a page in fault in the
+ * current context. If the request doesn't take
+ * input or produces output, apssing NULL is okay.
+ * @param cbReq The size of the data buffer.
+ *
+ * @remark This is called from the 16-bit thunker as well as directly from the 32-bit clients.
+ */
+DECLASM(int) VGDrvOS2IDCService(uint32_t u32Session, unsigned iFunction, PVBGLREQHDR pReqHdr, size_t cbReq)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)u32Session;
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertMsgReturn(pSession->sfn == 0xffff, ("%RX16\n", pSession->sfn), VERR_INVALID_HANDLE);
+ AssertMsgReturn(pSession->pDevExt == &g_DevExt, ("%p != %p\n", pSession->pDevExt, &g_DevExt), VERR_INVALID_HANDLE);
+
+ return VGDrvCommonIoCtl(iFunction, &g_DevExt, pSession, pReqHdr, cbReq);
+}
+
+
+/**
+ * Worker for VBoxGuestOS2IDC, it creates the kernel session.
+ *
+ * @returns Pointer to the session.
+ */
+DECLASM(PVBOXGUESTSESSION) vgdrvOS2IDCConnect(void)
+{
+ PVBOXGUESTSESSION pSession;
+ int rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ pSession->sfn = 0xffff;
+ return pSession;
+ }
+ return NULL;
+}
+
+
+DECLASM(int) vgdrvOS2IOCtl(uint16_t sfn, uint8_t iCat, uint8_t iFunction, void *pvParm, void *pvData,
+ uint16_t *pcbParm, uint16_t *pcbData)
+{
+ /*
+ * Find the session.
+ */
+ const RTPROCESS Process = RTProcSelf();
+ const unsigned iHash = SESSION_HASH(sfn);
+ PVBOXGUESTSESSION pSession;
+
+ RTSpinlockAcquire(g_Spinlock);
+ pSession = g_apSessionHashTab[iHash];
+ if (pSession && pSession->Process != Process)
+ {
+ do pSession = pSession->pNextHash;
+ while ( pSession
+ && ( pSession->sfn != sfn
+ || pSession->Process != Process));
+ }
+ RTSpinlockRelease(g_Spinlock);
+ if (!pSession)
+ {
+ Log(("VBoxGuestIoctl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d\n", (int)Process));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Verify the category and dispatch the IOCtl.
+ *
+ * The IOCtl call uses the parameter buffer as generic data input/output
+ * buffer similar to the one unix ioctl buffer argument. While the data
+ * buffer is not used.
+ */
+ if (RT_LIKELY(iCat == VBGL_IOCTL_CATEGORY))
+ {
+ Log(("vgdrvOS2IOCtl: pSession=%p iFunction=%#x pvParm=%p pvData=%p *pcbParm=%d *pcbData=%d\n", pSession, iFunction, pvParm, pvData, *pcbParm, *pcbData));
+ if ( pvParm
+ && *pcbParm >= sizeof(VBGLREQHDR)
+ && *pcbData == 0)
+ {
+ /*
+ * Lock the buffer.
+ */
+ KernVMLock_t ParmLock;
+ int32_t rc = KernVMLock(VMDHL_WRITE, pvParm, *pcbParm, &ParmLock, (KernPageList_t *)-1, NULL);
+ if (rc == 0)
+ {
+ /*
+ * Process the IOCtl.
+ */
+ PVBGLREQHDR pReqHdr = (PVBGLREQHDR)pvParm;
+ rc = VGDrvCommonIoCtl(iFunction, &g_DevExt, pSession, pReqHdr, *pcbParm);
+
+ /*
+ * Unlock the buffer.
+ */
+ *pcbParm = RT_SUCCESS(rc) ? pReqHdr->cbOut : sizeof(*pReqHdr);
+ int rc2 = KernVMUnlock(&ParmLock);
+ AssertMsg(rc2 == 0, ("rc2=%d\n", rc2)); NOREF(rc2);
+
+ Log2(("vgdrvOS2IOCtl: returns %d\n", rc));
+ return rc;
+ }
+ AssertMsgFailed(("KernVMLock(VMDHL_WRITE, %p, %#x, &p, NULL, NULL) -> %d\n", pvParm, *pcbParm, &ParmLock, rc));
+ return VERR_LOCK_FAILED;
+ }
+ Log2(("vgdrvOS2IOCtl: returns VERR_INVALID_PARAMETER (iFunction=%#x)\n", iFunction));
+ return VERR_INVALID_PARAMETER;
+ }
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * 32-bit ISR, called by 16-bit assembly thunker in VBoxGuestA-os2.asm.
+ *
+ * @returns true if it's our interrupt, false it isn't.
+ */
+DECLASM(bool) vgdrvOS2ISR(void)
+{
+ Log(("vgdrvOS2ISR\n"));
+
+ return VGDrvCommonISR(&g_DevExt);
+}
+
+
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+ /* No polling on OS/2 */
+ NOREF(pDevExt);
+}
+
+
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+#ifdef DEBUG_READ /** @todo figure out this one once and for all... */
+
+/**
+ * Callback for writing to the log buffer.
+ *
+ * @returns number of bytes written.
+ * @param pvArg Unused.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+static DECLCALLBACK(size_t) vgdrvOS2LogOutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ size_t cchWritten = 0;
+ while (cbChars-- > 0)
+ {
+ const uint16_t offLogHead = g_offLogHead;
+ const uint16_t offLogHeadNext = (offLogHead + 1) & (g_cchLogMax - 1);
+ if (offLogHeadNext == g_offLogTail)
+ break; /* no */
+ g_szLog[offLogHead] = *pachChars++;
+ g_offLogHead = offLogHeadNext;
+ cchWritten++;
+ }
+ return cchWritten;
+}
+
+
+int SUPR0Printf(const char *pszFormat, ...)
+{
+ va_list va;
+
+#if 0 //def DEBUG_bird
+ va_start(va, pszFormat);
+ RTLogComPrintfV(pszFormat, va);
+ va_end(va);
+#endif
+
+ va_start(va, pszFormat);
+ int cch = RTLogFormatV(vgdrvOS2LogOutput, NULL, pszFormat, va);
+ va_end(va);
+
+ return cch;
+}
+
+#endif /* DEBUG_READ */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def
new file mode 100644
index 00000000..dde341fe
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-os2.def
@@ -0,0 +1,55 @@
+; $Id: VBoxGuest-os2.def $
+;; @file
+; VBoxGuest - OS/2 definition file.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+PHYSICAL DEVICE VBoxGst
+DESCRIPTION 'VirtualBox Guest Additions Driver for OS/2.'
+CODE PRELOAD EXECUTEREAD
+DATA PRELOAD
+; We're using wlink.exe, so this doesn't work.
+;SEGMENTS
+; DATA16 class 'FAR_DATA'
+; DATA16_INIT class 'FAR_DATA'
+;
+; CODE16 class 'CODE'
+; CODE16_INIT class 'CODE'
+;
+; CODE32 class 'CODE'
+; TEXT32 class 'CODE'
+;
+; DATA32 class 'DATA'
+; BSS32 class 'BSS'
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c
new file mode 100644
index 00000000..b28f9382
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.c
@@ -0,0 +1,1138 @@
+/* $Id: VBoxGuest-solaris.c $ */
+/** @file
+ * VirtualBox Guest Additions Driver for Solaris.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/conf.h>
+#include <sys/modctl.h>
+#include <sys/mutex.h>
+#include <sys/pci.h>
+#include <sys/stat.h>
+#include <sys/ddi.h>
+#include <sys/ddi_intr.h>
+#include <sys/sunddi.h>
+#include <sys/open.h>
+#include <sys/sunldi.h>
+#include <sys/policy.h>
+#include <sys/file.h>
+#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
+
+#include "VBoxGuestInternal.h"
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/process.h>
+#include <iprt/mem.h>
+#include <iprt/cdefs.h>
+#include <iprt/asm.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The module name. */
+#define DEVICE_NAME "vboxguest"
+/** The module description as seen in 'modinfo'. */
+#define DEVICE_DESC "VirtualBox GstDrv"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int vgdrvSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred);
+static int vgdrvSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred);
+static int vgdrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred);
+static int vgdrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred);
+static int vgdrvSolarisIOCtl(dev_t Dev, int iCmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal);
+static int vgdrvSolarisIOCtlSlow(PVBOXGUESTSESSION pSession, int iCmd, int Mode, intptr_t iArgs);
+static int vgdrvSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead);
+
+static int vgdrvSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppResult);
+static int vgdrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd);
+static int vgdrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd);
+static int vgdrvSolarisQuiesce(dev_info_t *pDip);
+
+static int vgdrvSolarisAddIRQ(dev_info_t *pDip);
+static void vgdrvSolarisRemoveIRQ(dev_info_t *pDip);
+static uint_t vgdrvSolarisHighLevelISR(caddr_t Arg);
+static uint_t vgdrvSolarisISR(caddr_t Arg);
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * cb_ops: for drivers that support char/block entry points
+ */
+static struct cb_ops g_vgdrvSolarisCbOps =
+{
+ vgdrvSolarisOpen,
+ vgdrvSolarisClose,
+ nodev, /* b strategy */
+ nodev, /* b dump */
+ nodev, /* b print */
+ vgdrvSolarisRead,
+ vgdrvSolarisWrite,
+ vgdrvSolarisIOCtl,
+ nodev, /* c devmap */
+ nodev, /* c mmap */
+ nodev, /* c segmap */
+ vgdrvSolarisPoll,
+ ddi_prop_op, /* property ops */
+ NULL, /* streamtab */
+ D_NEW | D_MP, /* compat. flag */
+ CB_REV /* revision */
+};
+
+/**
+ * dev_ops: for driver device operations
+ */
+static struct dev_ops g_vgdrvSolarisDevOps =
+{
+ DEVO_REV, /* driver build revision */
+ 0, /* ref count */
+ vgdrvSolarisGetInfo,
+ nulldev, /* identify */
+ nulldev, /* probe */
+ vgdrvSolarisAttach,
+ vgdrvSolarisDetach,
+ nodev, /* reset */
+ &g_vgdrvSolarisCbOps,
+ (struct bus_ops *)0,
+ nodev, /* power */
+ vgdrvSolarisQuiesce
+};
+
+/**
+ * modldrv: export driver specifics to the kernel
+ */
+static struct modldrv g_vgdrvSolarisModule =
+{
+ &mod_driverops, /* extern from kernel */
+ DEVICE_DESC " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV),
+ &g_vgdrvSolarisDevOps
+};
+
+/**
+ * modlinkage: export install/remove/info to the kernel
+ */
+static struct modlinkage g_vgdrvSolarisModLinkage =
+{
+ MODREV_1, /* loadable module system revision */
+ &g_vgdrvSolarisModule,
+ NULL /* terminate array of linkage structures */
+};
+
+/**
+ * State info for each open file handle.
+ */
+typedef struct
+{
+ /** Pointer to the session handle. */
+ PVBOXGUESTSESSION pSession;
+ /** The process reference for posting signals */
+ void *pvProcRef;
+} vboxguest_state_t;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Device handle (we support only one instance). */
+static dev_info_t *g_pDip = NULL;
+/** Opaque pointer to file-descriptor states */
+static void *g_pvgdrvSolarisState = NULL;
+/** Device extention & session data association structure. */
+static VBOXGUESTDEVEXT g_DevExt;
+/** IO port handle. */
+static ddi_acc_handle_t g_PciIOHandle;
+/** MMIO handle. */
+static ddi_acc_handle_t g_PciMMIOHandle;
+/** IO Port. */
+static uint16_t g_uIOPortBase;
+/** Address of the MMIO region.*/
+static caddr_t g_pMMIOBase;
+/** Size of the MMIO region. */
+static off_t g_cbMMIO;
+/** Pointer to an array of interrupt handles. */
+static ddi_intr_handle_t *g_pahIntrs;
+/** Handle to the soft interrupt. */
+static ddi_softint_handle_t g_hSoftIntr;
+/** The pollhead structure */
+static pollhead_t g_PollHead;
+/** The IRQ Mutex */
+static kmutex_t g_IrqMtx;
+/** The IRQ high-level Mutex. */
+static kmutex_t g_HighLevelIrqMtx;
+/** Whether soft-ints are setup. */
+static bool g_fSoftIntRegistered = false;
+
+/** Additional IPRT function we need to drag in for vboxfs. */
+PFNRT g_Deps[] =
+{
+ (PFNRT)RTErrConvertToErrno,
+};
+
+
+/**
+ * Kernel entry points
+ */
+int _init(void)
+{
+ /*
+ * Initialize IPRT R0 driver, which internally calls OS-specific r0 init.
+ */
+ int rc = RTR0Init(0);
+ if (RT_SUCCESS(rc))
+ {
+ PRTLOGGER pRelLogger;
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ rc = RTLogCreate(&pRelLogger, 0 /* fFlags */, "all",
+ "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups,
+ RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER, NULL);
+ if (RT_SUCCESS(rc))
+ RTLogRelSetDefaultInstance(pRelLogger);
+ else
+ cmn_err(CE_NOTE, "failed to initialize driver logging rc=%d!\n", rc);
+
+ /*
+ * Prevent module autounloading.
+ */
+ modctl_t *pModCtl = mod_getctl(&g_vgdrvSolarisModLinkage);
+ if (pModCtl)
+ pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD;
+ else
+ LogRel((DEVICE_NAME ": failed to disable autounloading!\n"));
+
+ rc = ddi_soft_state_init(&g_pvgdrvSolarisState, sizeof(vboxguest_state_t), 1);
+ if (!rc)
+ {
+ rc = mod_install(&g_vgdrvSolarisModLinkage);
+ if (rc)
+ ddi_soft_state_fini(&g_pvgdrvSolarisState);
+ }
+ }
+ else
+ {
+ cmn_err(CE_NOTE, "_init: RTR0Init failed. rc=%d\n", rc);
+ return EINVAL;
+ }
+
+ return rc;
+}
+
+
+int _fini(void)
+{
+ LogFlow((DEVICE_NAME ":_fini\n"));
+ int rc = mod_remove(&g_vgdrvSolarisModLinkage);
+ if (!rc)
+ ddi_soft_state_fini(&g_pvgdrvSolarisState);
+
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+ RTLogDestroy(RTLogSetDefaultInstance(NULL));
+
+ if (!rc)
+ RTR0Term();
+ return rc;
+}
+
+
+int _info(struct modinfo *pModInfo)
+{
+ /* LogFlow((DEVICE_NAME ":_info\n")); - Called too early, causing RTThreadPreemtIsEnabled warning. */
+ return mod_info(&g_vgdrvSolarisModLinkage, pModInfo);
+}
+
+
+/**
+ * Attach entry point, to attach a device to the system or resume it.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Attach type (ddi_attach_cmd_t)
+ *
+ * @return corresponding solaris error code.
+ */
+static int vgdrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd)
+{
+ LogFlow(("vgdrvSolarisAttach:\n"));
+ switch (enmCmd)
+ {
+ case DDI_ATTACH:
+ {
+ if (g_pDip)
+ {
+ LogRel(("vgdrvSolarisAttach: Only one instance supported.\n"));
+ return DDI_FAILURE;
+ }
+
+ /*
+ * Enable resources for PCI access.
+ */
+ ddi_acc_handle_t PciHandle;
+ int rc = pci_config_setup(pDip, &PciHandle);
+ if (rc == DDI_SUCCESS)
+ {
+ /*
+ * Map the register address space.
+ */
+ caddr_t baseAddr;
+ ddi_device_acc_attr_t deviceAttr;
+ deviceAttr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
+ deviceAttr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
+ deviceAttr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
+ deviceAttr.devacc_attr_access = DDI_DEFAULT_ACC;
+ rc = ddi_regs_map_setup(pDip, 1, &baseAddr, 0, 0, &deviceAttr, &g_PciIOHandle);
+ if (rc == DDI_SUCCESS)
+ {
+ /*
+ * Read size of the MMIO region.
+ */
+ g_uIOPortBase = (uintptr_t)baseAddr;
+ rc = ddi_dev_regsize(pDip, 2, &g_cbMMIO);
+ if (rc == DDI_SUCCESS)
+ {
+ rc = ddi_regs_map_setup(pDip, 2, &g_pMMIOBase, 0, g_cbMMIO, &deviceAttr, &g_PciMMIOHandle);
+ if (rc == DDI_SUCCESS)
+ {
+ /*
+ * Call the common device extension initializer.
+ */
+ rc = VGDrvCommonInitDevExt(&g_DevExt, g_uIOPortBase, g_pMMIOBase, g_cbMMIO,
+#if ARCH_BITS == 64
+ VBOXOSTYPE_Solaris_x64,
+#else
+ VBOXOSTYPE_Solaris,
+#endif
+ VMMDEV_EVENT_MOUSE_POSITION_CHANGED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add IRQ of VMMDev.
+ */
+ rc = vgdrvSolarisAddIRQ(pDip);
+ if (rc == DDI_SUCCESS)
+ {
+ /*
+ * Read host configuration.
+ */
+ VGDrvCommonProcessOptionsFromHost(&g_DevExt);
+
+ rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO,
+ 0 /* fFlags */);
+ if (rc == DDI_SUCCESS)
+ {
+ g_pDip = pDip;
+ pci_config_teardown(&PciHandle);
+ return DDI_SUCCESS;
+ }
+
+ LogRel((DEVICE_NAME "::Attach: ddi_create_minor_node failed.\n"));
+ vgdrvSolarisRemoveIRQ(pDip);
+ }
+ else
+ LogRel((DEVICE_NAME "::Attach: vgdrvSolarisAddIRQ failed.\n"));
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ }
+ else
+ LogRel((DEVICE_NAME "::Attach: VGDrvCommonInitDevExt failed.\n"));
+ ddi_regs_map_free(&g_PciMMIOHandle);
+ }
+ else
+ LogRel((DEVICE_NAME "::Attach: ddi_regs_map_setup for MMIO region failed.\n"));
+ }
+ else
+ LogRel((DEVICE_NAME "::Attach: ddi_dev_regsize for MMIO region failed.\n"));
+ ddi_regs_map_free(&g_PciIOHandle);
+ }
+ else
+ LogRel((DEVICE_NAME "::Attach: ddi_regs_map_setup for IOport failed.\n"));
+ pci_config_teardown(&PciHandle);
+ }
+ else
+ LogRel((DEVICE_NAME "::Attach: pci_config_setup failed rc=%d.\n", rc));
+ return DDI_FAILURE;
+ }
+
+ case DDI_RESUME:
+ {
+ /** @todo implement resume for guest driver. */
+ return DDI_SUCCESS;
+ }
+
+ default:
+ return DDI_FAILURE;
+ }
+}
+
+
+/**
+ * Detach entry point, to detach a device to the system or suspend it.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Attach type (ddi_attach_cmd_t)
+ *
+ * @return corresponding solaris error code.
+ */
+static int vgdrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd)
+{
+ LogFlow(("vgdrvSolarisDetach:\n"));
+ switch (enmCmd)
+ {
+ case DDI_DETACH:
+ {
+ vgdrvSolarisRemoveIRQ(pDip);
+ ddi_regs_map_free(&g_PciIOHandle);
+ ddi_regs_map_free(&g_PciMMIOHandle);
+ ddi_remove_minor_node(pDip, NULL);
+ VGDrvCommonDeleteDevExt(&g_DevExt);
+ g_pDip = NULL;
+ return DDI_SUCCESS;
+ }
+
+ case DDI_SUSPEND:
+ {
+ /** @todo implement suspend for guest driver. */
+ return DDI_SUCCESS;
+ }
+
+ default:
+ return DDI_FAILURE;
+ }
+}
+
+
+/**
+ * Quiesce entry point, called by solaris kernel for disabling the device from
+ * generating any interrupts or doing in-bound DMA.
+ *
+ * @param pDip The module structure instance.
+ *
+ * @return corresponding solaris error code.
+ */
+static int vgdrvSolarisQuiesce(dev_info_t *pDip)
+{
+ int rc = ddi_intr_disable(g_pahIntrs[0]);
+ if (rc != DDI_SUCCESS)
+ return DDI_FAILURE;
+
+ /** @todo What about HGCM/HGSMI touching guest-memory? */
+
+ return DDI_SUCCESS;
+}
+
+
+/**
+ * Info entry point, called by solaris kernel for obtaining driver info.
+ *
+ * @param pDip The module structure instance (do not use).
+ * @param enmCmd Information request type.
+ * @param pvArg Type specific argument.
+ * @param ppvResult Where to store the requested info.
+ *
+ * @return corresponding solaris error code.
+ */
+static int vgdrvSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult)
+{
+ LogFlow(("vgdrvSolarisGetInfo:\n"));
+
+ int rc = DDI_SUCCESS;
+ switch (enmCmd)
+ {
+ case DDI_INFO_DEVT2DEVINFO:
+ {
+ *ppvResult = (void *)g_pDip;
+ if (!*ppvResult)
+ rc = DDI_FAILURE;
+ break;
+ }
+
+ case DDI_INFO_DEVT2INSTANCE:
+ {
+ /* There can only be a single-instance of this driver and thus its instance number is 0. */
+ *ppvResult = (void *)0;
+ break;
+ }
+
+ default:
+ rc = DDI_FAILURE;
+ break;
+ }
+
+ NOREF(pvArg);
+ return rc;
+}
+
+
+/**
+ * User context entry points
+ *
+ * @remarks fFlags are the flags passed to open() or to ldi_open_by_name. In
+ * the latter case the FKLYR flag is added to indicate that the caller
+ * is a kernel component rather than user land.
+ */
+static int vgdrvSolarisOpen(dev_t *pDev, int fFlags, int fType, cred_t *pCred)
+{
+ int rc;
+ PVBOXGUESTSESSION pSession = NULL;
+
+ LogFlow(("vgdrvSolarisOpen:\n"));
+
+ /*
+ * Verify we are being opened as a character device.
+ */
+ if (fType != OTYP_CHR)
+ return EINVAL;
+
+ vboxguest_state_t *pState = NULL;
+ unsigned iOpenInstance;
+ for (iOpenInstance = 0; iOpenInstance < 4096; iOpenInstance++)
+ {
+ if ( !ddi_get_soft_state(g_pvgdrvSolarisState, iOpenInstance) /* faster */
+ && ddi_soft_state_zalloc(g_pvgdrvSolarisState, iOpenInstance) == DDI_SUCCESS)
+ {
+ pState = ddi_get_soft_state(g_pvgdrvSolarisState, iOpenInstance);
+ break;
+ }
+ }
+ if (!pState)
+ {
+ Log(("vgdrvSolarisOpen: too many open instances."));
+ return ENXIO;
+ }
+
+ /*
+ * Create a new session.
+ *
+ * Note! The devfs inode with the gid isn't readily available here, so we cannot easily
+ * to the vbox group detection like on linux. Read config instead?
+ */
+ if (!(fFlags & FKLYR))
+ {
+ uint32_t fRequestor = VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
+ if (crgetruid(pCred) == 0)
+ fRequestor |= VMMDEV_REQUESTOR_USR_ROOT;
+ else
+ fRequestor |= VMMDEV_REQUESTOR_USR_USER;
+ if (secpolicy_coreadm(pCred) == 0)
+ fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL;
+ /** @todo is there any way of detecting that the process belongs to someone on the physical console?
+ * secpolicy_console() [== PRIV_SYS_DEVICES] doesn't look quite right, or does it? */
+ fRequestor |= VMMDEV_REQUESTOR_CON_DONT_KNOW;
+ fRequestor |= VMMDEV_REQUESTOR_NO_USER_DEVICE; /** @todo implement vboxuser device node. */
+
+ rc = VGDrvCommonCreateUserSession(&g_DevExt, fRequestor, &pSession);
+ }
+ else
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ if (!(fFlags & FKLYR))
+ pState->pvProcRef = proc_ref();
+ else
+ pState->pvProcRef = NULL;
+ pState->pSession = pSession;
+ *pDev = makedevice(getmajor(*pDev), iOpenInstance);
+ Log(("vgdrvSolarisOpen: pSession=%p pState=%p pid=%d\n", pSession, pState, (int)RTProcSelf()));
+ return 0;
+ }
+
+ /* Failed, clean up. */
+ ddi_soft_state_free(g_pvgdrvSolarisState, iOpenInstance);
+
+ LogRel((DEVICE_NAME "::Open: VGDrvCommonCreateUserSession failed. rc=%d\n", rc));
+ return EFAULT;
+}
+
+
+static int vgdrvSolarisClose(dev_t Dev, int flag, int fType, cred_t *pCred)
+{
+ LogFlow(("vgdrvSolarisClose: pid=%d\n", (int)RTProcSelf()));
+
+ PVBOXGUESTSESSION pSession = NULL;
+ vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev));
+ if (!pState)
+ {
+ Log(("vgdrvSolarisClose: failed to get pState.\n"));
+ return EFAULT;
+ }
+
+ if (pState->pvProcRef != NULL)
+ {
+ proc_unref(pState->pvProcRef);
+ pState->pvProcRef = NULL;
+ }
+ pSession = pState->pSession;
+ pState->pSession = NULL;
+ Log(("vgdrvSolarisClose: pSession=%p pState=%p\n", pSession, pState));
+ ddi_soft_state_free(g_pvgdrvSolarisState, getminor(Dev));
+ if (!pSession)
+ {
+ Log(("vgdrvSolarisClose: failed to get pSession.\n"));
+ return EFAULT;
+ }
+
+ /*
+ * Close the session.
+ */
+ if (pSession)
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ return 0;
+}
+
+
+static int vgdrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred)
+{
+ LogFlow((DEVICE_NAME "::Read\n"));
+
+ vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev));
+ if (!pState)
+ {
+ Log((DEVICE_NAME "::Close: failed to get pState.\n"));
+ return EFAULT;
+ }
+
+ PVBOXGUESTSESSION pSession = pState->pSession;
+ uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+ if (pSession->u32MousePosChangedSeq != u32CurSeq)
+ pSession->u32MousePosChangedSeq = u32CurSeq;
+
+ return 0;
+}
+
+
+static int vgdrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred)
+{
+ LogFlow(("vgdrvSolarisWrite:\n"));
+ return 0;
+}
+
+
+/** @def IOCPARM_LEN
+ * Gets the length from the ioctl number.
+ * This is normally defined by sys/ioccom.h on BSD systems...
+ */
+#ifndef IOCPARM_LEN
+# define IOCPARM_LEN(x) ( ((x) >> 16) & IOCPARM_MASK )
+#endif
+
+
+/**
+ * Driver ioctl, an alternate entry point for this character driver.
+ *
+ * @param Dev Device number
+ * @param iCmd Operation identifier
+ * @param iArgs Arguments from user to driver
+ * @param Mode Information bitfield (read/write, address space etc.)
+ * @param pCred User credentials
+ * @param pVal Return value for calling process.
+ *
+ * @return corresponding solaris error code.
+ */
+static int vgdrvSolarisIOCtl(dev_t Dev, int iCmd, intptr_t iArgs, int Mode, cred_t *pCred, int *pVal)
+{
+ /*
+ * Get the session from the soft state item.
+ */
+ vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev));
+ if (!pState)
+ {
+ LogRel(("vgdrvSolarisIOCtl: no state data for %#x (%d)\n", Dev, getminor(Dev)));
+ return EINVAL;
+ }
+
+ PVBOXGUESTSESSION pSession = pState->pSession;
+ if (!pSession)
+ {
+ LogRel(("vgdrvSolarisIOCtl: no session in state data for %#x (%d)\n", Dev, getminor(Dev)));
+ return DDI_SUCCESS;
+ }
+
+ /*
+ * Deal with fast requests.
+ */
+ if (VBGL_IOCTL_IS_FAST(iCmd))
+ {
+ *pVal = VGDrvCommonIoCtlFast(iCmd, &g_DevExt, pSession);
+ return 0;
+ }
+
+ /*
+ * It's kind of simple if this is a kernel session, take slow path if user land.
+ */
+ if (pSession->R0Process == NIL_RTR0PROCESS)
+ {
+ if (IOCPARM_LEN(iCmd) == sizeof(VBGLREQHDR))
+ {
+ PVBGLREQHDR pHdr = (PVBGLREQHDR)iArgs;
+ int rc;
+ if (iCmd != VBGL_IOCTL_IDC_DISCONNECT)
+ rc =VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, RT_MAX(pHdr->cbIn, pHdr->cbOut));
+ else
+ {
+ pState->pSession = NULL;
+ rc = VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, RT_MAX(pHdr->cbIn, pHdr->cbOut));
+ if (RT_FAILURE(rc))
+ pState->pSession = pSession;
+ }
+ return rc;
+ }
+ }
+
+ return vgdrvSolarisIOCtlSlow(pSession, iCmd, Mode, iArgs);
+}
+
+
+/**
+ * Worker for VBoxSupDrvIOCtl that takes the slow IOCtl functions.
+ *
+ * @returns Solaris errno.
+ *
+ * @param pSession The session.
+ * @param iCmd The IOCtl command.
+ * @param Mode Information bitfield (for specifying ownership of data)
+ * @param iArg User space address of the request buffer.
+ */
+static int vgdrvSolarisIOCtlSlow(PVBOXGUESTSESSION pSession, int iCmd, int Mode, intptr_t iArg)
+{
+ int rc;
+ uint32_t cbBuf = 0;
+ union
+ {
+ VBGLREQHDR Hdr;
+ uint8_t abBuf[64];
+ } StackBuf;
+ PVBGLREQHDR pHdr;
+
+
+ /*
+ * Read the header.
+ */
+ if (RT_UNLIKELY(IOCPARM_LEN(iCmd) != sizeof(StackBuf.Hdr)))
+ {
+ LogRel(("vgdrvSolarisIOCtlSlow: iCmd=%#x len %d expected %d\n", iCmd, IOCPARM_LEN(iCmd), sizeof(StackBuf.Hdr)));
+ return EINVAL;
+ }
+ rc = ddi_copyin((void *)iArg, &StackBuf.Hdr, sizeof(StackBuf.Hdr), Mode);
+ if (RT_UNLIKELY(rc))
+ {
+ LogRel(("vgdrvSolarisIOCtlSlow: ddi_copyin(,%#lx,) failed; iCmd=%#x. rc=%d\n", iArg, iCmd, rc));
+ return EFAULT;
+ }
+ if (RT_UNLIKELY(StackBuf.Hdr.uVersion != VBGLREQHDR_VERSION))
+ {
+ LogRel(("vgdrvSolarisIOCtlSlow: bad header version %#x; iCmd=%#x\n", StackBuf.Hdr.uVersion, iCmd));
+ return EINVAL;
+ }
+ cbBuf = RT_MAX(StackBuf.Hdr.cbIn, StackBuf.Hdr.cbOut);
+ if (RT_UNLIKELY( StackBuf.Hdr.cbIn < sizeof(StackBuf.Hdr)
+ || (StackBuf.Hdr.cbOut < sizeof(StackBuf.Hdr) && StackBuf.Hdr.cbOut != 0)
+ || cbBuf > _1M*16))
+ {
+ LogRel(("vgdrvSolarisIOCtlSlow: max(%#x,%#x); iCmd=%#x\n", StackBuf.Hdr.cbIn, StackBuf.Hdr.cbOut, iCmd));
+ return EINVAL;
+ }
+
+ /*
+ * Buffer the request.
+ *
+ * Note! Common code revalidates the header sizes and version. So it's
+ * fine to read it once more.
+ */
+ if (cbBuf <= sizeof(StackBuf))
+ pHdr = &StackBuf.Hdr;
+ else
+ {
+ pHdr = RTMemTmpAlloc(cbBuf);
+ if (RT_UNLIKELY(!pHdr))
+ {
+ LogRel(("vgdrvSolarisIOCtlSlow: failed to allocate buffer of %d bytes for iCmd=%#x.\n", cbBuf, iCmd));
+ return ENOMEM;
+ }
+ }
+ rc = ddi_copyin((void *)iArg, pHdr, cbBuf, Mode);
+ if (RT_UNLIKELY(rc))
+ {
+ LogRel(("vgdrvSolarisIOCtlSlow: copy_from_user(,%#lx, %#x) failed; iCmd=%#x. rc=%d\n", iArg, cbBuf, iCmd, rc));
+ if (pHdr != &StackBuf.Hdr)
+ RTMemFree(pHdr);
+ return EFAULT;
+ }
+
+ /*
+ * Process the IOCtl.
+ */
+ rc = VGDrvCommonIoCtl(iCmd, &g_DevExt, pSession, pHdr, cbBuf);
+
+ /*
+ * Copy ioctl data and output buffer back to user space.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbOut = pHdr->cbOut;
+ if (RT_UNLIKELY(cbOut > cbBuf))
+ {
+ LogRel(("vgdrvSolarisIOCtlSlow: too much output! %#x > %#x; iCmd=%#x!\n", cbOut, cbBuf, iCmd));
+ cbOut = cbBuf;
+ }
+ rc = ddi_copyout(pHdr, (void *)iArg, cbOut, Mode);
+ if (RT_UNLIKELY(rc != 0))
+ {
+ /* this is really bad */
+ LogRel(("vgdrvSolarisIOCtlSlow: ddi_copyout(,%p,%d) failed. rc=%d\n", (void *)iArg, cbBuf, rc));
+ rc = EFAULT;
+ }
+ }
+ else
+ rc = EINVAL;
+
+ if (pHdr != &StackBuf.Hdr)
+ RTMemTmpFree(pHdr);
+ return rc;
+}
+
+
+#if 0
+/**
+ * @note This code is duplicated on other platforms with variations, so please
+ * keep them all up to date when making changes!
+ */
+int VBOXCALL VBoxGuestIDC(void *pvSession, uintptr_t uReq, PVBGLREQHDR pReqHdr, size_t cbReq)
+{
+ /*
+ * Simple request validation (common code does the rest).
+ */
+ int rc;
+ if ( RT_VALID_PTR(pReqHdr)
+ && cbReq >= sizeof(*pReqHdr))
+ {
+ /*
+ * All requests except the connect one requires a valid session.
+ */
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pvSession;
+ if (pSession)
+ {
+ if ( RT_VALID_PTR(pSession)
+ && pSession->pDevExt == &g_DevExt)
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else if (uReq == VBGL_IOCTL_IDC_CONNECT)
+ {
+ rc = VGDrvCommonCreateKernelSession(&g_DevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VGDrvCommonIoCtl(uReq, &g_DevExt, pSession, pReqHdr, cbReq);
+ if (RT_FAILURE(rc))
+ VGDrvCommonCloseSession(&g_DevExt, pSession);
+ }
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+ return rc;
+}
+#endif
+
+
+static int vgdrvSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead)
+{
+ LogFlow(("vgdrvSolarisPoll: fEvents=%d fAnyYet=%d\n", fEvents, fAnyYet));
+
+ vboxguest_state_t *pState = ddi_get_soft_state(g_pvgdrvSolarisState, getminor(Dev));
+ if (RT_LIKELY(pState))
+ {
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pState->pSession;
+ uint32_t u32CurSeq = ASMAtomicUoReadU32(&g_DevExt.u32MousePosChangedSeq);
+ if (pSession->u32MousePosChangedSeq != u32CurSeq)
+ {
+ *pReqEvents |= (POLLIN | POLLRDNORM);
+ pSession->u32MousePosChangedSeq = u32CurSeq;
+ }
+ else
+ {
+ *pReqEvents = 0;
+ if (!fAnyYet)
+ *ppPollHead = &g_PollHead;
+ }
+
+ return 0;
+ }
+
+ Log(("vgdrvSolarisPoll: no state data for %d\n", getminor(Dev)));
+ return EINVAL;
+}
+
+
+/**
+ * Sets IRQ for VMMDev.
+ *
+ * @returns Solaris error code.
+ * @param pDip Pointer to the device info structure.
+ */
+static int vgdrvSolarisAddIRQ(dev_info_t *pDip)
+{
+ LogFlow(("vgdrvSolarisAddIRQ: pDip=%p\n", pDip));
+
+ /* Get the types of interrupt supported for this hardware. */
+ int fIntrType = 0;
+ int rc = ddi_intr_get_supported_types(pDip, &fIntrType);
+ if (rc == DDI_SUCCESS)
+ {
+ /* We only support fixed interrupts at this point, not MSIs. */
+ if (fIntrType & DDI_INTR_TYPE_FIXED)
+ {
+ /* Verify the number of interrupts supported by this device. There can only be one fixed interrupt. */
+ int cIntrCount = 0;
+ rc = ddi_intr_get_nintrs(pDip, fIntrType, &cIntrCount);
+ if ( rc == DDI_SUCCESS
+ && cIntrCount == 1)
+ {
+ /* Allocated kernel memory for the interrupt handle. The allocation size is stored internally. */
+ g_pahIntrs = RTMemAllocZ(cIntrCount * sizeof(ddi_intr_handle_t));
+ if (g_pahIntrs)
+ {
+ /* Allocate the interrupt for this device and verify the allocation. */
+ int cIntrAllocated;
+ rc = ddi_intr_alloc(pDip, g_pahIntrs, fIntrType, 0 /* interrupt number */, cIntrCount, &cIntrAllocated,
+ DDI_INTR_ALLOC_NORMAL);
+ if ( rc == DDI_SUCCESS
+ && cIntrAllocated == 1)
+ {
+ /* Get the interrupt priority assigned by the system. */
+ uint_t uIntrPriority;
+ rc = ddi_intr_get_pri(g_pahIntrs[0], &uIntrPriority);
+ if (rc == DDI_SUCCESS)
+ {
+ /* Check if the interrupt priority is scheduler level or above, if so we need to use a high-level
+ and low-level interrupt handlers with corresponding mutexes. */
+ cmn_err(CE_CONT, "!vboxguest: uIntrPriority=%d hilevel_pri=%d\n", uIntrPriority, ddi_intr_get_hilevel_pri());
+ if (uIntrPriority >= ddi_intr_get_hilevel_pri())
+ {
+ /* Initialize the high-level mutex. */
+ mutex_init(&g_HighLevelIrqMtx, NULL /* pszDesc */, MUTEX_DRIVER, DDI_INTR_PRI(uIntrPriority));
+
+ /* Assign interrupt handler function to the interrupt handle. */
+ rc = ddi_intr_add_handler(g_pahIntrs[0], (ddi_intr_handler_t *)&vgdrvSolarisHighLevelISR,
+ NULL /* pvArg1 */, NULL /* pvArg2 */);
+
+ if (rc == DDI_SUCCESS)
+ {
+ /* Add the low-level interrupt handler. */
+ rc = ddi_intr_add_softint(pDip, &g_hSoftIntr, DDI_INTR_SOFTPRI_MAX,
+ (ddi_intr_handler_t *)&vgdrvSolarisISR, NULL /* pvArg1 */);
+ if (rc == DDI_SUCCESS)
+ {
+ /* Initialize the low-level mutex at the corresponding level. */
+ mutex_init(&g_IrqMtx, NULL /* pszDesc */, MUTEX_DRIVER,
+ DDI_INTR_PRI(DDI_INTR_SOFTPRI_MAX));
+
+ g_fSoftIntRegistered = true;
+ /* Enable the high-level interrupt. */
+ rc = ddi_intr_enable(g_pahIntrs[0]);
+ if (rc == DDI_SUCCESS)
+ return rc;
+
+ LogRel((DEVICE_NAME "::AddIRQ: failed to enable interrupt. rc=%d\n", rc));
+ mutex_destroy(&g_IrqMtx);
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: failed to add soft interrupt handler. rc=%d\n", rc));
+
+ ddi_intr_remove_handler(g_pahIntrs[0]);
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: failed to add high-level interrupt handler. rc=%d\n", rc));
+
+ mutex_destroy(&g_HighLevelIrqMtx);
+ }
+ else
+ {
+ /* Interrupt handler runs at reschedulable level, initialize the mutex at the given priority. */
+ mutex_init(&g_IrqMtx, NULL /* pszDesc */, MUTEX_DRIVER, DDI_INTR_PRI(uIntrPriority));
+
+ /* Assign interrupt handler function to the interrupt handle. */
+ rc = ddi_intr_add_handler(g_pahIntrs[0], (ddi_intr_handler_t *)vgdrvSolarisISR,
+ NULL /* pvArg1 */, NULL /* pvArg2 */);
+ if (rc == DDI_SUCCESS)
+ {
+ /* Enable the interrupt. */
+ rc = ddi_intr_enable(g_pahIntrs[0]);
+ if (rc == DDI_SUCCESS)
+ return rc;
+
+ LogRel((DEVICE_NAME "::AddIRQ: failed to enable interrupt. rc=%d\n", rc));
+ mutex_destroy(&g_IrqMtx);
+ }
+ }
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: failed to get priority of interrupt. rc=%d\n", rc));
+
+ Assert(cIntrAllocated == 1);
+ ddi_intr_free(g_pahIntrs[0]);
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: failed to allocated IRQs. count=%d\n", cIntrCount));
+ RTMemFree(g_pahIntrs);
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: failed to allocated IRQs. count=%d\n", cIntrCount));
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: failed to get or insufficient number of IRQs. rc=%d cIntrCount=%d\n", rc, cIntrCount));
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: fixed-type interrupts not supported. IntrType=%#x\n", fIntrType));
+ }
+ else
+ LogRel((DEVICE_NAME "::AddIRQ: failed to get supported interrupt types. rc=%d\n", rc));
+ return rc;
+}
+
+
+/**
+ * Removes IRQ for VMMDev.
+ *
+ * @param pDip Pointer to the device info structure.
+ */
+static void vgdrvSolarisRemoveIRQ(dev_info_t *pDip)
+{
+ LogFlow(("vgdrvSolarisRemoveIRQ:\n"));
+
+ int rc = ddi_intr_disable(g_pahIntrs[0]);
+ if (rc == DDI_SUCCESS)
+ {
+ rc = ddi_intr_remove_handler(g_pahIntrs[0]);
+ if (rc == DDI_SUCCESS)
+ ddi_intr_free(g_pahIntrs[0]);
+ }
+
+ if (g_fSoftIntRegistered)
+ {
+ ddi_intr_remove_softint(g_hSoftIntr);
+ mutex_destroy(&g_HighLevelIrqMtx);
+ g_fSoftIntRegistered = false;
+ }
+
+ mutex_destroy(&g_IrqMtx);
+ RTMemFree(g_pahIntrs);
+}
+
+
+/**
+ * High-level Interrupt Service Routine for VMMDev.
+ *
+ * This routine simply dispatches a soft-interrupt at an acceptable IPL as
+ * VGDrvCommonISR() cannot be called at a high IPL (scheduler level or higher)
+ * due to pollwakeup() in VGDrvNativeISRMousePollEvent().
+ *
+ * @param Arg Private data (unused, will be NULL).
+ * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't.
+ */
+static uint_t vgdrvSolarisHighLevelISR(caddr_t Arg)
+{
+ bool const fOurIrq = VGDrvCommonIsOurIRQ(&g_DevExt);
+ if (fOurIrq)
+ {
+ ddi_intr_trigger_softint(g_hSoftIntr, NULL /* Arg */);
+ return DDI_INTR_CLAIMED;
+ }
+ return DDI_INTR_UNCLAIMED;
+}
+
+
+/**
+ * Interrupt Service Routine for VMMDev.
+ *
+ * @param Arg Private data (unused, will be NULL).
+ * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't.
+ */
+static uint_t vgdrvSolarisISR(caddr_t Arg)
+{
+ LogFlow(("vgdrvSolarisISR:\n"));
+
+ /* The mutex is required to protect against parallel executions (if possible?) and also the
+ mouse notify registeration race between VGDrvNativeSetMouseNotifyCallback() and VGDrvCommonISR(). */
+ mutex_enter(&g_IrqMtx);
+ bool fOurIRQ = VGDrvCommonISR(&g_DevExt);
+ mutex_exit(&g_IrqMtx);
+
+ return fOurIRQ ? DDI_INTR_CLAIMED : DDI_INTR_UNCLAIMED;
+}
+
+
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+ LogFlow(("VGDrvNativeISRMousePollEvent:\n"));
+
+ /*
+ * Wake up poll waiters.
+ */
+ pollwakeup(&g_PollHead, POLLIN | POLLRDNORM);
+}
+
+
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+/**
+ * Sets the mouse notification callback.
+ *
+ * @returns VBox status code.
+ * @param pDevExt Pointer to the device extension.
+ * @param pNotify Pointer to the mouse notify struct.
+ */
+int VGDrvNativeSetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify)
+{
+ /* Take the mutex here so as to not race with VGDrvCommonISR() which invokes the mouse notify callback. */
+ mutex_enter(&g_IrqMtx);
+ pDevExt->pfnMouseNotifyCallback = pNotify->u.In.pfnNotify;
+ pDevExt->pvMouseNotifyCallbackArg = pNotify->u.In.pvUser;
+ mutex_exit(&g_IrqMtx);
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf
new file mode 100644
index 00000000..91a245bf
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf
@@ -0,0 +1,41 @@
+# $Id: VBoxGuest-solaris.conf $
+## @file
+# OpenSolaris Guest Driver Configuration
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# This needs to go into /platform/i86pc/kernel/drv,
+# while the 64-bit driver object goes into the amd64
+# subdirectory (32-bit drivers goes into the same
+# directory).
+#
+name="vboxguest" parent="/pci@0,0/pci80ee,cafe" instance=0;
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp
new file mode 100644
index 00000000..c9e180af
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest-win.cpp
@@ -0,0 +1,3503 @@
+/* $Id: VBoxGuest-win.cpp $ */
+/** @file
+ * VBoxGuest - Windows specifics.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SUP_DRV
+#include <iprt/nt/nt.h>
+
+#include "VBoxGuestInternal.h"
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/log.h>
+
+#include <iprt/asm.h>
+#include <iprt/asm-amd64-x86.h>
+#include <iprt/critsect.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/memobj.h>
+#include <iprt/mem.h>
+#include <iprt/mp.h>
+#include <iprt/spinlock.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+
+#ifdef TARGET_NT4
+# include <VBox/pci.h>
+# define PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS32_IPRT
+# include <iprt/formats/mz.h>
+# include <iprt/formats/pecoff.h>
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#undef ExFreePool
+
+#ifndef PCI_MAX_BUSES
+# define PCI_MAX_BUSES 256
+#endif
+
+/** CM_RESOURCE_MEMORY_* flags which were used on XP or earlier. */
+#define VBOX_CM_PRE_VISTA_MASK (0x3f)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Possible device states for our state machine.
+ */
+typedef enum VGDRVNTDEVSTATE
+{
+ /** @name Stable states
+ * @{ */
+ VGDRVNTDEVSTATE_REMOVED = 0,
+ VGDRVNTDEVSTATE_STOPPED,
+ VGDRVNTDEVSTATE_OPERATIONAL,
+ /** @} */
+
+ /** @name Transitional states
+ * @{ */
+ VGDRVNTDEVSTATE_PENDINGSTOP,
+ VGDRVNTDEVSTATE_PENDINGREMOVE,
+ VGDRVNTDEVSTATE_SURPRISEREMOVED
+ /** @} */
+} VGDRVNTDEVSTATE;
+
+
+/**
+ * Subclassing the device extension for adding windows-specific bits.
+ */
+typedef struct VBOXGUESTDEVEXTWIN
+{
+ /** The common device extension core. */
+ VBOXGUESTDEVEXT Core;
+
+ /** Our functional driver object. */
+ PDEVICE_OBJECT pDeviceObject;
+ /** Top of the stack. */
+ PDEVICE_OBJECT pNextLowerDriver;
+
+ /** @name PCI bus and slot (device+function) set by for legacy NT only.
+ * @{ */
+ /** Bus number where the device is located. */
+ ULONG uBus;
+ /** Slot number where the device is located (PCI_SLOT_NUMBER). */
+ ULONG uSlot;
+ /** @} */
+
+ /** @name Interrupt stuff.
+ * @{ */
+ /** Interrupt object pointer. */
+ PKINTERRUPT pInterruptObject;
+ /** Device interrupt level. */
+ ULONG uInterruptLevel;
+ /** Device interrupt vector. */
+ ULONG uInterruptVector;
+ /** Affinity mask. */
+ KAFFINITY fInterruptAffinity;
+ /** LevelSensitive or Latched. */
+ KINTERRUPT_MODE enmInterruptMode;
+ /** @} */
+
+ /** Physical address and length of VMMDev memory. */
+ PHYSICAL_ADDRESS uVmmDevMemoryPhysAddr;
+ /** Length of VMMDev memory. */
+ ULONG cbVmmDevMemory;
+
+ /** Device state. */
+ VGDRVNTDEVSTATE volatile enmDevState;
+ /** The previous stable device state. */
+ VGDRVNTDEVSTATE enmPrevDevState;
+
+ /** Last system power action set (see VBoxGuestPower). */
+ POWER_ACTION enmLastSystemPowerAction;
+ /** Preallocated generic request for shutdown. */
+ VMMDevPowerStateRequest *pPowerStateRequest;
+
+ /** Spinlock protecting MouseNotifyCallback. Required since the consumer is
+ * in a DPC callback and not the ISR. */
+ KSPIN_LOCK MouseEventAccessSpinLock;
+
+ /** Read/write critical section for handling race between checking for idle
+ * driver (in IRP_MN_QUERY_REMOVE_DEVICE & IRP_MN_QUERY_STOP_DEVICE) and
+ * creating new sessions. The session creation code enteres the critical
+ * section in read (shared) access mode, whereas the idle checking code
+ * enteres is in write (exclusive) access mode. */
+ RTCRITSECTRW SessionCreateCritSect;
+} VBOXGUESTDEVEXTWIN;
+typedef VBOXGUESTDEVEXTWIN *PVBOXGUESTDEVEXTWIN;
+
+
+/** NT (windows) version identifier. */
+typedef enum VGDRVNTVER
+{
+ VGDRVNTVER_INVALID = 0,
+ VGDRVNTVER_WINNT310,
+ VGDRVNTVER_WINNT350,
+ VGDRVNTVER_WINNT351,
+ VGDRVNTVER_WINNT4,
+ VGDRVNTVER_WIN2K,
+ VGDRVNTVER_WINXP,
+ VGDRVNTVER_WIN2K3,
+ VGDRVNTVER_WINVISTA,
+ VGDRVNTVER_WIN7,
+ VGDRVNTVER_WIN8,
+ VGDRVNTVER_WIN81,
+ VGDRVNTVER_WIN10,
+ VGDRVNTVER_WIN11
+} VGDRVNTVER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+static NTSTATUS NTAPI vgdrvNtNt5PlusAddDevice(PDRIVER_OBJECT pDrvObj, PDEVICE_OBJECT pDevObj);
+static NTSTATUS NTAPI vgdrvNtNt5PlusPnP(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static NTSTATUS NTAPI vgdrvNtNt5PlusPower(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static NTSTATUS NTAPI vgdrvNtNt5PlusSystemControl(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static void NTAPI vgdrvNtUnload(PDRIVER_OBJECT pDrvObj);
+static NTSTATUS NTAPI vgdrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static NTSTATUS NTAPI vgdrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static NTSTATUS NTAPI vgdrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static NTSTATUS vgdrvNtDeviceControlSlow(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ PIRP pIrp, PIO_STACK_LOCATION pStack);
+static NTSTATUS NTAPI vgdrvNtInternalIOCtl(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static void vgdrvNtReadConfiguration(PVBOXGUESTDEVEXTWIN pDevExt);
+static NTSTATUS NTAPI vgdrvNtShutdown(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static NTSTATUS NTAPI vgdrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp);
+static VOID NTAPI vgdrvNtBugCheckCallback(PVOID pvBuffer, ULONG cbBuffer);
+static VOID NTAPI vgdrvNtDpcHandler(PKDPC pDPC, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVOID pContext);
+static BOOLEAN NTAPI vgdrvNtIsrHandler(PKINTERRUPT interrupt, PVOID serviceContext);
+#ifdef VBOX_STRICT
+static void vgdrvNtDoTests(void);
+#endif
+#ifdef TARGET_NT4
+static ULONG NTAPI vgdrvNt31GetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot,
+ void *pvData, ULONG offData, ULONG cbData);
+static ULONG NTAPI vgdrvNt31SetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot,
+ void *pvData, ULONG offData, ULONG cbData);
+#endif
+
+/*
+ * We only do INIT allocations. PAGE is too much work and risk for little gain.
+ */
+#ifdef ALLOC_PRAGMA
+NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath);
+# pragma alloc_text(INIT, DriverEntry)
+# ifdef TARGET_NT4
+static NTSTATUS vgdrvNt4CreateDevice(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath);
+# pragma alloc_text(INIT, vgdrvNt4CreateDevice)
+static NTSTATUS vgdrvNt4FindPciDevice(PULONG puluBusNumber, PPCI_SLOT_NUMBER puSlotNumber);
+# pragma alloc_text(INIT, vgdrvNt4FindPciDevice)
+# endif
+#endif
+RT_C_DECLS_END
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The detected NT (windows) version. */
+static VGDRVNTVER g_enmVGDrvNtVer = VGDRVNTVER_INVALID;
+/** Pointer to the PoStartNextPowerIrp routine (in the NT kernel).
+ * Introduced in Windows 2000. */
+static decltype(PoStartNextPowerIrp) *g_pfnPoStartNextPowerIrp = NULL;
+/** Pointer to the PoCallDriver routine (in the NT kernel).
+ * Introduced in Windows 2000. */
+static decltype(PoCallDriver) *g_pfnPoCallDriver = NULL;
+#ifdef TARGET_NT4
+/** Pointer to the HalAssignSlotResources routine (in the HAL).
+ * Introduced in NT 3.50. */
+static decltype(HalAssignSlotResources) *g_pfnHalAssignSlotResources= NULL;
+/** Pointer to the HalGetBusDataByOffset routine (in the HAL).
+ * Introduced in NT 3.50. */
+static decltype(HalGetBusDataByOffset) *g_pfnHalGetBusDataByOffset = NULL;
+/** Pointer to the HalSetBusDataByOffset routine (in the HAL).
+ * Introduced in NT 3.50 (we provide fallback and use it only for NT 3.1). */
+static decltype(HalSetBusDataByOffset) *g_pfnHalSetBusDataByOffset = NULL;
+#endif
+/** Pointer to the KeRegisterBugCheckCallback routine (in the NT kernel).
+ * Introduced in Windows 3.50. */
+static decltype(KeRegisterBugCheckCallback) *g_pfnKeRegisterBugCheckCallback = NULL;
+/** Pointer to the KeRegisterBugCheckCallback routine (in the NT kernel).
+ * Introduced in Windows 3.50. */
+static decltype(KeDeregisterBugCheckCallback) *g_pfnKeDeregisterBugCheckCallback = NULL;
+/** Pointer to the KiBugCheckData array (in the NT kernel).
+ * Introduced in Windows 4. */
+static uintptr_t const *g_pauKiBugCheckData = NULL;
+/** Set if the callback was successfully registered and needs deregistering. */
+static bool g_fBugCheckCallbackRegistered = false;
+/** The bugcheck callback record. */
+static KBUGCHECK_CALLBACK_RECORD g_BugCheckCallbackRec;
+
+
+
+/**
+ * Driver entry point.
+ *
+ * @returns appropriate status code.
+ * @param pDrvObj Pointer to driver object.
+ * @param pRegPath Registry base path.
+ */
+NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath)
+{
+ RT_NOREF1(pRegPath);
+#ifdef TARGET_NT4
+ /*
+ * Looks like NT 3.1 doesn't necessarily zero our uninitialized data segments
+ * (like ".bss"), at least not when loading at runtime, so do that.
+ */
+ PIMAGE_DOS_HEADER pMzHdr = &__ImageBase;
+ PIMAGE_NT_HEADERS32 pNtHdrs = (PIMAGE_NT_HEADERS32)((uint8_t *)pMzHdr + pMzHdr->e_lfanew);
+ if ( pNtHdrs->Signature == IMAGE_NT_SIGNATURE
+ && pNtHdrs->FileHeader.NumberOfSections > 2
+ && pNtHdrs->FileHeader.NumberOfSections < 64)
+ {
+ uint32_t iShdr = pNtHdrs->FileHeader.NumberOfSections;
+ uint32_t uRvaEnd = pNtHdrs->OptionalHeader.SizeOfImage; /* (may be changed to exclude tail sections) */
+ PIMAGE_SECTION_HEADER paShdrs;
+ paShdrs = (PIMAGE_SECTION_HEADER)&pNtHdrs->OptionalHeader.DataDirectory[pNtHdrs->OptionalHeader.NumberOfRvaAndSizes];
+ while (iShdr-- > 0)
+ {
+ if ( !(paShdrs[iShdr].Characteristics & IMAGE_SCN_TYPE_NOLOAD)
+ && paShdrs[iShdr].VirtualAddress < uRvaEnd)
+ {
+ uint32_t const cbSection = uRvaEnd - paShdrs[iShdr].VirtualAddress;
+ uint32_t const offUninitialized = paShdrs[iShdr].SizeOfRawData;
+ //RTLogBackdoorPrintf("section #%u: rva=%#x size=%#x calcsize=%#x) rawsize=%#x\n", iShdr,
+ // paShdrs[iShdr].VirtualAddress, paShdrs[iShdr].Misc.VirtualSize, cbSection, offUninitialized);
+ if ( offUninitialized < cbSection
+ && (paShdrs[iShdr].Characteristics & IMAGE_SCN_MEM_WRITE))
+ memset((uint8_t *)pMzHdr + paShdrs[iShdr].VirtualAddress + offUninitialized, 0, cbSection - offUninitialized);
+ uRvaEnd = paShdrs[iShdr].VirtualAddress;
+ }
+ }
+ }
+ else
+ RTLogBackdoorPrintf("VBoxGuest: Bad pNtHdrs=%p: %#x\n", pNtHdrs, pNtHdrs->Signature);
+#endif
+
+ /*
+ * Start by initializing IPRT.
+ */
+ int rc = RTR0Init(0);
+ if (RT_FAILURE(rc))
+ {
+ RTLogBackdoorPrintf("VBoxGuest: RTR0Init failed: %Rrc!\n", rc);
+ return STATUS_UNSUCCESSFUL;
+ }
+ VGDrvCommonInitLoggers();
+
+ LogFunc(("Driver built: %s %s\n", __DATE__, __TIME__));
+
+ /*
+ * Check if the NT version is supported and initialize g_enmVGDrvNtVer.
+ */
+ ULONG ulMajorVer;
+ ULONG ulMinorVer;
+ ULONG ulBuildNo;
+ BOOLEAN fCheckedBuild = PsGetVersion(&ulMajorVer, &ulMinorVer, &ulBuildNo, NULL);
+
+ /* Use RTLogBackdoorPrintf to make sure that this goes to VBox.log on the host. */
+ RTLogBackdoorPrintf("VBoxGuest: Windows version %u.%u, build %u\n", ulMajorVer, ulMinorVer, ulBuildNo);
+ if (fCheckedBuild)
+ RTLogBackdoorPrintf("VBoxGuest: Windows checked build\n");
+
+#ifdef VBOX_STRICT
+ vgdrvNtDoTests();
+#endif
+ NTSTATUS rcNt = STATUS_SUCCESS;
+ switch (ulMajorVer)
+ {
+ case 10:
+ /* Windows 10 Preview builds starting with 9926. */
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN10;
+ /* Windows 11 Preview builds starting with 22000. */
+ if (ulBuildNo >= 22000)
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN11;
+ break;
+ case 6: /* Windows Vista or Windows 7 (based on minor ver) */
+ switch (ulMinorVer)
+ {
+ case 0: /* Note: Also could be Windows 2008 Server! */
+ g_enmVGDrvNtVer = VGDRVNTVER_WINVISTA;
+ break;
+ case 1: /* Note: Also could be Windows 2008 Server R2! */
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN7;
+ break;
+ case 2:
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN8;
+ break;
+ case 3:
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN81;
+ break;
+ case 4: /* Windows 10 Preview builds. */
+ default:
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN10;
+ break;
+ }
+ break;
+ case 5:
+ switch (ulMinorVer)
+ {
+ default:
+ case 2:
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN2K3;
+ break;
+ case 1:
+ g_enmVGDrvNtVer = VGDRVNTVER_WINXP;
+ break;
+ case 0:
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN2K;
+ break;
+ }
+ break;
+ case 4:
+ g_enmVGDrvNtVer = VGDRVNTVER_WINNT4;
+ break;
+ case 3:
+ if (ulMinorVer > 50)
+ g_enmVGDrvNtVer = VGDRVNTVER_WINNT351;
+ else if (ulMinorVer >= 50)
+ g_enmVGDrvNtVer = VGDRVNTVER_WINNT350;
+ else
+ g_enmVGDrvNtVer = VGDRVNTVER_WINNT310;
+ break;
+ default:
+ /* Major versions above 6 gets classified as windows 10. */
+ if (ulMajorVer > 6)
+ g_enmVGDrvNtVer = VGDRVNTVER_WIN10;
+ else
+ {
+ RTLogBackdoorPrintf("At least Windows NT 3.10 required! Found %u.%u!\n", ulMajorVer, ulMinorVer);
+ rcNt = STATUS_DRIVER_UNABLE_TO_LOAD;
+ }
+ break;
+ }
+ if (NT_SUCCESS(rcNt))
+ {
+ /*
+ * Dynamically resolve symbols not present in NT4.
+ */
+ RTDBGKRNLINFO hKrnlInfo;
+ rc = RTR0DbgKrnlInfoOpen(&hKrnlInfo, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ g_pfnKeRegisterBugCheckCallback = (decltype(KeRegisterBugCheckCallback) *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "KeRegisterBugCheckCallback");
+ g_pfnKeDeregisterBugCheckCallback = (decltype(KeDeregisterBugCheckCallback) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "KeDeregisterBugCheckCallback");
+ g_pauKiBugCheckData = (uintptr_t const *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "KiBugCheckData");
+ g_pfnPoCallDriver = (decltype(PoCallDriver) *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "PoCallDriver");
+ g_pfnPoStartNextPowerIrp = (decltype(PoStartNextPowerIrp) *) RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "PoStartNextPowerIrp");
+#ifdef TARGET_NT4
+ if (g_enmVGDrvNtVer > VGDRVNTVER_WINNT4)
+#endif
+ {
+ if (!g_pfnPoCallDriver) { LogRelFunc(("Missing PoCallDriver!\n")); rc = VERR_SYMBOL_NOT_FOUND; }
+ if (!g_pfnPoStartNextPowerIrp) { LogRelFunc(("Missing PoStartNextPowerIrp!\n")); rc = VERR_SYMBOL_NOT_FOUND; }
+ }
+
+#ifdef TARGET_NT4
+ g_pfnHalAssignSlotResources = (decltype(HalAssignSlotResources) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "HalAssignSlotResources");
+ if (!g_pfnHalAssignSlotResources && g_enmVGDrvNtVer >= VGDRVNTVER_WINNT350 && g_enmVGDrvNtVer < VGDRVNTVER_WIN2K)
+ {
+ RTLogBackdoorPrintf("VBoxGuest: Missing HalAssignSlotResources!\n");
+ rc = VERR_SYMBOL_NOT_FOUND;
+ }
+
+ g_pfnHalGetBusDataByOffset = (decltype(HalGetBusDataByOffset) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "HalGetBusDataByOffset");
+ if (!g_pfnHalGetBusDataByOffset && g_enmVGDrvNtVer >= VGDRVNTVER_WINNT350 && g_enmVGDrvNtVer < VGDRVNTVER_WIN2K)
+ {
+ RTLogBackdoorPrintf("VBoxGuest: Missing HalGetBusDataByOffset!\n");
+ rc = VERR_SYMBOL_NOT_FOUND;
+ }
+ if (!g_pfnHalGetBusDataByOffset)
+ g_pfnHalGetBusDataByOffset = vgdrvNt31GetBusDataByOffset;
+
+ g_pfnHalSetBusDataByOffset = (decltype(HalSetBusDataByOffset) *)RTR0DbgKrnlInfoGetSymbol(hKrnlInfo, NULL, "HalSetBusDataByOffset");
+ if (!g_pfnHalSetBusDataByOffset && g_enmVGDrvNtVer >= VGDRVNTVER_WINNT350 && g_enmVGDrvNtVer < VGDRVNTVER_WIN2K)
+ {
+ RTLogBackdoorPrintf("VBoxGuest: Missing HalSetBusDataByOffset!\n");
+ rc = VERR_SYMBOL_NOT_FOUND;
+ }
+ if (!g_pfnHalSetBusDataByOffset)
+ g_pfnHalSetBusDataByOffset = vgdrvNt31SetBusDataByOffset;
+#endif
+ RTR0DbgKrnlInfoRelease(hKrnlInfo);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Setup the driver entry points in pDrvObj.
+ */
+ pDrvObj->DriverUnload = vgdrvNtUnload;
+ pDrvObj->MajorFunction[IRP_MJ_CREATE] = vgdrvNtCreate;
+ pDrvObj->MajorFunction[IRP_MJ_CLOSE] = vgdrvNtClose;
+ pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = vgdrvNtDeviceControl;
+ pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = vgdrvNtInternalIOCtl;
+ /** @todo Need to call IoRegisterShutdownNotification or
+ * IoRegisterLastChanceShutdownNotification, possibly hooking the
+ * HalReturnToFirmware import in NTOSKRNL on older systems (<= ~NT4) and
+ * check for power off requests. */
+ pDrvObj->MajorFunction[IRP_MJ_SHUTDOWN] = vgdrvNtShutdown;
+ pDrvObj->MajorFunction[IRP_MJ_READ] = vgdrvNtNotSupportedStub;
+ pDrvObj->MajorFunction[IRP_MJ_WRITE] = vgdrvNtNotSupportedStub;
+#ifdef TARGET_NT4
+ if (g_enmVGDrvNtVer <= VGDRVNTVER_WINNT4)
+ rcNt = vgdrvNt4CreateDevice(pDrvObj, pRegPath);
+ else
+#endif
+ {
+ pDrvObj->MajorFunction[IRP_MJ_PNP] = vgdrvNtNt5PlusPnP;
+ pDrvObj->MajorFunction[IRP_MJ_POWER] = vgdrvNtNt5PlusPower;
+ pDrvObj->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = vgdrvNtNt5PlusSystemControl;
+ pDrvObj->DriverExtension->AddDevice = vgdrvNtNt5PlusAddDevice;
+ }
+ if (NT_SUCCESS(rcNt))
+ {
+ /*
+ * Try register the bugcheck callback (non-fatal).
+ */
+ if ( g_pfnKeRegisterBugCheckCallback
+ && g_pfnKeDeregisterBugCheckCallback)
+ {
+ AssertCompile(BufferEmpty == 0);
+ KeInitializeCallbackRecord(&g_BugCheckCallbackRec);
+ if (g_pfnKeRegisterBugCheckCallback(&g_BugCheckCallbackRec, vgdrvNtBugCheckCallback,
+ NULL, 0, (PUCHAR)"VBoxGuest"))
+ g_fBugCheckCallbackRegistered = true;
+ else
+ g_fBugCheckCallbackRegistered = false;
+ }
+ else
+ Assert(g_pfnKeRegisterBugCheckCallback == NULL && g_pfnKeDeregisterBugCheckCallback == NULL);
+
+ LogFlowFunc(("Returning %#x\n", rcNt));
+ return rcNt;
+ }
+ }
+ else
+ rcNt = STATUS_PROCEDURE_NOT_FOUND;
+ }
+
+ /*
+ * Failed.
+ */
+ LogRelFunc(("Failed! rcNt=%#x\n", rcNt));
+ VGDrvCommonDestroyLoggers();
+ RTR0Term();
+ return rcNt;
+}
+
+
+/**
+ * Translates our internal NT version enum to VBox OS.
+ *
+ * @returns VBox OS type.
+ * @param enmNtVer The NT version.
+ */
+static VBOXOSTYPE vgdrvNtVersionToOSType(VGDRVNTVER enmNtVer)
+{
+ VBOXOSTYPE enmOsType;
+ switch (enmNtVer)
+ {
+ case VGDRVNTVER_WINNT310: enmOsType = VBOXOSTYPE_WinNT3x; break;
+ case VGDRVNTVER_WINNT350: enmOsType = VBOXOSTYPE_WinNT3x; break;
+ case VGDRVNTVER_WINNT351: enmOsType = VBOXOSTYPE_WinNT3x; break;
+ case VGDRVNTVER_WINNT4: enmOsType = VBOXOSTYPE_WinNT4; break;
+ case VGDRVNTVER_WIN2K: enmOsType = VBOXOSTYPE_Win2k; break;
+ case VGDRVNTVER_WINXP: enmOsType = VBOXOSTYPE_WinXP; break;
+ case VGDRVNTVER_WIN2K3: enmOsType = VBOXOSTYPE_Win2k3; break;
+ case VGDRVNTVER_WINVISTA: enmOsType = VBOXOSTYPE_WinVista; break;
+ case VGDRVNTVER_WIN7: enmOsType = VBOXOSTYPE_Win7; break;
+ case VGDRVNTVER_WIN8: enmOsType = VBOXOSTYPE_Win8; break;
+ case VGDRVNTVER_WIN81: enmOsType = VBOXOSTYPE_Win81; break;
+ case VGDRVNTVER_WIN10: enmOsType = VBOXOSTYPE_Win10; break;
+ case VGDRVNTVER_WIN11: enmOsType = VBOXOSTYPE_Win11_x64; break;
+
+ default:
+ /* We don't know, therefore NT family. */
+ enmOsType = VBOXOSTYPE_WinNT;
+ break;
+ }
+#if ARCH_BITS == 64
+ enmOsType = (VBOXOSTYPE)((int)enmOsType | VBOXOSTYPE_x64);
+#endif
+ return enmOsType;
+}
+
+
+/**
+ * Does the fundamental device extension initialization.
+ *
+ * @returns NT status.
+ * @param pDevExt The device extension.
+ * @param pDevObj The device object.
+ */
+static NTSTATUS vgdrvNtInitDevExtFundament(PVBOXGUESTDEVEXTWIN pDevExt, PDEVICE_OBJECT pDevObj)
+{
+ RT_ZERO(*pDevExt);
+
+ KeInitializeSpinLock(&pDevExt->MouseEventAccessSpinLock);
+ pDevExt->pDeviceObject = pDevObj;
+ pDevExt->enmPrevDevState = VGDRVNTDEVSTATE_STOPPED;
+ pDevExt->enmDevState = VGDRVNTDEVSTATE_STOPPED;
+
+ int rc = RTCritSectRwInit(&pDevExt->SessionCreateCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VGDrvCommonInitDevExtFundament(&pDevExt->Core);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("vgdrvNtInitDevExtFundament: returning success\n"));
+ return STATUS_SUCCESS;
+ }
+
+ RTCritSectRwDelete(&pDevExt->SessionCreateCritSect);
+ }
+ Log(("vgdrvNtInitDevExtFundament: failed: rc=%Rrc\n", rc));
+ return STATUS_UNSUCCESSFUL;
+}
+
+
+/**
+ * Counter part to vgdrvNtInitDevExtFundament.
+ *
+ * @param pDevExt The device extension.
+ */
+static void vgdrvNtDeleteDevExtFundament(PVBOXGUESTDEVEXTWIN pDevExt)
+{
+ LogFlow(("vgdrvNtDeleteDevExtFundament:\n"));
+ VGDrvCommonDeleteDevExtFundament(&pDevExt->Core);
+ RTCritSectRwDelete(&pDevExt->SessionCreateCritSect);
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Debug helper to dump a device resource list.
+ *
+ * @param pResourceList list of device resources.
+ */
+static void vgdrvNtShowDeviceResources(PCM_RESOURCE_LIST pRsrcList)
+{
+ for (uint32_t iList = 0; iList < pRsrcList->Count; iList++)
+ {
+ PCM_FULL_RESOURCE_DESCRIPTOR pList = &pRsrcList->List[iList];
+ LogFunc(("List #%u: InterfaceType=%#x BusNumber=%#x ListCount=%u ListRev=%#x ListVer=%#x\n",
+ iList, pList->InterfaceType, pList->BusNumber, pList->PartialResourceList.Count,
+ pList->PartialResourceList.Revision, pList->PartialResourceList.Version ));
+
+ PCM_PARTIAL_RESOURCE_DESCRIPTOR pResource = pList->PartialResourceList.PartialDescriptors;
+ for (ULONG i = 0; i < pList->PartialResourceList.Count; ++i, ++pResource)
+ {
+ ULONG uType = pResource->Type;
+ static char const * const s_apszName[] =
+ {
+ "CmResourceTypeNull",
+ "CmResourceTypePort",
+ "CmResourceTypeInterrupt",
+ "CmResourceTypeMemory",
+ "CmResourceTypeDma",
+ "CmResourceTypeDeviceSpecific",
+ "CmResourceTypeuBusNumber",
+ "CmResourceTypeDevicePrivate",
+ "CmResourceTypeAssignedResource",
+ "CmResourceTypeSubAllocateFrom",
+ };
+
+ if (uType < RT_ELEMENTS(s_apszName))
+ LogFunc((" %.30s Flags=%#x Share=%#x", s_apszName[uType], pResource->Flags, pResource->ShareDisposition));
+ else
+ LogFunc((" Type=%#x Flags=%#x Share=%#x", uType, pResource->Flags, pResource->ShareDisposition));
+ switch (uType)
+ {
+ case CmResourceTypePort:
+ case CmResourceTypeMemory:
+ Log((" Start %#RX64, length=%#x\n", pResource->u.Port.Start.QuadPart, pResource->u.Port.Length));
+ break;
+
+ case CmResourceTypeInterrupt:
+ Log((" Level=%X, vector=%#x, affinity=%#x\n",
+ pResource->u.Interrupt.Level, pResource->u.Interrupt.Vector, pResource->u.Interrupt.Affinity));
+ break;
+
+ case CmResourceTypeDma:
+ Log((" Channel %d, Port %#x\n", pResource->u.Dma.Channel, pResource->u.Dma.Port));
+ break;
+
+ default:
+ Log(("\n"));
+ break;
+ }
+ }
+ }
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Helper to scan the PCI resource list and remember stuff.
+ *
+ * @param pDevExt The device extension.
+ * @param pResList Resource list
+ * @param fTranslated Whether the addresses are translated or not.
+ */
+static NTSTATUS vgdrvNtScanPCIResourceList(PVBOXGUESTDEVEXTWIN pDevExt, PCM_RESOURCE_LIST pResList, bool fTranslated)
+{
+ LogFlowFunc(("Found %d resources\n", pResList->List->PartialResourceList.Count));
+ PCM_PARTIAL_RESOURCE_DESCRIPTOR pPartialData = NULL;
+ bool fGotIrq = false;
+ bool fGotMmio = false;
+ bool fGotIoPorts = false;
+ NTSTATUS rc = STATUS_SUCCESS;
+ for (ULONG i = 0; i < pResList->List->PartialResourceList.Count; i++)
+ {
+ pPartialData = &pResList->List->PartialResourceList.PartialDescriptors[i];
+ switch (pPartialData->Type)
+ {
+ case CmResourceTypePort:
+ LogFlowFunc(("I/O range: Base=%#RX64, length=%08x\n",
+ pPartialData->u.Port.Start.QuadPart, pPartialData->u.Port.Length));
+ /* Save the first I/O port base. */
+ if (!fGotIoPorts)
+ {
+ pDevExt->Core.IOPortBase = (RTIOPORT)pPartialData->u.Port.Start.LowPart;
+ fGotIoPorts = true;
+ LogFunc(("I/O range for VMMDev found! Base=%#RX64, length=%08x\n",
+ pPartialData->u.Port.Start.QuadPart, pPartialData->u.Port.Length));
+ }
+ else
+ LogRelFunc(("More than one I/O port range?!?\n"));
+ break;
+
+ case CmResourceTypeInterrupt:
+ LogFunc(("Interrupt: Level=%x, vector=%x, mode=%x\n",
+ pPartialData->u.Interrupt.Level, pPartialData->u.Interrupt.Vector, pPartialData->Flags));
+ if (!fGotIrq)
+ {
+ /* Save information. */
+ pDevExt->uInterruptLevel = pPartialData->u.Interrupt.Level;
+ pDevExt->uInterruptVector = pPartialData->u.Interrupt.Vector;
+ pDevExt->fInterruptAffinity = pPartialData->u.Interrupt.Affinity;
+
+ /* Check interrupt mode. */
+ if (pPartialData->Flags & CM_RESOURCE_INTERRUPT_LATCHED)
+ pDevExt->enmInterruptMode = Latched;
+ else
+ pDevExt->enmInterruptMode = LevelSensitive;
+ fGotIrq = true;
+ LogFunc(("Interrupt for VMMDev found! Vector=%#x Level=%#x Affinity=%zx Mode=%d\n", pDevExt->uInterruptVector,
+ pDevExt->uInterruptLevel, pDevExt->fInterruptAffinity, pDevExt->enmInterruptMode));
+ }
+ else
+ LogFunc(("More than one IRQ resource!\n"));
+ break;
+
+ case CmResourceTypeMemory:
+ LogFlowFunc(("Memory range: Base=%#RX64, length=%08x\n",
+ pPartialData->u.Memory.Start.QuadPart, pPartialData->u.Memory.Length));
+ /* We only care about the first read/write memory range. */
+ if ( !fGotMmio
+ && (pPartialData->Flags & CM_RESOURCE_MEMORY_WRITEABILITY_MASK) == CM_RESOURCE_MEMORY_READ_WRITE)
+ {
+ /* Save physical MMIO base + length for VMMDev. */
+ pDevExt->uVmmDevMemoryPhysAddr = pPartialData->u.Memory.Start;
+ pDevExt->cbVmmDevMemory = (ULONG)pPartialData->u.Memory.Length;
+
+ if (!fTranslated)
+ {
+ /* Technically we need to make the HAL translate the address. since we
+ didn't used to do this and it probably just returns the input address,
+ we allow ourselves to ignore failures. */
+ ULONG uAddressSpace = 0;
+ PHYSICAL_ADDRESS PhysAddr = pPartialData->u.Memory.Start;
+ if (HalTranslateBusAddress(pResList->List->InterfaceType, pResList->List->BusNumber, PhysAddr,
+ &uAddressSpace, &PhysAddr))
+ {
+ Log(("HalTranslateBusAddress(%#RX64) -> %RX64, type %#x\n",
+ pPartialData->u.Memory.Start.QuadPart, PhysAddr.QuadPart, uAddressSpace));
+ if (pPartialData->u.Memory.Start.QuadPart != PhysAddr.QuadPart)
+ pDevExt->uVmmDevMemoryPhysAddr = PhysAddr;
+ }
+ else
+ Log(("HalTranslateBusAddress(%#RX64) -> failed!\n", pPartialData->u.Memory.Start.QuadPart));
+ }
+
+ fGotMmio = true;
+ LogFunc(("Found memory range for VMMDev! Base = %#RX64, Length = %08x\n",
+ pPartialData->u.Memory.Start.QuadPart, pPartialData->u.Memory.Length));
+ }
+ else
+ LogFunc(("Ignoring memory: Flags=%08x Base=%#RX64\n",
+ pPartialData->Flags, pPartialData->u.Memory.Start.QuadPart));
+ break;
+
+ default:
+ LogFunc(("Unhandled resource found, type=%d\n", pPartialData->Type));
+ break;
+ }
+ }
+ return rc;
+}
+
+
+#ifdef TARGET_NT4
+
+/**
+ * Scans the PCI resources on NT 3.1.
+ *
+ * @returns STATUS_SUCCESS or STATUS_DEVICE_CONFIGURATION_ERROR.
+ * @param pDevExt The device extension.
+ * @param uBus The bus number.
+ * @param uSlot The PCI slot to scan.
+ */
+static NTSTATUS vgdrvNt31ScanSlotResources(PVBOXGUESTDEVEXTWIN pDevExt, ULONG uBus, ULONG uSlot)
+{
+ /*
+ * Disable memory mappings so we can determin the BAR lengths
+ * without upsetting other mappings.
+ */
+ uint16_t fCmd = 0;
+ g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmd, VBOX_PCI_COMMAND, sizeof(fCmd));
+ if (fCmd & VBOX_PCI_COMMAND_MEMORY)
+ {
+ uint16_t fCmdTmp = fCmd & ~VBOX_PCI_COMMAND_MEMORY;
+ g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmdTmp, VBOX_PCI_COMMAND, sizeof(fCmdTmp));
+ }
+
+ /*
+ * Scan the address resources first.
+ */
+ uint32_t aBars[6] = { UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX };
+ g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &aBars, VBOX_PCI_BASE_ADDRESS_0, sizeof(aBars));
+
+ bool fGotMmio = false;
+ bool fGotIoPorts = false;
+ for (uint32_t i = 0; i < RT_ELEMENTS(aBars); i++)
+ {
+ uint32_t uBar = aBars[i];
+ if (uBar == UINT32_MAX)
+ continue;
+ if ((uBar & 1) == PCI_ADDRESS_SPACE_IO)
+ {
+ uint32_t uAddr = uBar & UINT32_C(0xfffffffc);
+ if (!uAddr)
+ continue;
+ if (!fGotIoPorts)
+ {
+ pDevExt->Core.IOPortBase = (uint16_t)uAddr & UINT16_C(0xfffc);
+ fGotIoPorts = true;
+ LogFunc(("I/O range for VMMDev found in BAR%u! %#x\n", i, pDevExt->Core.IOPortBase));
+ }
+ else
+ LogRelFunc(("More than one I/O port range?!? BAR%u=%#x\n", i, uBar));
+ }
+ else
+ {
+ uint32_t uAddr = uBar & UINT32_C(0xfffffff0);
+ if (!uAddr)
+ continue;
+
+ if (!fGotMmio)
+ {
+ /* Figure the length by trying to set all address bits and seeing
+ how many we're allowed to set. */
+ uint32_t iBit = 4;
+ while (!(uAddr & RT_BIT_32(iBit)))
+ iBit++;
+
+ uint32_t const offPciBar = VBOX_PCI_BASE_ADDRESS_0 + i * 4;
+ uint32_t uTmpBar = uBar | ((RT_BIT_32(iBit) - 1) & UINT32_C(0xfffffff0));
+ g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &uTmpBar, offPciBar, sizeof(uTmpBar));
+ uTmpBar = uBar;
+ g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &uTmpBar, offPciBar, sizeof(uTmpBar));
+ g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &uBar, offPciBar, sizeof(uBar));
+
+ while (iBit > 4 && (uTmpBar & RT_BIT_32(iBit - 1)))
+ iBit--;
+
+ /* got it */
+ pDevExt->cbVmmDevMemory = RT_BIT_32(iBit);
+ pDevExt->uVmmDevMemoryPhysAddr.QuadPart = uAddr;
+ fGotMmio = true;
+ LogFunc(("Found memory range for VMMDev in BAR%u! %#RX64 LB %#x (raw %#x)\n",
+ i, pDevExt->uVmmDevMemoryPhysAddr.QuadPart, pDevExt->cbVmmDevMemory, uBar));
+ }
+ else
+ LogFunc(("Ignoring memory: BAR%u=%#x\n", i, uBar));
+ }
+ }
+
+ /*
+ * Get the IRQ
+ */
+ struct
+ {
+ uint8_t bInterruptLine;
+ uint8_t bInterruptPin;
+ } Buf = { 0, 0 };
+ g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, uSlot, &Buf, VBOX_PCI_INTERRUPT_LINE, sizeof(Buf));
+ if (Buf.bInterruptPin != 0)
+ {
+ pDevExt->uInterruptVector = Buf.bInterruptLine;
+ pDevExt->uInterruptLevel = Buf.bInterruptLine;
+ pDevExt->enmInterruptMode = LevelSensitive;
+ pDevExt->fInterruptAffinity = RT_BIT_32(RTMpGetCount()) - 1;
+ LogFunc(("Interrupt for VMMDev found! Vector=%#x Level=%#x Affinity=%zx Mode=%d\n",
+ pDevExt->uInterruptVector, pDevExt->uInterruptLevel, pDevExt->fInterruptAffinity, pDevExt->enmInterruptMode));
+ }
+
+ /*
+ * Got what we need?
+ */
+ if (fGotIoPorts && (!fGotMmio || Buf.bInterruptPin != 0))
+ {
+ /*
+ * Enable both MMIO, I/O space and busmastering so we can use the device.
+ */
+ uint16_t fCmdNew = fCmd | VBOX_PCI_COMMAND_IO | VBOX_PCI_COMMAND_MEMORY | VBOX_PCI_COMMAND_MASTER;
+ g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmdNew, VBOX_PCI_COMMAND, sizeof(fCmdNew));
+
+ return STATUS_SUCCESS;
+ }
+
+ /* No. Complain, restore device command value and return failure. */
+ if (!fGotIoPorts)
+ LogRel(("VBoxGuest: Did not find I/O port range: %#x %#x %#x %#x %#x %#x\n",
+ aBars[0], aBars[1], aBars[2], aBars[3], aBars[4], aBars[5]));
+ if (!fGotMmio || Buf.bInterruptPin != 0)
+ LogRel(("VBoxGuest: Got MMIO but no interrupts!\n"));
+
+ g_pfnHalSetBusDataByOffset(PCIConfiguration, uBus, uSlot, &fCmd, VBOX_PCI_COMMAND, sizeof(fCmd));
+ return STATUS_DEVICE_CONFIGURATION_ERROR;
+}
+
+#endif /* TARGET_NT4 */
+
+/**
+ * Unmaps the VMMDev I/O range from kernel space.
+ *
+ * @param pDevExt The device extension.
+ */
+static void vgdrvNtUnmapVMMDevMemory(PVBOXGUESTDEVEXTWIN pDevExt)
+{
+ LogFlowFunc(("pVMMDevMemory = %#x\n", pDevExt->Core.pVMMDevMemory));
+ if (pDevExt->Core.pVMMDevMemory)
+ {
+ MmUnmapIoSpace((void*)pDevExt->Core.pVMMDevMemory, pDevExt->cbVmmDevMemory);
+ pDevExt->Core.pVMMDevMemory = NULL;
+ }
+
+ pDevExt->uVmmDevMemoryPhysAddr.QuadPart = 0;
+ pDevExt->cbVmmDevMemory = 0;
+}
+
+
+/**
+ * Maps the I/O space from VMMDev to virtual kernel address space.
+ *
+ * @return NTSTATUS
+ *
+ * @param pDevExt The device extension.
+ * @param PhysAddr Physical address to map.
+ * @param cbToMap Number of bytes to map.
+ * @param ppvMMIOBase Pointer of mapped I/O base.
+ * @param pcbMMIO Length of mapped I/O base.
+ */
+static NTSTATUS vgdrvNtMapVMMDevMemory(PVBOXGUESTDEVEXTWIN pDevExt, PHYSICAL_ADDRESS PhysAddr, ULONG cbToMap,
+ void **ppvMMIOBase, uint32_t *pcbMMIO)
+{
+ AssertPtrReturn(pDevExt, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppvMMIOBase, VERR_INVALID_POINTER);
+ /* pcbMMIO is optional. */
+
+ NTSTATUS rc = STATUS_SUCCESS;
+ if (PhysAddr.LowPart > 0) /* We're mapping below 4GB. */
+ {
+ VMMDevMemory *pVMMDevMemory = (VMMDevMemory *)MmMapIoSpace(PhysAddr, cbToMap, MmNonCached);
+ LogFlowFunc(("pVMMDevMemory = %#x\n", pVMMDevMemory));
+ if (pVMMDevMemory)
+ {
+ LogFunc(("VMMDevMemory: Version = %#x, Size = %d\n", pVMMDevMemory->u32Version, pVMMDevMemory->u32Size));
+
+ /* Check version of the structure; do we have the right memory version? */
+ if (pVMMDevMemory->u32Version == VMMDEV_MEMORY_VERSION)
+ {
+ /* Save results. */
+ *ppvMMIOBase = pVMMDevMemory;
+ if (pcbMMIO) /* Optional. */
+ *pcbMMIO = pVMMDevMemory->u32Size;
+
+ LogFlowFunc(("VMMDevMemory found and mapped! pvMMIOBase = 0x%p\n", *ppvMMIOBase));
+ }
+ else
+ {
+ /* Not our version, refuse operation and unmap the memory. */
+ LogFunc(("Wrong version (%u), refusing operation!\n", pVMMDevMemory->u32Version));
+
+ vgdrvNtUnmapVMMDevMemory(pDevExt);
+ rc = STATUS_UNSUCCESSFUL;
+ }
+ }
+ else
+ rc = STATUS_UNSUCCESSFUL;
+ }
+ return rc;
+}
+
+
+/**
+ * Sets up the device and its resources.
+ *
+ * @param pDevExt Our device extension data.
+ * @param pDevObj The device object.
+ * @param pIrp The request packet if NT5+, NULL for NT4 and earlier.
+ * @param pDrvObj The driver object for NT4, NULL for NT5+.
+ * @param pRegPath The registry path for NT4, NULL for NT5+.
+ */
+static NTSTATUS vgdrvNtSetupDevice(PVBOXGUESTDEVEXTWIN pDevExt, PDEVICE_OBJECT pDevObj,
+ PIRP pIrp, PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath)
+{
+ LogFlowFunc(("ENTER: pDevExt=%p pDevObj=%p pIrq=%p pDrvObj=%p pRegPath=%p\n", pDevExt, pDevObj, pIrp, pDrvObj, pRegPath));
+
+ NTSTATUS rcNt;
+ if (!pIrp)
+ {
+#ifdef TARGET_NT4
+ /*
+ * NT4, NT3.x: Let's have a look at what our PCI adapter offers.
+ */
+ LogFlowFunc(("Starting to scan PCI resources of VBoxGuest ...\n"));
+
+ /* Assign the PCI resources. */
+ UNICODE_STRING ClassName;
+ RtlInitUnicodeString(&ClassName, L"VBoxGuestAdapter");
+ PCM_RESOURCE_LIST pResourceList = NULL;
+ if (g_pfnHalAssignSlotResources)
+ {
+ rcNt = g_pfnHalAssignSlotResources(pRegPath, &ClassName, pDrvObj, pDevObj, PCIBus, pDevExt->uBus, pDevExt->uSlot,
+ &pResourceList);
+# ifdef LOG_ENABLED
+ if (pResourceList)
+ vgdrvNtShowDeviceResources(pResourceList);
+# endif
+ if (NT_SUCCESS(rcNt))
+ {
+ rcNt = vgdrvNtScanPCIResourceList(pDevExt, pResourceList, false /*fTranslated*/);
+ ExFreePool(pResourceList);
+ }
+ }
+ else
+ rcNt = vgdrvNt31ScanSlotResources(pDevExt, pDevExt->uBus, pDevExt->uSlot);
+
+# else /* !TARGET_NT4 */
+ AssertFailed();
+ RT_NOREF(pDevObj, pDrvObj, pRegPath);
+ rcNt = STATUS_INTERNAL_ERROR;
+# endif /* !TARGET_NT4 */
+ }
+ else
+ {
+ /*
+ * NT5+: Scan the PCI resource list from the IRP.
+ */
+ PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
+# ifdef LOG_ENABLED
+ vgdrvNtShowDeviceResources(pStack->Parameters.StartDevice.AllocatedResourcesTranslated);
+# endif
+ rcNt = vgdrvNtScanPCIResourceList(pDevExt, pStack->Parameters.StartDevice.AllocatedResourcesTranslated,
+ true /*fTranslated*/);
+ }
+ if (NT_SUCCESS(rcNt))
+ {
+ /*
+ * Map physical address of VMMDev memory into MMIO region
+ * and init the common device extension bits.
+ */
+ void *pvMMIOBase = NULL;
+ uint32_t cbMMIO = 0;
+ rcNt = vgdrvNtMapVMMDevMemory(pDevExt,
+ pDevExt->uVmmDevMemoryPhysAddr,
+ pDevExt->cbVmmDevMemory,
+ &pvMMIOBase,
+ &cbMMIO);
+ if (NT_SUCCESS(rcNt))
+ {
+ pDevExt->Core.pVMMDevMemory = (VMMDevMemory *)pvMMIOBase;
+
+ LogFunc(("pvMMIOBase=0x%p, pDevExt=0x%p, pDevExt->Core.pVMMDevMemory=0x%p\n",
+ pvMMIOBase, pDevExt, pDevExt ? pDevExt->Core.pVMMDevMemory : NULL));
+
+ int vrc = VGDrvCommonInitDevExtResources(&pDevExt->Core,
+ pDevExt->Core.IOPortBase,
+ pvMMIOBase, cbMMIO,
+ vgdrvNtVersionToOSType(g_enmVGDrvNtVer),
+ VMMDEV_EVENT_MOUSE_POSITION_CHANGED);
+ if (RT_SUCCESS(vrc))
+ {
+
+ vrc = VbglR0GRAlloc((VMMDevRequestHeader **)&pDevExt->pPowerStateRequest,
+ sizeof(VMMDevPowerStateRequest), VMMDevReq_SetPowerStatus);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Register DPC and ISR.
+ */
+ LogFlowFunc(("Initializing DPC/ISR (pDevObj=%p)...\n", pDevExt->pDeviceObject));
+ IoInitializeDpcRequest(pDevExt->pDeviceObject, vgdrvNtDpcHandler);
+
+ ULONG uInterruptVector = pDevExt->uInterruptVector;
+ KIRQL uHandlerIrql = (KIRQL)pDevExt->uInterruptLevel;
+#ifdef TARGET_NT4
+ if (!pIrp)
+ {
+ /* NT4: Get an interrupt vector. Only proceed if the device provides an interrupt. */
+ if ( uInterruptVector
+ || pDevExt->uInterruptLevel)
+ {
+ LogFlowFunc(("Getting interrupt vector (HAL): Bus=%u, IRQL=%u, Vector=%u\n",
+ pDevExt->uBus, pDevExt->uInterruptLevel, pDevExt->uInterruptVector));
+ uInterruptVector = HalGetInterruptVector(g_enmVGDrvNtVer == VGDRVNTVER_WINNT310 ? Isa : PCIBus,
+ pDevExt->uBus,
+ pDevExt->uInterruptLevel,
+ pDevExt->uInterruptVector,
+ &uHandlerIrql,
+ &pDevExt->fInterruptAffinity);
+ LogFlowFunc(("HalGetInterruptVector returns vector=%u\n", uInterruptVector));
+ }
+ else
+ LogFunc(("Device does not provide an interrupt!\n"));
+ }
+#endif
+ if (uInterruptVector)
+ {
+ LogFlowFunc(("Connecting interrupt (IntVector=%#u), uHandlerIrql=%u) ...\n",
+ uInterruptVector, uHandlerIrql));
+
+ rcNt = IoConnectInterrupt(&pDevExt->pInterruptObject, /* Out: interrupt object. */
+ vgdrvNtIsrHandler, /* Our ISR handler. */
+ pDevExt, /* Device context. */
+ NULL, /* Optional spinlock. */
+ uInterruptVector, /* Interrupt vector. */
+ uHandlerIrql, /* Irql. */
+ uHandlerIrql, /* SynchronizeIrql. */
+ pDevExt->enmInterruptMode, /* LevelSensitive or Latched. */
+ TRUE, /* Shareable interrupt. */
+ pDevExt->fInterruptAffinity, /* CPU affinity. */
+ FALSE); /* Don't save FPU stack. */
+ if (NT_ERROR(rcNt))
+ LogFunc(("Could not connect interrupt: rcNt=%#x!\n", rcNt));
+ }
+ else
+ LogFunc(("No interrupt vector found!\n"));
+ if (NT_SUCCESS(rcNt))
+ {
+ /*
+ * Once we've read configuration from register and host, we're finally read.
+ */
+ /** @todo clean up guest ring-3 logging, keeping it separate from the kernel to avoid sharing limits with it. */
+ pDevExt->Core.fLoggingEnabled = true;
+ vgdrvNtReadConfiguration(pDevExt);
+
+ /* Ready to rumble! */
+ LogRelFunc(("Device is ready!\n"));
+ pDevExt->enmDevState = VGDRVNTDEVSTATE_OPERATIONAL;
+ pDevExt->enmPrevDevState = VGDRVNTDEVSTATE_OPERATIONAL;
+ return STATUS_SUCCESS;
+ }
+
+ pDevExt->pInterruptObject = NULL;
+
+ VbglR0GRFree(&pDevExt->pPowerStateRequest->header);
+ pDevExt->pPowerStateRequest = NULL;
+ }
+ else
+ {
+ LogFunc(("Alloc for pPowerStateRequest failed, vrc=%Rrc\n", vrc));
+ rcNt = STATUS_UNSUCCESSFUL;
+ }
+
+ VGDrvCommonDeleteDevExtResources(&pDevExt->Core);
+ }
+ else
+ {
+ LogFunc(("Could not init device extension resources: vrc=%Rrc\n", vrc));
+ rcNt = STATUS_DEVICE_CONFIGURATION_ERROR;
+ }
+ vgdrvNtUnmapVMMDevMemory(pDevExt);
+ }
+ else
+ LogFunc(("Could not map physical address of VMMDev, rcNt=%#x\n", rcNt));
+ }
+
+ LogFunc(("Returned with rcNt=%#x\n", rcNt));
+ return rcNt;
+}
+
+
+
+
+#ifdef TARGET_NT4
+# define PCI_CFG_ADDR 0xcf8
+# define PCI_CFG_DATA 0xcfc
+
+/**
+ * NT 3.1 doesn't do PCI nor HalSetBusDataByOffset, this is our fallback.
+ */
+static ULONG NTAPI vgdrvNt31SetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot,
+ void *pvData, ULONG offData, ULONG cbData)
+{
+ /*
+ * Validate input a little bit.
+ */
+ RT_NOREF(enmBusDataType);
+ Assert(idxBus <= 255);
+ Assert(uSlot <= 255);
+ Assert(offData <= 255);
+ Assert(cbData > 0);
+
+ PCI_SLOT_NUMBER PciSlot;
+ PciSlot.u.AsULONG = uSlot;
+ uint32_t const idxAddrTop = UINT32_C(0x80000000)
+ | (idxBus << 16)
+ | (PciSlot.u.bits.DeviceNumber << 11)
+ | (PciSlot.u.bits.FunctionNumber << 8);
+
+ /*
+ * Write the given bytes.
+ */
+ uint8_t const *pbData = (uint8_t const *)pvData;
+ uint32_t off = offData;
+ uint32_t cbRet = 0;
+
+ /* Unaligned start. */
+ if (off & 3)
+ {
+ ASMOutU32(PCI_CFG_ADDR, idxAddrTop | (off & ~3));
+ switch (off & 3)
+ {
+ case 1:
+ ASMOutU8(PCI_CFG_DATA + 1, pbData[cbRet++]);
+ if (cbRet >= cbData)
+ break;
+ RT_FALL_THRU();
+ case 2:
+ ASMOutU8(PCI_CFG_DATA + 2, pbData[cbRet++]);
+ if (cbRet >= cbData)
+ break;
+ RT_FALL_THRU();
+ case 3:
+ ASMOutU8(PCI_CFG_DATA + 3, pbData[cbRet++]);
+ break;
+ }
+ off = (off | 3) + 1;
+ }
+
+ /* Bulk. */
+ while (off < 256 && cbRet < cbData)
+ {
+ ASMOutU32(PCI_CFG_ADDR, idxAddrTop | off);
+ switch (cbData - cbRet)
+ {
+ case 1:
+ ASMOutU8(PCI_CFG_DATA, pbData[cbRet]);
+ cbRet += 1;
+ break;
+ case 2:
+ ASMOutU16(PCI_CFG_DATA, RT_MAKE_U16(pbData[cbRet], pbData[cbRet + 1]));
+ cbRet += 2;
+ break;
+ case 3:
+ ASMOutU16(PCI_CFG_DATA, RT_MAKE_U16(pbData[cbRet], pbData[cbRet + 1]));
+ ASMOutU8(PCI_CFG_DATA + 2, pbData[cbRet + 2]);
+ cbRet += 3;
+ break;
+ default:
+ ASMOutU32(PCI_CFG_DATA, RT_MAKE_U32_FROM_U8(pbData[cbRet], pbData[cbRet + 1],
+ pbData[cbRet + 2], pbData[cbRet + 3]));
+ cbRet += 4;
+ break;
+ }
+ off += 4;
+ }
+
+ return cbRet;
+}
+
+
+/**
+ * NT 3.1 doesn't do PCI nor HalGetBusDataByOffset, this is our fallback.
+ */
+static ULONG NTAPI vgdrvNt31GetBusDataByOffset(BUS_DATA_TYPE enmBusDataType, ULONG idxBus, ULONG uSlot,
+ void *pvData, ULONG offData, ULONG cbData)
+{
+ /*
+ * Validate input a little bit.
+ */
+ RT_NOREF(enmBusDataType);
+ Assert(idxBus <= 255);
+ Assert(uSlot <= 255);
+ Assert(offData <= 255);
+ Assert(cbData > 0);
+
+ PCI_SLOT_NUMBER PciSlot;
+ PciSlot.u.AsULONG = uSlot;
+ uint32_t const idxAddrTop = UINT32_C(0x80000000)
+ | (idxBus << 16)
+ | (PciSlot.u.bits.DeviceNumber << 11)
+ | (PciSlot.u.bits.FunctionNumber << 8);
+
+ /*
+ * Read the header type.
+ */
+ ASMOutU32(PCI_CFG_ADDR, idxAddrTop | (VBOX_PCI_HEADER_TYPE & ~3));
+ uint8_t bHdrType = ASMInU8(PCI_CFG_DATA + (VBOX_PCI_HEADER_TYPE & 3));
+ if (bHdrType == 0xff)
+ return idxBus < 8 ? 2 : 0; /* No device here */
+ if ( offData == VBOX_PCI_HEADER_TYPE
+ && cbData == 1)
+ {
+ *(uint8_t *)pvData = bHdrType;
+ /*Log("vgdrvNt31GetBusDataByOffset: PCI %#x/%#x -> %02x\n", idxAddrTop, offData, bHdrType);*/
+ return 1;
+ }
+
+ /*
+ * Read the requested bytes.
+ */
+ uint8_t *pbData = (uint8_t *)pvData;
+ uint32_t off = offData;
+ uint32_t cbRet = 0;
+
+ /* Unaligned start. */
+ if (off & 3)
+ {
+ ASMOutU32(PCI_CFG_ADDR, idxAddrTop | (off & ~3));
+ uint32_t uValue = ASMInU32(PCI_CFG_DATA);
+ switch (off & 3)
+ {
+ case 1:
+ pbData[cbRet++] = (uint8_t)(uValue >> 8);
+ if (cbRet >= cbData)
+ break;
+ RT_FALL_THRU();
+ case 2:
+ pbData[cbRet++] = (uint8_t)(uValue >> 16);
+ if (cbRet >= cbData)
+ break;
+ RT_FALL_THRU();
+ case 3:
+ pbData[cbRet++] = (uint8_t)(uValue >> 24);
+ break;
+ }
+ off = (off | 3) + 1;
+ }
+
+ /* Bulk. */
+ while (off < 256 && cbRet < cbData)
+ {
+ ASMOutU32(PCI_CFG_ADDR, idxAddrTop | off);
+ uint32_t uValue = ASMInU32(PCI_CFG_DATA);
+ switch (cbData - cbRet)
+ {
+ case 1:
+ pbData[cbRet++] = (uint8_t)uValue;
+ break;
+ case 2:
+ pbData[cbRet++] = (uint8_t)uValue;
+ pbData[cbRet++] = (uint8_t)(uValue >> 8);
+ break;
+ case 3:
+ pbData[cbRet++] = (uint8_t)uValue;
+ pbData[cbRet++] = (uint8_t)(uValue >> 8);
+ pbData[cbRet++] = (uint8_t)(uValue >> 16);
+ break;
+ default:
+ pbData[cbRet++] = (uint8_t)uValue;
+ pbData[cbRet++] = (uint8_t)(uValue >> 8);
+ pbData[cbRet++] = (uint8_t)(uValue >> 16);
+ pbData[cbRet++] = (uint8_t)(uValue >> 24);
+ break;
+ }
+ off += 4;
+ }
+
+ Log(("vgdrvNt31GetBusDataByOffset: PCI %#x/%#x -> %.*Rhxs\n", idxAddrTop, offData, cbRet, pvData));
+ return cbRet;
+}
+
+
+/**
+ * Helper function to handle the PCI device lookup.
+ *
+ * @returns NT status code.
+ *
+ * @param puBus Where to return the bus number on success.
+ * @param pSlot Where to return the slot number on success.
+ */
+static NTSTATUS vgdrvNt4FindPciDevice(PULONG puBus, PPCI_SLOT_NUMBER pSlot)
+{
+ Log(("vgdrvNt4FindPciDevice\n"));
+
+ PCI_SLOT_NUMBER Slot;
+ Slot.u.AsULONG = 0;
+
+ /* Scan each bus. */
+ for (ULONG uBus = 0; uBus < PCI_MAX_BUSES; uBus++)
+ {
+ /* Scan each device. */
+ for (ULONG idxDevice = 0; idxDevice < PCI_MAX_DEVICES; idxDevice++)
+ {
+ Slot.u.bits.DeviceNumber = idxDevice;
+ Slot.u.bits.FunctionNumber = 0;
+
+ /* Check the device header. */
+ uint8_t bHeaderType = 0xff;
+ ULONG cbRet = g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, Slot.u.AsULONG,
+ &bHeaderType, VBOX_PCI_HEADER_TYPE, sizeof(bHeaderType));
+ if (cbRet == 0)
+ break;
+ if (cbRet == 2 || bHeaderType == 0xff)
+ continue;
+
+ /* Scan functions. */
+ uint32_t const cFunctionStep = bHeaderType & 0x80 ? 1 : 8;
+ Log(("vgdrvNt4FindPciDevice: %#x:%#x cFunctionStep=%d bHeaderType=%#x\n", uBus, idxDevice, cFunctionStep, bHeaderType));
+ for (ULONG idxFunction = 0; idxFunction < PCI_MAX_FUNCTION; idxFunction += cFunctionStep)
+ {
+ Slot.u.bits.FunctionNumber = idxFunction;
+
+ /* Read the vendor and device IDs of this device and compare with the VMMDev. */
+ struct
+ {
+ uint16_t idVendor;
+ uint16_t idDevice;
+ } Buf = { PCI_INVALID_VENDORID, PCI_INVALID_VENDORID };
+ cbRet = g_pfnHalGetBusDataByOffset(PCIConfiguration, uBus, Slot.u.AsULONG, &Buf, VBOX_PCI_VENDOR_ID, sizeof(Buf));
+ if ( cbRet == sizeof(Buf)
+ && Buf.idVendor == VMMDEV_VENDORID
+ && Buf.idDevice == VMMDEV_DEVICEID)
+ {
+ /* Hooray, we've found it! */
+ Log(("vgdrvNt4FindPciDevice: Device found! Bus=%#x Slot=%#u (dev %#x, fun %#x, rvd %#x)\n",
+ uBus, Slot.u.AsULONG, Slot.u.bits.DeviceNumber, Slot.u.bits.FunctionNumber, Slot.u.bits.Reserved));
+
+ *puBus = uBus;
+ *pSlot = Slot;
+ return STATUS_SUCCESS;
+ }
+ }
+ }
+ }
+
+ return STATUS_DEVICE_DOES_NOT_EXIST;
+}
+
+
+/**
+ * Legacy helper function to create the device object.
+ *
+ * @returns NT status code.
+ *
+ * @param pDrvObj The driver object.
+ * @param pRegPath The driver registry path.
+ */
+static NTSTATUS vgdrvNt4CreateDevice(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath)
+{
+ Log(("vgdrvNt4CreateDevice: pDrvObj=%p, pRegPath=%p\n", pDrvObj, pRegPath));
+
+ /*
+ * Find our virtual PCI device
+ */
+ ULONG uBus;
+ PCI_SLOT_NUMBER uSlot;
+ NTSTATUS rc = vgdrvNt4FindPciDevice(&uBus, &uSlot);
+ if (NT_ERROR(rc))
+ {
+ Log(("vgdrvNt4CreateDevice: Device not found!\n"));
+ return rc;
+ }
+
+ /*
+ * Create device.
+ */
+ UNICODE_STRING DevName;
+ RtlInitUnicodeString(&DevName, VBOXGUEST_DEVICE_NAME_NT);
+ PDEVICE_OBJECT pDeviceObject = NULL;
+ rc = IoCreateDevice(pDrvObj, sizeof(VBOXGUESTDEVEXTWIN), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
+ if (NT_SUCCESS(rc))
+ {
+ Log(("vgdrvNt4CreateDevice: Device created\n"));
+
+ UNICODE_STRING DosName;
+ RtlInitUnicodeString(&DosName, VBOXGUEST_DEVICE_NAME_DOS);
+ rc = IoCreateSymbolicLink(&DosName, &DevName);
+ if (NT_SUCCESS(rc))
+ {
+ Log(("vgdrvNt4CreateDevice: Symlink created\n"));
+
+ /*
+ * Setup the device extension.
+ */
+ Log(("vgdrvNt4CreateDevice: Setting up device extension ...\n"));
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDeviceObject->DeviceExtension;
+ int vrc = vgdrvNtInitDevExtFundament(pDevExt, pDeviceObject);
+ if (RT_SUCCESS(vrc))
+ {
+ /* Store bus and slot number we've queried before. */
+ pDevExt->uBus = uBus;
+ pDevExt->uSlot = uSlot.u.AsULONG;
+
+ Log(("vgdrvNt4CreateDevice: Device extension created\n"));
+
+ /* Do the actual VBox init ... */
+ rc = vgdrvNtSetupDevice(pDevExt, pDeviceObject, NULL /*pIrp*/, pDrvObj, pRegPath);
+ if (NT_SUCCESS(rc))
+ {
+ Log(("vgdrvNt4CreateDevice: Returning rc = 0x%x (success)\n", rc));
+ return rc;
+ }
+
+ /* bail out */
+ vgdrvNtDeleteDevExtFundament(pDevExt);
+ }
+ IoDeleteSymbolicLink(&DosName);
+ }
+ else
+ Log(("vgdrvNt4CreateDevice: IoCreateSymbolicLink failed with rc = %#x\n", rc));
+ IoDeleteDevice(pDeviceObject);
+ }
+ else
+ Log(("vgdrvNt4CreateDevice: IoCreateDevice failed with rc = %#x\n", rc));
+ Log(("vgdrvNt4CreateDevice: Returning rc = 0x%x\n", rc));
+ return rc;
+}
+
+#endif /* TARGET_NT4 */
+
+/**
+ * Handle request from the Plug & Play subsystem.
+ *
+ * @returns NT status code
+ * @param pDrvObj Driver object
+ * @param pDevObj Device object
+ *
+ * @remarks Parts of this is duplicated in VBoxGuest-win-legacy.cpp.
+ */
+static NTSTATUS NTAPI vgdrvNtNt5PlusAddDevice(PDRIVER_OBJECT pDrvObj, PDEVICE_OBJECT pDevObj)
+{
+ LogFlowFuncEnter();
+
+ /*
+ * Create device.
+ */
+ UNICODE_STRING DevName;
+ RtlInitUnicodeString(&DevName, VBOXGUEST_DEVICE_NAME_NT);
+ PDEVICE_OBJECT pDeviceObject = NULL;
+ NTSTATUS rcNt = IoCreateDevice(pDrvObj, sizeof(VBOXGUESTDEVEXTWIN), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
+ if (NT_SUCCESS(rcNt))
+ {
+ /*
+ * Create symbolic link (DOS devices).
+ */
+ UNICODE_STRING DosName;
+ RtlInitUnicodeString(&DosName, VBOXGUEST_DEVICE_NAME_DOS);
+ rcNt = IoCreateSymbolicLink(&DosName, &DevName);
+ if (NT_SUCCESS(rcNt))
+ {
+ /*
+ * Setup the device extension.
+ */
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDeviceObject->DeviceExtension;
+ rcNt = vgdrvNtInitDevExtFundament(pDevExt, pDeviceObject);
+ if (NT_SUCCESS(rcNt))
+ {
+ pDevExt->pNextLowerDriver = IoAttachDeviceToDeviceStack(pDeviceObject, pDevObj);
+ if (pDevExt->pNextLowerDriver != NULL)
+ {
+ /* Ensure we are not called at elevated IRQL, even if our code isn't pagable any more. */
+ pDeviceObject->Flags |= DO_POWER_PAGABLE;
+
+ /* Driver is ready now. */
+ pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
+ LogFlowFunc(("Returning with rcNt=%#x (success)\n", rcNt));
+ return rcNt;
+ }
+ LogFunc(("IoAttachDeviceToDeviceStack did not give a nextLowerDriver!\n"));
+ rcNt = STATUS_DEVICE_NOT_CONNECTED;
+ vgdrvNtDeleteDevExtFundament(pDevExt);
+ }
+
+ IoDeleteSymbolicLink(&DosName);
+ }
+ else
+ LogFunc(("IoCreateSymbolicLink failed with rcNt=%#x!\n", rcNt));
+ IoDeleteDevice(pDeviceObject);
+ }
+ else
+ LogFunc(("IoCreateDevice failed with rcNt=%#x!\n", rcNt));
+
+ LogFunc(("Returning with rcNt=%#x\n", rcNt));
+ return rcNt;
+}
+
+
+/**
+ * Irp completion routine for PnP Irps we send.
+ *
+ * @returns NT status code.
+ * @param pDevObj Device object.
+ * @param pIrp Request packet.
+ * @param pEvent Semaphore.
+ */
+static NTSTATUS vgdrvNt5PlusPnpIrpComplete(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent)
+{
+ RT_NOREF2(pDevObj, pIrp);
+ KeSetEvent(pEvent, 0, FALSE);
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+
+/**
+ * Helper to send a PnP IRP and wait until it's done.
+ *
+ * @returns NT status code.
+ * @param pDevObj Device object.
+ * @param pIrp Request packet.
+ * @param fStrict When set, returns an error if the IRP gives an error.
+ */
+static NTSTATUS vgdrvNt5PlusPnPSendIrpSynchronously(PDEVICE_OBJECT pDevObj, PIRP pIrp, BOOLEAN fStrict)
+{
+ KEVENT Event;
+
+ KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
+
+ IoCopyCurrentIrpStackLocationToNext(pIrp);
+ IoSetCompletionRoutine(pIrp, (PIO_COMPLETION_ROUTINE)vgdrvNt5PlusPnpIrpComplete, &Event, TRUE, TRUE, TRUE);
+
+ NTSTATUS rcNt = IoCallDriver(pDevObj, pIrp);
+ if (rcNt == STATUS_PENDING)
+ {
+ KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
+ rcNt = pIrp->IoStatus.Status;
+ }
+
+ if ( !fStrict
+ && (rcNt == STATUS_NOT_SUPPORTED || rcNt == STATUS_INVALID_DEVICE_REQUEST))
+ {
+ rcNt = STATUS_SUCCESS;
+ }
+
+ Log(("vgdrvNt5PlusPnPSendIrpSynchronously: Returning %#x\n", rcNt));
+ return rcNt;
+}
+
+
+/**
+ * Deletes the device hardware resources.
+ *
+ * Used during removal, stopping and legacy module unloading.
+ *
+ * @param pDevExt The device extension.
+ */
+static void vgdrvNtDeleteDeviceResources(PVBOXGUESTDEVEXTWIN pDevExt)
+{
+ if (pDevExt->pInterruptObject)
+ {
+ IoDisconnectInterrupt(pDevExt->pInterruptObject);
+ pDevExt->pInterruptObject = NULL;
+ }
+ pDevExt->pPowerStateRequest = NULL; /* Will be deleted by the following call. */
+ if (pDevExt->Core.uInitState == VBOXGUESTDEVEXT_INIT_STATE_RESOURCES)
+ VGDrvCommonDeleteDevExtResources(&pDevExt->Core);
+ vgdrvNtUnmapVMMDevMemory(pDevExt);
+}
+
+
+/**
+ * Deletes the device extension fundament and unlinks the device
+ *
+ * Used during removal and legacy module unloading. Must have called
+ * vgdrvNtDeleteDeviceResources.
+ *
+ * @param pDevObj Device object.
+ * @param pDevExt The device extension.
+ */
+static void vgdrvNtDeleteDeviceFundamentAndUnlink(PDEVICE_OBJECT pDevObj, PVBOXGUESTDEVEXTWIN pDevExt)
+{
+ /*
+ * Delete the remainder of the device extension.
+ */
+ vgdrvNtDeleteDevExtFundament(pDevExt);
+
+ /*
+ * Delete the DOS symlink to the device and finally the device itself.
+ */
+ UNICODE_STRING DosName;
+ RtlInitUnicodeString(&DosName, VBOXGUEST_DEVICE_NAME_DOS);
+ IoDeleteSymbolicLink(&DosName);
+
+ Log(("vgdrvNtDeleteDeviceFundamentAndUnlink: Deleting device ...\n"));
+ IoDeleteDevice(pDevObj);
+}
+
+
+/**
+ * Checks if the device is idle.
+ * @returns STATUS_SUCCESS if idle, STATUS_UNSUCCESSFUL if busy.
+ * @param pDevExt The device extension.
+ * @param pszQueryNm The query name.
+ */
+static NTSTATUS vgdrvNtCheckIdle(PVBOXGUESTDEVEXTWIN pDevExt, const char *pszQueryNm)
+{
+ uint32_t cSessions = pDevExt->Core.cSessions;
+ if (cSessions == 0)
+ return STATUS_SUCCESS;
+ LogRel(("vgdrvNtCheckIdle/%s: cSessions=%d\n", pszQueryNm, cSessions));
+ return STATUS_UNSUCCESSFUL;
+}
+
+
+/**
+ * PnP Request handler.
+ *
+ * @param pDevObj Device object.
+ * @param pIrp Request packet.
+ */
+static NTSTATUS NTAPI vgdrvNtNt5PlusPnP(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+ PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
+
+#ifdef LOG_ENABLED
+ static char const * const s_apszFnctName[] =
+ {
+ "IRP_MN_START_DEVICE",
+ "IRP_MN_QUERY_REMOVE_DEVICE",
+ "IRP_MN_REMOVE_DEVICE",
+ "IRP_MN_CANCEL_REMOVE_DEVICE",
+ "IRP_MN_STOP_DEVICE",
+ "IRP_MN_QUERY_STOP_DEVICE",
+ "IRP_MN_CANCEL_STOP_DEVICE",
+ "IRP_MN_QUERY_DEVICE_RELATIONS",
+ "IRP_MN_QUERY_INTERFACE",
+ "IRP_MN_QUERY_CAPABILITIES",
+ "IRP_MN_QUERY_RESOURCES",
+ "IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
+ "IRP_MN_QUERY_DEVICE_TEXT",
+ "IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
+ "IRP_MN_0xE",
+ "IRP_MN_READ_CONFIG",
+ "IRP_MN_WRITE_CONFIG",
+ "IRP_MN_EJECT",
+ "IRP_MN_SET_LOCK",
+ "IRP_MN_QUERY_ID",
+ "IRP_MN_QUERY_PNP_DEVICE_STATE",
+ "IRP_MN_QUERY_BUS_INFORMATION",
+ "IRP_MN_DEVICE_USAGE_NOTIFICATION",
+ "IRP_MN_SURPRISE_REMOVAL",
+ };
+ Log(("vgdrvNtNt5PlusPnP: MinorFunction: %s\n",
+ pStack->MinorFunction < RT_ELEMENTS(s_apszFnctName) ? s_apszFnctName[pStack->MinorFunction] : "Unknown"));
+#endif
+
+ NTSTATUS rc = STATUS_SUCCESS;
+ uint8_t bMinorFunction = pStack->MinorFunction;
+ switch (bMinorFunction)
+ {
+ case IRP_MN_START_DEVICE:
+ {
+ Log(("vgdrvNtNt5PlusPnP: START_DEVICE\n"));
+
+ /* This must be handled first by the lower driver. */
+ rc = vgdrvNt5PlusPnPSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE);
+ if ( NT_SUCCESS(rc)
+ && NT_SUCCESS(pIrp->IoStatus.Status))
+ {
+ Log(("vgdrvNtNt5PlusPnP: START_DEVICE: pStack->Parameters.StartDevice.AllocatedResources = %p\n",
+ pStack->Parameters.StartDevice.AllocatedResources));
+ if (pStack->Parameters.StartDevice.AllocatedResources)
+ {
+ rc = vgdrvNtSetupDevice(pDevExt, pDevObj, pIrp, NULL, NULL);
+ if (NT_SUCCESS(rc))
+ Log(("vgdrvNtNt5PlusPnP: START_DEVICE: success\n"));
+ else
+ Log(("vgdrvNtNt5PlusPnP: START_DEVICE: vgdrvNtSetupDevice failed: %#x\n", rc));
+ }
+ else
+ {
+ Log(("vgdrvNtNt5PlusPnP: START_DEVICE: No resources, pDevExt = %p, nextLowerDriver = %p!\n",
+ pDevExt, pDevExt ? pDevExt->pNextLowerDriver : NULL));
+ rc = STATUS_UNSUCCESSFUL;
+ }
+ }
+ else
+ Log(("vgdrvNtNt5PlusPnP: START_DEVICE: vgdrvNt5PlusPnPSendIrpSynchronously failed: %#x + %#x\n",
+ rc, pIrp->IoStatus.Status));
+
+ pIrp->IoStatus.Status = rc;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+ return rc;
+ }
+
+
+ /*
+ * Sent before removing the device and/or driver.
+ */
+ case IRP_MN_QUERY_REMOVE_DEVICE:
+ {
+ Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE\n"));
+
+ RTCritSectRwEnterExcl(&pDevExt->SessionCreateCritSect);
+#ifdef VBOX_REBOOT_ON_UNINSTALL
+ Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE: Device cannot be removed without a reboot.\n"));
+ rc = STATUS_UNSUCCESSFUL;
+#endif
+ if (NT_SUCCESS(rc))
+ rc = vgdrvNtCheckIdle(pDevExt, "QUERY_REMOVE_DEVICE");
+ if (NT_SUCCESS(rc))
+ {
+ pDevExt->enmDevState = VGDRVNTDEVSTATE_PENDINGREMOVE;
+ RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect);
+
+ /* This IRP passed down to lower driver. */
+ pIrp->IoStatus.Status = STATUS_SUCCESS;
+
+ IoSkipCurrentIrpStackLocation(pIrp);
+ rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+ Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
+
+ /* We must not do anything the IRP after doing IoSkip & CallDriver
+ since the driver below us will complete (or already have completed) the IRP.
+ I.e. just return the status we got from IoCallDriver */
+ }
+ else
+ {
+ RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect);
+ pIrp->IoStatus.Status = rc;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+ }
+
+ Log(("vgdrvNtNt5PlusPnP: QUERY_REMOVE_DEVICE: Returning with rc = 0x%x\n", rc));
+ return rc;
+ }
+
+ /*
+ * Cancels a pending remove, IRP_MN_QUERY_REMOVE_DEVICE.
+ * We only have to revert the state.
+ */
+ case IRP_MN_CANCEL_REMOVE_DEVICE:
+ {
+ Log(("vgdrvNtNt5PlusPnP: CANCEL_REMOVE_DEVICE\n"));
+
+ /* This must be handled first by the lower driver. */
+ rc = vgdrvNt5PlusPnPSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE);
+ if ( NT_SUCCESS(rc)
+ && pDevExt->enmDevState == VGDRVNTDEVSTATE_PENDINGREMOVE)
+ {
+ /* Return to the state prior to receiving the IRP_MN_QUERY_REMOVE_DEVICE request. */
+ pDevExt->enmDevState = pDevExt->enmPrevDevState;
+ }
+
+ /* Complete the IRP. */
+ pIrp->IoStatus.Status = rc;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+ return rc;
+ }
+
+ /*
+ * We do nothing here actually, esp. since this request is not expected for VBoxGuest.
+ * The cleanup will be done in IRP_MN_REMOVE_DEVICE, which follows this call.
+ */
+ case IRP_MN_SURPRISE_REMOVAL:
+ {
+ Log(("vgdrvNtNt5PlusPnP: IRP_MN_SURPRISE_REMOVAL\n"));
+ pDevExt->enmDevState = VGDRVNTDEVSTATE_SURPRISEREMOVED;
+ LogRel(("VBoxGuest: unexpected device removal\n"));
+
+ /* Pass to the lower driver. */
+ pIrp->IoStatus.Status = STATUS_SUCCESS;
+
+ IoSkipCurrentIrpStackLocation(pIrp);
+ rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+
+ /* Do not complete the IRP. */
+ return rc;
+ }
+
+ /*
+ * Device and/or driver removal. Destroy everything.
+ */
+ case IRP_MN_REMOVE_DEVICE:
+ {
+ Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE\n"));
+ pDevExt->enmDevState = VGDRVNTDEVSTATE_REMOVED;
+
+ /*
+ * Disconnect interrupts and delete all hardware resources.
+ * Note! This may already have been done if we're STOPPED already, if that's a possibility.
+ */
+ vgdrvNtDeleteDeviceResources(pDevExt);
+
+ /*
+ * We need to send the remove down the stack before we detach, but we don't need
+ * to wait for the completion of this operation (nor register a completion routine).
+ */
+ pIrp->IoStatus.Status = STATUS_SUCCESS;
+
+ IoSkipCurrentIrpStackLocation(pIrp);
+ rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+ Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
+
+ IoDetachDevice(pDevExt->pNextLowerDriver);
+ Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE: Removing device ...\n"));
+
+ /*
+ * Delete the remainder of the device extension data, unlink it from the namespace and delete it.
+ */
+ vgdrvNtDeleteDeviceFundamentAndUnlink(pDevObj, pDevExt);
+
+ pDevObj = NULL; /* invalid */
+ pDevExt = NULL; /* invalid */
+
+ Log(("vgdrvNtNt5PlusPnP: REMOVE_DEVICE: Device removed!\n"));
+ return rc; /* Propagating rc from IoCallDriver. */
+ }
+
+
+ /*
+ * Sent before stopping the device/driver to check whether it is okay to do so.
+ */
+ case IRP_MN_QUERY_STOP_DEVICE:
+ {
+ Log(("vgdrvNtNt5PlusPnP: QUERY_STOP_DEVICE\n"));
+ RTCritSectRwEnterExcl(&pDevExt->SessionCreateCritSect);
+ rc = vgdrvNtCheckIdle(pDevExt, "QUERY_STOP_DEVICE");
+ if (NT_SUCCESS(rc))
+ {
+ pDevExt->enmPrevDevState = pDevExt->enmDevState;
+ pDevExt->enmDevState = VGDRVNTDEVSTATE_PENDINGSTOP;
+ RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect);
+
+ /* This IRP passed down to lower driver. */
+ pIrp->IoStatus.Status = STATUS_SUCCESS;
+
+ IoSkipCurrentIrpStackLocation(pIrp);
+
+ rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+ Log(("vgdrvNtNt5PlusPnP: QUERY_STOP_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
+
+ /* we must not do anything with the IRP after doing IoSkip & CallDriver since the
+ driver below us will complete (or already have completed) the IRP. I.e. just
+ return the status we got from IoCallDriver. */
+ }
+ else
+ {
+ RTCritSectRwLeaveExcl(&pDevExt->SessionCreateCritSect);
+ pIrp->IoStatus.Status = rc;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+ }
+
+ Log(("vgdrvNtNt5PlusPnP: QUERY_STOP_DEVICE: Returning with rc = 0x%x\n", rc));
+ return rc;
+ }
+
+ /*
+ * Cancels a pending remove, IRP_MN_QUERY_STOP_DEVICE.
+ * We only have to revert the state.
+ */
+ case IRP_MN_CANCEL_STOP_DEVICE:
+ {
+ Log(("vgdrvNtNt5PlusPnP: CANCEL_STOP_DEVICE\n"));
+
+ /* This must be handled first by the lower driver. */
+ rc = vgdrvNt5PlusPnPSendIrpSynchronously(pDevExt->pNextLowerDriver, pIrp, TRUE);
+ if ( NT_SUCCESS(rc)
+ && pDevExt->enmDevState == VGDRVNTDEVSTATE_PENDINGSTOP)
+ {
+ /* Return to the state prior to receiving the IRP_MN_QUERY_STOP_DEVICE request. */
+ pDevExt->enmDevState = pDevExt->enmPrevDevState;
+ }
+
+ /* Complete the IRP. */
+ pIrp->IoStatus.Status = rc;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+ return rc;
+ }
+
+ /*
+ * Stop the device.
+ */
+ case IRP_MN_STOP_DEVICE:
+ {
+ Log(("vgdrvNtNt5PlusPnP: STOP_DEVICE\n"));
+ pDevExt->enmDevState = VGDRVNTDEVSTATE_STOPPED;
+
+ /*
+ * Release the hardware resources.
+ */
+ vgdrvNtDeleteDeviceResources(pDevExt);
+
+ /*
+ * Pass the request to the lower driver.
+ */
+ pIrp->IoStatus.Status = STATUS_SUCCESS;
+ IoSkipCurrentIrpStackLocation(pIrp);
+ rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+ Log(("vgdrvNtNt5PlusPnP: STOP_DEVICE: Next lower driver replied rc = 0x%x\n", rc));
+ return rc;
+ }
+
+ default:
+ {
+ IoSkipCurrentIrpStackLocation(pIrp);
+ rc = IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+ Log(("vgdrvNtNt5PlusPnP: Unknown request %#x: Lower driver replied: %x\n", bMinorFunction, rc));
+ return rc;
+ }
+ }
+}
+
+
+/**
+ * Handle the power completion event.
+ *
+ * @returns NT status code.
+ * @param pDevObj Targetted device object.
+ * @param pIrp IO request packet.
+ * @param pContext Context value passed to IoSetCompletionRoutine in VBoxGuestPower.
+ */
+static NTSTATUS vgdrvNtNt5PlusPowerComplete(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pContext)
+{
+#ifdef VBOX_STRICT
+ RT_NOREF1(pDevObj);
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pContext;
+ PIO_STACK_LOCATION pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
+
+ Assert(pDevExt);
+
+ if (pIrpSp)
+ {
+ Assert(pIrpSp->MajorFunction == IRP_MJ_POWER);
+ if (NT_SUCCESS(pIrp->IoStatus.Status))
+ {
+ switch (pIrpSp->MinorFunction)
+ {
+ case IRP_MN_SET_POWER:
+ switch (pIrpSp->Parameters.Power.Type)
+ {
+ case DevicePowerState:
+ switch (pIrpSp->Parameters.Power.State.DeviceState)
+ {
+ case PowerDeviceD0:
+ break;
+ default: /* Shut up MSC */
+ break;
+ }
+ break;
+ default: /* Shut up MSC */
+ break;
+ }
+ break;
+ }
+ }
+ }
+#else
+ RT_NOREF3(pDevObj, pIrp, pContext);
+#endif
+
+ return STATUS_SUCCESS;
+}
+
+
+/**
+ * Handle the Power requests.
+ *
+ * @returns NT status code
+ * @param pDevObj device object
+ * @param pIrp IRP
+ */
+static NTSTATUS NTAPI vgdrvNtNt5PlusPower(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+ POWER_STATE_TYPE enmPowerType = pStack->Parameters.Power.Type;
+ POWER_STATE PowerState = pStack->Parameters.Power.State;
+ POWER_ACTION enmPowerAction = pStack->Parameters.Power.ShutdownType;
+
+ Log(("vgdrvNtNt5PlusPower:\n"));
+
+ switch (pStack->MinorFunction)
+ {
+ case IRP_MN_SET_POWER:
+ {
+ Log(("vgdrvNtNt5PlusPower: IRP_MN_SET_POWER, type= %d\n", enmPowerType));
+ switch (enmPowerType)
+ {
+ case SystemPowerState:
+ {
+ Log(("vgdrvNtNt5PlusPower: SystemPowerState, action = %d, state = %d/%d\n",
+ enmPowerAction, PowerState.SystemState, PowerState.DeviceState));
+
+ switch (enmPowerAction)
+ {
+ case PowerActionSleep:
+
+ /* System now is in a working state. */
+ if (PowerState.SystemState == PowerSystemWorking)
+ {
+ if ( pDevExt
+ && pDevExt->enmLastSystemPowerAction == PowerActionHibernate)
+ {
+ Log(("vgdrvNtNt5PlusPower: Returning from hibernation!\n"));
+ int rc = VGDrvCommonReinitDevExtAfterHibernation(&pDevExt->Core,
+ vgdrvNtVersionToOSType(g_enmVGDrvNtVer));
+ if (RT_FAILURE(rc))
+ Log(("vgdrvNtNt5PlusPower: Cannot re-init VMMDev chain, rc = %d!\n", rc));
+ }
+ }
+ break;
+
+ case PowerActionShutdownReset:
+ {
+ Log(("vgdrvNtNt5PlusPower: Power action reset!\n"));
+
+ /* Tell the VMM that we no longer support mouse pointer integration. */
+ VMMDevReqMouseStatus *pReq = NULL;
+ int vrc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof (VMMDevReqMouseStatus),
+ VMMDevReq_SetMouseStatus);
+ if (RT_SUCCESS(vrc))
+ {
+ pReq->mouseFeatures = 0;
+ pReq->pointerXPos = 0;
+ pReq->pointerYPos = 0;
+
+ vrc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(vrc))
+ {
+ Log(("vgdrvNtNt5PlusPower: error communicating new power status to VMMDev. vrc = %Rrc\n", vrc));
+ }
+
+ VbglR0GRFree(&pReq->header);
+ }
+
+ /* Don't do any cleanup here; there might be still coming in some IOCtls after we got this
+ * power action and would assert/crash when we already cleaned up all the stuff! */
+ break;
+ }
+
+ case PowerActionShutdown:
+ case PowerActionShutdownOff:
+ {
+ Log(("vgdrvNtNt5PlusPower: Power action shutdown!\n"));
+ if (PowerState.SystemState >= PowerSystemShutdown)
+ {
+ Log(("vgdrvNtNt5PlusPower: Telling the VMMDev to close the VM ...\n"));
+
+ VMMDevPowerStateRequest *pReq = pDevExt->pPowerStateRequest;
+ int vrc = VERR_NOT_IMPLEMENTED;
+ if (pReq)
+ {
+ pReq->header.requestType = VMMDevReq_SetPowerStatus;
+ pReq->powerState = VMMDevPowerState_PowerOff;
+
+ vrc = VbglR0GRPerform(&pReq->header);
+ }
+ if (RT_FAILURE(vrc))
+ Log(("vgdrvNtNt5PlusPower: Error communicating new power status to VMMDev. vrc = %Rrc\n", vrc));
+
+ /* No need to do cleanup here; at this point we should've been
+ * turned off by VMMDev already! */
+ }
+ break;
+ }
+
+ case PowerActionHibernate:
+ Log(("vgdrvNtNt5PlusPower: Power action hibernate!\n"));
+ break;
+
+ case PowerActionWarmEject:
+ Log(("vgdrvNtNt5PlusPower: PowerActionWarmEject!\n"));
+ break;
+
+ default:
+ Log(("vgdrvNtNt5PlusPower: %d\n", enmPowerAction));
+ break;
+ }
+
+ /*
+ * Save the current system power action for later use.
+ * This becomes handy when we return from hibernation for example.
+ */
+ if (pDevExt)
+ pDevExt->enmLastSystemPowerAction = enmPowerAction;
+
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ /*
+ * Whether we are completing or relaying this power IRP,
+ * we must call PoStartNextPowerIrp.
+ */
+ g_pfnPoStartNextPowerIrp(pIrp);
+
+ /*
+ * Send the IRP down the driver stack, using PoCallDriver
+ * (not IoCallDriver, as for non-power irps).
+ */
+ IoCopyCurrentIrpStackLocationToNext(pIrp);
+ IoSetCompletionRoutine(pIrp,
+ vgdrvNtNt5PlusPowerComplete,
+ (PVOID)pDevExt,
+ TRUE,
+ TRUE,
+ TRUE);
+ return g_pfnPoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+}
+
+
+/**
+ * IRP_MJ_SYSTEM_CONTROL handler.
+ *
+ * @returns NT status code
+ * @param pDevObj Device object.
+ * @param pIrp IRP.
+ */
+static NTSTATUS NTAPI vgdrvNtNt5PlusSystemControl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+
+ LogFlowFuncEnter();
+
+ /* Always pass it on to the next driver. */
+ IoSkipCurrentIrpStackLocation(pIrp);
+
+ return IoCallDriver(pDevExt->pNextLowerDriver, pIrp);
+}
+
+
+/**
+ * Unload the driver.
+ *
+ * @param pDrvObj Driver object.
+ */
+static void NTAPI vgdrvNtUnload(PDRIVER_OBJECT pDrvObj)
+{
+ LogFlowFuncEnter();
+
+#ifdef TARGET_NT4
+ /*
+ * We need to destroy the device object here on NT4 and earlier.
+ */
+ PDEVICE_OBJECT pDevObj = pDrvObj->DeviceObject;
+ if (pDevObj)
+ {
+ if (g_enmVGDrvNtVer <= VGDRVNTVER_WINNT4)
+ {
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+ AssertPtr(pDevExt);
+ AssertMsg(pDevExt->Core.uInitState == VBOXGUESTDEVEXT_INIT_STATE_RESOURCES,
+ ("uInitState=%#x\n", pDevExt->Core.uInitState));
+
+ vgdrvNtDeleteDeviceResources(pDevExt);
+ vgdrvNtDeleteDeviceFundamentAndUnlink(pDevObj, pDevExt);
+ }
+ }
+#else /* !TARGET_NT4 */
+ /*
+ * On a PnP driver this routine will be called after IRP_MN_REMOVE_DEVICE
+ * where we already did the cleanup, so don't do anything here (yet).
+ */
+ RT_NOREF1(pDrvObj);
+#endif /* !TARGET_NT4 */
+
+ VGDrvCommonDestroyLoggers();
+ RTR0Term();
+
+ /*
+ * Finally deregister the bugcheck callback. Do it late to catch trouble in RTR0Term.
+ */
+ if (g_fBugCheckCallbackRegistered)
+ {
+ g_pfnKeDeregisterBugCheckCallback(&g_BugCheckCallbackRec);
+ g_fBugCheckCallbackRegistered = false;
+ }
+}
+
+
+/**
+ * For simplifying request completion into a simple return statement, extended
+ * version.
+ *
+ * @returns rcNt
+ * @param rcNt The status code.
+ * @param uInfo Extra info value.
+ * @param pIrp The IRP.
+ */
+DECLINLINE(NTSTATUS) vgdrvNtCompleteRequestEx(NTSTATUS rcNt, ULONG_PTR uInfo, PIRP pIrp)
+{
+ pIrp->IoStatus.Status = rcNt;
+ pIrp->IoStatus.Information = uInfo;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+ return rcNt;
+}
+
+
+/**
+ * For simplifying request completion into a simple return statement.
+ *
+ * @returns rcNt
+ * @param rcNt The status code.
+ * @param pIrp The IRP.
+ */
+DECLINLINE(NTSTATUS) vgdrvNtCompleteRequest(NTSTATUS rcNt, PIRP pIrp)
+{
+ return vgdrvNtCompleteRequestEx(rcNt, 0 /*uInfo*/, pIrp);
+}
+
+
+/**
+ * Checks if NT authority rev 1 SID (SECURITY_NT_AUTHORITY).
+ *
+ * @returns true / false.
+ * @param pSid The SID to check.
+ */
+DECLINLINE(bool) vgdrvNtIsSidNtAuth(struct _SID const *pSid)
+{
+ return pSid != NULL
+ && pSid->Revision == 1
+ && pSid->IdentifierAuthority.Value[5] == 5
+ && pSid->IdentifierAuthority.Value[4] == 0
+ && pSid->IdentifierAuthority.Value[3] == 0
+ && pSid->IdentifierAuthority.Value[2] == 0
+ && pSid->IdentifierAuthority.Value[1] == 0
+ && pSid->IdentifierAuthority.Value[0] == 0;
+}
+
+
+/**
+ * Matches SID with local system user (S-1-5-18 / SECURITY_LOCAL_SYSTEM_RID).
+ */
+DECLINLINE(bool) vgdrvNtIsSidLocalSystemUser(SID const *pSid)
+{
+ return vgdrvNtIsSidNtAuth(pSid)
+ && pSid->SubAuthorityCount == 1
+ && pSid->SubAuthority[0] == SECURITY_LOCAL_SYSTEM_RID;
+}
+
+
+/**
+ * Matches SID with NT system admin user (S-1-5-*-500 / DOMAIN_USER_RID_ADMIN).
+ */
+DECLINLINE(bool) vgdrvNtIsSidAdminUser(SID const *pSid)
+{
+ /** @todo restrict to SECURITY_NT_NON_UNIQUE? */
+ return vgdrvNtIsSidNtAuth(pSid)
+ && pSid->SubAuthorityCount >= 2
+ && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES
+ && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_USER_RID_ADMIN;
+}
+
+
+/**
+ * Matches SID with NT system guest user (S-1-5-*-501 / DOMAIN_USER_RID_GUEST).
+ */
+DECLINLINE(bool) vgdrvNtIsSidGuestUser(SID const *pSid)
+{
+ /** @todo restrict to SECURITY_NT_NON_UNIQUE? */
+ return vgdrvNtIsSidNtAuth(pSid)
+ && pSid->SubAuthorityCount >= 2
+ && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES
+ && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_USER_RID_GUEST;
+}
+
+
+/**
+ * Matches SID with NT system admins group (S-1-5-32-544, S-1-5-*-512).
+ */
+DECLINLINE(bool) vgdrvNtIsSidAdminsGroup(SID const *pSid)
+{
+ return vgdrvNtIsSidNtAuth(pSid)
+ && ( ( pSid->SubAuthorityCount == 2
+ && pSid->SubAuthority[0] == SECURITY_BUILTIN_DOMAIN_RID
+ && pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_ADMINS)
+#if 0
+ /** @todo restrict to SECURITY_NT_NON_UNIQUE? */
+ || ( pSid->SubAuthorityCount >= 2
+ && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES
+ && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_GROUP_RID_ADMINS)
+#endif
+ );
+}
+
+
+/**
+ * Matches SID with NT system users group (S-1-5-32-545, S-1-5-32-547, S-1-5-*-512).
+ */
+DECLINLINE(bool) vgdrvNtIsSidUsersGroup(SID const *pSid)
+{
+ return vgdrvNtIsSidNtAuth(pSid)
+ && ( ( pSid->SubAuthorityCount == 2
+ && pSid->SubAuthority[0] == SECURITY_BUILTIN_DOMAIN_RID
+ && ( pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_USERS
+ || pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_POWER_USERS) )
+#if 0
+ /** @todo restrict to SECURITY_NT_NON_UNIQUE? */
+ || ( pSid->SubAuthorityCount >= 2
+ && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES
+ && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_GROUP_RID_USERS)
+#endif
+ );
+}
+
+
+/**
+ * Matches SID with NT system guests group (S-1-5-32-546, S-1-5-*-512).
+ */
+DECLINLINE(bool) vgdrvNtIsSidGuestsGroup(SID const *pSid)
+{
+ return vgdrvNtIsSidNtAuth(pSid)
+ && ( ( pSid->SubAuthorityCount == 2
+ && pSid->SubAuthority[0] == SECURITY_BUILTIN_DOMAIN_RID
+ && pSid->SubAuthority[1] == DOMAIN_ALIAS_RID_GUESTS)
+#if 0
+ /** @todo restrict to SECURITY_NT_NON_UNIQUE? */
+ || ( pSid->SubAuthorityCount >= 2
+ && pSid->SubAuthorityCount <= SID_MAX_SUB_AUTHORITIES
+ && pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_GROUP_RID_GUESTS)
+#endif
+ );
+}
+
+
+/**
+ * Checks if local authority rev 1 SID (SECURITY_LOCAL_SID_AUTHORITY).
+ *
+ * @returns true / false.
+ * @param pSid The SID to check.
+ */
+DECLINLINE(bool) vgdrvNtIsSidLocalAuth(struct _SID const *pSid)
+{
+ return pSid != NULL
+ && pSid->Revision == 1
+ && pSid->IdentifierAuthority.Value[5] == 2
+ && pSid->IdentifierAuthority.Value[4] == 0
+ && pSid->IdentifierAuthority.Value[3] == 0
+ && pSid->IdentifierAuthority.Value[2] == 0
+ && pSid->IdentifierAuthority.Value[1] == 0
+ && pSid->IdentifierAuthority.Value[0] == 0;
+}
+
+
+/**
+ * Matches SID with console logon group (S-1-2-1 / SECURITY_LOCAL_LOGON_RID).
+ */
+DECLINLINE(bool) vgdrvNtIsSidConsoleLogonGroup(SID const *pSid)
+{
+ return vgdrvNtIsSidLocalAuth(pSid)
+ && pSid->SubAuthorityCount == 1
+ && pSid->SubAuthority[0] == SECURITY_LOCAL_LOGON_RID;
+}
+
+
+/**
+ * Checks if mandatory label authority rev 1 SID (SECURITY_MANDATORY_LABEL_AUTHORITY).
+ *
+ * @returns true / false.
+ * @param pSid The SID to check.
+ */
+DECLINLINE(bool) vgdrvNtIsSidMandatoryLabelAuth(struct _SID const *pSid)
+{
+ return pSid != NULL
+ && pSid->Revision == 1
+ && pSid->IdentifierAuthority.Value[5] == 16
+ && pSid->IdentifierAuthority.Value[4] == 0
+ && pSid->IdentifierAuthority.Value[3] == 0
+ && pSid->IdentifierAuthority.Value[2] == 0
+ && pSid->IdentifierAuthority.Value[1] == 0
+ && pSid->IdentifierAuthority.Value[0] == 0;
+}
+
+
+#ifdef LOG_ENABLED
+/** Format an SID for logging. */
+static const char *vgdrvNtFormatSid(char *pszBuf, size_t cbBuf, struct _SID const *pSid)
+{
+ uint64_t uAuth = RT_MAKE_U64_FROM_U8(pSid->IdentifierAuthority.Value[5], pSid->IdentifierAuthority.Value[4],
+ pSid->IdentifierAuthority.Value[3], pSid->IdentifierAuthority.Value[2],
+ pSid->IdentifierAuthority.Value[1], pSid->IdentifierAuthority.Value[0],
+ 0, 0);
+ ssize_t offCur = RTStrPrintf2(pszBuf, cbBuf, "S-%u-%RU64", pSid->Revision, uAuth);
+ ULONG const *puSubAuth = &pSid->SubAuthority[0];
+ unsigned cSubAuths = pSid->SubAuthorityCount;
+ while (cSubAuths > 0 && (size_t)offCur < cbBuf)
+ {
+ ssize_t cchThis = RTStrPrintf2(&pszBuf[offCur], cbBuf - (size_t)offCur, "-%u", *puSubAuth);
+ if (cchThis > 0)
+ {
+ offCur += cchThis;
+ puSubAuth++;
+ cSubAuths--;
+ }
+ else
+ {
+ Assert(cbBuf >= 5);
+ pszBuf[cbBuf - 4] = '.';
+ pszBuf[cbBuf - 3] = '.';
+ pszBuf[cbBuf - 2] = '.';
+ pszBuf[cbBuf - 1] = '\0';
+ break;
+ }
+ }
+ return pszBuf;
+}
+#endif
+
+
+/**
+ * Calculate requestor flags for the current process.
+ *
+ * ASSUMES vgdrvNtCreate is executed in the context of the process and thread
+ * doing the NtOpenFile call.
+ *
+ * @returns VMMDEV_REQUESTOR_XXX
+ */
+static uint32_t vgdrvNtCalcRequestorFlags(void)
+{
+ uint32_t fRequestor = VMMDEV_REQUESTOR_USERMODE
+ | VMMDEV_REQUESTOR_USR_NOT_GIVEN
+ | VMMDEV_REQUESTOR_CON_DONT_KNOW
+ | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN
+ | VMMDEV_REQUESTOR_NO_USER_DEVICE;
+ HANDLE hToken = NULL;
+ NTSTATUS rcNt = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken);
+ if (NT_SUCCESS(rcNt))
+ {
+ union
+ {
+ TOKEN_USER CurUser;
+ TOKEN_GROUPS CurGroups;
+ uint8_t abPadding[256];
+ } Buf;
+#ifdef LOG_ENABLED
+ char szSid[200];
+#endif
+
+ /*
+ * Get the user SID and see if it's a standard one.
+ */
+ RT_ZERO(Buf.CurUser);
+ ULONG cbReturned = 0;
+ rcNt = ZwQueryInformationToken(hToken, TokenUser, &Buf.CurUser, sizeof(Buf), &cbReturned);
+ if (NT_SUCCESS(rcNt))
+ {
+ struct _SID const *pSid = (struct _SID const *)Buf.CurUser.User.Sid;
+ Log5(("vgdrvNtCalcRequestorFlags: TokenUser: %#010x %s\n",
+ Buf.CurUser.User.Attributes, vgdrvNtFormatSid(szSid, sizeof(szSid), pSid)));
+
+ if (vgdrvNtIsSidLocalSystemUser(pSid))
+ fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_SYSTEM;
+ else if (vgdrvNtIsSidAdminUser(pSid))
+ fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_ROOT;
+ else if (vgdrvNtIsSidGuestUser(pSid))
+ fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_GUEST;
+ }
+ else
+ LogRel(("vgdrvNtCalcRequestorFlags: TokenUser query failed: %#x\n", rcNt));
+
+ /*
+ * Get the groups.
+ */
+ TOKEN_GROUPS *pCurGroupsFree = NULL;
+ TOKEN_GROUPS *pCurGroups = &Buf.CurGroups;
+ uint32_t cbCurGroups = sizeof(Buf);
+ cbReturned = 0;
+ RT_ZERO(Buf);
+ rcNt = ZwQueryInformationToken(hToken, TokenGroups, pCurGroups, cbCurGroups, &cbReturned);
+ if (rcNt == STATUS_BUFFER_TOO_SMALL)
+ {
+ uint32_t cTries = 8;
+ do
+ {
+ RTMemTmpFree(pCurGroupsFree);
+ if (cbCurGroups < cbReturned)
+ cbCurGroups = RT_ALIGN_32(cbCurGroups + 32, 64);
+ else
+ cbCurGroups += 64;
+ pCurGroupsFree = pCurGroups = (TOKEN_GROUPS *)RTMemTmpAllocZ(cbCurGroups);
+ if (pCurGroupsFree)
+ rcNt = ZwQueryInformationToken(hToken, TokenGroups, pCurGroups, cbCurGroups, &cbReturned);
+ else
+ rcNt = STATUS_NO_MEMORY;
+ } while (rcNt == STATUS_BUFFER_TOO_SMALL && cTries-- > 0);
+ }
+ if (NT_SUCCESS(rcNt))
+ {
+ bool fGuestsMember = false;
+ bool fUsersMember = false;
+ if (g_enmVGDrvNtVer >= VGDRVNTVER_WIN7)
+ fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_CON_MASK) | VMMDEV_REQUESTOR_CON_NO;
+
+ for (uint32_t iGrp = 0; iGrp < pCurGroups->GroupCount; iGrp++)
+ {
+ uint32_t const fAttribs = pCurGroups->Groups[iGrp].Attributes;
+ struct _SID const *pSid = (struct _SID const *)pCurGroups->Groups[iGrp].Sid;
+ Log5(("vgdrvNtCalcRequestorFlags: TokenGroups[%u]: %#10x %s\n",
+ iGrp, fAttribs, vgdrvNtFormatSid(szSid, sizeof(szSid), pSid)));
+
+ if ( (fAttribs & SE_GROUP_INTEGRITY_ENABLED)
+ && vgdrvNtIsSidMandatoryLabelAuth(pSid)
+ && pSid->SubAuthorityCount == 1
+ && (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_NOT_GIVEN)
+ {
+ fRequestor &= ~VMMDEV_REQUESTOR_TRUST_MASK;
+ if (pSid->SubAuthority[0] < SECURITY_MANDATORY_LOW_RID)
+ fRequestor |= VMMDEV_REQUESTOR_TRUST_UNTRUSTED;
+ else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_MEDIUM_RID)
+ fRequestor |= VMMDEV_REQUESTOR_TRUST_LOW;
+ else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_MEDIUM_PLUS_RID)
+ fRequestor |= VMMDEV_REQUESTOR_TRUST_MEDIUM;
+ else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_HIGH_RID)
+ fRequestor |= VMMDEV_REQUESTOR_TRUST_MEDIUM_PLUS;
+ else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_SYSTEM_RID)
+ fRequestor |= VMMDEV_REQUESTOR_TRUST_HIGH;
+ else if (pSid->SubAuthority[0] < SECURITY_MANDATORY_PROTECTED_PROCESS_RID)
+ fRequestor |= VMMDEV_REQUESTOR_TRUST_SYSTEM;
+ else
+ fRequestor |= VMMDEV_REQUESTOR_TRUST_PROTECTED;
+ Log5(("vgdrvNtCalcRequestorFlags: mandatory label %u: => %#x\n", pSid->SubAuthority[0], fRequestor));
+ }
+ else if ( (fAttribs & (SE_GROUP_ENABLED | SE_GROUP_MANDATORY | SE_GROUP_USE_FOR_DENY_ONLY))
+ == (SE_GROUP_ENABLED | SE_GROUP_MANDATORY)
+ && vgdrvNtIsSidConsoleLogonGroup(pSid))
+ {
+ fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_CON_MASK) | VMMDEV_REQUESTOR_CON_YES;
+ Log5(("vgdrvNtCalcRequestorFlags: console: => %#x\n", fRequestor));
+ }
+ else if ( (fAttribs & (SE_GROUP_ENABLED | SE_GROUP_MANDATORY | SE_GROUP_USE_FOR_DENY_ONLY))
+ == (SE_GROUP_ENABLED | SE_GROUP_MANDATORY)
+ && vgdrvNtIsSidNtAuth(pSid))
+ {
+ if (vgdrvNtIsSidAdminsGroup(pSid))
+ {
+ fRequestor |= VMMDEV_REQUESTOR_GRP_WHEEL;
+ Log5(("vgdrvNtCalcRequestorFlags: admins group: => %#x\n", fRequestor));
+ }
+ else if (vgdrvNtIsSidUsersGroup(pSid))
+ {
+ Log5(("vgdrvNtCalcRequestorFlags: users group\n"));
+ fUsersMember = true;
+ }
+ else if (vgdrvNtIsSidGuestsGroup(pSid))
+ {
+ Log5(("vgdrvNtCalcRequestorFlags: guests group\n"));
+ fGuestsMember = true;
+ }
+ }
+ }
+ if ((fRequestor & VMMDEV_REQUESTOR_USR_MASK) == VMMDEV_REQUESTOR_USR_NOT_GIVEN)
+ {
+ if (fUsersMember)
+ fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_USER;
+ else if (fGuestsMember)
+ fRequestor = (fRequestor & ~VMMDEV_REQUESTOR_USR_MASK) | VMMDEV_REQUESTOR_USR_GUEST;
+ }
+ }
+ else
+ LogRel(("vgdrvNtCalcRequestorFlags: TokenGroups query failed: %#x\n", rcNt));
+
+ RTMemTmpFree(pCurGroupsFree);
+ ZwClose(hToken);
+
+ /*
+ * Determine whether we should set VMMDEV_REQUESTOR_USER_DEVICE or not.
+ *
+ * The purpose here is to differentiate VBoxService accesses
+ * from VBoxTray and VBoxControl, as VBoxService should be allowed to
+ * do more than the latter two. VBoxService normally runs under the
+ * system account which is easily detected, but for debugging and
+ * similar purposes we also allow an elevated admin to run it as well.
+ */
+ if ( (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_UNTRUSTED /* general paranoia wrt system account */
+ || (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_LOW /* ditto */
+ || (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_MEDIUM /* ditto */
+ || !( (fRequestor & VMMDEV_REQUESTOR_USR_MASK) == VMMDEV_REQUESTOR_USR_SYSTEM
+ || ( ( (fRequestor & VMMDEV_REQUESTOR_GRP_WHEEL)
+ || (fRequestor & VMMDEV_REQUESTOR_USR_MASK) == VMMDEV_REQUESTOR_USR_ROOT)
+ && ( (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) >= VMMDEV_REQUESTOR_TRUST_HIGH
+ || (fRequestor & VMMDEV_REQUESTOR_TRUST_MASK) == VMMDEV_REQUESTOR_TRUST_NOT_GIVEN)) ))
+ fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE;
+ }
+ else
+ {
+ LogRel(("vgdrvNtCalcRequestorFlags: NtOpenProcessToken query failed: %#x\n", rcNt));
+ fRequestor |= VMMDEV_REQUESTOR_USER_DEVICE;
+ }
+
+ Log5(("vgdrvNtCalcRequestorFlags: returns %#x\n", fRequestor));
+ return fRequestor;
+}
+
+
+/**
+ * Create (i.e. Open) file entry point.
+ *
+ * @param pDevObj Device object.
+ * @param pIrp Request packet.
+ */
+static NTSTATUS NTAPI vgdrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ Log(("vgdrvNtCreate: RequestorMode=%d\n", pIrp->RequestorMode));
+ PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
+ PFILE_OBJECT pFileObj = pStack->FileObject;
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+
+ Assert(pFileObj->FsContext == NULL);
+
+ /*
+ * We are not remotely similar to a directory...
+ */
+ NTSTATUS rcNt;
+ if (!(pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE))
+ {
+ /*
+ * Check the device state. We enter the critsect in shared mode to
+ * prevent race with PnP system requests checking whether we're idle.
+ */
+ RTCritSectRwEnterShared(&pDevExt->SessionCreateCritSect);
+ VGDRVNTDEVSTATE const enmDevState = pDevExt->enmDevState;
+ if (enmDevState == VGDRVNTDEVSTATE_OPERATIONAL)
+ {
+ /*
+ * Create a client session.
+ */
+ int rc;
+ PVBOXGUESTSESSION pSession;
+ if (pIrp->RequestorMode == KernelMode)
+ rc = VGDrvCommonCreateKernelSession(&pDevExt->Core, &pSession);
+ else
+ rc = VGDrvCommonCreateUserSession(&pDevExt->Core, vgdrvNtCalcRequestorFlags(), &pSession);
+ RTCritSectRwLeaveShared(&pDevExt->SessionCreateCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pFileObj->FsContext = pSession;
+ Log(("vgdrvNtCreate: Successfully created %s session %p (fRequestor=%#x)\n",
+ pIrp->RequestorMode == KernelMode ? "kernel" : "user", pSession, pSession->fRequestor));
+
+ return vgdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp);
+ }
+
+ /* Note. the IoStatus is completely ignored on error. */
+ Log(("vgdrvNtCreate: Failed to create session: rc=%Rrc\n", rc));
+ if (rc == VERR_NO_MEMORY)
+ rcNt = STATUS_NO_MEMORY;
+ else
+ rcNt = STATUS_UNSUCCESSFUL;
+ }
+ else
+ {
+ RTCritSectRwLeaveShared(&pDevExt->SessionCreateCritSect);
+ LogFlow(("vgdrvNtCreate: Failed. Device is not in 'working' state: %d\n", enmDevState));
+ rcNt = STATUS_DEVICE_NOT_READY;
+ }
+ }
+ else
+ {
+ LogFlow(("vgdrvNtCreate: Failed. FILE_DIRECTORY_FILE set\n"));
+ rcNt = STATUS_NOT_A_DIRECTORY;
+ }
+ return vgdrvNtCompleteRequest(rcNt, pIrp);
+}
+
+
+/**
+ * Close file entry point.
+ *
+ * @param pDevObj Device object.
+ * @param pIrp Request packet.
+ */
+static NTSTATUS NTAPI vgdrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+ PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
+ PFILE_OBJECT pFileObj = pStack->FileObject;
+
+ LogFlowFunc(("pDevExt=0x%p, pFileObj=0x%p, FsContext=0x%p\n", pDevExt, pFileObj, pFileObj->FsContext));
+
+#ifdef VBOX_WITH_HGCM
+ /* Close both, R0 and R3 sessions. */
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)pFileObj->FsContext;
+ if (pSession)
+ VGDrvCommonCloseSession(&pDevExt->Core, pSession);
+#endif
+
+ pFileObj->FsContext = NULL;
+ pIrp->IoStatus.Information = 0;
+ pIrp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+
+ return STATUS_SUCCESS;
+}
+
+
+/**
+ * Device I/O Control entry point.
+ *
+ * @param pDevObj Device object.
+ * @param pIrp Request packet.
+ */
+NTSTATUS NTAPI vgdrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+ PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
+ PVBOXGUESTSESSION pSession = pStack->FileObject ? (PVBOXGUESTSESSION)pStack->FileObject->FsContext : NULL;
+
+ if (!RT_VALID_PTR(pSession))
+ return vgdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp);
+
+#if 0 /* No fast I/O controls defined yet. */
+ /*
+ * Deal with the 2-3 high-speed IOCtl that takes their arguments from
+ * the session and iCmd, and does not return anything.
+ */
+ if (pSession->fUnrestricted)
+ {
+ ULONG ulCmd = pStack->Parameters.DeviceIoControl.IoControlCode;
+ if ( ulCmd == SUP_IOCTL_FAST_DO_RAW_RUN
+ || ulCmd == SUP_IOCTL_FAST_DO_HM_RUN
+ || ulCmd == SUP_IOCTL_FAST_DO_NOP)
+ {
+ int rc = supdrvIOCtlFast(ulCmd, (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, pDevExt, pSession);
+
+ /* Complete the I/O request. */
+ supdrvSessionRelease(pSession);
+ return vgdrvNtCompleteRequest(RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER, pIrp);
+ }
+ }
+#endif
+
+ return vgdrvNtDeviceControlSlow(&pDevExt->Core, pSession, pIrp, pStack);
+}
+
+
+/**
+ * Device I/O Control entry point.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pIrp Request packet.
+ * @param pStack The request stack pointer.
+ */
+static NTSTATUS vgdrvNtDeviceControlSlow(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ PIRP pIrp, PIO_STACK_LOCATION pStack)
+{
+ NTSTATUS rcNt;
+ uint32_t cbOut = 0;
+ int rc = 0;
+ Log2(("vgdrvNtDeviceControlSlow(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n",
+ pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode,
+ pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength,
+ pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession));
+
+#if 0 /*def RT_ARCH_AMD64*/
+ /* Don't allow 32-bit processes to do any I/O controls. */
+ if (!IoIs32bitProcess(pIrp))
+#endif
+ {
+ /* Verify that it's a buffered CTL. */
+ if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED)
+ {
+ /* Verify that the sizes in the request header are correct. */
+ PVBGLREQHDR pHdr = (PVBGLREQHDR)pIrp->AssociatedIrp.SystemBuffer;
+ if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr)
+ && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cbIn
+ && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cbOut)
+ {
+ /* Zero extra output bytes to make sure we don't leak anything. */
+ if (pHdr->cbIn < pHdr->cbOut)
+ RtlZeroMemory((uint8_t *)pHdr + pHdr->cbIn, pHdr->cbOut - pHdr->cbIn);
+
+ /*
+ * Do the job.
+ */
+ rc = VGDrvCommonIoCtl(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr,
+ RT_MAX(pHdr->cbIn, pHdr->cbOut));
+ if (RT_SUCCESS(rc))
+ {
+ rcNt = STATUS_SUCCESS;
+ cbOut = pHdr->cbOut;
+ if (cbOut > pStack->Parameters.DeviceIoControl.OutputBufferLength)
+ {
+ cbOut = pStack->Parameters.DeviceIoControl.OutputBufferLength;
+ LogRel(("vgdrvNtDeviceControlSlow: too much output! %#x > %#x; uCmd=%#x!\n",
+ pHdr->cbOut, cbOut, pStack->Parameters.DeviceIoControl.IoControlCode));
+ }
+
+ /* If IDC successful disconnect request, we must set the context pointer to NULL. */
+ if ( pStack->Parameters.DeviceIoControl.IoControlCode == VBGL_IOCTL_IDC_DISCONNECT
+ && RT_SUCCESS(pHdr->rc))
+ pStack->FileObject->FsContext = NULL;
+ }
+ else if (rc == VERR_NOT_SUPPORTED)
+ rcNt = STATUS_NOT_SUPPORTED;
+ else
+ rcNt = STATUS_INVALID_PARAMETER;
+ Log2(("vgdrvNtDeviceControlSlow: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc));
+ }
+ else
+ {
+ Log(("vgdrvNtDeviceControlSlow: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n",
+ pStack->Parameters.DeviceIoControl.IoControlCode,
+ pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbIn : 0,
+ pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbOut : 0,
+ pStack->Parameters.DeviceIoControl.InputBufferLength,
+ pStack->Parameters.DeviceIoControl.OutputBufferLength));
+ rcNt = STATUS_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ Log(("vgdrvNtDeviceControlSlow: not buffered request (%#x) - not supported\n",
+ pStack->Parameters.DeviceIoControl.IoControlCode));
+ rcNt = STATUS_NOT_SUPPORTED;
+ }
+ }
+#if 0 /*def RT_ARCH_AMD64*/
+ else
+ {
+ Log(("VBoxDrvNtDeviceControlSlow: WOW64 req - not supported\n"));
+ rcNt = STATUS_NOT_SUPPORTED;
+ }
+#endif
+
+ return vgdrvNtCompleteRequestEx(rcNt, cbOut, pIrp);
+}
+
+
+/**
+ * Internal Device I/O Control entry point (for IDC).
+ *
+ * @param pDevObj Device object.
+ * @param pIrp Request packet.
+ */
+static NTSTATUS NTAPI vgdrvNtInternalIOCtl(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ /* Currently no special code here. */
+ return vgdrvNtDeviceControl(pDevObj, pIrp);
+}
+
+
+/**
+ * IRP_MJ_SHUTDOWN handler.
+ *
+ * @returns NT status code
+ * @param pDevObj Device object.
+ * @param pIrp IRP.
+ */
+static NTSTATUS NTAPI vgdrvNtShutdown(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+ LogFlowFuncEnter();
+
+ VMMDevPowerStateRequest *pReq = pDevExt->pPowerStateRequest;
+ if (pReq)
+ {
+ pReq->header.requestType = VMMDevReq_SetPowerStatus;
+ pReq->powerState = VMMDevPowerState_PowerOff;
+
+ int rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ LogFunc(("Error performing request to VMMDev, rc=%Rrc\n", rc));
+ }
+
+ /* just in case, since we shouldn't normally get here. */
+ pIrp->IoStatus.Information = 0;
+ pIrp->IoStatus.Status = STATUS_SUCCESS;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+ return STATUS_SUCCESS;
+}
+
+
+/**
+ * Stub function for functions we don't implemented.
+ *
+ * @returns STATUS_NOT_SUPPORTED
+ * @param pDevObj Device object.
+ * @param pIrp IRP.
+ */
+static NTSTATUS vgdrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp)
+{
+ RT_NOREF1(pDevObj);
+ LogFlowFuncEnter();
+
+ pIrp->IoStatus.Information = 0;
+ pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
+ IoCompleteRequest(pIrp, IO_NO_INCREMENT);
+
+ return STATUS_NOT_SUPPORTED;
+}
+
+
+/**
+ * Bug check callback (KBUGCHECK_CALLBACK_ROUTINE).
+ *
+ * This adds a log entry on the host, in case Hyper-V isn't active or the guest
+ * is too old for reporting it itself via the crash MSRs.
+ *
+ * @param pvBuffer Not used.
+ * @param cbBuffer Not used.
+ */
+static VOID NTAPI vgdrvNtBugCheckCallback(PVOID pvBuffer, ULONG cbBuffer)
+{
+ if (g_pauKiBugCheckData)
+ {
+ RTLogBackdoorPrintf("VBoxGuest: BugCheck! P0=%#zx P1=%#zx P2=%#zx P3=%#zx P4=%#zx\n", g_pauKiBugCheckData[0],
+ g_pauKiBugCheckData[1], g_pauKiBugCheckData[2], g_pauKiBugCheckData[3], g_pauKiBugCheckData[4]);
+
+ VMMDevReqNtBugCheck *pReq = NULL;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_NtBugCheck);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->uBugCheck = g_pauKiBugCheckData[0];
+ pReq->auParameters[0] = g_pauKiBugCheckData[1];
+ pReq->auParameters[1] = g_pauKiBugCheckData[2];
+ pReq->auParameters[2] = g_pauKiBugCheckData[3];
+ pReq->auParameters[3] = g_pauKiBugCheckData[4];
+ VbglR0GRPerform(&pReq->header);
+ VbglR0GRFree(&pReq->header);
+ }
+ }
+ else
+ {
+ RTLogBackdoorPrintf("VBoxGuest: BugCheck!\n");
+
+ VMMDevRequestHeader *pReqHdr = NULL;
+ int rc = VbglR0GRAlloc(&pReqHdr, sizeof(*pReqHdr), VMMDevReq_NtBugCheck);
+ if (RT_SUCCESS(rc))
+ {
+ VbglR0GRPerform(pReqHdr);
+ VbglR0GRFree(pReqHdr);
+ }
+ }
+
+ RT_NOREF(pvBuffer, cbBuffer);
+}
+
+
+/**
+ * Sets the mouse notification callback.
+ *
+ * @returns VBox status code.
+ * @param pDevExt Pointer to the device extension.
+ * @param pNotify Pointer to the mouse notify struct.
+ */
+int VGDrvNativeSetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify)
+{
+ PVBOXGUESTDEVEXTWIN pDevExtWin = (PVBOXGUESTDEVEXTWIN)pDevExt;
+ /* we need a lock here to avoid concurrency with the set event functionality */
+ KIRQL OldIrql;
+ KeAcquireSpinLock(&pDevExtWin->MouseEventAccessSpinLock, &OldIrql);
+ pDevExtWin->Core.pfnMouseNotifyCallback = pNotify->u.In.pfnNotify;
+ pDevExtWin->Core.pvMouseNotifyCallbackArg = pNotify->u.In.pvUser;
+ KeReleaseSpinLock(&pDevExtWin->MouseEventAccessSpinLock, OldIrql);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * DPC handler.
+ *
+ * @param pDPC DPC descriptor.
+ * @param pDevObj Device object.
+ * @param pIrp Interrupt request packet.
+ * @param pContext Context specific pointer.
+ */
+static void NTAPI vgdrvNtDpcHandler(PKDPC pDPC, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVOID pContext)
+{
+ RT_NOREF3(pDPC, pIrp, pContext);
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pDevObj->DeviceExtension;
+ Log3Func(("pDevExt=0x%p\n", pDevExt));
+
+ /* Test & reset the counter. */
+ if (ASMAtomicXchgU32(&pDevExt->Core.u32MousePosChangedSeq, 0))
+ {
+ /* we need a lock here to avoid concurrency with the set event ioctl handler thread,
+ * i.e. to prevent the event from destroyed while we're using it */
+ Assert(KeGetCurrentIrql() == DISPATCH_LEVEL);
+ KeAcquireSpinLockAtDpcLevel(&pDevExt->MouseEventAccessSpinLock);
+
+ if (pDevExt->Core.pfnMouseNotifyCallback)
+ pDevExt->Core.pfnMouseNotifyCallback(pDevExt->Core.pvMouseNotifyCallbackArg);
+
+ KeReleaseSpinLockFromDpcLevel(&pDevExt->MouseEventAccessSpinLock);
+ }
+
+ /* Process the wake-up list we were asked by the scheduling a DPC
+ * in vgdrvNtIsrHandler(). */
+ VGDrvCommonWaitDoWakeUps(&pDevExt->Core);
+}
+
+
+/**
+ * ISR handler.
+ *
+ * @return BOOLEAN Indicates whether the IRQ came from us (TRUE) or not (FALSE).
+ * @param pInterrupt Interrupt that was triggered.
+ * @param pServiceContext Context specific pointer.
+ */
+static BOOLEAN NTAPI vgdrvNtIsrHandler(PKINTERRUPT pInterrupt, PVOID pServiceContext)
+{
+ RT_NOREF1(pInterrupt);
+ PVBOXGUESTDEVEXTWIN pDevExt = (PVBOXGUESTDEVEXTWIN)pServiceContext;
+ if (pDevExt == NULL)
+ return FALSE;
+
+ /*Log3Func(("pDevExt=0x%p, pVMMDevMemory=0x%p\n", pDevExt, pDevExt ? pDevExt->pVMMDevMemory : NULL));*/
+
+ /* Enter the common ISR routine and do the actual work. */
+ BOOLEAN fIRQTaken = VGDrvCommonISR(&pDevExt->Core);
+
+ /* If we need to wake up some events we do that in a DPC to make
+ * sure we're called at the right IRQL. */
+ if (fIRQTaken)
+ {
+ Log3Func(("IRQ was taken! pInterrupt=0x%p, pDevExt=0x%p\n", pInterrupt, pDevExt));
+ if (ASMAtomicUoReadU32( &pDevExt->Core.u32MousePosChangedSeq)
+ || !RTListIsEmpty(&pDevExt->Core.WakeUpList))
+ {
+ Log3Func(("Requesting DPC...\n"));
+ IoRequestDpc(pDevExt->pDeviceObject, NULL /*pIrp*/, NULL /*pvContext*/);
+ }
+ }
+ return fIRQTaken;
+}
+
+
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt)
+{
+ NOREF(pDevExt);
+ /* nothing to do here - i.e. since we can not KeSetEvent from ISR level,
+ * we rely on the pDevExt->u32MousePosChangedSeq to be set to a non-zero value on a mouse event
+ * and queue the DPC in our ISR routine in that case doing KeSetEvent from the DPC routine */
+}
+
+
+/**
+ * Hook for handling OS specfic options from the host.
+ *
+ * @returns true if handled, false if not.
+ * @param pDevExt The device extension.
+ * @param pszName The option name.
+ * @param pszValue The option value.
+ */
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ RT_NOREF(pDevExt); RT_NOREF(pszName); RT_NOREF(pszValue);
+ return false;
+}
+
+
+/**
+ * Implements RTL_QUERY_REGISTRY_ROUTINE for enumerating our registry key.
+ */
+static NTSTATUS NTAPI vgdrvNtRegistryEnumCallback(PWSTR pwszValueName, ULONG uValueType,
+ PVOID pvValue, ULONG cbValue, PVOID pvUser, PVOID pvEntryCtx)
+{
+ Log4(("vgdrvNtRegistryEnumCallback: pwszValueName=%ls uValueType=%#x Value=%.*Rhxs\n", pwszValueName, uValueType, cbValue, pvValue));
+
+ /*
+ * Filter out general service config values.
+ */
+ if ( RTUtf16ICmpAscii(pwszValueName, "Type") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "Start") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "ErrorControl") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "Tag") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "ImagePath") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "DisplayName") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "Group") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "DependOnGroup") == 0
+ || RTUtf16ICmpAscii(pwszValueName, "DependOnService") == 0
+ )
+ {
+ return STATUS_SUCCESS;
+ }
+
+ /*
+ * Convert the value name.
+ */
+ size_t cch = RTUtf16CalcUtf8Len(pwszValueName);
+ if (cch < 64 && cch > 0)
+ {
+ char szValueName[72];
+ char *pszTmp = szValueName;
+ int rc = RTUtf16ToUtf8Ex(pwszValueName, RTSTR_MAX, &pszTmp, sizeof(szValueName), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert the value.
+ */
+ char szValue[72];
+ char *pszFree = NULL;
+ char *pszValue = NULL;
+ szValue[0] = '\0';
+ switch (uValueType)
+ {
+ case REG_SZ:
+ case REG_EXPAND_SZ:
+ rc = RTUtf16CalcUtf8LenEx((PCRTUTF16)pvValue, cbValue / sizeof(RTUTF16), &cch);
+ if (RT_SUCCESS(rc) && cch < _1K)
+ {
+ if (cch < sizeof(szValue))
+ {
+ pszValue = szValue;
+ rc = RTUtf16ToUtf8Ex((PCRTUTF16)pvValue, cbValue / sizeof(RTUTF16), &pszValue, sizeof(szValue), NULL);
+ }
+ else
+ {
+ rc = RTUtf16ToUtf8Ex((PCRTUTF16)pvValue, cbValue / sizeof(RTUTF16), &pszValue, sizeof(szValue), NULL);
+ if (RT_SUCCESS(rc))
+ pszFree = pszValue;
+ }
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("VBoxGuest: Failed to convert registry value '%ls' string data to UTF-8: %Rrc\n",
+ pwszValueName, rc));
+ pszValue = NULL;
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ LogRel(("VBoxGuest: Registry value '%ls' has a too long value: %#x (uvalueType=%#x)\n",
+ pwszValueName, cbValue, uValueType));
+ else
+ LogRel(("VBoxGuest: Registry value '%ls' has an invalid string value (cbValue=%#x, uvalueType=%#x)\n",
+ pwszValueName, cbValue, uValueType));
+ break;
+
+ case REG_DWORD:
+ if (cbValue == sizeof(uint32_t))
+ {
+ RTStrFormatU32(szValue, sizeof(szValue), *(uint32_t const *)pvValue, 10, 0, 0, 0);
+ pszValue = szValue;
+ }
+ else
+ LogRel(("VBoxGuest: Registry value '%ls' has wrong length for REG_DWORD: %#x\n", pwszValueName, cbValue));
+ break;
+
+ case REG_QWORD:
+ if (cbValue == sizeof(uint64_t))
+ {
+ RTStrFormatU32(szValue, sizeof(szValue), *(uint32_t const *)pvValue, 10, 0, 0, 0);
+ pszValue = szValue;
+ }
+ else
+ LogRel(("VBoxGuest: Registry value '%ls' has wrong length for REG_DWORD: %#x\n", pwszValueName, cbValue));
+ break;
+
+ default:
+ LogRel(("VBoxGuest: Ignoring registry value '%ls': Unsupported type %#x\n", pwszValueName, uValueType));
+ break;
+ }
+ if (pszValue)
+ {
+ /*
+ * Process it.
+ */
+ PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser;
+ VGDrvCommonProcessOption(pDevExt, szValueName, pszValue);
+ if (pszFree)
+ RTStrFree(pszFree);
+ }
+ }
+ }
+ else if (cch > 0)
+ LogRel(("VBoxGuest: Ignoring registery value '%ls': name too long\n", pwszValueName));
+ else
+ LogRel(("VBoxGuest: Ignoring registery value with bad name\n", pwszValueName));
+ NOREF(pvEntryCtx);
+ return STATUS_SUCCESS;
+}
+
+
+/**
+ * Reads configuration from the registry and guest properties.
+ *
+ * We ignore failures and instead preserve existing configuration values.
+ *
+ * Thie routine will block.
+ *
+ * @param pDevExt The device extension.
+ */
+static void vgdrvNtReadConfiguration(PVBOXGUESTDEVEXTWIN pDevExt)
+{
+ /*
+ * First the registry.
+ *
+ * Note! RTL_QUERY_REGISTRY_NOEXPAND is sensible (no environment) and also necessary to
+ * avoid crash on NT 3.1 because RtlExpandEnvironmentStrings_U thinks its in ring-3
+ * and tries to get the default heap from the PEB via the TEB. No TEB in ring-0.
+ */
+ RTL_QUERY_REGISTRY_TABLE aQuery[2];
+ RT_ZERO(aQuery);
+ aQuery[0].QueryRoutine = vgdrvNtRegistryEnumCallback;
+ aQuery[0].Flags = RTL_QUERY_REGISTRY_NOEXPAND;
+ aQuery[0].Name = NULL;
+ aQuery[0].EntryContext = NULL;
+ aQuery[0].DefaultType = REG_NONE;
+ NTSTATUS rcNt = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES, L"VBoxGuest", &aQuery[0], pDevExt, NULL /*pwszzEnv*/);
+ if (!NT_SUCCESS(rcNt))
+ LogRel(("VBoxGuest: RtlQueryRegistryValues failed: %#x\n", rcNt));
+
+ /*
+ * Read configuration from the host.
+ */
+ VGDrvCommonProcessOptionsFromHost(&pDevExt->Core);
+}
+
+#ifdef VBOX_STRICT
+
+/**
+ * A quick implementation of AtomicTestAndClear for uint32_t and multiple bits.
+ */
+static uint32_t vgdrvNtAtomicBitsTestAndClear(void *pu32Bits, uint32_t u32Mask)
+{
+ AssertPtrReturn(pu32Bits, 0);
+ LogFlowFunc(("*pu32Bits=%#x, u32Mask=%#x\n", *(uint32_t *)pu32Bits, u32Mask));
+ uint32_t u32Result = 0;
+ uint32_t u32WorkingMask = u32Mask;
+ int iBitOffset = ASMBitFirstSetU32 (u32WorkingMask);
+
+ while (iBitOffset > 0)
+ {
+ bool fSet = ASMAtomicBitTestAndClear(pu32Bits, iBitOffset - 1);
+ if (fSet)
+ u32Result |= 1 << (iBitOffset - 1);
+ u32WorkingMask &= ~(1 << (iBitOffset - 1));
+ iBitOffset = ASMBitFirstSetU32 (u32WorkingMask);
+ }
+ LogFlowFunc(("Returning %#x\n", u32Result));
+ return u32Result;
+}
+
+
+static void vgdrvNtTestAtomicTestAndClearBitsU32(uint32_t u32Mask, uint32_t u32Bits, uint32_t u32Exp)
+{
+ ULONG u32Bits2 = u32Bits;
+ uint32_t u32Result = vgdrvNtAtomicBitsTestAndClear(&u32Bits2, u32Mask);
+ if ( u32Result != u32Exp
+ || (u32Bits2 & u32Mask)
+ || (u32Bits2 & u32Result)
+ || ((u32Bits2 | u32Result) != u32Bits)
+ )
+ AssertLogRelMsgFailed(("TEST FAILED: u32Mask=%#x, u32Bits (before)=%#x, u32Bits (after)=%#x, u32Result=%#x, u32Exp=%#x\n",
+ u32Mask, u32Bits, u32Bits2, u32Result));
+}
+
+
+static void vgdrvNtDoTests(void)
+{
+ vgdrvNtTestAtomicTestAndClearBitsU32(0x00, 0x23, 0);
+ vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0, 0);
+ vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0x22, 0);
+ vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0x23, 0x1);
+ vgdrvNtTestAtomicTestAndClearBitsU32(0x11, 0x32, 0x10);
+ vgdrvNtTestAtomicTestAndClearBitsU32(0x22, 0x23, 0x22);
+}
+
+#endif /* VBOX_STRICT */
+
+#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
+
+/*
+ * DPC latency checker.
+ */
+
+/**
+ * One DPC latency sample.
+ */
+typedef struct DPCSAMPLE
+{
+ LARGE_INTEGER PerfDelta;
+ LARGE_INTEGER PerfCounter;
+ LARGE_INTEGER PerfFrequency;
+ uint64_t u64TSC;
+} DPCSAMPLE;
+AssertCompileSize(DPCSAMPLE, 4*8);
+
+/**
+ * The DPC latency measurement workset.
+ */
+typedef struct DPCDATA
+{
+ KDPC Dpc;
+ KTIMER Timer;
+ KSPIN_LOCK SpinLock;
+
+ ULONG ulTimerRes;
+
+ bool volatile fFinished;
+
+ /** The timer interval (relative). */
+ LARGE_INTEGER DueTime;
+
+ LARGE_INTEGER PerfCounterPrev;
+
+ /** Align the sample array on a 64 byte boundrary just for the off chance
+ * that we'll get cache line aligned memory backing this structure. */
+ uint32_t auPadding[ARCH_BITS == 32 ? 5 : 7];
+
+ int cSamples;
+ DPCSAMPLE aSamples[8192];
+} DPCDATA;
+
+AssertCompileMemberAlignment(DPCDATA, aSamples, 64);
+
+/**
+ * DPC callback routine for the DPC latency measurement code.
+ *
+ * @param pDpc The DPC, not used.
+ * @param pvDeferredContext Pointer to the DPCDATA.
+ * @param SystemArgument1 System use, ignored.
+ * @param SystemArgument2 System use, ignored.
+ */
+static VOID vgdrvNtDpcLatencyCallback(PKDPC pDpc, PVOID pvDeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
+{
+ DPCDATA *pData = (DPCDATA *)pvDeferredContext;
+ RT_NOREF(pDpc, SystemArgument1, SystemArgument2);
+
+ KeAcquireSpinLockAtDpcLevel(&pData->SpinLock);
+
+ if (pData->cSamples >= RT_ELEMENTS(pData->aSamples))
+ pData->fFinished = true;
+ else
+ {
+ DPCSAMPLE *pSample = &pData->aSamples[pData->cSamples++];
+
+ pSample->u64TSC = ASMReadTSC();
+ pSample->PerfCounter = KeQueryPerformanceCounter(&pSample->PerfFrequency);
+ pSample->PerfDelta.QuadPart = pSample->PerfCounter.QuadPart - pData->PerfCounterPrev.QuadPart;
+
+ pData->PerfCounterPrev.QuadPart = pSample->PerfCounter.QuadPart;
+
+ KeSetTimer(&pData->Timer, pData->DueTime, &pData->Dpc);
+ }
+
+ KeReleaseSpinLockFromDpcLevel(&pData->SpinLock);
+}
+
+
+/**
+ * Handles the DPC latency checker request.
+ *
+ * @returns VBox status code.
+ */
+int VGDrvNtIOCtl_DpcLatencyChecker(void)
+{
+ /*
+ * Allocate a block of non paged memory for samples and related data.
+ */
+ DPCDATA *pData = (DPCDATA *)RTMemAlloc(sizeof(DPCDATA));
+ if (!pData)
+ {
+ RTLogBackdoorPrintf("VBoxGuest: DPC: DPCDATA allocation failed.\n");
+ return VERR_NO_MEMORY;
+ }
+
+ /*
+ * Initialize the data.
+ */
+ KeInitializeDpc(&pData->Dpc, vgdrvNtDpcLatencyCallback, pData);
+ KeInitializeTimer(&pData->Timer);
+ KeInitializeSpinLock(&pData->SpinLock);
+
+ pData->fFinished = false;
+ pData->cSamples = 0;
+ pData->PerfCounterPrev.QuadPart = 0;
+
+ pData->ulTimerRes = ExSetTimerResolution(1000 * 10, 1);
+ pData->DueTime.QuadPart = -(int64_t)pData->ulTimerRes / 10;
+
+ /*
+ * Start the DPC measurements and wait for a full set.
+ */
+ KeSetTimer(&pData->Timer, pData->DueTime, &pData->Dpc);
+
+ while (!pData->fFinished)
+ {
+ LARGE_INTEGER Interval;
+ Interval.QuadPart = -100 * 1000 * 10;
+ KeDelayExecutionThread(KernelMode, TRUE, &Interval);
+ }
+
+ ExSetTimerResolution(0, 0);
+
+ /*
+ * Log everything to the host.
+ */
+ RTLogBackdoorPrintf("DPC: ulTimerRes = %d\n", pData->ulTimerRes);
+ for (int i = 0; i < pData->cSamples; i++)
+ {
+ DPCSAMPLE *pSample = &pData->aSamples[i];
+
+ RTLogBackdoorPrintf("[%d] pd %lld pc %lld pf %lld t %lld\n",
+ i,
+ pSample->PerfDelta.QuadPart,
+ pSample->PerfCounter.QuadPart,
+ pSample->PerfFrequency.QuadPart,
+ pSample->u64TSC);
+ }
+
+ RTMemFree(pData);
+ return VINF_SUCCESS;
+}
+
+#endif /* VBOX_WITH_DPC_LATENCY_CHECKER */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp b/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp
new file mode 100644
index 00000000..e6485c0b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp
@@ -0,0 +1,4516 @@
+/* $Id: VBoxGuest.cpp $ */
+/** @file
+ * VBoxGuest - Guest Additions Driver, Common Code.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/** @page pg_vbdrv VBoxGuest
+ *
+ * VBoxGuest is the device driver for VMMDev.
+ *
+ * The device driver is shipped as part of the guest additions. It has roots in
+ * the host VMM support driver (usually known as VBoxDrv), so fixes in platform
+ * specific code may apply to both drivers.
+ *
+ * The common code lives in VBoxGuest.cpp and is compiled both as C++ and C.
+ * The VBoxGuest.cpp source file shall not contain platform specific code,
+ * though it must occationally do a few \#ifdef RT_OS_XXX tests to cater for
+ * platform differences. Though, in those cases, it is common that more than
+ * one platform needs special handling.
+ *
+ * On most platforms the device driver should create two device nodes, one for
+ * full (unrestricted) access to the feature set, and one which only provides a
+ * restrict set of functions. These are generally referred to as 'vboxguest'
+ * and 'vboxuser' respectively. Currently, this two device approach is only
+ * implemented on Linux!
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEFAULT
+#include "VBoxGuestInternal.h"
+#include <VBox/VMMDev.h> /* for VMMDEV_RAM_SIZE */
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/HostServices/GuestPropertySvc.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/time.h>
+#include <iprt/memobj.h>
+#include <iprt/asm.h>
+#include <iprt/asm-amd64-x86.h>
+#include <iprt/string.h>
+#include <iprt/process.h>
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/timer.h>
+#ifdef VBOX_WITH_HGCM
+# include <iprt/thread.h>
+#endif
+#include "version-generated.h"
+#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD)
+# include "revision-generated.h"
+#endif
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN)
+# include <iprt/rand.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VBOXGUEST_ACQUIRE_STYLE_EVENTS (VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST | VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef VBOX_WITH_HGCM
+static DECLCALLBACK(int) vgdrvHgcmAsyncWaitCallback(VMMDevHGCMRequestHeader *pHdrNonVolatile, void *pvUser, uint32_t u32User);
+#endif
+static int vgdrvIoCtl_CancelAllWaitEvents(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession);
+static void vgdrvBitUsageTrackerClear(PVBOXGUESTBITUSAGETRACER pTracker);
+static uint32_t vgdrvGetAllowedEventMaskForSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession);
+static int vgdrvResetEventFilterOnHost(PVBOXGUESTDEVEXT pDevExt, uint32_t fFixedEvents);
+static int vgdrvResetMouseStatusOnHost(PVBOXGUESTDEVEXT pDevExt);
+static int vgdrvResetCapabilitiesOnHost(PVBOXGUESTDEVEXT pDevExt);
+static int vgdrvSetSessionEventFilter(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination);
+static int vgdrvSetSessionMouseStatus(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination);
+static int vgdrvSetSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNoMask,
+ uint32_t *pfSessionCaps, uint32_t *pfGlobalCaps, bool fSessionTermination);
+static int vgdrvAcquireSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNotMask, uint32_t fFlags, bool fSessionTermination);
+static int vgdrvDispatchEventsLocked(PVBOXGUESTDEVEXT pDevExt, uint32_t fEvents);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const uint32_t g_cbChangeMemBalloonReq = RT_UOFFSETOF(VMMDevChangeMemBalloon, aPhysPage[VMMDEV_MEMORY_BALLOON_CHUNK_PAGES]);
+
+#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS)
+/**
+ * Drag in the rest of IRPT since we share it with the
+ * rest of the kernel modules on Solaris.
+ */
+struct CLANG11WEIRDNESS { PFNRT pfn; } g_apfnVBoxGuestIPRTDeps[] =
+{
+ /* VirtioNet */
+ { (PFNRT)RTRandBytes },
+ /* RTSemMutex* */
+ { (PFNRT)RTSemMutexCreate },
+ { (PFNRT)RTSemMutexDestroy },
+ { (PFNRT)RTSemMutexRequest },
+ { (PFNRT)RTSemMutexRequestNoResume },
+ { (PFNRT)RTSemMutexRequestDebug },
+ { (PFNRT)RTSemMutexRequestNoResumeDebug },
+ { (PFNRT)RTSemMutexRelease },
+ { (PFNRT)RTSemMutexIsOwned },
+ { NULL }
+};
+#endif /* RT_OS_DARWIN || RT_OS_SOLARIS */
+
+
+/**
+ * Reserves memory in which the VMM can relocate any guest mappings
+ * that are floating around.
+ *
+ * This operation is a little bit tricky since the VMM might not accept
+ * just any address because of address clashes between the three contexts
+ * it operates in, so use a small stack to perform this operation.
+ *
+ * @returns VBox status code (ignored).
+ * @param pDevExt The device extension.
+ */
+static int vgdrvInitFixateGuestMappings(PVBOXGUESTDEVEXT pDevExt)
+{
+ /*
+ * Query the required space.
+ */
+ VMMDevReqHypervisorInfo *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(VMMDevReqHypervisorInfo), VMMDevReq_GetHypervisorInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ pReq->hypervisorStart = 0;
+ pReq->hypervisorSize = 0;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc)) /* this shouldn't happen! */
+ {
+ VbglR0GRFree(&pReq->header);
+ return rc;
+ }
+
+ /*
+ * The VMM will report back if there is nothing it wants to map, like for
+ * instance in VT-x and AMD-V mode.
+ */
+ if (pReq->hypervisorSize == 0)
+ Log(("vgdrvInitFixateGuestMappings: nothing to do\n"));
+ else
+ {
+ /*
+ * We have to try several times since the host can be picky
+ * about certain addresses.
+ */
+ RTR0MEMOBJ hFictive = NIL_RTR0MEMOBJ;
+ uint32_t cbHypervisor = pReq->hypervisorSize;
+ RTR0MEMOBJ ahTries[5];
+ uint32_t iTry;
+ bool fBitched = false;
+ Log(("vgdrvInitFixateGuestMappings: cbHypervisor=%#x\n", cbHypervisor));
+ for (iTry = 0; iTry < RT_ELEMENTS(ahTries); iTry++)
+ {
+ /*
+ * Reserve space, or if that isn't supported, create a object for
+ * some fictive physical memory and map that in to kernel space.
+ *
+ * To make the code a bit uglier, most systems cannot help with
+ * 4MB alignment, so we have to deal with that in addition to
+ * having two ways of getting the memory.
+ */
+ uint32_t uAlignment = _4M;
+ RTR0MEMOBJ hObj;
+ rc = RTR0MemObjReserveKernel(&hObj, (void *)-1, RT_ALIGN_32(cbHypervisor, _4M), uAlignment);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ uAlignment = PAGE_SIZE;
+ rc = RTR0MemObjReserveKernel(&hObj, (void *)-1, RT_ALIGN_32(cbHypervisor, _4M) + _4M, uAlignment);
+ }
+ /*
+ * If both RTR0MemObjReserveKernel calls above failed because either not supported or
+ * not implemented at all at the current platform, try to map the memory object into the
+ * virtual kernel space.
+ */
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ if (hFictive == NIL_RTR0MEMOBJ)
+ {
+ rc = RTR0MemObjEnterPhys(&hObj, VBOXGUEST_HYPERVISOR_PHYSICAL_START, cbHypervisor + _4M, RTMEM_CACHE_POLICY_DONT_CARE);
+ if (RT_FAILURE(rc))
+ break;
+ hFictive = hObj;
+ }
+ uAlignment = _4M;
+ rc = RTR0MemObjMapKernel(&hObj, hFictive, (void *)-1, uAlignment, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ uAlignment = PAGE_SIZE;
+ rc = RTR0MemObjMapKernel(&hObj, hFictive, (void *)-1, uAlignment, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ }
+ }
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("VBoxGuest: Failed to reserve memory for the hypervisor: rc=%Rrc (cbHypervisor=%#x uAlignment=%#x iTry=%u)\n",
+ rc, cbHypervisor, uAlignment, iTry));
+ fBitched = true;
+ break;
+ }
+
+ /*
+ * Try set it.
+ */
+ pReq->header.requestType = VMMDevReq_SetHypervisorInfo;
+ pReq->header.rc = VERR_INTERNAL_ERROR;
+ pReq->hypervisorSize = cbHypervisor;
+ pReq->hypervisorStart = (RTGCPTR32)(uintptr_t)RTR0MemObjAddress(hObj);
+ if ( uAlignment == PAGE_SIZE
+ && pReq->hypervisorStart & (_4M - 1))
+ pReq->hypervisorStart = RT_ALIGN_32(pReq->hypervisorStart, _4M);
+ AssertMsg(RT_ALIGN_32(pReq->hypervisorStart, _4M) == pReq->hypervisorStart, ("%#x\n", pReq->hypervisorStart));
+
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_SUCCESS(rc))
+ {
+ pDevExt->hGuestMappings = hFictive != NIL_RTR0MEMOBJ ? hFictive : hObj;
+ Log(("VBoxGuest: %p LB %#x; uAlignment=%#x iTry=%u hGuestMappings=%p (%s)\n",
+ RTR0MemObjAddress(pDevExt->hGuestMappings),
+ RTR0MemObjSize(pDevExt->hGuestMappings),
+ uAlignment, iTry, pDevExt->hGuestMappings, hFictive != NIL_RTR0PTR ? "fictive" : "reservation"));
+ break;
+ }
+ ahTries[iTry] = hObj;
+ }
+
+ /*
+ * Cleanup failed attempts.
+ */
+ while (iTry-- > 0)
+ RTR0MemObjFree(ahTries[iTry], false /* fFreeMappings */);
+ if ( RT_FAILURE(rc)
+ && hFictive != NIL_RTR0PTR)
+ RTR0MemObjFree(hFictive, false /* fFreeMappings */);
+ if (RT_FAILURE(rc) && !fBitched)
+ LogRel(("VBoxGuest: Warning: failed to reserve %#d of memory for guest mappings.\n", cbHypervisor));
+ }
+ VbglR0GRFree(&pReq->header);
+
+ /*
+ * We ignore failed attempts for now.
+ */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Undo what vgdrvInitFixateGuestMappings did.
+ *
+ * @param pDevExt The device extension.
+ */
+static void vgdrvTermUnfixGuestMappings(PVBOXGUESTDEVEXT pDevExt)
+{
+ if (pDevExt->hGuestMappings != NIL_RTR0PTR)
+ {
+ /*
+ * Tell the host that we're going to free the memory we reserved for
+ * it, the free it up. (Leak the memory if anything goes wrong here.)
+ */
+ VMMDevReqHypervisorInfo *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(VMMDevReqHypervisorInfo), VMMDevReq_SetHypervisorInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->hypervisorStart = 0;
+ pReq->hypervisorSize = 0;
+ rc = VbglR0GRPerform(&pReq->header);
+ VbglR0GRFree(&pReq->header);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTR0MemObjFree(pDevExt->hGuestMappings, true /* fFreeMappings */);
+ AssertRC(rc);
+ }
+ else
+ LogRel(("vgdrvTermUnfixGuestMappings: Failed to unfix the guest mappings! rc=%Rrc\n", rc));
+
+ pDevExt->hGuestMappings = NIL_RTR0MEMOBJ;
+ }
+}
+
+
+
+/**
+ * Report the guest information to the host.
+ *
+ * @returns IPRT status code.
+ * @param enmOSType The OS type to report.
+ */
+static int vgdrvReportGuestInfo(VBOXOSTYPE enmOSType)
+{
+ /*
+ * Allocate and fill in the two guest info reports.
+ */
+ VMMDevReportGuestInfo2 *pReqInfo2 = NULL;
+ VMMDevReportGuestInfo *pReqInfo1 = NULL;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReqInfo2, sizeof (VMMDevReportGuestInfo2), VMMDevReq_ReportGuestInfo2);
+ Log(("vgdrvReportGuestInfo: VbglR0GRAlloc VMMDevReportGuestInfo2 completed with rc=%Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ pReqInfo2->guestInfo.additionsMajor = VBOX_VERSION_MAJOR;
+ pReqInfo2->guestInfo.additionsMinor = VBOX_VERSION_MINOR;
+ pReqInfo2->guestInfo.additionsBuild = VBOX_VERSION_BUILD;
+ pReqInfo2->guestInfo.additionsRevision = VBOX_SVN_REV;
+ pReqInfo2->guestInfo.additionsFeatures = VBOXGSTINFO2_F_REQUESTOR_INFO;
+ RTStrCopy(pReqInfo2->guestInfo.szName, sizeof(pReqInfo2->guestInfo.szName), VBOX_VERSION_STRING);
+
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReqInfo1, sizeof (VMMDevReportGuestInfo), VMMDevReq_ReportGuestInfo);
+ Log(("vgdrvReportGuestInfo: VbglR0GRAlloc VMMDevReportGuestInfo completed with rc=%Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ pReqInfo1->guestInfo.interfaceVersion = VMMDEV_VERSION;
+ pReqInfo1->guestInfo.osType = enmOSType;
+
+ /*
+ * There are two protocols here:
+ * 1. Info2 + Info1. Supported by >=3.2.51.
+ * 2. Info1 and optionally Info2. The old protocol.
+ *
+ * We try protocol 1 first. It will fail with VERR_NOT_SUPPORTED
+ * if not supported by the VMMDev (message ordering requirement).
+ */
+ rc = VbglR0GRPerform(&pReqInfo2->header);
+ Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo2 completed with rc=%Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0GRPerform(&pReqInfo1->header);
+ Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo completed with rc=%Rrc\n", rc));
+ }
+ else if ( rc == VERR_NOT_SUPPORTED
+ || rc == VERR_NOT_IMPLEMENTED)
+ {
+ rc = VbglR0GRPerform(&pReqInfo1->header);
+ Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo completed with rc=%Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0GRPerform(&pReqInfo2->header);
+ Log(("vgdrvReportGuestInfo: VbglR0GRPerform VMMDevReportGuestInfo2 completed with rc=%Rrc\n", rc));
+ if (rc == VERR_NOT_IMPLEMENTED)
+ rc = VINF_SUCCESS;
+ }
+ }
+ VbglR0GRFree(&pReqInfo1->header);
+ }
+ VbglR0GRFree(&pReqInfo2->header);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Report the guest driver status to the host.
+ *
+ * @returns IPRT status code.
+ * @param fActive Flag whether the driver is now active or not.
+ */
+static int vgdrvReportDriverStatus(bool fActive)
+{
+ /*
+ * Report guest status of the VBox driver to the host.
+ */
+ VMMDevReportGuestStatus *pReq2 = NULL;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq2, sizeof(*pReq2), VMMDevReq_ReportGuestStatus);
+ Log(("vgdrvReportDriverStatus: VbglR0GRAlloc VMMDevReportGuestStatus completed with rc=%Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ pReq2->guestStatus.facility = VBoxGuestFacilityType_VBoxGuestDriver;
+ pReq2->guestStatus.status = fActive ?
+ VBoxGuestFacilityStatus_Active
+ : VBoxGuestFacilityStatus_Inactive;
+ pReq2->guestStatus.flags = 0;
+ rc = VbglR0GRPerform(&pReq2->header);
+ Log(("vgdrvReportDriverStatus: VbglR0GRPerform VMMDevReportGuestStatus completed with fActive=%d, rc=%Rrc\n",
+ fActive ? 1 : 0, rc));
+ if (rc == VERR_NOT_IMPLEMENTED) /* Compatibility with older hosts. */
+ rc = VINF_SUCCESS;
+ VbglR0GRFree(&pReq2->header);
+ }
+
+ return rc;
+}
+
+
+/** @name Memory Ballooning
+ * @{
+ */
+
+/**
+ * Inflate the balloon by one chunk represented by an R0 memory object.
+ *
+ * The caller owns the balloon mutex.
+ *
+ * @returns IPRT status code.
+ * @param pMemObj Pointer to the R0 memory object.
+ * @param pReq The pre-allocated request for performing the VMMDev call.
+ */
+static int vgdrvBalloonInflate(PRTR0MEMOBJ pMemObj, VMMDevChangeMemBalloon *pReq)
+{
+ uint32_t iPage;
+ int rc;
+
+ for (iPage = 0; iPage < VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; iPage++)
+ {
+ RTHCPHYS phys = RTR0MemObjGetPagePhysAddr(*pMemObj, iPage);
+ pReq->aPhysPage[iPage] = phys;
+ }
+
+ pReq->fInflate = true;
+ pReq->header.size = g_cbChangeMemBalloonReq;
+ pReq->cPages = VMMDEV_MEMORY_BALLOON_CHUNK_PAGES;
+
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ LogRel(("vgdrvBalloonInflate: VbglR0GRPerform failed. rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Deflate the balloon by one chunk - info the host and free the memory object.
+ *
+ * The caller owns the balloon mutex.
+ *
+ * @returns IPRT status code.
+ * @param pMemObj Pointer to the R0 memory object.
+ * The memory object will be freed afterwards.
+ * @param pReq The pre-allocated request for performing the VMMDev call.
+ */
+static int vgdrvBalloonDeflate(PRTR0MEMOBJ pMemObj, VMMDevChangeMemBalloon *pReq)
+{
+ uint32_t iPage;
+ int rc;
+
+ for (iPage = 0; iPage < VMMDEV_MEMORY_BALLOON_CHUNK_PAGES; iPage++)
+ {
+ RTHCPHYS phys = RTR0MemObjGetPagePhysAddr(*pMemObj, iPage);
+ pReq->aPhysPage[iPage] = phys;
+ }
+
+ pReq->fInflate = false;
+ pReq->header.size = g_cbChangeMemBalloonReq;
+ pReq->cPages = VMMDEV_MEMORY_BALLOON_CHUNK_PAGES;
+
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("vgdrvBalloonDeflate: VbglR0GRPerform failed. rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ rc = RTR0MemObjFree(*pMemObj, true);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("vgdrvBalloonDeflate: RTR0MemObjFree(%p,true) -> %Rrc; this is *BAD*!\n", *pMemObj, rc));
+ return rc;
+ }
+
+ *pMemObj = NIL_RTR0MEMOBJ;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Inflate/deflate the memory balloon and notify the host.
+ *
+ * This is a worker used by vgdrvIoCtl_CheckMemoryBalloon - it takes the mutex.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param cBalloonChunks The new size of the balloon in chunks of 1MB.
+ * @param pfHandleInR3 Where to return the handle-in-ring3 indicator
+ * (VINF_SUCCESS if set).
+ */
+static int vgdrvSetBalloonSizeKernel(PVBOXGUESTDEVEXT pDevExt, uint32_t cBalloonChunks, bool *pfHandleInR3)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pDevExt->MemBalloon.fUseKernelAPI)
+ {
+ VMMDevChangeMemBalloon *pReq;
+ uint32_t i;
+
+ if (cBalloonChunks > pDevExt->MemBalloon.cMaxChunks)
+ {
+ LogRel(("vgdrvSetBalloonSizeKernel: illegal balloon size %u (max=%u)\n",
+ cBalloonChunks, pDevExt->MemBalloon.cMaxChunks));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (cBalloonChunks == pDevExt->MemBalloon.cMaxChunks)
+ return VINF_SUCCESS; /* nothing to do */
+
+ if ( cBalloonChunks > pDevExt->MemBalloon.cChunks
+ && !pDevExt->MemBalloon.paMemObj)
+ {
+ pDevExt->MemBalloon.paMemObj = (PRTR0MEMOBJ)RTMemAllocZ(sizeof(RTR0MEMOBJ) * pDevExt->MemBalloon.cMaxChunks);
+ if (!pDevExt->MemBalloon.paMemObj)
+ {
+ LogRel(("vgdrvSetBalloonSizeKernel: no memory for paMemObj!\n"));
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, g_cbChangeMemBalloonReq, VMMDevReq_ChangeMemBalloon);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (cBalloonChunks > pDevExt->MemBalloon.cChunks)
+ {
+ /* inflate */
+ for (i = pDevExt->MemBalloon.cChunks; i < cBalloonChunks; i++)
+ {
+ rc = RTR0MemObjAllocPhysNC(&pDevExt->MemBalloon.paMemObj[i],
+ VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, NIL_RTHCPHYS);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ /* not supported -- fall back to the R3-allocated memory. */
+ rc = VINF_SUCCESS;
+ pDevExt->MemBalloon.fUseKernelAPI = false;
+ Assert(pDevExt->MemBalloon.cChunks == 0);
+ Log(("VBoxGuestSetBalloonSizeKernel: PhysNC allocs not supported, falling back to R3 allocs.\n"));
+ }
+ /* else if (rc == VERR_NO_MEMORY || rc == VERR_NO_PHYS_MEMORY):
+ * cannot allocate more memory => don't try further, just stop here */
+ /* else: XXX what else can fail? VERR_MEMOBJ_INIT_FAILED for instance. just stop. */
+ break;
+ }
+
+ rc = vgdrvBalloonInflate(&pDevExt->MemBalloon.paMemObj[i], pReq);
+ if (RT_FAILURE(rc))
+ {
+ Log(("vboxGuestSetBalloonSize(inflate): failed, rc=%Rrc!\n", rc));
+ RTR0MemObjFree(pDevExt->MemBalloon.paMemObj[i], true);
+ pDevExt->MemBalloon.paMemObj[i] = NIL_RTR0MEMOBJ;
+ break;
+ }
+ pDevExt->MemBalloon.cChunks++;
+ }
+ }
+ else
+ {
+ /* deflate */
+ for (i = pDevExt->MemBalloon.cChunks; i-- > cBalloonChunks;)
+ {
+ rc = vgdrvBalloonDeflate(&pDevExt->MemBalloon.paMemObj[i], pReq);
+ if (RT_FAILURE(rc))
+ {
+ Log(("vboxGuestSetBalloonSize(deflate): failed, rc=%Rrc!\n", rc));
+ break;
+ }
+ pDevExt->MemBalloon.cChunks--;
+ }
+ }
+
+ VbglR0GRFree(&pReq->header);
+ }
+
+ /*
+ * Set the handle-in-ring3 indicator. When set Ring-3 will have to work
+ * the balloon changes via the other API.
+ */
+ *pfHandleInR3 = pDevExt->MemBalloon.fUseKernelAPI ? false : true;
+
+ return rc;
+}
+
+
+/**
+ * Inflate/deflate the balloon by one chunk.
+ *
+ * Worker for vgdrvIoCtl_ChangeMemoryBalloon - it takes the mutex.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pvChunk The address of the chunk to add to / remove from the
+ * balloon. (user space address)
+ * @param fInflate Inflate if true, deflate if false.
+ */
+static int vgdrvSetBalloonSizeFromUser(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, RTR3PTR pvChunk, bool fInflate)
+{
+ VMMDevChangeMemBalloon *pReq;
+ PRTR0MEMOBJ pMemObj = NULL;
+ int rc = VINF_SUCCESS;
+ uint32_t i;
+ RT_NOREF1(pSession);
+
+ if (fInflate)
+ {
+ if ( pDevExt->MemBalloon.cChunks > pDevExt->MemBalloon.cMaxChunks - 1
+ || pDevExt->MemBalloon.cMaxChunks == 0 /* If called without first querying. */)
+ {
+ LogRel(("vgdrvSetBalloonSizeFromUser: cannot inflate balloon, already have %u chunks (max=%u)\n",
+ pDevExt->MemBalloon.cChunks, pDevExt->MemBalloon.cMaxChunks));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (!pDevExt->MemBalloon.paMemObj)
+ {
+ pDevExt->MemBalloon.paMemObj = (PRTR0MEMOBJ)RTMemAlloc(sizeof(RTR0MEMOBJ) * pDevExt->MemBalloon.cMaxChunks);
+ if (!pDevExt->MemBalloon.paMemObj)
+ {
+ LogRel(("vgdrvSetBalloonSizeFromUser: no memory for paMemObj!\n"));
+ return VERR_NO_MEMORY;
+ }
+ for (i = 0; i < pDevExt->MemBalloon.cMaxChunks; i++)
+ pDevExt->MemBalloon.paMemObj[i] = NIL_RTR0MEMOBJ;
+ }
+ }
+ else
+ {
+ if (pDevExt->MemBalloon.cChunks == 0)
+ {
+ AssertMsgFailed(("vgdrvSetBalloonSizeFromUser: cannot decrease balloon, already at size 0\n"));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ /*
+ * Enumerate all memory objects and check if the object is already registered.
+ */
+ for (i = 0; i < pDevExt->MemBalloon.cMaxChunks; i++)
+ {
+ if ( fInflate
+ && !pMemObj
+ && pDevExt->MemBalloon.paMemObj[i] == NIL_RTR0MEMOBJ)
+ pMemObj = &pDevExt->MemBalloon.paMemObj[i]; /* found free object pointer */
+ if (RTR0MemObjAddressR3(pDevExt->MemBalloon.paMemObj[i]) == pvChunk)
+ {
+ if (fInflate)
+ return VERR_ALREADY_EXISTS; /* don't provide the same memory twice */
+ pMemObj = &pDevExt->MemBalloon.paMemObj[i];
+ break;
+ }
+ }
+ if (!pMemObj)
+ {
+ if (fInflate)
+ {
+ /* no free object pointer found -- should not happen */
+ return VERR_NO_MEMORY;
+ }
+
+ /* cannot free this memory as it wasn't provided before */
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Try inflate / default the balloon as requested.
+ */
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, g_cbChangeMemBalloonReq, VMMDevReq_ChangeMemBalloon);
+ if (RT_FAILURE(rc))
+ return rc;
+ pReq->header.fRequestor = pSession->fRequestor;
+
+ if (fInflate)
+ {
+ rc = RTR0MemObjLockUser(pMemObj, pvChunk, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE,
+ RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgdrvBalloonInflate(pMemObj, pReq);
+ if (RT_SUCCESS(rc))
+ pDevExt->MemBalloon.cChunks++;
+ else
+ {
+ Log(("vgdrvSetBalloonSizeFromUser(inflate): failed, rc=%Rrc!\n", rc));
+ RTR0MemObjFree(*pMemObj, true);
+ *pMemObj = NIL_RTR0MEMOBJ;
+ }
+ }
+ }
+ else
+ {
+ rc = vgdrvBalloonDeflate(pMemObj, pReq);
+ if (RT_SUCCESS(rc))
+ pDevExt->MemBalloon.cChunks--;
+ else
+ Log(("vgdrvSetBalloonSizeFromUser(deflate): failed, rc=%Rrc!\n", rc));
+ }
+
+ VbglR0GRFree(&pReq->header);
+ return rc;
+}
+
+
+/**
+ * Cleanup the memory balloon of a session.
+ *
+ * Will request the balloon mutex, so it must be valid and the caller must not
+ * own it already.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session. Can be NULL at unload.
+ */
+static void vgdrvCloseMemBalloon(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession)
+{
+ RTSemFastMutexRequest(pDevExt->MemBalloon.hMtx);
+ if ( pDevExt->MemBalloon.pOwner == pSession
+ || pSession == NULL /*unload*/)
+ {
+ if (pDevExt->MemBalloon.paMemObj)
+ {
+ VMMDevChangeMemBalloon *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, g_cbChangeMemBalloonReq, VMMDevReq_ChangeMemBalloon);
+ if (RT_SUCCESS(rc))
+ {
+ /* fRequestor is kernel here, as we're cleaning up. */
+
+ uint32_t i;
+ for (i = pDevExt->MemBalloon.cChunks; i-- > 0;)
+ {
+ rc = vgdrvBalloonDeflate(&pDevExt->MemBalloon.paMemObj[i], pReq);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("vgdrvCloseMemBalloon: Deflate failed with rc=%Rrc. Will leak %u chunks.\n",
+ rc, pDevExt->MemBalloon.cChunks));
+ break;
+ }
+ pDevExt->MemBalloon.paMemObj[i] = NIL_RTR0MEMOBJ;
+ pDevExt->MemBalloon.cChunks--;
+ }
+ VbglR0GRFree(&pReq->header);
+ }
+ else
+ LogRel(("vgdrvCloseMemBalloon: Failed to allocate VMMDev request buffer (rc=%Rrc). Will leak %u chunks.\n",
+ rc, pDevExt->MemBalloon.cChunks));
+ RTMemFree(pDevExt->MemBalloon.paMemObj);
+ pDevExt->MemBalloon.paMemObj = NULL;
+ }
+
+ pDevExt->MemBalloon.pOwner = NULL;
+ }
+ RTSemFastMutexRelease(pDevExt->MemBalloon.hMtx);
+}
+
+/** @} */
+
+
+
+/** @name Heartbeat
+ * @{
+ */
+
+/**
+ * Sends heartbeat to host.
+ *
+ * @returns VBox status code.
+ */
+static int vgdrvHeartbeatSend(PVBOXGUESTDEVEXT pDevExt)
+{
+ int rc;
+ if (pDevExt->pReqGuestHeartbeat)
+ {
+ rc = VbglR0GRPerform(pDevExt->pReqGuestHeartbeat);
+ Log3(("vgdrvHeartbeatSend: VbglR0GRPerform vgdrvHeartbeatSend completed with rc=%Rrc\n", rc));
+ }
+ else
+ rc = VERR_INVALID_STATE;
+ return rc;
+}
+
+
+/**
+ * Callback for heartbeat timer.
+ */
+static DECLCALLBACK(void) vgdrvHeartbeatTimerHandler(PRTTIMER hTimer, void *pvUser, uint64_t iTick)
+{
+ PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser;
+ int rc;
+ AssertReturnVoid(pDevExt);
+
+ rc = vgdrvHeartbeatSend(pDevExt);
+ if (RT_FAILURE(rc))
+ Log(("HB Timer: vgdrvHeartbeatSend failed: rc=%Rrc\n", rc));
+
+ NOREF(hTimer); NOREF(iTick);
+}
+
+
+/**
+ * Configure the host to check guest's heartbeat
+ * and get heartbeat interval from the host.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param fEnabled Set true to enable guest heartbeat checks on host.
+ */
+static int vgdrvHeartbeatHostConfigure(PVBOXGUESTDEVEXT pDevExt, bool fEnabled)
+{
+ VMMDevReqHeartbeat *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_HeartbeatConfigure);
+ Log(("vgdrvHeartbeatHostConfigure: VbglR0GRAlloc vgdrvHeartbeatHostConfigure completed with rc=%Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ pReq->fEnabled = fEnabled;
+ pReq->cNsInterval = 0;
+ rc = VbglR0GRPerform(&pReq->header);
+ Log(("vgdrvHeartbeatHostConfigure: VbglR0GRPerform vgdrvHeartbeatHostConfigure completed with rc=%Rrc\n", rc));
+ pDevExt->cNsHeartbeatInterval = pReq->cNsInterval;
+ VbglR0GRFree(&pReq->header);
+ }
+ return rc;
+}
+
+
+/**
+ * Initializes the heartbeat timer.
+ *
+ * This feature may be disabled by the host.
+ *
+ * @returns VBox status (ignored).
+ * @param pDevExt The device extension.
+ */
+static int vgdrvHeartbeatInit(PVBOXGUESTDEVEXT pDevExt)
+{
+ /*
+ * Make sure that heartbeat checking is disabled.
+ */
+ int rc = vgdrvHeartbeatHostConfigure(pDevExt, false);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgdrvHeartbeatHostConfigure(pDevExt, true);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Preallocate the request to use it from the timer callback because:
+ * 1) on Windows VbglR0GRAlloc must be called at IRQL <= APC_LEVEL
+ * and the timer callback runs at DISPATCH_LEVEL;
+ * 2) avoid repeated allocations.
+ */
+ rc = VbglR0GRAlloc(&pDevExt->pReqGuestHeartbeat, sizeof(*pDevExt->pReqGuestHeartbeat), VMMDevReq_GuestHeartbeat);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel(("vgdrvHeartbeatInit: Setting up heartbeat to trigger every %RU64 milliseconds\n",
+ pDevExt->cNsHeartbeatInterval / RT_NS_1MS));
+ rc = RTTimerCreateEx(&pDevExt->pHeartbeatTimer, pDevExt->cNsHeartbeatInterval, 0 /*fFlags*/,
+ (PFNRTTIMER)vgdrvHeartbeatTimerHandler, pDevExt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTimerStart(pDevExt->pHeartbeatTimer, 0);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ LogRel(("vgdrvHeartbeatInit: Heartbeat timer failed to start, rc=%Rrc\n", rc));
+ }
+ else
+ LogRel(("vgdrvHeartbeatInit: Failed to create heartbeat timer: %Rrc\n", rc));
+
+ VbglR0GRFree(pDevExt->pReqGuestHeartbeat);
+ pDevExt->pReqGuestHeartbeat = NULL;
+ }
+ else
+ LogRel(("vgdrvHeartbeatInit: VbglR0GRAlloc(VMMDevReq_GuestHeartbeat): %Rrc\n", rc));
+
+ LogRel(("vgdrvHeartbeatInit: Failed to set up the timer, guest heartbeat is disabled\n"));
+ vgdrvHeartbeatHostConfigure(pDevExt, false);
+ }
+ else
+ LogRel(("vgdrvHeartbeatInit: Failed to configure host for heartbeat checking: rc=%Rrc\n", rc));
+ }
+ return rc;
+}
+
+/** @} */
+
+
+/**
+ * Helper to reinit the VMMDev communication after hibernation.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param enmOSType The OS type.
+ *
+ * @todo Call this on all platforms, not just windows.
+ */
+int VGDrvCommonReinitDevExtAfterHibernation(PVBOXGUESTDEVEXT pDevExt, VBOXOSTYPE enmOSType)
+{
+ int rc = vgdrvReportGuestInfo(enmOSType);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgdrvReportDriverStatus(true /* Driver is active */);
+ if (RT_FAILURE(rc))
+ Log(("VGDrvCommonReinitDevExtAfterHibernation: could not report guest driver status, rc=%Rrc\n", rc));
+ }
+ else
+ Log(("VGDrvCommonReinitDevExtAfterHibernation: could not report guest information to host, rc=%Rrc\n", rc));
+ LogFlow(("VGDrvCommonReinitDevExtAfterHibernation: returned with rc=%Rrc\n", rc));
+ RT_NOREF1(pDevExt);
+ return rc;
+}
+
+
+/**
+ * Initializes the release logger (debug is implicit), if configured.
+ *
+ * @returns IPRT status code.
+ */
+int VGDrvCommonInitLoggers(void)
+{
+#ifdef VBOX_GUESTDRV_WITH_RELEASE_LOGGER
+ /*
+ * Create the release log.
+ */
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ PRTLOGGER pRelLogger;
+ int rc = RTLogCreate(&pRelLogger, 0 /*fFlags*/, "all", "VBOXGUEST_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups,
+ RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER, NULL);
+ if (RT_SUCCESS(rc))
+ RTLogRelSetDefaultInstance(pRelLogger);
+ /** @todo Add native hook for getting logger config parameters and setting
+ * them. On linux we should use the module parameter stuff... */
+ return rc;
+#else
+ return VINF_SUCCESS;
+#endif
+}
+
+
+/**
+ * Destroys the loggers.
+ */
+void VGDrvCommonDestroyLoggers(void)
+{
+#ifdef VBOX_GUESTDRV_WITH_RELEASE_LOGGER
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+ RTLogDestroy(RTLogSetDefaultInstance(NULL));
+#endif
+}
+
+
+/**
+ * Initialize the device extension fundament.
+ *
+ * There are no device resources at this point, VGDrvCommonInitDevExtResources
+ * should be called when they are available.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension to init.
+ */
+int VGDrvCommonInitDevExtFundament(PVBOXGUESTDEVEXT pDevExt)
+{
+ int rc;
+ AssertMsg( pDevExt->uInitState != VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT
+ && pDevExt->uInitState != VBOXGUESTDEVEXT_INIT_STATE_RESOURCES, ("uInitState=%#x\n", pDevExt->uInitState));
+
+ /*
+ * Initialize the data.
+ */
+ pDevExt->IOPortBase = UINT16_MAX;
+ pDevExt->pVMMDevMemory = NULL;
+ pDevExt->hGuestMappings = NIL_RTR0MEMOBJ;
+ pDevExt->EventSpinlock = NIL_RTSPINLOCK;
+ pDevExt->fHostFeatures = 0;
+ pDevExt->pIrqAckEvents = NULL;
+ pDevExt->PhysIrqAckEvents = NIL_RTCCPHYS;
+ RTListInit(&pDevExt->WaitList);
+#ifdef VBOX_WITH_HGCM
+ RTListInit(&pDevExt->HGCMWaitList);
+#endif
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ RTListInit(&pDevExt->WakeUpList);
+#endif
+ RTListInit(&pDevExt->WokenUpList);
+ RTListInit(&pDevExt->FreeList);
+ RTListInit(&pDevExt->SessionList);
+ pDevExt->cSessions = 0;
+ pDevExt->fLoggingEnabled = false;
+ pDevExt->f32PendingEvents = 0;
+ pDevExt->u32MousePosChangedSeq = 0;
+ pDevExt->SessionSpinlock = NIL_RTSPINLOCK;
+ pDevExt->MemBalloon.hMtx = NIL_RTSEMFASTMUTEX;
+ pDevExt->MemBalloon.cChunks = 0;
+ pDevExt->MemBalloon.cMaxChunks = 0;
+ pDevExt->MemBalloon.fUseKernelAPI = true;
+ pDevExt->MemBalloon.paMemObj = NULL;
+ pDevExt->MemBalloon.pOwner = NULL;
+ pDevExt->pfnMouseNotifyCallback = NULL;
+ pDevExt->pvMouseNotifyCallbackArg = NULL;
+ pDevExt->pReqGuestHeartbeat = NULL;
+
+ pDevExt->fFixedEvents = 0;
+ vgdrvBitUsageTrackerClear(&pDevExt->EventFilterTracker);
+ pDevExt->fEventFilterHost = UINT32_MAX; /* forces a report */
+
+ vgdrvBitUsageTrackerClear(&pDevExt->MouseStatusTracker);
+ pDevExt->fMouseStatusHost = UINT32_MAX; /* forces a report */
+
+ pDevExt->fAcquireModeGuestCaps = 0;
+ pDevExt->fSetModeGuestCaps = 0;
+ pDevExt->fAcquiredGuestCaps = 0;
+ vgdrvBitUsageTrackerClear(&pDevExt->SetGuestCapsTracker);
+ pDevExt->fGuestCapsHost = UINT32_MAX; /* forces a report */
+
+ /*
+ * Create the wait and session spinlocks as well as the ballooning mutex.
+ */
+ rc = RTSpinlockCreate(&pDevExt->EventSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestEvent");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSpinlockCreate(&pDevExt->SessionSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxGuestSession");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemFastMutexCreate(&pDevExt->MemBalloon.hMtx);
+ if (RT_SUCCESS(rc))
+ {
+ pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT;
+ return VINF_SUCCESS;
+ }
+
+ LogRel(("VGDrvCommonInitDevExt: failed to create mutex, rc=%Rrc!\n", rc));
+ RTSpinlockDestroy(pDevExt->SessionSpinlock);
+ }
+ else
+ LogRel(("VGDrvCommonInitDevExt: failed to create spinlock, rc=%Rrc!\n", rc));
+ RTSpinlockDestroy(pDevExt->EventSpinlock);
+ }
+ else
+ LogRel(("VGDrvCommonInitDevExt: failed to create spinlock, rc=%Rrc!\n", rc));
+
+ pDevExt->uInitState = 0;
+ return rc;
+}
+
+
+/**
+ * Counter to VGDrvCommonInitDevExtFundament.
+ *
+ * @param pDevExt The device extension.
+ */
+void VGDrvCommonDeleteDevExtFundament(PVBOXGUESTDEVEXT pDevExt)
+{
+ int rc2;
+ AssertMsgReturnVoid(pDevExt->uInitState == VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT, ("uInitState=%#x\n", pDevExt->uInitState));
+ pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_DELETED;
+
+ rc2 = RTSemFastMutexDestroy(pDevExt->MemBalloon.hMtx); AssertRC(rc2);
+ rc2 = RTSpinlockDestroy(pDevExt->EventSpinlock); AssertRC(rc2);
+ rc2 = RTSpinlockDestroy(pDevExt->SessionSpinlock); AssertRC(rc2);
+}
+
+
+/**
+ * Initializes the VBoxGuest device extension resource parts.
+ *
+ * The native code locates the VMMDev on the PCI bus and retrieve the MMIO and
+ * I/O port ranges, this function will take care of mapping the MMIO memory (if
+ * present). Upon successful return the native code should set up the interrupt
+ * handler.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension. Allocated by the native code.
+ * @param IOPortBase The base of the I/O port range.
+ * @param pvMMIOBase The base of the MMIO memory mapping.
+ * This is optional, pass NULL if not present.
+ * @param cbMMIO The size of the MMIO memory mapping.
+ * This is optional, pass 0 if not present.
+ * @param enmOSType The guest OS type to report to the VMMDev.
+ * @param fFixedEvents Events that will be enabled upon init and no client
+ * will ever be allowed to mask.
+ */
+int VGDrvCommonInitDevExtResources(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase,
+ void *pvMMIOBase, uint32_t cbMMIO, VBOXOSTYPE enmOSType, uint32_t fFixedEvents)
+{
+ int rc;
+ AssertMsgReturn(pDevExt->uInitState == VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT, ("uInitState=%#x\n", pDevExt->uInitState),
+ VERR_INVALID_STATE);
+
+ /*
+ * If there is an MMIO region validate the version and size.
+ */
+ if (pvMMIOBase)
+ {
+ VMMDevMemory *pVMMDev = (VMMDevMemory *)pvMMIOBase;
+ Assert(cbMMIO);
+ if ( pVMMDev->u32Version == VMMDEV_MEMORY_VERSION
+ && pVMMDev->u32Size >= 32
+ && pVMMDev->u32Size <= cbMMIO)
+ {
+ pDevExt->pVMMDevMemory = pVMMDev;
+ Log(("VGDrvCommonInitDevExtResources: VMMDevMemory: mapping=%p size=%#RX32 (%#RX32) version=%#RX32\n",
+ pVMMDev, pVMMDev->u32Size, cbMMIO, pVMMDev->u32Version));
+ }
+ else /* try live without it. */
+ LogRel(("VGDrvCommonInitDevExtResources: Bogus VMMDev memory; u32Version=%RX32 (expected %RX32) u32Size=%RX32 (expected <= %RX32)\n",
+ pVMMDev->u32Version, VMMDEV_MEMORY_VERSION, pVMMDev->u32Size, cbMMIO));
+ }
+
+ /*
+ * Initialize the guest library and report the guest info back to VMMDev,
+ * set the interrupt control filter mask, and fixate the guest mappings
+ * made by the VMM.
+ */
+ pDevExt->IOPortBase = IOPortBase;
+ rc = VbglR0InitPrimary(pDevExt->IOPortBase, (VMMDevMemory *)pDevExt->pVMMDevMemory, &pDevExt->fHostFeatures);
+ if (RT_SUCCESS(rc))
+ {
+ VMMDevRequestHeader *pAckReq = NULL;
+ rc = VbglR0GRAlloc(&pAckReq, sizeof(VMMDevEvents), VMMDevReq_AcknowledgeEvents);
+ if (RT_SUCCESS(rc))
+ {
+ pDevExt->PhysIrqAckEvents = VbglR0PhysHeapGetPhysAddr(pAckReq);
+ Assert(pDevExt->PhysIrqAckEvents != 0);
+ ASMCompilerBarrier(); /* linux + solaris already have IRQs hooked up at this point, so take care. */
+ pDevExt->pIrqAckEvents = (VMMDevEvents *)pAckReq;
+
+ rc = vgdrvReportGuestInfo(enmOSType);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Set the fixed event and make sure the host doesn't have any lingering
+ * the guest capabilities or mouse status bits set.
+ */
+#ifdef VBOX_WITH_HGCM
+ fFixedEvents |= VMMDEV_EVENT_HGCM;
+#endif
+ pDevExt->fFixedEvents = fFixedEvents;
+ rc = vgdrvResetEventFilterOnHost(pDevExt, fFixedEvents);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgdrvResetCapabilitiesOnHost(pDevExt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgdrvResetMouseStatusOnHost(pDevExt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize stuff which may fail without requiring the driver init to fail.
+ */
+ vgdrvInitFixateGuestMappings(pDevExt);
+ vgdrvHeartbeatInit(pDevExt);
+
+ /*
+ * Done!
+ */
+ rc = vgdrvReportDriverStatus(true /* Driver is active */);
+ if (RT_FAILURE(rc))
+ LogRel(("VGDrvCommonInitDevExtResources: VBoxReportGuestDriverStatus failed, rc=%Rrc\n", rc));
+
+ pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_RESOURCES;
+ LogFlowFunc(("VGDrvCommonInitDevExtResources: returns success\n"));
+ return VINF_SUCCESS;
+ }
+ LogRel(("VGDrvCommonInitDevExtResources: failed to clear mouse status: rc=%Rrc\n", rc));
+ }
+ else
+ LogRel(("VGDrvCommonInitDevExtResources: failed to clear guest capabilities: rc=%Rrc\n", rc));
+ }
+ else
+ LogRel(("VGDrvCommonInitDevExtResources: failed to set fixed event filter: rc=%Rrc\n", rc));
+ pDevExt->fFixedEvents = 0;
+ }
+ else
+ LogRel(("VGDrvCommonInitDevExtResources: vgdrvReportGuestInfo failed: rc=%Rrc\n", rc));
+ VbglR0GRFree((VMMDevRequestHeader *)pDevExt->pIrqAckEvents);
+ }
+ else
+ LogRel(("VGDrvCommonInitDevExtResources: VbglR0GRAlloc failed: rc=%Rrc\n", rc));
+
+ VbglR0TerminatePrimary();
+ }
+ else
+ LogRel(("VGDrvCommonInitDevExtResources: VbglR0InitPrimary failed: rc=%Rrc\n", rc));
+ pDevExt->IOPortBase = UINT16_MAX;
+ return rc;
+}
+
+
+/**
+ * Deletes all the items in a wait chain.
+ * @param pList The head of the chain.
+ */
+static void vgdrvDeleteWaitList(PRTLISTNODE pList)
+{
+ while (!RTListIsEmpty(pList))
+ {
+ int rc2;
+ PVBOXGUESTWAIT pWait = RTListGetFirst(pList, VBOXGUESTWAIT, ListNode);
+ RTListNodeRemove(&pWait->ListNode);
+
+ rc2 = RTSemEventMultiDestroy(pWait->Event); AssertRC(rc2);
+ pWait->Event = NIL_RTSEMEVENTMULTI;
+ pWait->pSession = NULL;
+ RTMemFree(pWait);
+ }
+}
+
+
+/**
+ * Counter to VGDrvCommonInitDevExtResources.
+ *
+ * @param pDevExt The device extension.
+ */
+void VGDrvCommonDeleteDevExtResources(PVBOXGUESTDEVEXT pDevExt)
+{
+ Log(("VGDrvCommonDeleteDevExtResources:\n"));
+ AssertMsgReturnVoid(pDevExt->uInitState == VBOXGUESTDEVEXT_INIT_STATE_RESOURCES, ("uInitState=%#x\n", pDevExt->uInitState));
+ pDevExt->uInitState = VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT;
+
+ /*
+ * Stop and destroy HB timer and disable host heartbeat checking.
+ */
+ if (pDevExt->pHeartbeatTimer)
+ {
+ RTTimerDestroy(pDevExt->pHeartbeatTimer);
+ vgdrvHeartbeatHostConfigure(pDevExt, false);
+ }
+
+ VbglR0GRFree(pDevExt->pReqGuestHeartbeat);
+ pDevExt->pReqGuestHeartbeat = NULL;
+
+ /*
+ * Clean up the bits that involves the host first.
+ */
+ vgdrvTermUnfixGuestMappings(pDevExt);
+ if (!RTListIsEmpty(&pDevExt->SessionList))
+ {
+ LogRelFunc(("session list not empty!\n"));
+ RTListInit(&pDevExt->SessionList);
+ }
+
+ /*
+ * Update the host flags (mouse status etc) not to reflect this session.
+ */
+ pDevExt->fFixedEvents = 0;
+ vgdrvResetEventFilterOnHost(pDevExt, 0 /*fFixedEvents*/);
+ vgdrvResetCapabilitiesOnHost(pDevExt);
+ vgdrvResetMouseStatusOnHost(pDevExt);
+
+ vgdrvCloseMemBalloon(pDevExt, (PVBOXGUESTSESSION)NULL);
+
+ /*
+ * No more IRQs.
+ */
+ pDevExt->pIrqAckEvents = NULL; /* Will be freed by VbglR0TerminatePrimary. */
+ ASMAtomicWriteU32(&pDevExt->fHostFeatures, 0);
+
+ /*
+ * Cleanup all the other resources.
+ */
+ vgdrvDeleteWaitList(&pDevExt->WaitList);
+#ifdef VBOX_WITH_HGCM
+ vgdrvDeleteWaitList(&pDevExt->HGCMWaitList);
+#endif
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ vgdrvDeleteWaitList(&pDevExt->WakeUpList);
+#endif
+ vgdrvDeleteWaitList(&pDevExt->WokenUpList);
+ vgdrvDeleteWaitList(&pDevExt->FreeList);
+
+ VbglR0TerminatePrimary();
+
+
+ pDevExt->pVMMDevMemory = NULL;
+ pDevExt->IOPortBase = 0;
+}
+
+
+/**
+ * Initializes the VBoxGuest device extension when the device driver is loaded.
+ *
+ * The native code locates the VMMDev on the PCI bus and retrieve the MMIO and
+ * I/O port ranges, this function will take care of mapping the MMIO memory (if
+ * present). Upon successful return the native code should set up the interrupt
+ * handler.
+ *
+ * Instead of calling this method, the host specific code choose to perform a
+ * more granular initialization using:
+ * 1. VGDrvCommonInitLoggers
+ * 2. VGDrvCommonInitDevExtFundament
+ * 3. VGDrvCommonInitDevExtResources
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension. Allocated by the native code.
+ * @param IOPortBase The base of the I/O port range.
+ * @param pvMMIOBase The base of the MMIO memory mapping.
+ * This is optional, pass NULL if not present.
+ * @param cbMMIO The size of the MMIO memory mapping.
+ * This is optional, pass 0 if not present.
+ * @param enmOSType The guest OS type to report to the VMMDev.
+ * @param fFixedEvents Events that will be enabled upon init and no client
+ * will ever be allowed to mask.
+ */
+int VGDrvCommonInitDevExt(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase,
+ void *pvMMIOBase, uint32_t cbMMIO, VBOXOSTYPE enmOSType, uint32_t fFixedEvents)
+{
+ int rc;
+ VGDrvCommonInitLoggers();
+
+ rc = VGDrvCommonInitDevExtFundament(pDevExt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VGDrvCommonInitDevExtResources(pDevExt, IOPortBase, pvMMIOBase, cbMMIO, enmOSType, fFixedEvents);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ VGDrvCommonDeleteDevExtFundament(pDevExt);
+ }
+ VGDrvCommonDestroyLoggers();
+ return rc; /* (failed) */
+}
+
+
+/**
+ * Checks if the given option can be taken to not mean 'false'.
+ *
+ * @returns true or false accordingly.
+ * @param pszValue The value to consider.
+ */
+bool VBDrvCommonIsOptionValueTrue(const char *pszValue)
+{
+ if (pszValue)
+ {
+ char ch;
+ while ( (ch = *pszValue) != '\0'
+ && RT_C_IS_SPACE(ch))
+ pszValue++;
+
+ return ch != '\0'
+ && ch != 'n' /* no */
+ && ch != 'N' /* NO */
+ && ch != 'd' /* disabled */
+ && ch != 'f' /* false*/
+ && ch != 'F' /* FALSE */
+ && ch != 'D' /* DISABLED */
+ && ( (ch != 'o' && ch != 'O') /* off, OFF, Off */
+ || (pszValue[1] != 'f' && pszValue[1] != 'F') )
+ && (ch != '0' || pszValue[1] != '\0') /* '0' */
+ ;
+ }
+ return false;
+}
+
+
+/**
+ * Processes a option.
+ *
+ * This will let the OS specific code have a go at it too.
+ *
+ * @param pDevExt The device extension.
+ * @param pszName The option name, sans prefix.
+ * @param pszValue The option value.
+ */
+void VGDrvCommonProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue)
+{
+ Log(("VGDrvCommonProcessOption: pszName='%s' pszValue='%s'\n", pszName, pszValue));
+
+ if ( RTStrICmpAscii(pszName, "r3_log_to_host") == 0
+ || RTStrICmpAscii(pszName, "LoggingEnabled") == 0 /*legacy*/ )
+ pDevExt->fLoggingEnabled = VBDrvCommonIsOptionValueTrue(pszValue);
+ else if ( RTStrNICmpAscii(pszName, RT_STR_TUPLE("log")) == 0
+ || RTStrNICmpAscii(pszName, RT_STR_TUPLE("dbg_log")) == 0)
+ {
+ bool const fDbgRel = *pszName == 'd' || *pszName == 'D';
+ const char *pszSubName = &pszName[fDbgRel ? 4 + 3 : 3];
+ if ( !*pszSubName
+ || RTStrICmpAscii(pszSubName, "_flags") == 0
+ || RTStrICmpAscii(pszSubName, "_dest") == 0)
+ {
+ PRTLOGGER pLogger = !fDbgRel ? RTLogRelGetDefaultInstance() : RTLogDefaultInstance();
+ if (pLogger)
+ {
+ if (!*pszSubName)
+ RTLogGroupSettings(pLogger, pszValue);
+ else if (RTStrICmpAscii(pszSubName, "_flags"))
+ RTLogFlags(pLogger, pszValue);
+ else
+ RTLogDestinations(pLogger, pszValue);
+ }
+ }
+ else if (!VGDrvNativeProcessOption(pDevExt, pszName, pszValue))
+ LogRel(("VBoxGuest: Ignoring unknown option '%s' (value '%s')\n", pszName, pszValue));
+ }
+ else if (!VGDrvNativeProcessOption(pDevExt, pszName, pszValue))
+ LogRel(("VBoxGuest: Ignoring unknown option '%s' (value '%s')\n", pszName, pszValue));
+}
+
+
+/**
+ * Read driver configuration from the host.
+ *
+ * This involves connecting to the guest properties service, which means that
+ * interrupts needs to work and that the calling thread must be able to block.
+ *
+ * @param pDevExt The device extension.
+ */
+void VGDrvCommonProcessOptionsFromHost(PVBOXGUESTDEVEXT pDevExt)
+{
+ /*
+ * Create a kernel session without our selves, then connect to the HGCM service.
+ */
+ PVBOXGUESTSESSION pSession;
+ int rc = VGDrvCommonCreateKernelSession(pDevExt, &pSession);
+ if (RT_SUCCESS(rc))
+ {
+ union
+ {
+ VBGLIOCHGCMCONNECT Connect;
+ VBGLIOCHGCMDISCONNECT Disconnect;
+ GuestPropMsgEnumProperties EnumMsg;
+ } uBuf;
+
+ RT_ZERO(uBuf.Connect);
+ VBGLREQHDR_INIT(&uBuf.Connect.Hdr, HGCM_CONNECT);
+ uBuf.Connect.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing;
+ RTStrCopy(uBuf.Connect.u.In.Loc.u.host.achName, sizeof(uBuf.Connect.u.In.Loc.u.host.achName),
+ "VBoxGuestPropSvc"); /** @todo Add a define to the header for the name. */
+ rc = VGDrvCommonIoCtl(VBGL_IOCTL_HGCM_CONNECT, pDevExt, pSession, &uBuf.Connect.Hdr, sizeof(uBuf.Connect));
+ if (RT_SUCCESS(rc))
+ {
+ static const char g_szzPattern[] = "/VirtualBox/GuestAdd/VBoxGuest/*\0";
+ uint32_t const idClient = uBuf.Connect.u.Out.idClient;
+ char *pszzStrings = NULL;
+ uint32_t cbStrings;
+
+ /*
+ * Enumerate all the relevant properties. We try with a 1KB buffer, but
+ * will double it until we get what we want or go beyond 16KB.
+ */
+ for (cbStrings = _1K; cbStrings <= _16K; cbStrings *= 2)
+ {
+ pszzStrings = (char *)RTMemAllocZ(cbStrings);
+ if (pszzStrings)
+ {
+ VBGL_HGCM_HDR_INIT(&uBuf.EnumMsg.hdr, idClient, GUEST_PROP_FN_ENUM_PROPS, 3);
+
+ uBuf.EnumMsg.patterns.type = VMMDevHGCMParmType_LinAddr;
+ uBuf.EnumMsg.patterns.u.Pointer.size = sizeof(g_szzPattern);
+ uBuf.EnumMsg.patterns.u.Pointer.u.linearAddr = (uintptr_t)g_szzPattern;
+
+ uBuf.EnumMsg.strings.type = VMMDevHGCMParmType_LinAddr;
+ uBuf.EnumMsg.strings.u.Pointer.size = cbStrings;
+ uBuf.EnumMsg.strings.u.Pointer.u.linearAddr = (uintptr_t)pszzStrings;
+
+ uBuf.EnumMsg.size.type = VMMDevHGCMParmType_32bit;
+ uBuf.EnumMsg.size.u.value32 = 0;
+
+ rc = VGDrvCommonIoCtl(VBGL_IOCTL_HGCM_CALL(sizeof(uBuf.EnumMsg)), pDevExt, pSession,
+ &uBuf.EnumMsg.hdr.Hdr, sizeof(uBuf.EnumMsg));
+ if (RT_SUCCESS(rc))
+ {
+ if ( uBuf.EnumMsg.size.type == VMMDevHGCMParmType_32bit
+ && uBuf.EnumMsg.size.u.value32 <= cbStrings
+ && uBuf.EnumMsg.size.u.value32 > 0)
+ cbStrings = uBuf.EnumMsg.size.u.value32;
+ Log(("VGDrvCommonReadConfigurationFromHost: GUEST_PROP_FN_ENUM_PROPS -> %#x bytes (cbStrings=%#x)\n",
+ uBuf.EnumMsg.size.u.value32, cbStrings));
+ break;
+ }
+
+ RTMemFree(pszzStrings);
+ pszzStrings = NULL;
+ }
+ else
+ {
+ LogRel(("VGDrvCommonReadConfigurationFromHost: failed to allocate %#x bytes\n", cbStrings));
+ break;
+ }
+ }
+
+ /*
+ * Disconnect and destroy the session.
+ */
+ VBGLREQHDR_INIT(&uBuf.Disconnect.Hdr, HGCM_DISCONNECT);
+ uBuf.Disconnect.u.In.idClient = idClient;
+ VGDrvCommonIoCtl(VBGL_IOCTL_HGCM_DISCONNECT, pDevExt, pSession, &uBuf.Disconnect.Hdr, sizeof(uBuf.Disconnect));
+
+ VGDrvCommonCloseSession(pDevExt, pSession);
+
+ /*
+ * Process the properties if we got any.
+ *
+ * The string buffer contains packed strings in groups of four - name, value,
+ * timestamp (as a decimal string) and flags. It is terminated by four empty
+ * strings. Layout:
+ * Name\0Value\0Timestamp\0Flags\0
+ */
+ if (pszzStrings)
+ {
+ uint32_t off;
+ for (off = 0; off < cbStrings; off++)
+ {
+ /*
+ * Parse the four fields, checking that it's all plain ASCII w/o any control characters.
+ */
+ const char *apszFields[4] = { NULL, NULL, NULL, NULL };
+ bool fValidFields = true;
+ unsigned iField;
+ for (iField = 0; iField < RT_ELEMENTS(apszFields); iField++)
+ {
+ apszFields[0] = &pszzStrings[off];
+ while (off < cbStrings)
+ {
+ char ch = pszzStrings[off++];
+ if ((unsigned)ch < 0x20U || (unsigned)ch > 0x7fU)
+ {
+ if (!ch)
+ break;
+ if (fValidFields)
+ Log(("VGDrvCommonReadConfigurationFromHost: Invalid char %#x at %#x (field %u)\n",
+ ch, off - 1, iField));
+ fValidFields = false;
+ }
+ }
+ }
+ if ( off <= cbStrings
+ && fValidFields
+ && *apszFields[0] != '\0')
+ {
+ /*
+ * Validate and convert the flags to integer, then process the option.
+ */
+ uint32_t fFlags = 0;
+ rc = GuestPropValidateFlags(apszFields[3], &fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags & GUEST_PROP_F_RDONLYGUEST)
+ {
+ apszFields[0] += sizeof(g_szzPattern) - 2;
+ VGDrvCommonProcessOption(pDevExt, apszFields[0], apszFields[1]);
+ }
+ else
+ LogRel(("VBoxGuest: Ignoring '%s' as it does not have RDONLYGUEST set\n", apszFields[0]));
+ }
+ else
+ LogRel(("VBoxGuest: Invalid flags '%s' for '%s': %Rrc\n", apszFields[2], apszFields[0], rc));
+ }
+ else if (off < cbStrings)
+ {
+ LogRel(("VBoxGuest: Malformed guest properties enum result!\n"));
+ Log(("VBoxGuest: off=%#x cbStrings=%#x\n%.*Rhxd\n", off, cbStrings, cbStrings, pszzStrings));
+ break;
+ }
+ else if (!fValidFields)
+ LogRel(("VBoxGuest: Ignoring %.*Rhxs as it has invalid characters in one or more fields\n",
+ (int)strlen(apszFields[0]), apszFields[0]));
+ else
+ break;
+ }
+
+ RTMemFree(pszzStrings);
+ }
+ else
+ LogRel(("VGDrvCommonReadConfigurationFromHost: failed to enumerate '%s': %Rrc\n", g_szzPattern, rc));
+
+ }
+ else
+ LogRel(("VGDrvCommonReadConfigurationFromHost: failed to connect: %Rrc\n", rc));
+ }
+ else
+ LogRel(("VGDrvCommonReadConfigurationFromHost: failed to connect: %Rrc\n", rc));
+}
+
+
+/**
+ * Destroys the VBoxGuest device extension.
+ *
+ * The native code should call this before the driver is unloaded,
+ * but don't call this on shutdown.
+ *
+ * @param pDevExt The device extension.
+ */
+void VGDrvCommonDeleteDevExt(PVBOXGUESTDEVEXT pDevExt)
+{
+ Log(("VGDrvCommonDeleteDevExt:\n"));
+ Log(("VBoxGuest: The additions driver is terminating.\n"));
+ VGDrvCommonDeleteDevExtResources(pDevExt);
+ VGDrvCommonDeleteDevExtFundament(pDevExt);
+ VGDrvCommonDestroyLoggers();
+}
+
+
+/**
+ * Creates a VBoxGuest user session.
+ *
+ * The native code calls this when a ring-3 client opens the device.
+ * Use VGDrvCommonCreateKernelSession when a ring-0 client connects.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param fRequestor VMMDEV_REQUESTOR_XXX.
+ * @param ppSession Where to store the session on success.
+ */
+int VGDrvCommonCreateUserSession(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)RTMemAllocZ(sizeof(*pSession));
+ if (RT_UNLIKELY(!pSession))
+ {
+ LogRel(("VGDrvCommonCreateUserSession: no memory!\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ pSession->Process = RTProcSelf();
+ pSession->R0Process = RTR0ProcHandleSelf();
+ pSession->pDevExt = pDevExt;
+ pSession->fRequestor = fRequestor;
+ pSession->fUserSession = RT_BOOL(fRequestor & VMMDEV_REQUESTOR_USER_DEVICE);
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+ RTListAppend(&pDevExt->SessionList, &pSession->ListNode);
+ pDevExt->cSessions++;
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+
+ *ppSession = pSession;
+ LogFlow(("VGDrvCommonCreateUserSession: pSession=%p proc=%RTproc (%d) r0proc=%p\n",
+ pSession, pSession->Process, (int)pSession->Process, (uintptr_t)pSession->R0Process)); /** @todo %RTr0proc */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates a VBoxGuest kernel session.
+ *
+ * The native code calls this when a ring-0 client connects to the device.
+ * Use VGDrvCommonCreateUserSession when a ring-3 client opens the device.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param ppSession Where to store the session on success.
+ */
+int VGDrvCommonCreateKernelSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION *ppSession)
+{
+ PVBOXGUESTSESSION pSession = (PVBOXGUESTSESSION)RTMemAllocZ(sizeof(*pSession));
+ if (RT_UNLIKELY(!pSession))
+ {
+ LogRel(("VGDrvCommonCreateKernelSession: no memory!\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ pSession->Process = NIL_RTPROCESS;
+ pSession->R0Process = NIL_RTR0PROCESS;
+ pSession->pDevExt = pDevExt;
+ pSession->fRequestor = VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV_OTHER
+ | VMMDEV_REQUESTOR_CON_DONT_KNOW | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+ RTListAppend(&pDevExt->SessionList, &pSession->ListNode);
+ pDevExt->cSessions++;
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+
+ *ppSession = pSession;
+ LogFlow(("VGDrvCommonCreateKernelSession: pSession=%p proc=%RTproc (%d) r0proc=%p\n",
+ pSession, pSession->Process, (int)pSession->Process, (uintptr_t)pSession->R0Process)); /** @todo %RTr0proc */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Closes a VBoxGuest session.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session to close (and free).
+ */
+void VGDrvCommonCloseSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession)
+{
+#ifdef VBOX_WITH_HGCM
+ unsigned i;
+#endif
+ LogFlow(("VGDrvCommonCloseSession: pSession=%p proc=%RTproc (%d) r0proc=%p\n",
+ pSession, pSession->Process, (int)pSession->Process, (uintptr_t)pSession->R0Process)); /** @todo %RTr0proc */
+
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+ RTListNodeRemove(&pSession->ListNode);
+ pDevExt->cSessions--;
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+ vgdrvAcquireSessionCapabilities(pDevExt, pSession, 0, UINT32_MAX, VBGL_IOC_AGC_FLAGS_DEFAULT, true /*fSessionTermination*/);
+ vgdrvSetSessionCapabilities(pDevExt, pSession, 0 /*fOrMask*/, UINT32_MAX /*fNotMask*/,
+ NULL /*pfSessionCaps*/, NULL /*pfGlobalCaps*/, true /*fSessionTermination*/);
+ vgdrvSetSessionEventFilter(pDevExt, pSession, 0 /*fOrMask*/, UINT32_MAX /*fNotMask*/, true /*fSessionTermination*/);
+ vgdrvSetSessionMouseStatus(pDevExt, pSession, 0 /*fOrMask*/, UINT32_MAX /*fNotMask*/, true /*fSessionTermination*/);
+
+ vgdrvIoCtl_CancelAllWaitEvents(pDevExt, pSession);
+
+#ifdef VBOX_WITH_HGCM
+ for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++)
+ if (pSession->aHGCMClientIds[i])
+ {
+ uint32_t idClient = pSession->aHGCMClientIds[i];
+ pSession->aHGCMClientIds[i] = 0;
+ Log(("VGDrvCommonCloseSession: disconnecting client id %#RX32\n", idClient));
+ VbglR0HGCMInternalDisconnect(idClient, VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV,
+ vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT);
+ }
+#endif
+
+ pSession->pDevExt = NULL;
+ pSession->Process = NIL_RTPROCESS;
+ pSession->R0Process = NIL_RTR0PROCESS;
+ vgdrvCloseMemBalloon(pDevExt, pSession);
+ RTMemFree(pSession);
+}
+
+
+/**
+ * Allocates a wait-for-event entry.
+ *
+ * @returns The wait-for-event entry.
+ * @param pDevExt The device extension.
+ * @param pSession The session that's allocating this. Can be NULL.
+ */
+static PVBOXGUESTWAIT vgdrvWaitAlloc(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession)
+{
+ /*
+ * Allocate it one way or the other.
+ */
+ PVBOXGUESTWAIT pWait = RTListGetFirst(&pDevExt->FreeList, VBOXGUESTWAIT, ListNode);
+ if (pWait)
+ {
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+
+ pWait = RTListGetFirst(&pDevExt->FreeList, VBOXGUESTWAIT, ListNode);
+ if (pWait)
+ RTListNodeRemove(&pWait->ListNode);
+
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ }
+ if (!pWait)
+ {
+ int rc;
+
+ pWait = (PVBOXGUESTWAIT)RTMemAlloc(sizeof(*pWait));
+ if (!pWait)
+ {
+ LogRelMax(32, ("vgdrvWaitAlloc: out-of-memory!\n"));
+ return NULL;
+ }
+
+ rc = RTSemEventMultiCreate(&pWait->Event);
+ if (RT_FAILURE(rc))
+ {
+ LogRelMax(32, ("vgdrvWaitAlloc: RTSemEventMultiCreate failed with rc=%Rrc!\n", rc));
+ RTMemFree(pWait);
+ return NULL;
+ }
+
+ pWait->ListNode.pNext = NULL;
+ pWait->ListNode.pPrev = NULL;
+ }
+
+ /*
+ * Zero members just as an precaution.
+ */
+ pWait->fReqEvents = 0;
+ pWait->fResEvents = 0;
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ pWait->fPendingWakeUp = false;
+ pWait->fFreeMe = false;
+#endif
+ pWait->pSession = pSession;
+#ifdef VBOX_WITH_HGCM
+ pWait->pHGCMReq = NULL;
+#endif
+ RTSemEventMultiReset(pWait->Event);
+ return pWait;
+}
+
+
+/**
+ * Frees the wait-for-event entry.
+ *
+ * The caller must own the wait spinlock !
+ * The entry must be in a list!
+ *
+ * @param pDevExt The device extension.
+ * @param pWait The wait-for-event entry to free.
+ */
+static void vgdrvWaitFreeLocked(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTWAIT pWait)
+{
+ pWait->fReqEvents = 0;
+ pWait->fResEvents = 0;
+#ifdef VBOX_WITH_HGCM
+ pWait->pHGCMReq = NULL;
+#endif
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ Assert(!pWait->fFreeMe);
+ if (pWait->fPendingWakeUp)
+ pWait->fFreeMe = true;
+ else
+#endif
+ {
+ RTListNodeRemove(&pWait->ListNode);
+ RTListAppend(&pDevExt->FreeList, &pWait->ListNode);
+ }
+}
+
+
+/**
+ * Frees the wait-for-event entry.
+ *
+ * @param pDevExt The device extension.
+ * @param pWait The wait-for-event entry to free.
+ */
+static void vgdrvWaitFreeUnlocked(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTWAIT pWait)
+{
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ vgdrvWaitFreeLocked(pDevExt, pWait);
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+}
+
+
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+/**
+ * Processes the wake-up list.
+ *
+ * All entries in the wake-up list gets signalled and moved to the woken-up
+ * list.
+ * At least on Windows this function can be invoked concurrently from
+ * different VCPUs. So, be thread-safe.
+ *
+ * @param pDevExt The device extension.
+ */
+void VGDrvCommonWaitDoWakeUps(PVBOXGUESTDEVEXT pDevExt)
+{
+ if (!RTListIsEmpty(&pDevExt->WakeUpList))
+ {
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ for (;;)
+ {
+ int rc;
+ PVBOXGUESTWAIT pWait = RTListGetFirst(&pDevExt->WakeUpList, VBOXGUESTWAIT, ListNode);
+ if (!pWait)
+ break;
+ /* Prevent other threads from accessing pWait when spinlock is released. */
+ RTListNodeRemove(&pWait->ListNode);
+
+ pWait->fPendingWakeUp = true;
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ rc = RTSemEventMultiSignal(pWait->Event);
+ AssertRC(rc);
+
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ Assert(pWait->ListNode.pNext == NULL && pWait->ListNode.pPrev == NULL);
+ RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode);
+ pWait->fPendingWakeUp = false;
+ if (RT_LIKELY(!pWait->fFreeMe))
+ { /* likely */ }
+ else
+ {
+ pWait->fFreeMe = false;
+ vgdrvWaitFreeLocked(pDevExt, pWait);
+ }
+ }
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ }
+}
+#endif /* VBOXGUEST_USE_DEFERRED_WAKE_UP */
+
+
+/**
+ * Implements the fast (no input or output) type of IOCtls.
+ *
+ * This is currently just a placeholder stub inherited from the support driver code.
+ *
+ * @returns VBox status code.
+ * @param iFunction The IOCtl function number.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ */
+int VGDrvCommonIoCtlFast(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession)
+{
+ LogFlow(("VGDrvCommonIoCtlFast: iFunction=%#x pDevExt=%p pSession=%p\n", iFunction, pDevExt, pSession));
+
+ NOREF(iFunction);
+ NOREF(pDevExt);
+ NOREF(pSession);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * Gets the driver I/O control interface version, maybe adjusting it for
+ * backwards compatibility.
+ *
+ * The adjusting is currently not implemented as we only have one major I/O
+ * control interface version out there to support. This is something we will
+ * implement as needed.
+ *
+ * returns IPRT status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pReq The request info.
+ */
+static int vgdrvIoCtl_DriverVersionInfo(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCDRIVERVERSIONINFO pReq)
+{
+ int rc;
+ LogFlow(("VBGL_IOCTL_DRIVER_VERSION_INFO: uReqVersion=%#x uMinVersion=%#x uReserved1=%#x uReserved2=%#x\n",
+ pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, pReq->u.In.uReserved1, pReq->u.In.uReserved2));
+ RT_NOREF2(pDevExt, pSession);
+
+ /*
+ * Input validation.
+ */
+ if ( pReq->u.In.uMinVersion <= pReq->u.In.uReqVersion
+ && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(pReq->u.In.uReqVersion))
+ {
+ /*
+ * Match the version.
+ * The current logic is very simple, match the major interface version.
+ */
+ if ( pReq->u.In.uMinVersion <= VBGL_IOC_VERSION
+ && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(VBGL_IOC_VERSION))
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("VBGL_IOCTL_DRIVER_VERSION_INFO: Version mismatch. Requested: %#x Min: %#x Current: %#x\n",
+ pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, VBGL_IOC_VERSION));
+ rc = VERR_VERSION_MISMATCH;
+ }
+ }
+ else
+ {
+ LogRel(("VBGL_IOCTL_DRIVER_VERSION_INFO: uMinVersion=%#x uMaxVersion=%#x doesn't match!\n",
+ pReq->u.In.uMinVersion, pReq->u.In.uReqVersion));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ pReq->u.Out.uSessionVersion = RT_SUCCESS(rc) ? VBGL_IOC_VERSION : UINT32_MAX;
+ pReq->u.Out.uDriverVersion = VBGL_IOC_VERSION;
+ pReq->u.Out.uDriverRevision = VBOX_SVN_REV;
+ pReq->u.Out.uReserved1 = 0;
+ pReq->u.Out.uReserved2 = 0;
+ return rc;
+}
+
+
+/**
+ * Similar to vgdrvIoCtl_DriverVersionInfo, except its for IDC.
+ *
+ * returns IPRT status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pReq The request info.
+ */
+static int vgdrvIoCtl_IdcConnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCIDCCONNECT pReq)
+{
+ int rc;
+ LogFlow(("VBGL_IOCTL_IDC_CONNECT: u32MagicCookie=%#x uReqVersion=%#x uMinVersion=%#x uReserved=%#x\n",
+ pReq->u.In.u32MagicCookie, pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, pReq->u.In.uReserved));
+ Assert(pSession != NULL);
+ RT_NOREF(pDevExt);
+
+ /*
+ * Input validation.
+ */
+ if (pReq->u.In.u32MagicCookie == VBGL_IOCTL_IDC_CONNECT_MAGIC_COOKIE)
+ {
+ if ( pReq->u.In.uMinVersion <= pReq->u.In.uReqVersion
+ && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(pReq->u.In.uReqVersion))
+ {
+ /*
+ * Match the version.
+ * The current logic is very simple, match the major interface version.
+ */
+ if ( pReq->u.In.uMinVersion <= VBGL_IOC_VERSION
+ && RT_HI_U16(pReq->u.In.uMinVersion) == RT_HI_U16(VBGL_IOC_VERSION))
+ {
+ pReq->u.Out.pvSession = pSession;
+ pReq->u.Out.uSessionVersion = VBGL_IOC_VERSION;
+ pReq->u.Out.uDriverVersion = VBGL_IOC_VERSION;
+ pReq->u.Out.uDriverRevision = VBOX_SVN_REV;
+ pReq->u.Out.uReserved1 = 0;
+ pReq->u.Out.pvReserved2 = NULL;
+ return VINF_SUCCESS;
+
+ }
+ LogRel(("VBGL_IOCTL_IDC_CONNECT: Version mismatch. Requested: %#x Min: %#x Current: %#x\n",
+ pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, VBGL_IOC_VERSION));
+ rc = VERR_VERSION_MISMATCH;
+ }
+ else
+ {
+ LogRel(("VBGL_IOCTL_IDC_CONNECT: uMinVersion=%#x uMaxVersion=%#x doesn't match!\n",
+ pReq->u.In.uMinVersion, pReq->u.In.uReqVersion));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ pReq->u.Out.pvSession = NULL;
+ pReq->u.Out.uSessionVersion = UINT32_MAX;
+ pReq->u.Out.uDriverVersion = VBGL_IOC_VERSION;
+ pReq->u.Out.uDriverRevision = VBOX_SVN_REV;
+ pReq->u.Out.uReserved1 = 0;
+ pReq->u.Out.pvReserved2 = NULL;
+ }
+ else
+ {
+ LogRel(("VBGL_IOCTL_IDC_CONNECT: u32MagicCookie=%#x expected %#x!\n",
+ pReq->u.In.u32MagicCookie, VBGL_IOCTL_IDC_CONNECT_MAGIC_COOKIE));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ return rc;
+}
+
+
+/**
+ * Counterpart to vgdrvIoCtl_IdcConnect, destroys the session.
+ *
+ * returns IPRT status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pReq The request info.
+ */
+static int vgdrvIoCtl_IdcDisconnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCIDCDISCONNECT pReq)
+{
+ LogFlow(("VBGL_IOCTL_IDC_DISCONNECT: pvSession=%p vs pSession=%p\n", pReq->u.In.pvSession, pSession));
+ RT_NOREF(pDevExt);
+ Assert(pSession != NULL);
+
+ if (pReq->u.In.pvSession == pSession)
+ {
+ VGDrvCommonCloseSession(pDevExt, pSession);
+ return VINF_SUCCESS;
+ }
+ LogRel(("VBGL_IOCTL_IDC_DISCONNECT: In.pvSession=%p is not equal to pSession=%p!\n", pReq->u.In.pvSession, pSession));
+ return VERR_INVALID_PARAMETER;
+}
+
+
+/**
+ * Return the VMM device I/O info.
+ *
+ * returns IPRT status code.
+ * @param pDevExt The device extension.
+ * @param pInfo The request info.
+ * @note Ring-0 only, caller checked.
+ */
+static int vgdrvIoCtl_GetVMMDevIoInfo(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCGETVMMDEVIOINFO pInfo)
+{
+ LogFlow(("VBGL_IOCTL_GET_VMMDEV_IO_INFO\n"));
+
+ pInfo->u.Out.IoPort = pDevExt->IOPortBase;
+ pInfo->u.Out.pvVmmDevMapping = pDevExt->pVMMDevMemory;
+ pInfo->u.Out.auPadding[0] = 0;
+#if HC_ARCH_BITS != 32
+ pInfo->u.Out.auPadding[1] = 0;
+ pInfo->u.Out.auPadding[2] = 0;
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Set the callback for the kernel mouse handler.
+ *
+ * returns IPRT status code.
+ * @param pDevExt The device extension.
+ * @param pNotify The new callback information.
+ */
+static int vgdrvIoCtl_SetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify)
+{
+ LogFlow(("VBOXGUEST_IOCTL_SET_MOUSE_NOTIFY_CALLBACK: pfnNotify=%p pvUser=%p\n", pNotify->u.In.pfnNotify, pNotify->u.In.pvUser));
+
+#ifdef VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT
+ VGDrvNativeSetMouseNotifyCallback(pDevExt, pNotify);
+#else
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ pDevExt->pfnMouseNotifyCallback = pNotify->u.In.pfnNotify;
+ pDevExt->pvMouseNotifyCallbackArg = pNotify->u.In.pvUser;
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker vgdrvIoCtl_WaitEvent.
+ *
+ * The caller enters the spinlock, we leave it.
+ *
+ * @returns VINF_SUCCESS if we've left the spinlock and can return immediately.
+ */
+DECLINLINE(int) vbdgCheckWaitEventCondition(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ PVBGLIOCWAITFOREVENTS pInfo, int iEvent, const uint32_t fReqEvents)
+{
+ uint32_t fMatches = pDevExt->f32PendingEvents & fReqEvents;
+ if (fMatches & VBOXGUEST_ACQUIRE_STYLE_EVENTS)
+ fMatches &= vgdrvGetAllowedEventMaskForSession(pDevExt, pSession);
+ if (fMatches || pSession->fPendingCancelWaitEvents)
+ {
+ ASMAtomicAndU32(&pDevExt->f32PendingEvents, ~fMatches);
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ pInfo->u.Out.fEvents = fMatches;
+ if (fReqEvents & ~((uint32_t)1 << iEvent))
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x\n", pInfo->u.Out.fEvents));
+ else
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x/%d\n", pInfo->u.Out.fEvents, iEvent));
+ pSession->fPendingCancelWaitEvents = false;
+ return VINF_SUCCESS;
+ }
+
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ return VERR_TIMEOUT;
+}
+
+
+static int vgdrvIoCtl_WaitForEvents(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ PVBGLIOCWAITFOREVENTS pInfo, bool fInterruptible)
+{
+ uint32_t const cMsTimeout = pInfo->u.In.cMsTimeOut;
+ const uint32_t fReqEvents = pInfo->u.In.fEvents;
+ uint32_t fResEvents;
+ int iEvent;
+ PVBOXGUESTWAIT pWait;
+ int rc;
+
+ pInfo->u.Out.fEvents = 0; /* Note! This overwrites pInfo->u.In.* fields! */
+
+ /*
+ * Copy and verify the input mask.
+ */
+ iEvent = ASMBitFirstSetU32(fReqEvents) - 1;
+ if (RT_UNLIKELY(iEvent < 0))
+ {
+ LogRel(("VBOXGUEST_IOCTL_WAITEVENT: Invalid input mask %#x!!\n", fReqEvents));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Check the condition up front, before doing the wait-for-event allocations.
+ */
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ rc = vbdgCheckWaitEventCondition(pDevExt, pSession, pInfo, iEvent, fReqEvents);
+ if (rc == VINF_SUCCESS)
+ return rc;
+
+ if (!cMsTimeout)
+ {
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns VERR_TIMEOUT\n"));
+ return VERR_TIMEOUT;
+ }
+
+ pWait = vgdrvWaitAlloc(pDevExt, pSession);
+ if (!pWait)
+ return VERR_NO_MEMORY;
+ pWait->fReqEvents = fReqEvents;
+
+ /*
+ * We've got the wait entry now, re-enter the spinlock and check for the condition.
+ * If the wait condition is met, return.
+ * Otherwise enter into the list and go to sleep waiting for the ISR to signal us.
+ */
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ RTListAppend(&pDevExt->WaitList, &pWait->ListNode);
+ rc = vbdgCheckWaitEventCondition(pDevExt, pSession, pInfo, iEvent, fReqEvents);
+ if (rc == VINF_SUCCESS)
+ {
+ vgdrvWaitFreeUnlocked(pDevExt, pWait);
+ return rc;
+ }
+
+ if (fInterruptible)
+ rc = RTSemEventMultiWaitNoResume(pWait->Event, cMsTimeout == UINT32_MAX ? RT_INDEFINITE_WAIT : cMsTimeout);
+ else
+ rc = RTSemEventMultiWait(pWait->Event, cMsTimeout == UINT32_MAX ? RT_INDEFINITE_WAIT : cMsTimeout);
+
+ /*
+ * There is one special case here and that's when the semaphore is
+ * destroyed upon device driver unload. This shouldn't happen of course,
+ * but in case it does, just get out of here ASAP.
+ */
+ if (rc == VERR_SEM_DESTROYED)
+ return rc;
+
+ /*
+ * Unlink the wait item and dispose of it.
+ */
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ fResEvents = pWait->fResEvents;
+ vgdrvWaitFreeLocked(pDevExt, pWait);
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ /*
+ * Now deal with the return code.
+ */
+ if ( fResEvents
+ && fResEvents != UINT32_MAX)
+ {
+ pInfo->u.Out.fEvents = fResEvents;
+ if (fReqEvents & ~((uint32_t)1 << iEvent))
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x\n", pInfo->u.Out.fEvents));
+ else
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %#x/%d\n", pInfo->u.Out.fEvents, iEvent));
+ rc = VINF_SUCCESS;
+ }
+ else if ( fResEvents == UINT32_MAX
+ || rc == VERR_INTERRUPTED)
+ {
+ rc = VERR_INTERRUPTED;
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns VERR_INTERRUPTED\n"));
+ }
+ else if (rc == VERR_TIMEOUT)
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns VERR_TIMEOUT (2)\n"));
+ else
+ {
+ if (RT_SUCCESS(rc))
+ {
+ LogRelMax(32, ("VBOXGUEST_IOCTL_WAITEVENT: returns %Rrc but no events!\n", rc));
+ rc = VERR_INTERNAL_ERROR;
+ }
+ LogFlow(("VBOXGUEST_IOCTL_WAITEVENT: returns %Rrc\n", rc));
+ }
+
+ return rc;
+}
+
+
+/** @todo the semantics of this IoCtl have been tightened, so that no calls to
+ * VBOXGUEST_IOCTL_WAITEVENT are allowed in a session after it has been
+ * called. Change the code to make calls to VBOXGUEST_IOCTL_WAITEVENT made
+ * after that to return VERR_INTERRUPTED or something appropriate. */
+static int vgdrvIoCtl_CancelAllWaitEvents(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession)
+{
+ PVBOXGUESTWAIT pWait;
+ PVBOXGUESTWAIT pSafe;
+ int rc = 0;
+ /* Was as least one WAITEVENT in process for this session? If not we
+ * set a flag that the next call should be interrupted immediately. This
+ * is needed so that a user thread can reliably interrupt another one in a
+ * WAITEVENT loop. */
+ bool fCancelledOne = false;
+
+ LogFlow(("VBOXGUEST_IOCTL_CANCEL_ALL_WAITEVENTS\n"));
+
+ /*
+ * Walk the event list and wake up anyone with a matching session.
+ */
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ RTListForEachSafe(&pDevExt->WaitList, pWait, pSafe, VBOXGUESTWAIT, ListNode)
+ {
+ if (pWait->pSession == pSession)
+ {
+ fCancelledOne = true;
+ pWait->fResEvents = UINT32_MAX;
+ RTListNodeRemove(&pWait->ListNode);
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ RTListAppend(&pDevExt->WakeUpList, &pWait->ListNode);
+#else
+ rc |= RTSemEventMultiSignal(pWait->Event);
+ RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode);
+#endif
+ }
+ }
+ if (!fCancelledOne)
+ pSession->fPendingCancelWaitEvents = true;
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ Assert(rc == 0);
+ NOREF(rc);
+
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ VGDrvCommonWaitDoWakeUps(pDevExt);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if the VMM request is allowed in the context of the given session.
+ *
+ * @returns VINF_SUCCESS or VERR_PERMISSION_DENIED.
+ * @param pDevExt The device extension.
+ * @param pSession The calling session.
+ * @param enmType The request type.
+ * @param pReqHdr The request.
+ */
+static int vgdrvCheckIfVmmReqIsAllowed(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, VMMDevRequestType enmType,
+ VMMDevRequestHeader const *pReqHdr)
+{
+ /*
+ * Categorize the request being made.
+ */
+ /** @todo This need quite some more work! */
+ enum
+ {
+ kLevel_Invalid, kLevel_NoOne, kLevel_OnlyVBoxGuest, kLevel_OnlyKernel, kLevel_TrustedUsers, kLevel_AllUsers
+ } enmRequired;
+ RT_NOREF1(pDevExt);
+
+ switch (enmType)
+ {
+ /*
+ * Deny access to anything we don't know or provide specialized I/O controls for.
+ */
+#ifdef VBOX_WITH_HGCM
+ case VMMDevReq_HGCMConnect:
+ case VMMDevReq_HGCMDisconnect:
+# ifdef VBOX_WITH_64_BITS_GUESTS
+ case VMMDevReq_HGCMCall64:
+# endif
+ case VMMDevReq_HGCMCall32:
+ case VMMDevReq_HGCMCancel:
+ case VMMDevReq_HGCMCancel2:
+#endif /* VBOX_WITH_HGCM */
+ case VMMDevReq_SetGuestCapabilities:
+ default:
+ enmRequired = kLevel_NoOne;
+ break;
+
+ /*
+ * There are a few things only this driver can do (and it doesn't use
+ * the VMMRequst I/O control route anyway, but whatever).
+ */
+ case VMMDevReq_ReportGuestInfo:
+ case VMMDevReq_ReportGuestInfo2:
+ case VMMDevReq_GetHypervisorInfo:
+ case VMMDevReq_SetHypervisorInfo:
+ case VMMDevReq_RegisterPatchMemory:
+ case VMMDevReq_DeregisterPatchMemory:
+ case VMMDevReq_GetMemBalloonChangeRequest:
+ case VMMDevReq_ChangeMemBalloon:
+ enmRequired = kLevel_OnlyVBoxGuest;
+ break;
+
+ /*
+ * Trusted users apps only.
+ */
+ case VMMDevReq_QueryCredentials:
+ case VMMDevReq_ReportCredentialsJudgement:
+ case VMMDevReq_RegisterSharedModule:
+ case VMMDevReq_UnregisterSharedModule:
+ case VMMDevReq_WriteCoreDump:
+ case VMMDevReq_GetCpuHotPlugRequest:
+ case VMMDevReq_SetCpuHotPlugStatus:
+ case VMMDevReq_CheckSharedModules:
+ case VMMDevReq_GetPageSharingStatus:
+ case VMMDevReq_DebugIsPageShared:
+ case VMMDevReq_ReportGuestStats:
+ case VMMDevReq_ReportGuestUserState:
+ case VMMDevReq_GetStatisticsChangeRequest:
+ enmRequired = kLevel_TrustedUsers;
+ break;
+
+ /*
+ * Anyone.
+ */
+ case VMMDevReq_GetMouseStatus:
+ case VMMDevReq_SetMouseStatus:
+ case VMMDevReq_SetPointerShape:
+ case VMMDevReq_GetHostVersion:
+ case VMMDevReq_Idle:
+ case VMMDevReq_GetHostTime:
+ case VMMDevReq_SetPowerStatus:
+ case VMMDevReq_AcknowledgeEvents:
+ case VMMDevReq_CtlGuestFilterMask:
+ case VMMDevReq_ReportGuestStatus:
+ case VMMDevReq_GetDisplayChangeRequest:
+ case VMMDevReq_VideoModeSupported:
+ case VMMDevReq_GetHeightReduction:
+ case VMMDevReq_GetDisplayChangeRequest2:
+ case VMMDevReq_VideoModeSupported2:
+ case VMMDevReq_VideoAccelEnable:
+ case VMMDevReq_VideoAccelFlush:
+ case VMMDevReq_VideoSetVisibleRegion:
+ case VMMDevReq_VideoUpdateMonitorPositions:
+ case VMMDevReq_GetDisplayChangeRequestEx:
+ case VMMDevReq_GetDisplayChangeRequestMulti:
+ case VMMDevReq_GetSeamlessChangeRequest:
+ case VMMDevReq_GetVRDPChangeRequest:
+ case VMMDevReq_LogString:
+ case VMMDevReq_GetSessionId:
+ enmRequired = kLevel_AllUsers;
+ break;
+
+ /*
+ * Depends on the request parameters...
+ */
+ /** @todo this have to be changed into an I/O control and the facilities
+ * tracked in the session so they can automatically be failed when the
+ * session terminates without reporting the new status.
+ *
+ * The information presented by IGuest is not reliable without this! */
+ case VMMDevReq_ReportGuestCapabilities:
+ switch (((VMMDevReportGuestStatus const *)pReqHdr)->guestStatus.facility)
+ {
+ case VBoxGuestFacilityType_All:
+ case VBoxGuestFacilityType_VBoxGuestDriver:
+ enmRequired = kLevel_OnlyVBoxGuest;
+ break;
+ case VBoxGuestFacilityType_VBoxService:
+ enmRequired = kLevel_TrustedUsers;
+ break;
+ case VBoxGuestFacilityType_VBoxTrayClient:
+ case VBoxGuestFacilityType_Seamless:
+ case VBoxGuestFacilityType_Graphics:
+ default:
+ enmRequired = kLevel_AllUsers;
+ break;
+ }
+ break;
+ }
+
+ /*
+ * Check against the session.
+ */
+ switch (enmRequired)
+ {
+ default:
+ case kLevel_NoOne:
+ break;
+ case kLevel_OnlyVBoxGuest:
+ case kLevel_OnlyKernel:
+ if (pSession->R0Process == NIL_RTR0PROCESS)
+ return VINF_SUCCESS;
+ break;
+ case kLevel_TrustedUsers:
+ if (pSession->fUserSession)
+ break;
+ RT_FALL_THRU();
+ case kLevel_AllUsers:
+ return VINF_SUCCESS;
+ }
+
+ return VERR_PERMISSION_DENIED;
+}
+
+static int vgdrvIoCtl_VMMDevRequest(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ VMMDevRequestHeader *pReqHdr, size_t cbData)
+{
+ int rc;
+ VMMDevRequestHeader *pReqCopy;
+
+ /*
+ * Validate the header and request size.
+ */
+ const VMMDevRequestType enmType = pReqHdr->requestType;
+ const uint32_t cbReq = pReqHdr->size;
+ const uint32_t cbMinSize = (uint32_t)vmmdevGetRequestSize(enmType);
+
+ LogFlow(("VBOXGUEST_IOCTL_VMMREQUEST: type %d\n", pReqHdr->requestType));
+
+ if (cbReq < cbMinSize)
+ {
+ LogRel(("VBOXGUEST_IOCTL_VMMREQUEST: invalid hdr size %#x, expected >= %#x; type=%#x!!\n",
+ cbReq, cbMinSize, enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+ if (cbReq > cbData)
+ {
+ LogRel(("VBOXGUEST_IOCTL_VMMREQUEST: invalid size %#x, expected >= %#x (hdr); type=%#x!!\n",
+ cbData, cbReq, enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+ rc = VbglGR0Verify(pReqHdr, cbData);
+ if (RT_FAILURE(rc))
+ {
+ Log(("VBOXGUEST_IOCTL_VMMREQUEST: invalid header: size %#x, expected >= %#x (hdr); type=%#x; rc=%Rrc!!\n",
+ cbData, cbReq, enmType, rc));
+ return rc;
+ }
+
+ rc = vgdrvCheckIfVmmReqIsAllowed(pDevExt, pSession, enmType, pReqHdr);
+ if (RT_FAILURE(rc))
+ {
+ Log(("VBOXGUEST_IOCTL_VMMREQUEST: Operation not allowed! type=%#x rc=%Rrc\n", enmType, rc));
+ return rc;
+ }
+
+ /*
+ * Make a copy of the request in the physical memory heap so
+ * the VBoxGuestLibrary can more easily deal with the request.
+ * (This is really a waste of time since the OS or the OS specific
+ * code has already buffered or locked the input/output buffer, but
+ * it does makes things a bit simpler wrt to phys address.)
+ */
+ rc = VbglR0GRAlloc(&pReqCopy, cbReq, enmType);
+ if (RT_FAILURE(rc))
+ {
+ Log(("VBOXGUEST_IOCTL_VMMREQUEST: failed to allocate %u (%#x) bytes to cache the request. rc=%Rrc!!\n",
+ cbReq, cbReq, rc));
+ return rc;
+ }
+ memcpy(pReqCopy, pReqHdr, cbReq);
+ Assert(pReqCopy->reserved1 == cbReq);
+ pReqCopy->reserved1 = 0; /* VGDrvCommonIoCtl or caller sets cbOut, so clear it. */
+ pReqCopy->fRequestor = pSession->fRequestor;
+
+ if (enmType == VMMDevReq_GetMouseStatus) /* clear poll condition. */
+ pSession->u32MousePosChangedSeq = ASMAtomicUoReadU32(&pDevExt->u32MousePosChangedSeq);
+
+ rc = VbglR0GRPerform(pReqCopy);
+ if ( RT_SUCCESS(rc)
+ && RT_SUCCESS(pReqCopy->rc))
+ {
+ Assert(rc != VINF_HGCM_ASYNC_EXECUTE);
+ Assert(pReqCopy->rc != VINF_HGCM_ASYNC_EXECUTE);
+
+ memcpy(pReqHdr, pReqCopy, cbReq);
+ pReqHdr->reserved1 = cbReq; /* preserve cbOut */
+ }
+ else if (RT_FAILURE(rc))
+ Log(("VBOXGUEST_IOCTL_VMMREQUEST: VbglR0GRPerform - rc=%Rrc!\n", rc));
+ else
+ {
+ Log(("VBOXGUEST_IOCTL_VMMREQUEST: request execution failed; VMMDev rc=%Rrc!\n", pReqCopy->rc));
+ rc = pReqCopy->rc;
+ }
+
+ VbglR0GRFree(pReqCopy);
+ return rc;
+}
+
+
+#ifdef VBOX_WITH_HGCM
+
+AssertCompile(RT_INDEFINITE_WAIT == (uint32_t)RT_INDEFINITE_WAIT); /* assumed by code below */
+
+/** Worker for vgdrvHgcmAsyncWaitCallback*. */
+static int vgdrvHgcmAsyncWaitCallbackWorker(VMMDevHGCMRequestHeader volatile *pHdr, PVBOXGUESTDEVEXT pDevExt,
+ bool fInterruptible, uint32_t cMillies)
+{
+ int rc;
+
+ /*
+ * Check to see if the condition was met by the time we got here.
+ *
+ * We create a simple poll loop here for dealing with out-of-memory
+ * conditions since the caller isn't necessarily able to deal with
+ * us returning too early.
+ */
+ PVBOXGUESTWAIT pWait;
+ for (;;)
+ {
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ if ((pHdr->fu32Flags & VBOX_HGCM_REQ_DONE) != 0)
+ {
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ return VINF_SUCCESS;
+ }
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ pWait = vgdrvWaitAlloc(pDevExt, NULL);
+ if (pWait)
+ break;
+ if (fInterruptible)
+ return VERR_INTERRUPTED;
+ RTThreadSleep(1);
+ }
+ pWait->fReqEvents = VMMDEV_EVENT_HGCM;
+ pWait->pHGCMReq = pHdr;
+
+ /*
+ * Re-enter the spinlock and re-check for the condition.
+ * If the condition is met, return.
+ * Otherwise link us into the HGCM wait list and go to sleep.
+ */
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ RTListAppend(&pDevExt->HGCMWaitList, &pWait->ListNode);
+ if ((pHdr->fu32Flags & VBOX_HGCM_REQ_DONE) != 0)
+ {
+ vgdrvWaitFreeLocked(pDevExt, pWait);
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ return VINF_SUCCESS;
+ }
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ if (fInterruptible)
+ rc = RTSemEventMultiWaitNoResume(pWait->Event, cMillies);
+ else
+ rc = RTSemEventMultiWait(pWait->Event, cMillies);
+ if (rc == VERR_SEM_DESTROYED)
+ return rc;
+
+ /*
+ * Unlink, free and return.
+ */
+ if ( RT_FAILURE(rc)
+ && rc != VERR_TIMEOUT
+ && ( !fInterruptible
+ || rc != VERR_INTERRUPTED))
+ LogRel(("vgdrvHgcmAsyncWaitCallback: wait failed! %Rrc\n", rc));
+
+ vgdrvWaitFreeUnlocked(pDevExt, pWait);
+ return rc;
+}
+
+
+/**
+ * This is a callback for dealing with async waits.
+ *
+ * It operates in a manner similar to vgdrvIoCtl_WaitEvent.
+ */
+static DECLCALLBACK(int) vgdrvHgcmAsyncWaitCallback(VMMDevHGCMRequestHeader *pHdr, void *pvUser, uint32_t u32User)
+{
+ PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser;
+ LogFlow(("vgdrvHgcmAsyncWaitCallback: requestType=%d\n", pHdr->header.requestType));
+ return vgdrvHgcmAsyncWaitCallbackWorker((VMMDevHGCMRequestHeader volatile *)pHdr, pDevExt,
+ false /* fInterruptible */, u32User /* cMillies */);
+}
+
+
+/**
+ * This is a callback for dealing with async waits with a timeout.
+ *
+ * It operates in a manner similar to vgdrvIoCtl_WaitEvent.
+ */
+static DECLCALLBACK(int) vgdrvHgcmAsyncWaitCallbackInterruptible(VMMDevHGCMRequestHeader *pHdr, void *pvUser, uint32_t u32User)
+{
+ PVBOXGUESTDEVEXT pDevExt = (PVBOXGUESTDEVEXT)pvUser;
+ LogFlow(("vgdrvHgcmAsyncWaitCallbackInterruptible: requestType=%d\n", pHdr->header.requestType));
+ return vgdrvHgcmAsyncWaitCallbackWorker((VMMDevHGCMRequestHeader volatile *)pHdr, pDevExt,
+ true /* fInterruptible */, u32User /* cMillies */);
+}
+
+
+static int vgdrvIoCtl_HGCMConnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCONNECT pInfo)
+{
+ int rc;
+ HGCMCLIENTID idClient = 0;
+
+ /*
+ * The VbglHGCMConnect call will invoke the callback if the HGCM
+ * call is performed in an ASYNC fashion. The function is not able
+ * to deal with cancelled requests.
+ */
+ Log(("VBOXGUEST_IOCTL_HGCM_CONNECT: %.128s\n",
+ pInfo->u.In.Loc.type == VMMDevHGCMLoc_LocalHost || pInfo->u.In.Loc.type == VMMDevHGCMLoc_LocalHost_Existing
+ ? pInfo->u.In.Loc.u.host.achName : "<not local host>"));
+
+ rc = VbglR0HGCMInternalConnect(&pInfo->u.In.Loc, pSession->fRequestor, &idClient,
+ vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT);
+ Log(("VBOXGUEST_IOCTL_HGCM_CONNECT: idClient=%RX32 (rc=%Rrc)\n", idClient, rc));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Append the client id to the client id table.
+ * If the table has somehow become filled up, we'll disconnect the session.
+ */
+ unsigned i;
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+ for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++)
+ if (!pSession->aHGCMClientIds[i])
+ {
+ pSession->aHGCMClientIds[i] = idClient;
+ break;
+ }
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+ if (i >= RT_ELEMENTS(pSession->aHGCMClientIds))
+ {
+ LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_CONNECT: too many HGCMConnect calls for one session!\n"));
+ VbglR0HGCMInternalDisconnect(idClient, pSession->fRequestor, vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT);
+
+ pInfo->u.Out.idClient = 0;
+ return VERR_TOO_MANY_OPEN_FILES;
+ }
+ }
+ pInfo->u.Out.idClient = idClient;
+ return rc;
+}
+
+
+static int vgdrvIoCtl_HGCMDisconnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMDISCONNECT pInfo)
+{
+ /*
+ * Validate the client id and invalidate its entry while we're in the call.
+ */
+ int rc;
+ const uint32_t idClient = pInfo->u.In.idClient;
+ unsigned i;
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+ for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++)
+ if (pSession->aHGCMClientIds[i] == idClient)
+ {
+ pSession->aHGCMClientIds[i] = UINT32_MAX;
+ break;
+ }
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+ if (i >= RT_ELEMENTS(pSession->aHGCMClientIds))
+ {
+ LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_DISCONNECT: idClient=%RX32\n", idClient));
+ return VERR_INVALID_HANDLE;
+ }
+
+ /*
+ * The VbglHGCMConnect call will invoke the callback if the HGCM
+ * call is performed in an ASYNC fashion. The function is not able
+ * to deal with cancelled requests.
+ */
+ Log(("VBOXGUEST_IOCTL_HGCM_DISCONNECT: idClient=%RX32\n", idClient));
+ rc = VbglR0HGCMInternalDisconnect(idClient, pSession->fRequestor, vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT);
+ LogFlow(("VBOXGUEST_IOCTL_HGCM_DISCONNECT: rc=%Rrc\n", rc));
+
+ /* Update the client id array according to the result. */
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+ if (pSession->aHGCMClientIds[i] == UINT32_MAX)
+ pSession->aHGCMClientIds[i] = RT_SUCCESS(rc) ? 0 : idClient;
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+
+ return rc;
+}
+
+
+static int vgdrvIoCtl_HGCMCallInner(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCALL pInfo,
+ uint32_t cMillies, bool fInterruptible, bool f32bit, bool fUserData,
+ size_t cbExtra, size_t cbData)
+{
+ const uint32_t u32ClientId = pInfo->u32ClientID;
+ uint32_t fFlags;
+ size_t cbActual;
+ unsigned i;
+ int rc;
+
+ /*
+ * Some more validations.
+ */
+ if (RT_LIKELY(pInfo->cParms <= VMMDEV_MAX_HGCM_PARMS)) /* (Just make sure it doesn't overflow the next check.) */
+ { /* likely */}
+ else
+ {
+ LogRel(("VBOXGUEST_IOCTL_HGCM_CALL: cParm=%RX32 is not sane\n", pInfo->cParms));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ cbActual = cbExtra + sizeof(*pInfo);
+#ifdef RT_ARCH_AMD64
+ if (f32bit)
+ cbActual += pInfo->cParms * sizeof(HGCMFunctionParameter32);
+ else
+#endif
+ cbActual += pInfo->cParms * sizeof(HGCMFunctionParameter);
+ if (RT_LIKELY(cbData >= cbActual))
+ { /* likely */}
+ else
+ {
+ LogRel(("VBOXGUEST_IOCTL_HGCM_CALL: cbData=%#zx (%zu) required size is %#zx (%zu)\n",
+ cbData, cbData, cbActual, cbActual));
+ return VERR_INVALID_PARAMETER;
+ }
+ pInfo->Hdr.cbOut = (uint32_t)cbActual;
+
+ /*
+ * Validate the client id.
+ */
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+ for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++)
+ if (pSession->aHGCMClientIds[i] == u32ClientId)
+ break;
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+ if (RT_LIKELY(i < RT_ELEMENTS(pSession->aHGCMClientIds)))
+ { /* likely */}
+ else
+ {
+ LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_CALL: Invalid handle. u32Client=%RX32\n", u32ClientId));
+ return VERR_INVALID_HANDLE;
+ }
+
+ /*
+ * The VbglHGCMCall call will invoke the callback if the HGCM
+ * call is performed in an ASYNC fashion. This function can
+ * deal with cancelled requests, so we let user more requests
+ * be interruptible (should add a flag for this later I guess).
+ */
+ LogFlow(("VBOXGUEST_IOCTL_HGCM_CALL: u32Client=%RX32\n", pInfo->u32ClientID));
+ fFlags = !fUserData && pSession->R0Process == NIL_RTR0PROCESS ? VBGLR0_HGCMCALL_F_KERNEL : VBGLR0_HGCMCALL_F_USER;
+ uint32_t cbInfo = (uint32_t)(cbData - cbExtra);
+#ifdef RT_ARCH_AMD64
+ if (f32bit)
+ {
+ if (fInterruptible)
+ rc = VbglR0HGCMInternalCall32(pInfo, cbInfo, fFlags, pSession->fRequestor,
+ vgdrvHgcmAsyncWaitCallbackInterruptible, pDevExt, cMillies);
+ else
+ rc = VbglR0HGCMInternalCall32(pInfo, cbInfo, fFlags, pSession->fRequestor,
+ vgdrvHgcmAsyncWaitCallback, pDevExt, cMillies);
+ }
+ else
+#endif
+ {
+ if (fInterruptible)
+ rc = VbglR0HGCMInternalCall(pInfo, cbInfo, fFlags, pSession->fRequestor,
+ vgdrvHgcmAsyncWaitCallbackInterruptible, pDevExt, cMillies);
+ else
+ rc = VbglR0HGCMInternalCall(pInfo, cbInfo, fFlags, pSession->fRequestor,
+ vgdrvHgcmAsyncWaitCallback, pDevExt, cMillies);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = pInfo->Hdr.rc;
+ LogFlow(("VBOXGUEST_IOCTL_HGCM_CALL: result=%Rrc\n", rc));
+ }
+ else
+ {
+ if ( rc != VERR_INTERRUPTED
+ && rc != VERR_TIMEOUT)
+ LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_CALL: %s Failed. rc=%Rrc (Hdr.rc=%Rrc).\n", f32bit ? "32" : "64", rc, pInfo->Hdr.rc));
+ else
+ Log(("VBOXGUEST_IOCTL_HGCM_CALL: %s Failed. rc=%Rrc (Hdr.rc=%Rrc).\n", f32bit ? "32" : "64", rc, pInfo->Hdr.rc));
+ }
+ return rc;
+}
+
+
+static int vgdrvIoCtl_HGCMCallWrapper(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCALL pInfo,
+ bool f32bit, bool fUserData, size_t cbData)
+{
+ return vgdrvIoCtl_HGCMCallInner(pDevExt, pSession, pInfo, pInfo->cMsTimeout,
+ pInfo->fInterruptible || pSession->R0Process != NIL_RTR0PROCESS,
+ f32bit, fUserData, 0 /*cbExtra*/, cbData);
+}
+
+
+/**
+ * Handles a fast HGCM call from another driver.
+ *
+ * The driver has provided a fully assembled HGCM call request and all we need
+ * to do is send it to the host and do the wait processing.
+ *
+ * @returns VBox status code of the request submission part.
+ * @param pDevExt The device extension.
+ * @param pCallReq The call request.
+ */
+static int vgdrvIoCtl_HGCMFastCall(PVBOXGUESTDEVEXT pDevExt, VBGLIOCIDCHGCMFASTCALL volatile *pCallReq)
+{
+ VMMDevHGCMCall volatile *pHgcmCall = (VMMDevHGCMCall volatile *)(pCallReq + 1);
+ int rc;
+
+ /*
+ * Check out the physical address.
+ */
+ Assert((pCallReq->GCPhysReq & PAGE_OFFSET_MASK) == ((uintptr_t)pHgcmCall & PAGE_OFFSET_MASK));
+
+ AssertReturn(!pCallReq->fInterruptible, VERR_NOT_IMPLEMENTED);
+
+ /*
+ * Submit the request.
+ */
+ Log(("vgdrvIoCtl_HGCMFastCall -> host\n"));
+ ASMOutU32(pDevExt->IOPortBase + VMMDEV_PORT_OFF_REQUEST, (uint32_t)pCallReq->GCPhysReq);
+
+ /* Make the compiler aware that the host has changed memory. */
+ ASMCompilerBarrier();
+
+ rc = pHgcmCall->header.header.rc;
+ Log(("vgdrvIoCtl_HGCMFastCall -> %Rrc (header rc=%Rrc)\n", rc, pHgcmCall->header.result));
+
+ /*
+ * The host is likely to engage in asynchronous execution of HGCM, unless it fails.
+ */
+ if (rc == VINF_HGCM_ASYNC_EXECUTE)
+ {
+ rc = vgdrvHgcmAsyncWaitCallbackWorker(&pHgcmCall->header, pDevExt, false /* fInterruptible */, RT_INDEFINITE_WAIT);
+ if (pHgcmCall->header.fu32Flags & VBOX_HGCM_REQ_DONE)
+ {
+ Assert(!(pHgcmCall->header.fu32Flags & VBOX_HGCM_REQ_CANCELLED));
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /*
+ * Timeout and interrupt scenarios are messy and requires
+ * cancelation, so implement later.
+ */
+ AssertReleaseMsgFailed(("rc=%Rrc\n", rc));
+ }
+ }
+ else
+ Assert((pHgcmCall->header.fu32Flags & VBOX_HGCM_REQ_DONE) || RT_FAILURE_NP(rc));
+
+ Log(("vgdrvIoCtl_HGCMFastCall: rc=%Rrc result=%Rrc fu32Flags=%#x\n", rc, pHgcmCall->header.result, pHgcmCall->header.fu32Flags));
+ return rc;
+
+}
+
+#endif /* VBOX_WITH_HGCM */
+
+/**
+ * Handle VBGL_IOCTL_CHECK_BALLOON from R3.
+ *
+ * Ask the host for the size of the balloon and try to set it accordingly. If
+ * this approach fails because it's not supported, return with fHandleInR3 set
+ * and let the user land supply memory we can lock via the other ioctl.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pInfo The output buffer.
+ */
+static int vgdrvIoCtl_CheckMemoryBalloon(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCCHECKBALLOON pInfo)
+{
+ VMMDevGetMemBalloonChangeRequest *pReq;
+ int rc;
+
+ LogFlow(("VBGL_IOCTL_CHECK_BALLOON:\n"));
+ rc = RTSemFastMutexRequest(pDevExt->MemBalloon.hMtx);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * The first user trying to query/change the balloon becomes the
+ * owner and owns it until the session is closed (vgdrvCloseMemBalloon).
+ */
+ if ( pDevExt->MemBalloon.pOwner != pSession
+ && pDevExt->MemBalloon.pOwner == NULL)
+ pDevExt->MemBalloon.pOwner = pSession;
+
+ if (pDevExt->MemBalloon.pOwner == pSession)
+ {
+ /*
+ * This is a response to that event. Setting this bit means that
+ * we request the value from the host and change the guest memory
+ * balloon according to this value.
+ */
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(VMMDevGetMemBalloonChangeRequest), VMMDevReq_GetMemBalloonChangeRequest);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->header.fRequestor = pSession->fRequestor;
+ pReq->eventAck = VMMDEV_EVENT_BALLOON_CHANGE_REQUEST;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pDevExt->MemBalloon.cMaxChunks == pReq->cPhysMemChunks || pDevExt->MemBalloon.cMaxChunks == 0);
+ pDevExt->MemBalloon.cMaxChunks = pReq->cPhysMemChunks;
+
+ pInfo->u.Out.cBalloonChunks = pReq->cBalloonChunks;
+ pInfo->u.Out.fHandleInR3 = false;
+ pInfo->u.Out.afPadding[0] = false;
+ pInfo->u.Out.afPadding[1] = false;
+ pInfo->u.Out.afPadding[2] = false;
+
+ rc = vgdrvSetBalloonSizeKernel(pDevExt, pReq->cBalloonChunks, &pInfo->u.Out.fHandleInR3);
+ /* Ignore various out of memory failures. */
+ if ( rc == VERR_NO_MEMORY
+ || rc == VERR_NO_PHYS_MEMORY
+ || rc == VERR_NO_CONT_MEMORY)
+ rc = VINF_SUCCESS;
+ }
+ else
+ LogRel(("VBGL_IOCTL_CHECK_BALLOON: VbglR0GRPerform failed. rc=%Rrc\n", rc));
+ VbglR0GRFree(&pReq->header);
+ }
+ }
+ else
+ rc = VERR_PERMISSION_DENIED;
+
+ RTSemFastMutexRelease(pDevExt->MemBalloon.hMtx);
+ LogFlow(("VBGL_IOCTL_CHECK_BALLOON returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Handle a request for changing the memory balloon.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extention.
+ * @param pSession The session.
+ * @param pInfo The change request structure (input).
+ */
+static int vgdrvIoCtl_ChangeMemoryBalloon(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCCHANGEBALLOON pInfo)
+{
+ int rc;
+ LogFlow(("VBGL_IOCTL_CHANGE_BALLOON: fInflate=%RTbool u64ChunkAddr=%p\n", pInfo->u.In.fInflate, pInfo->u.In.pvChunk));
+ if ( pInfo->u.In.abPadding[0]
+ || pInfo->u.In.abPadding[1]
+ || pInfo->u.In.abPadding[2]
+ || pInfo->u.In.abPadding[3]
+ || pInfo->u.In.abPadding[4]
+ || pInfo->u.In.abPadding[5]
+ || pInfo->u.In.abPadding[6]
+#if ARCH_BITS == 32
+ || pInfo->u.In.abPadding[7]
+ || pInfo->u.In.abPadding[8]
+ || pInfo->u.In.abPadding[9]
+#endif
+ )
+ {
+ Log(("VBGL_IOCTL_CHANGE_BALLOON: Padding isn't all zero: %.*Rhxs\n", sizeof(pInfo->u.In.abPadding), pInfo->u.In.abPadding));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ rc = RTSemFastMutexRequest(pDevExt->MemBalloon.hMtx);
+ AssertRCReturn(rc, rc);
+
+ if (!pDevExt->MemBalloon.fUseKernelAPI)
+ {
+ /*
+ * The first user trying to query/change the balloon becomes the
+ * owner and owns it until the session is closed (vgdrvCloseMemBalloon).
+ */
+ if ( pDevExt->MemBalloon.pOwner != pSession
+ && pDevExt->MemBalloon.pOwner == NULL)
+ pDevExt->MemBalloon.pOwner = pSession;
+
+ if (pDevExt->MemBalloon.pOwner == pSession)
+ rc = vgdrvSetBalloonSizeFromUser(pDevExt, pSession, pInfo->u.In.pvChunk, pInfo->u.In.fInflate != false);
+ else
+ rc = VERR_PERMISSION_DENIED;
+ }
+ else
+ rc = VERR_PERMISSION_DENIED;
+
+ RTSemFastMutexRelease(pDevExt->MemBalloon.hMtx);
+ return rc;
+}
+
+
+/**
+ * Handle a request for writing a core dump of the guest on the host.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pInfo The output buffer.
+ */
+static int vgdrvIoCtl_WriteCoreDump(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCWRITECOREDUMP pInfo)
+{
+ VMMDevReqWriteCoreDump *pReq = NULL;
+ int rc;
+ LogFlow(("VBOXGUEST_IOCTL_WRITE_CORE_DUMP\n"));
+ RT_NOREF1(pDevExt);
+
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_WriteCoreDump);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->header.fRequestor = pSession->fRequestor;
+ pReq->fFlags = pInfo->u.In.fFlags;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ Log(("VBOXGUEST_IOCTL_WRITE_CORE_DUMP: VbglR0GRPerform failed, rc=%Rrc!\n", rc));
+
+ VbglR0GRFree(&pReq->header);
+ }
+ else
+ Log(("VBOXGUEST_IOCTL_WRITE_CORE_DUMP: failed to allocate %u (%#x) bytes to cache the request. rc=%Rrc!!\n",
+ sizeof(*pReq), sizeof(*pReq), rc));
+ return rc;
+}
+
+
+/**
+ * Guest backdoor logging.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension.
+ * @param pch The log message (need not be NULL terminated).
+ * @param cbData Size of the buffer.
+ * @param fUserSession Copy of VBOXGUESTSESSION::fUserSession for the
+ * call. True normal user, false root user.
+ */
+static int vgdrvIoCtl_Log(PVBOXGUESTDEVEXT pDevExt, const char *pch, size_t cbData, bool fUserSession)
+{
+ if (pDevExt->fLoggingEnabled)
+ RTLogBackdoorPrintf("%.*s", cbData, pch);
+ else if (!fUserSession)
+ LogRel(("%.*s", cbData, pch));
+ else
+ Log(("%.*s", cbData, pch));
+ return VINF_SUCCESS;
+}
+
+
+/** @name Guest Capabilities, Mouse Status and Event Filter
+ * @{
+ */
+
+/**
+ * Clears a bit usage tracker (init time).
+ *
+ * @param pTracker The tracker to clear.
+ */
+static void vgdrvBitUsageTrackerClear(PVBOXGUESTBITUSAGETRACER pTracker)
+{
+ uint32_t iBit;
+ AssertCompile(sizeof(pTracker->acPerBitUsage) == 32 * sizeof(uint32_t));
+
+ for (iBit = 0; iBit < 32; iBit++)
+ pTracker->acPerBitUsage[iBit] = 0;
+ pTracker->fMask = 0;
+}
+
+
+#ifdef VBOX_STRICT
+/**
+ * Checks that pTracker->fMask is correct and that the usage values are within
+ * the valid range.
+ *
+ * @param pTracker The tracker.
+ * @param cMax Max valid usage value.
+ * @param pszWhat Identifies the tracker in assertions.
+ */
+static void vgdrvBitUsageTrackerCheckMask(PCVBOXGUESTBITUSAGETRACER pTracker, uint32_t cMax, const char *pszWhat)
+{
+ uint32_t fMask = 0;
+ uint32_t iBit;
+ AssertCompile(sizeof(pTracker->acPerBitUsage) == 32 * sizeof(uint32_t));
+
+ for (iBit = 0; iBit < 32; iBit++)
+ if (pTracker->acPerBitUsage[iBit])
+ {
+ fMask |= RT_BIT_32(iBit);
+ AssertMsg(pTracker->acPerBitUsage[iBit] <= cMax,
+ ("%s: acPerBitUsage[%u]=%#x cMax=%#x\n", pszWhat, iBit, pTracker->acPerBitUsage[iBit], cMax));
+ }
+
+ AssertMsg(fMask == pTracker->fMask, ("%s: %#x vs %#x\n", pszWhat, fMask, pTracker->fMask));
+}
+#endif
+
+
+/**
+ * Applies a change to the bit usage tracker.
+ *
+ *
+ * @returns true if the mask changed, false if not.
+ * @param pTracker The bit usage tracker.
+ * @param fChanged The bits to change.
+ * @param fPrevious The previous value of the bits.
+ * @param cMax The max valid usage value for assertions.
+ * @param pszWhat Identifies the tracker in assertions.
+ */
+static bool vgdrvBitUsageTrackerChange(PVBOXGUESTBITUSAGETRACER pTracker, uint32_t fChanged, uint32_t fPrevious,
+ uint32_t cMax, const char *pszWhat)
+{
+ bool fGlobalChange = false;
+ AssertCompile(sizeof(pTracker->acPerBitUsage) == 32 * sizeof(uint32_t));
+
+ while (fChanged)
+ {
+ uint32_t const iBit = ASMBitFirstSetU32(fChanged) - 1;
+ uint32_t const fBitMask = RT_BIT_32(iBit);
+ Assert(iBit < 32); Assert(fBitMask & fChanged);
+
+ if (fBitMask & fPrevious)
+ {
+ pTracker->acPerBitUsage[iBit] -= 1;
+ AssertMsg(pTracker->acPerBitUsage[iBit] <= cMax,
+ ("%s: acPerBitUsage[%u]=%#x cMax=%#x\n", pszWhat, iBit, pTracker->acPerBitUsage[iBit], cMax));
+ if (pTracker->acPerBitUsage[iBit] == 0)
+ {
+ fGlobalChange = true;
+ pTracker->fMask &= ~fBitMask;
+ }
+ }
+ else
+ {
+ pTracker->acPerBitUsage[iBit] += 1;
+ AssertMsg(pTracker->acPerBitUsage[iBit] > 0 && pTracker->acPerBitUsage[iBit] <= cMax,
+ ("pTracker->acPerBitUsage[%u]=%#x cMax=%#x\n", pszWhat, iBit, pTracker->acPerBitUsage[iBit], cMax));
+ if (pTracker->acPerBitUsage[iBit] == 1)
+ {
+ fGlobalChange = true;
+ pTracker->fMask |= fBitMask;
+ }
+ }
+
+ fChanged &= ~fBitMask;
+ }
+
+#ifdef VBOX_STRICT
+ vgdrvBitUsageTrackerCheckMask(pTracker, cMax, pszWhat);
+#endif
+ NOREF(pszWhat); NOREF(cMax);
+ return fGlobalChange;
+}
+
+
+/**
+ * Init and termination worker for resetting the (host) event filter on the host
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param fFixedEvents Fixed events (init time).
+ */
+static int vgdrvResetEventFilterOnHost(PVBOXGUESTDEVEXT pDevExt, uint32_t fFixedEvents)
+{
+ VMMDevCtlGuestFilterMask *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_CtlGuestFilterMask);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->u32NotMask = UINT32_MAX & ~fFixedEvents;
+ pReq->u32OrMask = fFixedEvents;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ LogRelFunc(("failed with rc=%Rrc\n", rc));
+ VbglR0GRFree(&pReq->header);
+ }
+ RT_NOREF1(pDevExt);
+ return rc;
+}
+
+
+/**
+ * Changes the event filter mask for the given session.
+ *
+ * This is called in response to VBGL_IOCTL_CHANGE_FILTER_MASK as well as to do
+ * session cleanup.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param fOrMask The events to add.
+ * @param fNotMask The events to remove.
+ * @param fSessionTermination Set if we're called by the session cleanup code.
+ * This tweaks the error handling so we perform
+ * proper session cleanup even if the host
+ * misbehaves.
+ *
+ * @remarks Takes the session spinlock.
+ */
+static int vgdrvSetSessionEventFilter(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination)
+{
+ VMMDevCtlGuestFilterMask *pReq;
+ uint32_t fChanged;
+ uint32_t fPrevious;
+ int rc;
+
+ /*
+ * Preallocate a request buffer so we can do all in one go without leaving the spinlock.
+ */
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_CtlGuestFilterMask);
+ if (RT_SUCCESS(rc))
+ { /* nothing */ }
+ else if (!fSessionTermination)
+ {
+ LogRel(("vgdrvSetSessionFilterMask: VbglR0GRAlloc failure: %Rrc\n", rc));
+ return rc;
+ }
+ else
+ pReq = NULL; /* Ignore failure, we must do session cleanup. */
+
+
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+
+ /*
+ * Apply the changes to the session mask.
+ */
+ fPrevious = pSession->fEventFilter;
+ pSession->fEventFilter |= fOrMask;
+ pSession->fEventFilter &= ~fNotMask;
+
+ /*
+ * If anything actually changed, update the global usage counters.
+ */
+ fChanged = fPrevious ^ pSession->fEventFilter;
+ LogFlow(("vgdrvSetSessionEventFilter: Session->fEventFilter: %#x -> %#x (changed %#x)\n",
+ fPrevious, pSession->fEventFilter, fChanged));
+ if (fChanged)
+ {
+ bool fGlobalChange = vgdrvBitUsageTrackerChange(&pDevExt->EventFilterTracker, fChanged, fPrevious,
+ pDevExt->cSessions, "EventFilterTracker");
+
+ /*
+ * If there are global changes, update the event filter on the host.
+ */
+ if (fGlobalChange || pDevExt->fEventFilterHost == UINT32_MAX)
+ {
+ Assert(pReq || fSessionTermination);
+ if (pReq)
+ {
+ pReq->u32OrMask = pDevExt->fFixedEvents | pDevExt->EventFilterTracker.fMask;
+ if (pReq->u32OrMask == pDevExt->fEventFilterHost)
+ rc = VINF_SUCCESS;
+ else
+ {
+ pDevExt->fEventFilterHost = pReq->u32OrMask;
+ pReq->u32NotMask = ~pReq->u32OrMask;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Failed, roll back (unless it's session termination time).
+ */
+ pDevExt->fEventFilterHost = UINT32_MAX;
+ if (!fSessionTermination)
+ {
+ vgdrvBitUsageTrackerChange(&pDevExt->EventFilterTracker, fChanged, pSession->fEventFilter,
+ pDevExt->cSessions, "EventFilterTracker");
+ pSession->fEventFilter = fPrevious;
+ }
+ }
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+ if (pReq)
+ VbglR0GRFree(&pReq->header);
+ return rc;
+}
+
+
+/**
+ * Handle VBGL_IOCTL_CHANGE_FILTER_MASK.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pInfo The request.
+ */
+static int vgdrvIoCtl_ChangeFilterMask(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCCHANGEFILTERMASK pInfo)
+{
+ LogFlow(("VBGL_IOCTL_CHANGE_FILTER_MASK: or=%#x not=%#x\n", pInfo->u.In.fOrMask, pInfo->u.In.fNotMask));
+
+ if ((pInfo->u.In.fOrMask | pInfo->u.In.fNotMask) & ~VMMDEV_EVENT_VALID_EVENT_MASK)
+ {
+ Log(("VBGL_IOCTL_CHANGE_FILTER_MASK: or=%#x not=%#x: Invalid masks!\n", pInfo->u.In.fOrMask, pInfo->u.In.fNotMask));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ return vgdrvSetSessionEventFilter(pDevExt, pSession, pInfo->u.In.fOrMask, pInfo->u.In.fNotMask, false /*fSessionTermination*/);
+}
+
+
+/**
+ * Init and termination worker for set mouse feature status to zero on the host.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ */
+static int vgdrvResetMouseStatusOnHost(PVBOXGUESTDEVEXT pDevExt)
+{
+ VMMDevReqMouseStatus *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetMouseStatus);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->mouseFeatures = 0;
+ pReq->pointerXPos = 0;
+ pReq->pointerYPos = 0;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ LogRelFunc(("failed with rc=%Rrc\n", rc));
+ VbglR0GRFree(&pReq->header);
+ }
+ RT_NOREF1(pDevExt);
+ return rc;
+}
+
+
+/**
+ * Changes the mouse status mask for the given session.
+ *
+ * This is called in response to VBOXGUEST_IOCTL_SET_MOUSE_STATUS as well as to
+ * do session cleanup.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param fOrMask The status flags to add.
+ * @param fNotMask The status flags to remove.
+ * @param fSessionTermination Set if we're called by the session cleanup code.
+ * This tweaks the error handling so we perform
+ * proper session cleanup even if the host
+ * misbehaves.
+ *
+ * @remarks Takes the session spinlock.
+ */
+static int vgdrvSetSessionMouseStatus(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNotMask, bool fSessionTermination)
+{
+ VMMDevReqMouseStatus *pReq;
+ uint32_t fChanged;
+ uint32_t fPrevious;
+ int rc;
+
+ /*
+ * Preallocate a request buffer so we can do all in one go without leaving the spinlock.
+ */
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetMouseStatus);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fSessionTermination)
+ pReq->header.fRequestor = pSession->fRequestor;
+ }
+ else if (!fSessionTermination)
+ {
+ LogRel(("vgdrvSetSessionMouseStatus: VbglR0GRAlloc failure: %Rrc\n", rc));
+ return rc;
+ }
+ else
+ pReq = NULL; /* Ignore failure, we must do session cleanup. */
+
+
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+
+ /*
+ * Apply the changes to the session mask.
+ */
+ fPrevious = pSession->fMouseStatus;
+ pSession->fMouseStatus |= fOrMask;
+ pSession->fMouseStatus &= ~fNotMask;
+
+ /*
+ * If anything actually changed, update the global usage counters.
+ */
+ fChanged = fPrevious ^ pSession->fMouseStatus;
+ if (fChanged)
+ {
+ bool fGlobalChange = vgdrvBitUsageTrackerChange(&pDevExt->MouseStatusTracker, fChanged, fPrevious,
+ pDevExt->cSessions, "MouseStatusTracker");
+
+ /*
+ * If there are global changes, update the event filter on the host.
+ */
+ if (fGlobalChange || pDevExt->fMouseStatusHost == UINT32_MAX)
+ {
+ Assert(pReq || fSessionTermination);
+ if (pReq)
+ {
+ pReq->mouseFeatures = pDevExt->MouseStatusTracker.fMask;
+ if (pReq->mouseFeatures == pDevExt->fMouseStatusHost)
+ rc = VINF_SUCCESS;
+ else
+ {
+ pDevExt->fMouseStatusHost = pReq->mouseFeatures;
+ pReq->pointerXPos = 0;
+ pReq->pointerYPos = 0;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Failed, roll back (unless it's session termination time).
+ */
+ pDevExt->fMouseStatusHost = UINT32_MAX;
+ if (!fSessionTermination)
+ {
+ vgdrvBitUsageTrackerChange(&pDevExt->MouseStatusTracker, fChanged, pSession->fMouseStatus,
+ pDevExt->cSessions, "MouseStatusTracker");
+ pSession->fMouseStatus = fPrevious;
+ }
+ }
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+ if (pReq)
+ VbglR0GRFree(&pReq->header);
+ return rc;
+}
+
+
+/**
+ * Sets the mouse status features for this session and updates them globally.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extention.
+ * @param pSession The session.
+ * @param fFeatures New bitmap of enabled features.
+ */
+static int vgdrvIoCtl_SetMouseStatus(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, uint32_t fFeatures)
+{
+ LogFlow(("VBGL_IOCTL_SET_MOUSE_STATUS: features=%#x\n", fFeatures));
+
+ if (fFeatures & ~VMMDEV_MOUSE_GUEST_MASK)
+ return VERR_INVALID_PARAMETER;
+
+ return vgdrvSetSessionMouseStatus(pDevExt, pSession, fFeatures, ~fFeatures, false /*fSessionTermination*/);
+}
+
+
+/**
+ * Return the mask of VMM device events that this session is allowed to see (wrt
+ * to "acquire" mode guest capabilities).
+ *
+ * The events associated with guest capabilities in "acquire" mode will be
+ * restricted to sessions which has acquired the respective capabilities.
+ * If someone else tries to wait for acquired events, they won't be woken up
+ * when the event becomes pending. Should some other thread in the session
+ * acquire the capability while the corresponding event is pending, the waiting
+ * thread will woken up.
+ *
+ * @returns Mask of events valid for the given session.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ *
+ * @remarks Needs only be called when dispatching events in the
+ * VBOXGUEST_ACQUIRE_STYLE_EVENTS mask.
+ */
+static uint32_t vgdrvGetAllowedEventMaskForSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession)
+{
+ uint32_t fAcquireModeGuestCaps;
+ uint32_t fAcquiredGuestCaps;
+ uint32_t fAllowedEvents;
+
+ /*
+ * Note! Reads pSession->fAcquiredGuestCaps and pDevExt->fAcquireModeGuestCaps
+ * WITHOUT holding VBOXGUESTDEVEXT::SessionSpinlock.
+ */
+ fAcquireModeGuestCaps = ASMAtomicUoReadU32(&pDevExt->fAcquireModeGuestCaps);
+ if (fAcquireModeGuestCaps == 0)
+ return VMMDEV_EVENT_VALID_EVENT_MASK;
+ fAcquiredGuestCaps = ASMAtomicUoReadU32(&pSession->fAcquiredGuestCaps);
+
+ /*
+ * Calculate which events to allow according to the cap config and caps
+ * acquired by the session.
+ */
+ fAllowedEvents = VMMDEV_EVENT_VALID_EVENT_MASK;
+ if ( !(fAcquiredGuestCaps & VMMDEV_GUEST_SUPPORTS_GRAPHICS)
+ && (fAcquireModeGuestCaps & VMMDEV_GUEST_SUPPORTS_GRAPHICS))
+ fAllowedEvents &= ~VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
+
+ if ( !(fAcquiredGuestCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS)
+ && (fAcquireModeGuestCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS))
+ fAllowedEvents &= ~VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST;
+
+ return fAllowedEvents;
+}
+
+
+/**
+ * Init and termination worker for set guest capabilities to zero on the host.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ */
+static int vgdrvResetCapabilitiesOnHost(PVBOXGUESTDEVEXT pDevExt)
+{
+ VMMDevReqGuestCapabilities2 *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetGuestCapabilities);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->u32NotMask = UINT32_MAX;
+ pReq->u32OrMask = 0;
+ rc = VbglR0GRPerform(&pReq->header);
+
+ if (RT_FAILURE(rc))
+ LogRelFunc(("failed with rc=%Rrc\n", rc));
+ VbglR0GRFree(&pReq->header);
+ }
+ RT_NOREF1(pDevExt);
+ return rc;
+}
+
+
+/**
+ * Sets the guest capabilities to the host while holding the lock.
+ *
+ * This will ASSUME that we're the ones in charge of the mask, so
+ * we'll simply clear all bits we don't set.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param pReq The request.
+ */
+static int vgdrvUpdateCapabilitiesOnHostWithReqAndLock(PVBOXGUESTDEVEXT pDevExt, VMMDevReqGuestCapabilities2 *pReq)
+{
+ int rc;
+
+ pReq->u32OrMask = pDevExt->fAcquiredGuestCaps | pDevExt->SetGuestCapsTracker.fMask;
+ if (pReq->u32OrMask == pDevExt->fGuestCapsHost)
+ rc = VINF_SUCCESS;
+ else
+ {
+ pDevExt->fGuestCapsHost = pReq->u32OrMask;
+ pReq->u32NotMask = ~pReq->u32OrMask;
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_FAILURE(rc))
+ pDevExt->fGuestCapsHost = UINT32_MAX;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Switch a set of capabilities into "acquire" mode and (maybe) acquire them for
+ * the given session.
+ *
+ * This is called in response to VBOXGUEST_IOCTL_GUEST_CAPS_ACQUIRE as well as
+ * to do session cleanup.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param fOrMask The capabilities to add .
+ * @param fNotMask The capabilities to remove. Ignored in
+ * VBOXGUESTCAPSACQUIRE_FLAGS_CONFIG_ACQUIRE_MODE.
+ * @param fFlags Confusing operation modifier.
+ * VBOXGUESTCAPSACQUIRE_FLAGS_NONE means to both
+ * configure and acquire/release the capabilities.
+ * VBOXGUESTCAPSACQUIRE_FLAGS_CONFIG_ACQUIRE_MODE
+ * means only configure capabilities in the
+ * @a fOrMask capabilities for "acquire" mode.
+ * @param fSessionTermination Set if we're called by the session cleanup code.
+ * This tweaks the error handling so we perform
+ * proper session cleanup even if the host
+ * misbehaves.
+ *
+ * @remarks Takes both the session and event spinlocks.
+ */
+static int vgdrvAcquireSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNotMask, uint32_t fFlags,
+ bool fSessionTermination)
+{
+ uint32_t fCurrentOwnedCaps;
+ uint32_t fSessionRemovedCaps;
+ uint32_t fSessionAddedCaps;
+ uint32_t fOtherConflictingCaps;
+ VMMDevReqGuestCapabilities2 *pReq = NULL;
+ int rc;
+
+
+ /*
+ * Validate and adjust input.
+ */
+ if (fOrMask & ~( VMMDEV_GUEST_SUPPORTS_SEAMLESS
+ | VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING
+ | VMMDEV_GUEST_SUPPORTS_GRAPHICS ) )
+ {
+ LogRel(("vgdrvAcquireSessionCapabilities: invalid fOrMask=%#x (pSession=%p fNotMask=%#x fFlags=%#x)\n",
+ fOrMask, pSession, fNotMask, fFlags));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if ((fFlags & ~VBGL_IOC_AGC_FLAGS_VALID_MASK) != 0)
+ {
+ LogRel(("vgdrvAcquireSessionCapabilities: invalid fFlags=%#x (pSession=%p fOrMask=%#x fNotMask=%#x)\n",
+ fFlags, pSession, fOrMask, fNotMask));
+ return VERR_INVALID_PARAMETER;
+ }
+ Assert(!fOrMask || !fSessionTermination);
+
+ /* The fNotMask no need to have all values valid, invalid ones will simply be ignored. */
+ fNotMask &= ~fOrMask;
+
+ /*
+ * Preallocate a update request if we're about to do more than just configure
+ * the capability mode.
+ */
+ if (!(fFlags & VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE))
+ {
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetGuestCapabilities);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fSessionTermination)
+ pReq->header.fRequestor = pSession->fRequestor;
+ }
+ else if (!fSessionTermination)
+ {
+ LogRel(("vgdrvAcquireSessionCapabilities: pSession=%p fOrMask=%#x fNotMask=%#x fFlags=%#x: VbglR0GRAlloc failure: %Rrc\n",
+ pSession, fOrMask, fNotMask, fFlags, rc));
+ return rc;
+ }
+ else
+ pReq = NULL; /* Ignore failure, we must do session cleanup. */
+ }
+
+ /*
+ * Try switch the capabilities in the OR mask into "acquire" mode.
+ *
+ * Note! We currently ignore anyone which may already have "set" the capabilities
+ * in fOrMask. Perhaps not the best way to handle it, but it's simple...
+ */
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+
+ if (!(pDevExt->fSetModeGuestCaps & fOrMask))
+ pDevExt->fAcquireModeGuestCaps |= fOrMask;
+ else
+ {
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ if (pReq)
+ VbglR0GRFree(&pReq->header);
+ AssertMsgFailed(("Trying to change caps mode: %#x\n", fOrMask));
+ LogRel(("vgdrvAcquireSessionCapabilities: pSession=%p fOrMask=%#x fNotMask=%#x fFlags=%#x: calling caps acquire for set caps\n",
+ pSession, fOrMask, fNotMask, fFlags));
+ return VERR_INVALID_STATE;
+ }
+
+ /*
+ * If we only wanted to switch the capabilities into "acquire" mode, we're done now.
+ */
+ if (fFlags & VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE)
+ {
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ Assert(!pReq);
+ Log(("vgdrvAcquireSessionCapabilities: pSession=%p fOrMask=%#x fNotMask=%#x fFlags=%#x: configured acquire caps: 0x%x\n",
+ pSession, fOrMask, fNotMask, fFlags));
+ return VINF_SUCCESS;
+ }
+ Assert(pReq || fSessionTermination);
+
+ /*
+ * Caller wants to acquire/release the capabilities too.
+ *
+ * Note! The mode change of the capabilities above won't be reverted on
+ * failure, this is intentional.
+ */
+ fCurrentOwnedCaps = pSession->fAcquiredGuestCaps;
+ fSessionRemovedCaps = fCurrentOwnedCaps & fNotMask;
+ fSessionAddedCaps = fOrMask & ~fCurrentOwnedCaps;
+ fOtherConflictingCaps = pDevExt->fAcquiredGuestCaps & ~fCurrentOwnedCaps;
+ fOtherConflictingCaps &= fSessionAddedCaps;
+
+ if (!fOtherConflictingCaps)
+ {
+ if (fSessionAddedCaps)
+ {
+ pSession->fAcquiredGuestCaps |= fSessionAddedCaps;
+ pDevExt->fAcquiredGuestCaps |= fSessionAddedCaps;
+ }
+
+ if (fSessionRemovedCaps)
+ {
+ pSession->fAcquiredGuestCaps &= ~fSessionRemovedCaps;
+ pDevExt->fAcquiredGuestCaps &= ~fSessionRemovedCaps;
+ }
+
+ /*
+ * If something changes (which is very likely), tell the host.
+ */
+ if (fSessionAddedCaps || fSessionRemovedCaps || pDevExt->fGuestCapsHost == UINT32_MAX)
+ {
+ Assert(pReq || fSessionTermination);
+ if (pReq)
+ {
+ rc = vgdrvUpdateCapabilitiesOnHostWithReqAndLock(pDevExt, pReq);
+ if (RT_FAILURE(rc) && !fSessionTermination)
+ {
+ /* Failed, roll back. */
+ if (fSessionAddedCaps)
+ {
+ pSession->fAcquiredGuestCaps &= ~fSessionAddedCaps;
+ pDevExt->fAcquiredGuestCaps &= ~fSessionAddedCaps;
+ }
+ if (fSessionRemovedCaps)
+ {
+ pSession->fAcquiredGuestCaps |= fSessionRemovedCaps;
+ pDevExt->fAcquiredGuestCaps |= fSessionRemovedCaps;
+ }
+
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ LogRel(("vgdrvAcquireSessionCapabilities: vgdrvUpdateCapabilitiesOnHostWithReqAndLock failed: rc=%Rrc\n", rc));
+ VbglR0GRFree(&pReq->header);
+ return rc;
+ }
+ }
+ }
+ }
+ else
+ {
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ Log(("vgdrvAcquireSessionCapabilities: Caps %#x were busy\n", fOtherConflictingCaps));
+ VbglR0GRFree(&pReq->header);
+ return VERR_RESOURCE_BUSY;
+ }
+
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+ if (pReq)
+ VbglR0GRFree(&pReq->header);
+
+ /*
+ * If we added a capability, check if that means some other thread in our
+ * session should be unblocked because there are events pending.
+ *
+ * HACK ALERT! When the seamless support capability is added we generate a
+ * seamless change event so that the ring-3 client can sync with
+ * the seamless state. Although this introduces a spurious
+ * wakeups of the ring-3 client, it solves the problem of client
+ * state inconsistency in multiuser environment (on Windows).
+ */
+ if (fSessionAddedCaps)
+ {
+ uint32_t fGenFakeEvents = 0;
+ if (fSessionAddedCaps & VMMDEV_GUEST_SUPPORTS_SEAMLESS)
+ fGenFakeEvents |= VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST;
+
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ if (fGenFakeEvents || pDevExt->f32PendingEvents)
+ vgdrvDispatchEventsLocked(pDevExt, fGenFakeEvents);
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ VGDrvCommonWaitDoWakeUps(pDevExt);
+#endif
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handle VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pAcquire The request.
+ */
+static int vgdrvIoCtl_GuestCapsAcquire(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCACQUIREGUESTCAPS pAcquire)
+{
+ int rc;
+ LogFlow(("VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES: or=%#x not=%#x flags=%#x\n",
+ pAcquire->u.In.fOrMask, pAcquire->u.In.fNotMask, pAcquire->u.In.fFlags));
+
+ rc = vgdrvAcquireSessionCapabilities(pDevExt, pSession, pAcquire->u.In.fOrMask, pAcquire->u.In.fNotMask,
+ pAcquire->u.In.fFlags, false /*fSessionTermination*/);
+ if (RT_FAILURE(rc))
+ LogRel(("VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES failed rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Sets the guest capabilities for a session.
+ *
+ * @returns VBox status code.
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param fOrMask The capabilities to add.
+ * @param fNotMask The capabilities to remove.
+ * @param pfSessionCaps Where to return the guest capabilities reported
+ * for this session. Optional.
+ * @param pfGlobalCaps Where to return the guest capabilities reported
+ * for all the sessions. Optional.
+ *
+ * @param fSessionTermination Set if we're called by the session cleanup code.
+ * This tweaks the error handling so we perform
+ * proper session cleanup even if the host
+ * misbehaves.
+ *
+ * @remarks Takes the session spinlock.
+ */
+static int vgdrvSetSessionCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ uint32_t fOrMask, uint32_t fNotMask, uint32_t *pfSessionCaps, uint32_t *pfGlobalCaps,
+ bool fSessionTermination)
+{
+ /*
+ * Preallocate a request buffer so we can do all in one go without leaving the spinlock.
+ */
+ VMMDevReqGuestCapabilities2 *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pReq, sizeof(*pReq), VMMDevReq_SetGuestCapabilities);
+ if (RT_SUCCESS(rc))
+ {
+ if (!fSessionTermination)
+ pReq->header.fRequestor = pSession->fRequestor;
+ }
+ else if (!fSessionTermination)
+ {
+ if (pfSessionCaps)
+ *pfSessionCaps = UINT32_MAX;
+ if (pfGlobalCaps)
+ *pfGlobalCaps = UINT32_MAX;
+ LogRel(("vgdrvSetSessionCapabilities: VbglR0GRAlloc failure: %Rrc\n", rc));
+ return rc;
+ }
+ else
+ pReq = NULL; /* Ignore failure, we must do session cleanup. */
+
+
+ RTSpinlockAcquire(pDevExt->SessionSpinlock);
+
+#ifndef VBOXGUEST_DISREGARD_ACQUIRE_MODE_GUEST_CAPS
+ /*
+ * Capabilities in "acquire" mode cannot be set via this API.
+ * (Acquire mode is only used on windows at the time of writing.)
+ */
+ if (!(fOrMask & pDevExt->fAcquireModeGuestCaps))
+#endif
+ {
+ /*
+ * Apply the changes to the session mask.
+ */
+ uint32_t fChanged;
+ uint32_t fPrevious = pSession->fCapabilities;
+ pSession->fCapabilities |= fOrMask;
+ pSession->fCapabilities &= ~fNotMask;
+
+ /*
+ * If anything actually changed, update the global usage counters.
+ */
+ fChanged = fPrevious ^ pSession->fCapabilities;
+ if (fChanged)
+ {
+ bool fGlobalChange = vgdrvBitUsageTrackerChange(&pDevExt->SetGuestCapsTracker, fChanged, fPrevious,
+ pDevExt->cSessions, "SetGuestCapsTracker");
+
+ /*
+ * If there are global changes, update the capabilities on the host.
+ */
+ if (fGlobalChange || pDevExt->fGuestCapsHost == UINT32_MAX)
+ {
+ Assert(pReq || fSessionTermination);
+ if (pReq)
+ {
+ rc = vgdrvUpdateCapabilitiesOnHostWithReqAndLock(pDevExt, pReq);
+
+ /* On failure, roll back (unless it's session termination time). */
+ if (RT_FAILURE(rc) && !fSessionTermination)
+ {
+ vgdrvBitUsageTrackerChange(&pDevExt->SetGuestCapsTracker, fChanged, pSession->fCapabilities,
+ pDevExt->cSessions, "SetGuestCapsTracker");
+ pSession->fCapabilities = fPrevious;
+ }
+ }
+ }
+ }
+ }
+#ifndef VBOXGUEST_DISREGARD_ACQUIRE_MODE_GUEST_CAPS
+ else
+ rc = VERR_RESOURCE_BUSY;
+#endif
+
+ if (pfSessionCaps)
+ *pfSessionCaps = pSession->fCapabilities;
+ if (pfGlobalCaps)
+ *pfGlobalCaps = pDevExt->fAcquiredGuestCaps | pDevExt->SetGuestCapsTracker.fMask;
+
+ RTSpinlockRelease(pDevExt->SessionSpinlock);
+ if (pReq)
+ VbglR0GRFree(&pReq->header);
+ return rc;
+}
+
+
+/**
+ * Handle VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevExt The device extension.
+ * @param pSession The session.
+ * @param pInfo The request.
+ */
+static int vgdrvIoCtl_SetCapabilities(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCSETGUESTCAPS pInfo)
+{
+ int rc;
+ LogFlow(("VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES: or=%#x not=%#x\n", pInfo->u.In.fOrMask, pInfo->u.In.fNotMask));
+
+ if (!((pInfo->u.In.fOrMask | pInfo->u.In.fNotMask) & ~VMMDEV_GUEST_CAPABILITIES_MASK))
+ rc = vgdrvSetSessionCapabilities(pDevExt, pSession, pInfo->u.In.fOrMask, pInfo->u.In.fNotMask,
+ &pInfo->u.Out.fSessionCaps, &pInfo->u.Out.fGlobalCaps, false /*fSessionTermination*/);
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+/** @} */
+
+
+/**
+ * Common IOCtl for user to kernel and kernel to kernel communication.
+ *
+ * This function only does the basic validation and then invokes
+ * worker functions that takes care of each specific function.
+ *
+ * @returns VBox status code.
+ *
+ * @param iFunction The requested function.
+ * @param pDevExt The device extension.
+ * @param pSession The client session.
+ * @param pReqHdr Pointer to the request. This always starts with
+ * a request common header.
+ * @param cbReq The max size of the request buffer.
+ */
+int VGDrvCommonIoCtl(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLREQHDR pReqHdr, size_t cbReq)
+{
+ uintptr_t const iFunctionStripped = VBGL_IOCTL_CODE_STRIPPED(iFunction);
+ int rc;
+
+ LogFlow(("VGDrvCommonIoCtl: iFunction=%#x pDevExt=%p pSession=%p pReqHdr=%p cbReq=%zu\n",
+ iFunction, pDevExt, pSession, pReqHdr, cbReq));
+
+ /*
+ * Define some helper macros to simplify validation.
+ */
+#define REQ_CHECK_SIZES_EX(Name, cbInExpect, cbOutExpect) \
+ do { \
+ if (RT_LIKELY( pReqHdr->cbIn == (cbInExpect) \
+ && ( pReqHdr->cbOut == (cbOutExpect) \
+ || ((cbInExpect) == (cbOutExpect) && pReqHdr->cbOut == 0) ) )) \
+ { /* likely */ } \
+ else \
+ { \
+ Log(( #Name ": Invalid input/output sizes. cbIn=%ld expected %ld. cbOut=%ld expected %ld.\n", \
+ (long)pReqHdr->cbIn, (long)(cbInExpect), (long)pReqHdr->cbOut, (long)(cbOutExpect))); \
+ return pReqHdr->rc = VERR_INVALID_PARAMETER; \
+ } \
+ } while (0)
+
+#define REQ_CHECK_SIZES(Name) REQ_CHECK_SIZES_EX(Name, Name ## _SIZE_IN, Name ## _SIZE_OUT)
+
+#define REQ_CHECK_SIZE_IN(Name, cbInExpect) \
+ do { \
+ if (RT_LIKELY(pReqHdr->cbIn == (cbInExpect))) \
+ { /* likely */ } \
+ else \
+ { \
+ Log(( #Name ": Invalid input/output sizes. cbIn=%ld expected %ld.\n", \
+ (long)pReqHdr->cbIn, (long)(cbInExpect))); \
+ return pReqHdr->rc = VERR_INVALID_PARAMETER; \
+ } \
+ } while (0)
+
+#define REQ_CHECK_SIZE_OUT(Name, cbOutExpect) \
+ do { \
+ if (RT_LIKELY( pReqHdr->cbOut == (cbOutExpect) \
+ || (pReqHdr->cbOut == 0 && pReqHdr->cbIn == (cbOutExpect)))) \
+ { /* likely */ } \
+ else \
+ { \
+ Log(( #Name ": Invalid input/output sizes. cbOut=%ld (%ld) expected %ld.\n", \
+ (long)pReqHdr->cbOut, (long)pReqHdr->cbIn, (long)(cbOutExpect))); \
+ return pReqHdr->rc = VERR_INVALID_PARAMETER; \
+ } \
+ } while (0)
+
+#define REQ_CHECK_EXPR(Name, expr) \
+ do { \
+ if (RT_LIKELY(!!(expr))) \
+ { /* likely */ } \
+ else \
+ { \
+ Log(( #Name ": %s\n", #expr)); \
+ return pReqHdr->rc = VERR_INVALID_PARAMETER; \
+ } \
+ } while (0)
+
+#define REQ_CHECK_EXPR_FMT(expr, fmt) \
+ do { \
+ if (RT_LIKELY(!!(expr))) \
+ { /* likely */ } \
+ else \
+ { \
+ Log( fmt ); \
+ return pReqHdr->rc = VERR_INVALID_PARAMETER; \
+ } \
+ } while (0)
+
+#define REQ_CHECK_RING0(mnemonic) \
+ do { \
+ if (pSession->R0Process != NIL_RTR0PROCESS) \
+ { \
+ LogFunc((mnemonic ": Ring-0 only, caller is %RTproc/%p\n", \
+ pSession->Process, (uintptr_t)pSession->R0Process)); \
+ return pReqHdr->rc = VERR_PERMISSION_DENIED; \
+ } \
+ } while (0)
+
+
+ /*
+ * Validate the request.
+ */
+ if (RT_LIKELY(cbReq >= sizeof(*pReqHdr)))
+ { /* likely */ }
+ else
+ {
+ Log(("VGDrvCommonIoCtl: Bad ioctl request size; cbReq=%#lx\n", (long)cbReq));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (pReqHdr->cbOut == 0)
+ pReqHdr->cbOut = pReqHdr->cbIn;
+
+ if (RT_LIKELY( pReqHdr->uVersion == VBGLREQHDR_VERSION
+ && pReqHdr->cbIn >= sizeof(*pReqHdr)
+ && pReqHdr->cbIn <= cbReq
+ && pReqHdr->cbOut >= sizeof(*pReqHdr)
+ && pReqHdr->cbOut <= cbReq))
+ { /* likely */ }
+ else
+ {
+ Log(("VGDrvCommonIoCtl: Bad ioctl request header; cbIn=%#lx cbOut=%#lx version=%#lx\n",
+ (long)pReqHdr->cbIn, (long)pReqHdr->cbOut, (long)pReqHdr->uVersion));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_LIKELY(RT_VALID_PTR(pSession)))
+ { /* likely */ }
+ else
+ {
+ Log(("VGDrvCommonIoCtl: Invalid pSession value %p (ioctl=%#x)\n", pSession, iFunction));
+ return VERR_INVALID_PARAMETER;
+ }
+
+
+ /*
+ * Deal with variably sized requests first.
+ */
+ rc = VINF_SUCCESS;
+ if ( iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_VMMDEV_REQUEST(0))
+ || iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_VMMDEV_REQUEST_BIG) )
+ {
+ REQ_CHECK_EXPR(VBGL_IOCTL_VMMDEV_REQUEST, pReqHdr->uType != VBGLREQHDR_TYPE_DEFAULT);
+ REQ_CHECK_EXPR_FMT(pReqHdr->cbIn == pReqHdr->cbOut,
+ ("VBGL_IOCTL_VMMDEV_REQUEST: cbIn=%ld != cbOut=%ld\n", (long)pReqHdr->cbIn, (long)pReqHdr->cbOut));
+ pReqHdr->rc = vgdrvIoCtl_VMMDevRequest(pDevExt, pSession, (VMMDevRequestHeader *)pReqHdr, cbReq);
+ }
+ else if (RT_LIKELY(pReqHdr->uType == VBGLREQHDR_TYPE_DEFAULT))
+ {
+ if (iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_LOG(0)))
+ {
+ REQ_CHECK_SIZE_OUT(VBGL_IOCTL_LOG, VBGL_IOCTL_LOG_SIZE_OUT);
+ pReqHdr->rc = vgdrvIoCtl_Log(pDevExt, &((PVBGLIOCLOG)pReqHdr)->u.In.szMsg[0], pReqHdr->cbIn - sizeof(VBGLREQHDR),
+ pSession->fUserSession);
+ }
+#ifdef VBOX_WITH_HGCM
+ else if (iFunction == VBGL_IOCTL_IDC_HGCM_FAST_CALL) /* (is variable size, but we don't bother encoding it) */
+ {
+ REQ_CHECK_RING0("VBGL_IOCTL_IDC_HGCM_FAST_CALL");
+ REQ_CHECK_EXPR(VBGL_IOCTL_IDC_HGCM_FAST_CALL, cbReq >= sizeof(VBGLIOCIDCHGCMFASTCALL) + sizeof(VMMDevHGCMCall));
+ pReqHdr->rc = vgdrvIoCtl_HGCMFastCall(pDevExt, (VBGLIOCIDCHGCMFASTCALL volatile *)pReqHdr);
+ }
+ else if ( iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL(0))
+# if ARCH_BITS == 64
+ || iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_32(0))
+# endif
+ )
+ {
+ REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn >= sizeof(VBGLIOCHGCMCALL));
+ REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn == pReqHdr->cbOut);
+ pReqHdr->rc = vgdrvIoCtl_HGCMCallWrapper(pDevExt, pSession, (PVBGLIOCHGCMCALL)pReqHdr,
+ iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_32(0)),
+ false /*fUserData*/, cbReq);
+ }
+ else if (iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA(0)))
+ {
+ REQ_CHECK_RING0("VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA");
+ REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn >= sizeof(VBGLIOCHGCMCALL));
+ REQ_CHECK_EXPR(VBGL_IOCTL_HGCM_CALL, pReqHdr->cbIn == pReqHdr->cbOut);
+ pReqHdr->rc = vgdrvIoCtl_HGCMCallWrapper(pDevExt, pSession, (PVBGLIOCHGCMCALL)pReqHdr,
+ ARCH_BITS == 32, true /*fUserData*/, cbReq);
+ }
+#endif /* VBOX_WITH_HGCM */
+ else
+ {
+ switch (iFunction)
+ {
+ /*
+ * Ring-0 only:
+ */
+ case VBGL_IOCTL_IDC_CONNECT:
+ REQ_CHECK_RING0("VBGL_IOCL_IDC_CONNECT");
+ REQ_CHECK_SIZES(VBGL_IOCTL_IDC_CONNECT);
+ pReqHdr->rc = vgdrvIoCtl_IdcConnect(pDevExt, pSession, (PVBGLIOCIDCCONNECT)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_IDC_DISCONNECT:
+ REQ_CHECK_RING0("VBGL_IOCTL_IDC_DISCONNECT");
+ REQ_CHECK_SIZES(VBGL_IOCTL_IDC_DISCONNECT);
+ pReqHdr->rc = vgdrvIoCtl_IdcDisconnect(pDevExt, pSession, (PVBGLIOCIDCDISCONNECT)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_GET_VMMDEV_IO_INFO:
+ REQ_CHECK_RING0("GET_VMMDEV_IO_INFO");
+ REQ_CHECK_SIZES(VBGL_IOCTL_GET_VMMDEV_IO_INFO);
+ pReqHdr->rc = vgdrvIoCtl_GetVMMDevIoInfo(pDevExt, (PVBGLIOCGETVMMDEVIOINFO)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_SET_MOUSE_NOTIFY_CALLBACK:
+ REQ_CHECK_RING0("SET_MOUSE_NOTIFY_CALLBACK");
+ REQ_CHECK_SIZES(VBGL_IOCTL_SET_MOUSE_NOTIFY_CALLBACK);
+ pReqHdr->rc = vgdrvIoCtl_SetMouseNotifyCallback(pDevExt, (PVBGLIOCSETMOUSENOTIFYCALLBACK)pReqHdr);
+ break;
+
+ /*
+ * Ring-3 only:
+ */
+ case VBGL_IOCTL_DRIVER_VERSION_INFO:
+ REQ_CHECK_SIZES(VBGL_IOCTL_DRIVER_VERSION_INFO);
+ pReqHdr->rc = vgdrvIoCtl_DriverVersionInfo(pDevExt, pSession, (PVBGLIOCDRIVERVERSIONINFO)pReqHdr);
+ break;
+
+ /*
+ * Both ring-3 and ring-0:
+ */
+ case VBGL_IOCTL_WAIT_FOR_EVENTS:
+ REQ_CHECK_SIZES(VBGL_IOCTL_WAIT_FOR_EVENTS);
+ pReqHdr->rc = vgdrvIoCtl_WaitForEvents(pDevExt, pSession, (VBGLIOCWAITFOREVENTS *)pReqHdr,
+ pSession->R0Process != NIL_RTR0PROCESS);
+ break;
+
+ case VBGL_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS:
+ REQ_CHECK_SIZES(VBGL_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS);
+ pReqHdr->rc = vgdrvIoCtl_CancelAllWaitEvents(pDevExt, pSession);
+ break;
+
+ case VBGL_IOCTL_CHANGE_FILTER_MASK:
+ REQ_CHECK_SIZES(VBGL_IOCTL_CHANGE_FILTER_MASK);
+ pReqHdr->rc = vgdrvIoCtl_ChangeFilterMask(pDevExt, pSession, (PVBGLIOCCHANGEFILTERMASK)pReqHdr);
+ break;
+
+#ifdef VBOX_WITH_HGCM
+ case VBGL_IOCTL_HGCM_CONNECT:
+ REQ_CHECK_SIZES(VBGL_IOCTL_HGCM_CONNECT);
+ pReqHdr->rc = vgdrvIoCtl_HGCMConnect(pDevExt, pSession, (PVBGLIOCHGCMCONNECT)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_HGCM_DISCONNECT:
+ REQ_CHECK_SIZES(VBGL_IOCTL_HGCM_DISCONNECT);
+ pReqHdr->rc = vgdrvIoCtl_HGCMDisconnect(pDevExt, pSession, (PVBGLIOCHGCMDISCONNECT)pReqHdr);
+ break;
+#endif
+
+ case VBGL_IOCTL_CHECK_BALLOON:
+ REQ_CHECK_SIZES(VBGL_IOCTL_CHECK_BALLOON);
+ pReqHdr->rc = vgdrvIoCtl_CheckMemoryBalloon(pDevExt, pSession, (PVBGLIOCCHECKBALLOON)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_CHANGE_BALLOON:
+ REQ_CHECK_SIZES(VBGL_IOCTL_CHANGE_BALLOON);
+ pReqHdr->rc = vgdrvIoCtl_ChangeMemoryBalloon(pDevExt, pSession, (PVBGLIOCCHANGEBALLOON)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_WRITE_CORE_DUMP:
+ REQ_CHECK_SIZES(VBGL_IOCTL_WRITE_CORE_DUMP);
+ pReqHdr->rc = vgdrvIoCtl_WriteCoreDump(pDevExt, pSession, (PVBGLIOCWRITECOREDUMP)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_SET_MOUSE_STATUS:
+ REQ_CHECK_SIZES(VBGL_IOCTL_SET_MOUSE_STATUS);
+ pReqHdr->rc = vgdrvIoCtl_SetMouseStatus(pDevExt, pSession, ((PVBGLIOCSETMOUSESTATUS)pReqHdr)->u.In.fStatus);
+ break;
+
+ case VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES:
+ REQ_CHECK_SIZES(VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES);
+ pReqHdr->rc = vgdrvIoCtl_GuestCapsAcquire(pDevExt, pSession, (PVBGLIOCACQUIREGUESTCAPS)pReqHdr);
+ break;
+
+ case VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES:
+ REQ_CHECK_SIZES(VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES);
+ pReqHdr->rc = vgdrvIoCtl_SetCapabilities(pDevExt, pSession, (PVBGLIOCSETGUESTCAPS)pReqHdr);
+ break;
+
+#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
+ case VBGL_IOCTL_DPC_LATENCY_CHECKER:
+ REQ_CHECK_SIZES(VBGL_IOCTL_DPC_LATENCY_CHECKER);
+ pReqHdr->rc = VGDrvNtIOCtl_DpcLatencyChecker();
+ break;
+#endif
+
+ default:
+ {
+ LogRel(("VGDrvCommonIoCtl: Unknown request iFunction=%#x (stripped %#x) cbReq=%#x\n",
+ iFunction, iFunctionStripped, cbReq));
+ pReqHdr->rc = rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ Log(("VGDrvCommonIoCtl: uType=%#x, expected default (ioctl=%#x)\n", pReqHdr->uType, iFunction));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ LogFlow(("VGDrvCommonIoCtl: returns %Rrc (req: rc=%Rrc cbOut=%#x)\n", rc, pReqHdr->rc, pReqHdr->cbOut));
+ return rc;
+}
+
+
+/**
+ * Used by VGDrvCommonISR as well as the acquire guest capability code.
+ *
+ * @returns VINF_SUCCESS on success. On failure, ORed together
+ * RTSemEventMultiSignal errors (completes processing despite errors).
+ * @param pDevExt The VBoxGuest device extension.
+ * @param fEvents The events to dispatch.
+ */
+static int vgdrvDispatchEventsLocked(PVBOXGUESTDEVEXT pDevExt, uint32_t fEvents)
+{
+ PVBOXGUESTWAIT pWait;
+ PVBOXGUESTWAIT pSafe;
+ int rc = VINF_SUCCESS;
+
+ fEvents |= pDevExt->f32PendingEvents;
+
+ RTListForEachSafe(&pDevExt->WaitList, pWait, pSafe, VBOXGUESTWAIT, ListNode)
+ {
+ uint32_t fHandledEvents = pWait->fReqEvents & fEvents;
+ if ( fHandledEvents != 0
+ && !pWait->fResEvents)
+ {
+ /* Does this one wait on any of the events we're dispatching? We do a quick
+ check first, then deal with VBOXGUEST_ACQUIRE_STYLE_EVENTS as applicable. */
+ if (fHandledEvents & VBOXGUEST_ACQUIRE_STYLE_EVENTS)
+ fHandledEvents &= vgdrvGetAllowedEventMaskForSession(pDevExt, pWait->pSession);
+ if (fHandledEvents)
+ {
+ pWait->fResEvents = pWait->fReqEvents & fEvents & fHandledEvents;
+ fEvents &= ~pWait->fResEvents;
+ RTListNodeRemove(&pWait->ListNode);
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ RTListAppend(&pDevExt->WakeUpList, &pWait->ListNode);
+#else
+ RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode);
+ rc |= RTSemEventMultiSignal(pWait->Event);
+#endif
+ if (!fEvents)
+ break;
+ }
+ }
+ }
+
+ ASMAtomicWriteU32(&pDevExt->f32PendingEvents, fEvents);
+ return rc;
+}
+
+
+/**
+ * Simply checks whether the IRQ is ours or not, does not do any interrupt
+ * procesing.
+ *
+ * @returns true if it was our interrupt, false if it wasn't.
+ * @param pDevExt The VBoxGuest device extension.
+ */
+bool VGDrvCommonIsOurIRQ(PVBOXGUESTDEVEXT pDevExt)
+{
+ VMMDevMemory volatile *pVMMDevMemory;
+ bool fOurIrq;
+
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ pVMMDevMemory = pDevExt->pVMMDevMemory;
+ fOurIrq = pVMMDevMemory ? pVMMDevMemory->V.V1_04.fHaveEvents : false;
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ return fOurIrq;
+}
+
+
+/**
+ * Common interrupt service routine.
+ *
+ * This deals with events and with waking up thread waiting for those events.
+ *
+ * @returns true if it was our interrupt, false if it wasn't.
+ * @param pDevExt The VBoxGuest device extension.
+ */
+bool VGDrvCommonISR(PVBOXGUESTDEVEXT pDevExt)
+{
+ VMMDevEvents volatile *pReq;
+ bool fMousePositionChanged = false;
+ int rc = 0;
+ VMMDevMemory volatile *pVMMDevMemory;
+ bool fOurIrq;
+
+ /*
+ * Make sure we've initialized the device extension.
+ */
+ if (RT_LIKELY(pDevExt->fHostFeatures & VMMDEV_HVF_FAST_IRQ_ACK))
+ pReq = NULL;
+ else if (RT_LIKELY((pReq = pDevExt->pIrqAckEvents) != NULL))
+ { /* likely */ }
+ else
+ return false;
+
+ /*
+ * Enter the spinlock and check if it's our IRQ or not.
+ */
+ RTSpinlockAcquire(pDevExt->EventSpinlock);
+ pVMMDevMemory = pDevExt->pVMMDevMemory;
+ fOurIrq = pVMMDevMemory ? pVMMDevMemory->V.V1_04.fHaveEvents : false;
+ if (fOurIrq)
+ {
+ /*
+ * Acknowledge events.
+ * We don't use VbglR0GRPerform here as it may take another spinlocks.
+ */
+ uint32_t fEvents;
+ if (!pReq)
+ {
+ fEvents = ASMInU32(pDevExt->IOPortBase + VMMDEV_PORT_OFF_REQUEST_FAST);
+ ASMCompilerBarrier(); /* paranoia */
+ rc = fEvents != UINT32_MAX ? VINF_SUCCESS : VERR_INTERNAL_ERROR;
+ }
+ else
+ {
+ pReq->header.rc = VERR_INTERNAL_ERROR;
+ pReq->events = 0;
+ ASMCompilerBarrier();
+ ASMOutU32(pDevExt->IOPortBase + VMMDEV_PORT_OFF_REQUEST, (uint32_t)pDevExt->PhysIrqAckEvents);
+ ASMCompilerBarrier(); /* paranoia */
+ fEvents = pReq->events;
+ rc = pReq->header.rc;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("VGDrvCommonISR: acknowledge events succeeded %#RX32\n", fEvents));
+
+ /*
+ * VMMDEV_EVENT_MOUSE_POSITION_CHANGED can only be polled for.
+ */
+ if (fEvents & VMMDEV_EVENT_MOUSE_POSITION_CHANGED)
+ {
+ fMousePositionChanged = true;
+ fEvents &= ~VMMDEV_EVENT_MOUSE_POSITION_CHANGED;
+#if !defined(VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT)
+ if (pDevExt->pfnMouseNotifyCallback)
+ pDevExt->pfnMouseNotifyCallback(pDevExt->pvMouseNotifyCallbackArg);
+#endif
+ }
+
+#ifdef VBOX_WITH_HGCM
+ /*
+ * The HGCM event/list is kind of different in that we evaluate all entries.
+ */
+ if (fEvents & VMMDEV_EVENT_HGCM)
+ {
+ PVBOXGUESTWAIT pWait;
+ PVBOXGUESTWAIT pSafe;
+ RTListForEachSafe(&pDevExt->HGCMWaitList, pWait, pSafe, VBOXGUESTWAIT, ListNode)
+ {
+ if (pWait->pHGCMReq->fu32Flags & VBOX_HGCM_REQ_DONE)
+ {
+ pWait->fResEvents = VMMDEV_EVENT_HGCM;
+ RTListNodeRemove(&pWait->ListNode);
+# ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ RTListAppend(&pDevExt->WakeUpList, &pWait->ListNode);
+# else
+ RTListAppend(&pDevExt->WokenUpList, &pWait->ListNode);
+ rc |= RTSemEventMultiSignal(pWait->Event);
+# endif
+ }
+ }
+ fEvents &= ~VMMDEV_EVENT_HGCM;
+ }
+#endif
+
+ /*
+ * Normal FIFO waiter evaluation.
+ */
+ rc |= vgdrvDispatchEventsLocked(pDevExt, fEvents);
+ }
+ else /* something is serious wrong... */
+ Log(("VGDrvCommonISR: acknowledge events failed rc=%Rrc (events=%#x)!!\n", rc, fEvents));
+ }
+ else
+ Log3(("VGDrvCommonISR: not ours\n"));
+
+ RTSpinlockRelease(pDevExt->EventSpinlock);
+
+ /*
+ * Execute the mouse notification callback here if it cannot be executed while
+ * holding the interrupt safe spinlock, see @bugref{8639}.
+ */
+#if defined(VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT) && !defined(RT_OS_WINDOWS) /* (Windows does this in the Dpc callback) */
+ if ( fMousePositionChanged
+ && pDevExt->pfnMouseNotifyCallback)
+ pDevExt->pfnMouseNotifyCallback(pDevExt->pvMouseNotifyCallbackArg);
+#endif
+
+#if defined(VBOXGUEST_USE_DEFERRED_WAKE_UP) && !defined(RT_OS_WINDOWS)
+ /*
+ * Do wake-ups.
+ * Note. On Windows this isn't possible at this IRQL, so a DPC will take
+ * care of it. Same on darwin, doing it in the work loop callback.
+ */
+ VGDrvCommonWaitDoWakeUps(pDevExt);
+#endif
+
+ /*
+ * Work the poll and async notification queues on OSes that implements that.
+ * (Do this outside the spinlock to prevent some recursive spinlocking.)
+ */
+ if (fMousePositionChanged)
+ {
+ ASMAtomicIncU32(&pDevExt->u32MousePosChangedSeq);
+ VGDrvNativeISRMousePollEvent(pDevExt);
+ }
+
+ AssertMsg(rc == 0, ("rc=%#x (%d)\n", rc, rc));
+ return fOurIrq;
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm b/src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm
new file mode 100644
index 00000000..12df6b42
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuestA-os2.asm
@@ -0,0 +1,1679 @@
+; $Id: VBoxGuestA-os2.asm $
+;; @file
+; VBoxGuest - OS/2 assembly file, the first file in the link.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;-----------------------------------------------------------------------------
+; This code is based on:
+;
+; VBoxDrv - OS/2 assembly file, the first file in the link.
+;
+; Copyright (c) 2007-2010 knut st. osmundsen <bird-src-spam@anduin.net>
+;
+; Permission is hereby granted, free of charge, to any person
+; obtaining a copy of this software and associated documentation
+; files (the "Software"), to deal in the Software without
+; restriction, including without limitation the rights to use,
+; copy, modify, merge, publish, distribute, sublicense, and/or sell
+; copies of the Software, and to permit persons to whom the
+; Software is furnished to do so, subject to the following
+; conditions:
+;
+; The above copyright notice and this permission notice shall be
+; included in all copies or substantial portions of the Software.
+;
+; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+; OTHER DEALINGS IN THE SOFTWARE.
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_INCL_16BIT_SEGMENTS
+%include "iprt/asmdefs.mac"
+%include "iprt/err.mac"
+%include "VBox/VBoxGuest.mac"
+
+
+;*******************************************************************************
+;* Structures and Typedefs *
+;*******************************************************************************
+;;
+; Request packet header.
+struc PKTHDR
+ .cb resb 1
+ .unit resb 1
+ .cmd resb 1
+ .status resw 1
+ .res1 resd 1
+ .link resd 1
+endstruc
+
+
+;;
+; Init request packet - input.
+struc PKTINITIN
+ .cb resb 1
+ .unit resb 1
+ .cmd resb 1
+ .status resw 1
+ .res1 resd 1
+ .link resd 1
+
+ .data_1 resb 1
+ .fpfnDevHlp resd 1
+ .fpszArgs resd 1
+ .data_2 resb 1
+endstruc
+
+;;
+; Init request packet - output.
+struc PKTINITOUT
+ .cb resb 1
+ .unit resb 1
+ .cmd resb 1
+ .status resw 1
+ .res1 resd 1
+ .link resd 1
+
+ .cUnits resb 1 ; block devs only.
+ .cbCode16 resw 1
+ .cbData16 resw 1
+ .fpaBPBs resd 1 ; block devs only.
+ .data_2 resb 1
+endstruc
+
+;;
+; Open request packet.
+struc PKTOPEN
+ .cb resb 1
+ .unit resb 1
+ .cmd resb 1
+ .status resw 1
+ .res1 resd 1
+ .link resd 1
+ .sfn resw 1
+endstruc
+
+;;
+; Close request packet.
+struc PKTCLOSE
+ .cb resb 1
+ .unit resb 1
+ .cmd resb 1
+ .status resw 1
+ .res1 resd 1
+ .link resd 1
+ .sfn resw 1
+endstruc
+
+;;
+; IOCtl request packet.
+struc PKTIOCTL
+ .cb resb 1
+ .unit resb 1
+ .cmd resb 1
+ .status resw 1
+ .res1 resd 1
+ .link resd 1
+
+ .cat resb 1
+ .fun resb 1
+ .pParm resd 1
+ .pData resd 1
+ .sfn resw 1
+ .cbParm resw 1
+ .cbData resw 1
+endstruc
+
+;;
+; Read/Write request packet
+struc PKTRW
+ .cb resb 1
+ .unit resb 1
+ .cmd resb 1
+ .status resw 1
+ .res1 resd 1
+ .link resd 1
+
+ .media resb 1
+ .PhysTrans resd 1
+ .cbTrans resw 1
+ .start resd 1
+ .sfn resw 1
+endstruc
+
+
+;*******************************************************************************
+;* Defined Constants And Macros *
+;*******************************************************************************
+; Some devhdr.inc stuff.
+%define DEVLEV_3 0180h
+%define DEV_30 0800h
+%define DEV_IOCTL 4000h
+%define DEV_CHAR_DEV 8000h
+
+%define DEV_16MB 0002h
+%define DEV_IOCTL2 0001h
+
+; Some dhcalls.h stuff.
+%define DevHlp_VirtToLin 05bh
+%define DevHlp_SAVE_MESSAGE 03dh
+%define DevHlp_EOI 031h
+%define DevHlp_SetIRQ 01bh
+%define DevHlp_PhysToVirt 015h
+
+; Fast IOCtl category, also defined in VBoxGuest.h
+%define VBGL_IOCTL_CATEGORY_FAST 0c3h
+
+;;
+; Got some nasm/link trouble, so emit the stuff ourselves.
+; @param %1 Must be a GLOBALNAME.
+%macro JMP32TO16 1
+ ;jmp far dword NAME(%1) wrt CODE16
+ db 066h
+ db 0eah
+ dw NAME(%1) wrt CODE16
+ dw CODE16
+%endmacro
+
+;;
+; Got some nasm/link trouble, so emit the stuff ourselves.
+; @param %1 Must be a GLOBALNAME.
+%macro JMP16TO32 1
+ ;jmp far dword NAME(%1) wrt FLAT
+ db 066h
+ db 0eah
+ dd NAME(%1) ;wrt FLAT
+ dw TEXT32 wrt FLAT
+%endmacro
+
+
+;*******************************************************************************
+;* External Symbols *
+;*******************************************************************************
+segment CODE16
+extern DOS16OPEN
+extern DOS16CLOSE
+extern DOS16WRITE
+extern DOS16DEVIOCTL2
+segment TEXT32
+extern KernThunkStackTo32
+extern KernThunkStackTo16
+
+extern NAME(vgdrvOS2Init)
+extern NAME(vgdrvOS2Open)
+extern NAME(vgdrvOS2Close)
+extern NAME(vgdrvOS2IOCtl)
+extern NAME(vgdrvOS2IOCtlFast)
+extern NAME(vgdrvOS2IDCConnect)
+extern NAME(VGDrvOS2IDCService)
+extern NAME(vgdrvOS2ISR)
+
+
+segment DATA16
+
+;;
+; Device headers. The first one is the one we'll be opening and the
+; latter is only used for 32-bit initialization.
+GLOBALNAME g_VBoxGuestHdr1
+ dw NAME(g_VBoxGuestHdr2) wrt DATA16 ; NextHeader.off
+ dw DATA16 ; NextHeader.sel
+ dw DEVLEV_3 | DEV_30 | DEV_CHAR_DEV | DEV_IOCTL; SDevAtt
+ dw NAME(VGDrvOS2Entrypoint) wrt CODE16 ; StrategyEP
+ dw NAME(VGDrvOS2IDC) wrt CODE16 ; IDCEP
+ db 'vboxgst$' ; DevName
+ dw 0 ; SDevProtCS
+ dw 0 ; SDevProtDS
+ dw 0 ; SDevRealCS
+ dw 0 ; SDevRealDS
+ dd DEV_16MB | DEV_IOCTL2 ; SDevCaps
+
+align 4
+GLOBALNAME g_VBoxGuestHdr2
+ dd 0ffffffffh ; NextHeader (NIL)
+ dw DEVLEV_3 | DEV_30 | DEV_CHAR_DEV ; SDevAtt
+ dw NAME(vgdrvOS2InitEntrypoint) wrt CODE16 ; StrategyEP
+ dw 0 ; IDCEP
+ db 'vboxgs1$' ; DevName
+ dw 0 ; SDevProtCS
+ dw 0 ; SDevProtDS
+ dw 0 ; SDevRealCS
+ dw 0 ; SDevRealDS
+ dd DEV_16MB | DEV_IOCTL2 ; SDevCaps
+
+
+;; Tristate 32-bit initialization indicator [0 = need init, -1 = init failed, 1 init succeeded].
+; Check in the open path of the primary driver. The secondary driver will
+; open the primary one during it's init and thereby trigger the 32-bit init.
+GLOBALNAME g_fInitialized
+ db 0
+
+align 4
+;; Pointer to the device helper service routine
+; This is set during the initialization of the 2nd device driver.
+GLOBALNAME g_fpfnDevHlp
+ dd 0
+
+
+;; vgdrvFindAdapter Output
+; @{
+
+;; The MMIO base of the VMMDev.
+GLOBALNAME g_PhysMMIOBase
+ dd 0
+;; The size of the MMIO memory of the VMMDev.
+GLOBALNAME g_cbMMIO
+ dd 0
+;; The I/O port base of the VMMDev.
+GLOBALNAME g_IOPortBase
+ dw 0
+;; The VMMDev Interrupt Line.
+GLOBALNAME g_bInterruptLine
+ db 0
+;; The PCI bus number returned by Find PCI Device.
+GLOBALNAME g_bPciBusNo
+ db 0
+;; The PCI Device No / Function Number returned by Find PCI Device.
+; (The upper 5 bits is the number, and the lower 3 the function.)
+GLOBALNAME g_bPciDevFunNo
+ db 0
+;; Flag that is set by the vboxgst$ init routine if VMMDev was found.
+; Both init routines must refuse loading the driver if the
+; device cannot be located.
+GLOBALNAME g_fFoundAdapter
+ db 0
+;; @}
+
+
+%ifdef DEBUG_READ
+;; Where we write to the log.
+GLOBALNAME g_offLogHead
+ dw 0
+;; Where we read from the log.
+GLOBALNAME g_offLogTail
+ dw 0
+;; The size of the log. (power of two!)
+%define LOG_SIZE 16384
+GLOBALNAME g_cchLogMax
+ dw LOG_SIZE
+;; The log buffer.
+GLOBALNAME g_szLog
+ times LOG_SIZE db 0
+%endif ; DEBUG_READ
+
+
+;
+; The init data.
+;
+segment DATA16_INIT
+GLOBALNAME g_InitDataStart
+
+;; Far pointer to the device argument.
+g_fpszArgs:
+ dd 0
+
+%if 0
+;; Message table for the Save_Message device helper.
+GLOBALNAME g_MsgTab
+ dw 1178 ; MsgId - 'MSG_REPLACEMENT_STRING'.
+ dw 1 ; cMsgStrings
+ dw NAME(g_szInitText) ; MsgStrings[0]
+ dw seg NAME(g_szInitText)
+%else
+;; Far pointer to DOS16WRITE (corrected set before called).
+; Just a temporary hack to work around a wlink issue.
+GLOBALNAME g_fpfnDos16Write
+ dw DOS16WRITE
+ dw seg DOS16WRITE
+%endif
+
+;; Size of the text currently in the g_szInitText buffer.
+GLOBALNAME g_cchInitText
+ dw 0
+;; The max size of text that can fit into the g_szInitText buffer.
+GLOBALNAME g_cchInitTextMax
+ dw 512
+;; The init text buffer.
+GLOBALNAME g_szInitText
+ times 512 db 0
+
+;; Message string that's written on failure.
+g_achLoadFailureMsg1:
+ db 0dh,0ah,'VBoxGuest: load failure no. '
+g_cchLoadFailureMsg1 EQU $ - g_achLoadFailureMsg1
+g_achLoadFailureMsg2:
+ db '!',0dh,0ah
+g_cchLoadFailureMsg2 EQU $ - g_achLoadFailureMsg2
+
+
+;
+; The 16-bit code segment.
+;
+segment CODE16
+
+
+;;
+; The strategy entry point (vboxdrv$).
+;
+; ss:bx -> request packet
+; ds:si -> device header
+;
+; Can clobber any registers it likes except SP.
+;
+BEGINPROC VGDrvOS2Entrypoint
+ push ebp
+ mov ebp, esp
+ push es ; bp - 2
+ push bx ; bp - 4
+ and sp, 0fffch
+
+ ;
+ ; Check for the most frequent first.
+ ;
+ cmp byte [es:bx + PKTHDR.cmd], 10h ; Generic IOCtl
+ jne near vgdrvOS2EP_NotGenIOCtl
+
+
+ ;
+ ; Generic I/O Control Request.
+ ;
+vgdrvOS2EP_GenIOCtl:
+
+ ; Fast IOCtl?
+ cmp byte [es:bx + PKTIOCTL.cat], VBGL_IOCTL_CATEGORY_FAST
+ jne vgdrvOS2EP_GenIOCtl_Other
+
+ ;
+ ; Fast IOCtl.
+ ; DECLASM(int) vgdrvOS2IOCtlFast(uint16_t sfn, uint8_t iFunction, uint16_t *pcbParm)
+ ;
+vgdrvOS2EP_GenIOCtl_Fast:
+ mov ax, [es:bx + PKTIOCTL.pData + 2] ; LDT selector to flat address.
+ shr ax, 3
+ shl eax, 16
+ mov ax, [es:bx + PKTIOCTL.pData]
+ push eax ; 08h - pointer to the rc buffer.
+
+ ; function.
+ movzx edx, byte [es:bx + PKTIOCTL.fun]
+ push edx ; 04h
+
+ ; system file number.
+ movzx eax, word [es:bx + PKTIOCTL.sfn]
+ push eax ; 00h
+
+ JMP16TO32 vgdrvOS2EP_GenIOCtl_Fast_32
+segment TEXT32
+GLOBALNAME vgdrvOS2EP_GenIOCtl_Fast_32
+
+ ; switch stack to 32-bit.
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+ call KernThunkStackTo32
+
+ ; call the C code (don't cleanup the stack).
+ call NAME(vgdrvOS2IOCtlFast)
+
+ ; switch back the stack.
+ push eax
+ call KernThunkStackTo16
+ pop eax
+
+ JMP32TO16 vgdrvOS2EP_GenIOCtl_Fast_32
+segment CODE16
+GLOBALNAME vgdrvOS2EP_GenIOCtl_Fast_16
+
+ les bx, [bp - 4] ; Reload the packet pointer.
+ or eax, eax
+ jnz near vgdrvOS2EP_GeneralFailure
+
+ ; setup output stuff.
+ mov edx, esp
+ mov eax, [ss:edx + 0ch] ; output sizes.
+ mov [es:bx + PKTIOCTL.cbParm], eax ; update cbParm and cbData.
+ mov word [es:bx + PKTHDR.status], 00100h ; done, ok.
+
+ mov sp, bp
+ pop ebp
+ retf
+
+ ;
+ ; Other IOCtl (slow)
+ ;
+vgdrvOS2EP_GenIOCtl_Other:
+ mov eax, [es:bx + PKTIOCTL.cbParm] ; Load cbParm and cbData
+ push eax ; 1eh - in/out data size.
+ ; 1ch - in/out parameter size.
+ push edx ; 18h - pointer to data size (filled in later).
+ push ecx ; 14h - pointer to param size (filled in later).
+
+ ; pData (convert to flat 32-bit)
+ mov ax, word [es:bx + PKTIOCTL.pData + 2] ; selector
+ cmp ax, 3 ; <= 3 -> nil selector...
+ jbe .no_data
+ movzx esi, word [es:bx + PKTIOCTL.pData] ; offset
+ mov dl, DevHlp_VirtToLin
+ call far [NAME(g_fpfnDevHlp)]
+ jc near vgdrvOS2EP_GeneralFailure
+ jmp .finish_data
+.no_data:
+ xor eax, eax
+.finish_data:
+ push eax ; 10h
+
+ ; pParm (convert to flat 32-bit)
+ mov ax, word [es:bx + PKTIOCTL.pParm + 2] ; selector
+ cmp ax, 3 ; <= 3 -> nil selector...
+ jbe .no_parm
+ movzx esi, word [es:bx + PKTIOCTL.pParm] ; offset
+ mov dl, DevHlp_VirtToLin
+ call far [NAME(g_fpfnDevHlp)]
+ jc near vgdrvOS2EP_GeneralFailure
+ jmp .finish_parm
+.no_parm:
+ xor eax, eax
+.finish_parm:
+ push eax ; 0ch
+
+ ; function.
+ movzx edx, byte [es:bx + PKTIOCTL.fun]
+ push edx ; 08h
+
+ ; category.
+ movzx ecx, byte [es:bx + PKTIOCTL.cat]
+ push ecx ; 04h
+
+ ; system file number.
+ movzx eax, word [es:bx + PKTIOCTL.sfn]
+ push eax ; 00h
+
+ JMP16TO32 vgdrvOS2EP_GenIOCtl_Other_32
+segment TEXT32
+GLOBALNAME vgdrvOS2EP_GenIOCtl_Other_32
+
+ ; switch stack to 32-bit.
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+ call KernThunkStackTo32
+
+ ; update in/out parameter pointers
+ lea eax, [esp + 1ch]
+ mov [esp + 14h], eax
+ lea edx, [esp + 1eh]
+ mov [esp + 18h], edx
+
+ ; call the C code (don't cleanup the stack).
+ call NAME(vgdrvOS2IOCtl)
+
+ ; switch back the stack.
+ push eax
+ call KernThunkStackTo16
+ pop eax
+
+ JMP32TO16 vgdrvOS2EP_GenIOCtl_Other_16
+segment CODE16
+GLOBALNAME vgdrvOS2EP_GenIOCtl_Other_16
+
+ les bx, [bp - 4] ; Reload the packet pointer.
+ or eax, eax
+ jnz near vgdrvOS2EP_GeneralFailure
+
+ ; setup output stuff.
+ mov edx, esp
+ mov eax, [ss:edx + 1ch] ; output sizes.
+ mov [es:bx + PKTIOCTL.cbParm], eax ; update cbParm and cbData.
+ mov word [es:bx + PKTHDR.status], 00100h ; done, ok.
+
+ mov sp, bp
+ pop ebp
+ retf
+
+
+ ;
+ ; Less Performance Critical Requests.
+ ;
+vgdrvOS2EP_NotGenIOCtl:
+ cmp byte [es:bx + PKTHDR.cmd], 0dh ; Open
+ je vgdrvOS2EP_Open
+ cmp byte [es:bx + PKTHDR.cmd], 0eh ; Close
+ je vgdrvOS2EP_Close
+ cmp byte [es:bx + PKTHDR.cmd], 00h ; Init
+ je vgdrvOS2EP_Init
+%ifdef DEBUG_READ
+ cmp byte [es:bx + PKTHDR.cmd], 04h ; Read
+ je near vgdrvOS2EP_Read
+%endif
+ jmp near vgdrvOS2EP_NotSupported
+
+
+ ;
+ ; Open Request. w/ ring-0 init.
+ ;
+vgdrvOS2EP_Open:
+ cmp byte [NAME(g_fInitialized)], 1
+ jne vgdrvOS2EP_OpenOther
+
+ ; First argument, the system file number.
+ movzx eax, word [es:bx + PKTOPEN.sfn]
+ push eax
+
+ JMP16TO32 vgdrvOS2EP_Open_32
+segment TEXT32
+GLOBALNAME vgdrvOS2EP_Open_32
+
+ ; switch stack to 32-bit.
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+ call KernThunkStackTo32
+
+ ; call the C code.
+ call NAME(vgdrvOS2Open)
+
+ ; switch back the stack.
+ push eax
+ call KernThunkStackTo16
+ pop eax
+
+ JMP32TO16 vgdrvOS2EP_Open_16
+segment CODE16
+GLOBALNAME vgdrvOS2EP_Open_16
+
+ les bx, [bp - 4] ; Reload the packet pointer.
+ or eax, eax
+ jnz near vgdrvOS2EP_GeneralFailure
+ mov word [es:bx + PKTHDR.status], 00100h ; done, ok.
+ jmp near vgdrvOS2EP_Done
+
+ ; Initializing or failed init?
+vgdrvOS2EP_OpenOther:
+ cmp byte [NAME(g_fInitialized)], 0
+ jne vgdrvOS2EP_OpenFailed
+
+ mov byte [NAME(g_fInitialized)], -1
+ call NAME(vgdrvRing0Init)
+ cmp byte [NAME(g_fInitialized)], 1
+ je vgdrvOS2EP_Open
+
+vgdrvOS2EP_OpenFailed:
+ mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed.
+ jmp near vgdrvOS2EP_Done
+
+
+ ;
+ ; Close Request.
+ ;
+vgdrvOS2EP_Close:
+ ; First argument, the system file number.
+ movzx eax, word [es:bx + PKTOPEN.sfn]
+ push eax
+
+ JMP16TO32 vgdrvOS2EP_Close_32
+segment TEXT32
+GLOBALNAME vgdrvOS2EP_Close_32
+
+ ; switch stack to 32-bit.
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+ call KernThunkStackTo32
+
+ ; call the C code.
+ call NAME(vgdrvOS2Close)
+
+ ; switch back the stack.
+ push eax
+ call KernThunkStackTo16
+ pop eax
+
+ JMP32TO16 vgdrvOS2EP_Close_16
+segment CODE16
+GLOBALNAME vgdrvOS2EP_Close_16
+
+ les bx, [bp - 4] ; Reload the packet pointer.
+ or eax, eax
+ jnz near vgdrvOS2EP_GeneralFailure
+ mov word [es:bx + PKTHDR.status], 00100h ; done, ok.
+ jmp near vgdrvOS2EP_Done
+
+
+ ;
+ ; Init Request.
+ ; Find the VMMDev adapter so we can unload the driver (and avoid trouble) if not found.
+ ;
+vgdrvOS2EP_Init:
+ call NAME(vgdrvFindAdapter)
+ test ax, ax
+ jz .ok
+ mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed.
+ call NAME(vgdrvOS2InitFlushText)
+ jmp .next
+.ok:
+ mov word [es:bx + PKTHDR.status], 00100h ; done, ok.
+.next:
+ mov byte [es:bx + PKTINITOUT.cUnits], 0
+ mov word [es:bx + PKTINITOUT.cbCode16], NAME(g_InitCodeStart) wrt CODE16
+ mov word [es:bx + PKTINITOUT.cbData16], NAME(g_InitDataStart) wrt DATA16
+ mov dword [es:bx + PKTINITOUT.fpaBPBs], 0
+ jmp near vgdrvOS2EP_Done
+
+
+%ifdef DEBUG_READ
+ ;
+ ; Read Request.
+ ; Return log data.
+ ;
+vgdrvOS2EP_Read:
+ ; Any log data available?
+ xor dx, dx
+ mov ax, [NAME(g_offLogTail)]
+ cmp ax, [NAME(g_offLogHead)]
+ jz near .log_done
+
+ ; create a temporary mapping of the physical buffer. Docs claims it trashes nearly everything...
+ push ebp
+ mov cx, [es:bx + PKTRW.cbTrans]
+ push cx
+ mov ax, [es:bx + PKTRW.PhysTrans + 2]
+ mov bx, [es:bx + PKTRW.PhysTrans]
+ mov dh, 1
+ mov dl, DevHlp_PhysToVirt
+ call far [NAME(g_fpfnDevHlp)]
+ pop bx ; bx = cbTrans
+ pop ebp
+ jc near .log_phystovirt_failed
+ ; es:di -> the output buffer.
+
+ ; setup the copy operation.
+ mov ax, [NAME(g_offLogTail)]
+ xor dx, dx ; dx tracks the number of bytes copied.
+.log_loop:
+ mov cx, [NAME(g_offLogHead)]
+ cmp ax, cx
+ je .log_done
+ jb .log_loop_before
+ mov cx, LOG_SIZE
+.log_loop_before: ; cx = end offset
+ sub cx, ax ; cx = sequential bytes to copy.
+ cmp cx, bx
+ jbe .log_loop_min
+ mov cx, bx ; output buffer is smaller than available data.
+.log_loop_min:
+ mov si, NAME(g_szLog)
+ add si, ax ; ds:si -> the log buffer.
+ add dx, cx ; update output counter
+ add ax, cx ; calc new offLogTail
+ and ax, LOG_SIZE - 1
+ rep movsb ; do the copy
+ mov [NAME(g_offLogTail)], ax ; commit the read.
+ jmp .log_loop
+
+.log_done:
+ les bx, [bp - 4] ; Reload the packet pointer.
+ mov word [es:bx + PKTRW.cbTrans], dx
+ mov word [es:bx + PKTHDR.status], 00100h ; done, ok.
+ jmp near vgdrvOS2EP_Done
+
+.log_phystovirt_failed:
+ les bx, [bp - 4] ; Reload the packet pointer.
+ jmp vgdrvOS2EP_GeneralFailure
+%endif ; DEBUG_READ
+
+
+ ;
+ ; Return 'unknown command' error.
+ ;
+vgdrvOS2EP_NotSupported:
+ mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command.
+ jmp vgdrvOS2EP_Done
+
+ ;
+ ; Return 'general failure' error.
+ ;
+vgdrvOS2EP_GeneralFailure:
+ mov word [es:bx + PKTHDR.status], 0810ch ; error, done, general failure.
+ jmp vgdrvOS2EP_Done
+
+ ;
+ ; Non-optimized return path.
+ ;
+vgdrvOS2EP_Done:
+ mov sp, bp
+ pop ebp
+ retf
+ENDPROC VGDrvOS2Entrypoint
+
+
+;;
+; The helper device entry point.
+;
+; This is only used to do the DosOpen on the main driver so we can
+; do ring-3 init and report failures.
+;
+GLOBALNAME vgdrvOS2InitEntrypoint
+ ; The only request we're servicing is the 'init' one.
+ cmp word [es:bx + PKTHDR.cmd], 0
+ je near NAME(vgdrvOS2InitEntrypointServiceInitReq)
+
+ ; Ok, it's not the init request, just fail it.
+ mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command.
+ retf
+
+
+;;
+; The OS/2 IDC entry point.
+;
+; This is only used to setup connection, the returned structure
+; will provide the entry points that we'll be using.
+;
+; @cproto void far __cdecl VGDrvOS2IDC(VBOXGUESTOS2IDCCONNECT far *fpConnectInfo);
+;
+; @param fpConnectInfo [bp + 8] Pointer to an VBOXGUESTOS2IDCCONNECT structure.
+;
+GLOBALNAME VGDrvOS2IDC
+ push ebp ; bp - 0h
+ mov ebp, esp
+ ; save everything we might touch.
+ push es ; bp - 2h
+ push ds ; bp - 4h
+ push eax ; bp - 8h
+ push ebx ; bp - 0ch
+ push ecx ; bp - 10h
+ push edx ; bp - 14h
+ and sp, 0fffch
+
+ JMP16TO32 VGDrvOS2IDC_32
+segment TEXT32
+GLOBALNAME VGDrvOS2IDC_32
+
+ ; switch stack to 32-bit.
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+ call KernThunkStackTo32
+
+ ; call the C code.
+ call NAME(vgdrvOS2IDCConnect)
+
+ ;
+ ; Load the return buffer address into ds:ebx and setup the buffer.
+ ; (eax == u32Session)
+ ;
+ mov cx, [ebp + 08h + 2]
+ mov ds, cx
+ movzx ebx, word [ebp + 08h]
+
+ mov dword [ebx + VBGLOS2ATTACHDD.u32Version ], VBGL_IOC_VERSION
+ mov dword [ebx + VBGLOS2ATTACHDD.u32Session ], eax
+ mov dword [ebx + VBGLOS2ATTACHDD.pfnServiceEP ], NAME(VGDrvOS2IDCService)
+ mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceEP ], NAME(VGDrvOS2IDCService16) wrt CODE16
+ mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceEP + 2], CODE16
+ mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceAsmEP ], NAME(VGDrvOS2IDCService16Asm) wrt CODE16
+ mov word [ebx + VBGLOS2ATTACHDD.fpfnServiceAsmEP+2],CODE16
+
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+
+ ; switch back the stack.
+ call KernThunkStackTo16
+
+ JMP32TO16 VGDrvOS2IDC_16
+segment CODE16
+GLOBALNAME VGDrvOS2IDC_16
+
+ ; restore.
+ lea sp, [bp - 14h]
+ pop edx
+ pop ecx
+ pop ebx
+ pop eax
+ pop ds
+ pop es
+ pop ebp
+ retf
+ENDPROC VGDrvOS2IDC
+
+
+;;
+; The 16-bit IDC entry point, cdecl.
+;
+; All this does is thunking the request into something that fits
+; the 32-bit IDC service routine.
+;
+;
+; @returns VBox status code.
+; @param u32Session bp + 8h - The above session handle.
+; @param iFunction bp + 0ch - The requested function.
+; @param fpReqHdr bp + 0eh - The input/output data buffer. The caller ensures that this
+; cannot be swapped out, or that it's acceptable to take a
+; page in fault in the current context. If the request doesn't
+; take input or produces output, passing NULL is okay.
+; @param cbReq bp + 12h - The size of the data buffer.
+;
+; @cproto long far __cdecl VGDrvOS2IDCService16(uint32_t u32Session, uint16_t iFunction, void far *fpReqHdr, uint16_t cbReq);
+;
+GLOBALNAME VGDrvOS2IDCService16
+ push ebp ; bp - 0h
+ mov ebp, esp
+ push es ; bp - 2h
+ push ds ; bp - 4h
+ push ecx ; bp - 8h
+ push edx ; bp - 0ch
+ push esi ; bp - 10h
+ and sp, 0fffch ; align the stack.
+
+ ; locals
+ push dword 0 ; esp + 18h (dd): cbDataReturned
+
+ ; load our ds (for g_fpfnDevHlp).
+ mov ax, DATA16
+ mov ds, ax
+
+ ;
+ ; Create the call frame before switching.
+ ;
+ movzx ecx, word [bp + 12h]
+ push ecx ; esp + 10h: cbData
+
+ ; thunk data argument if present.
+ mov ax, [bp + 0eh + 2] ; selector
+ cmp ax, 3 ; <= 3 -> nil selector...
+ jbe .no_data
+ movzx esi, word [bp + 0eh] ; offset
+ mov dl, DevHlp_VirtToLin
+ call far [NAME(g_fpfnDevHlp)]
+ jc near VGDrvOS2IDCService16_InvalidPointer
+ jmp .finish_data
+.no_data:
+ xor eax, eax
+.finish_data:
+ push eax ; esp + 08h: pvData
+ movzx edx, word [bp + 0ch]
+ push edx ; esp + 04h: iFunction
+ mov ecx, [bp + 08h]
+ push ecx ; esp + 00h: u32Session
+
+ JMP16TO32 VGDrvOS2IDCService16_32
+segment TEXT32
+GLOBALNAME VGDrvOS2IDCService16_32
+
+ ; switch stack to 32-bit.
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+ call KernThunkStackTo32
+
+ ; call the C code (don't cleanup the stack).
+ call NAME(VGDrvOS2IDCService)
+
+ ; switch back the stack.
+ push eax
+ call KernThunkStackTo16
+ pop eax
+
+ JMP32TO16 VGDrvOS2IDCService16_16
+segment CODE16
+GLOBALNAME VGDrvOS2IDCService16_16
+
+VGDrvOS2IDCService16_Done:
+ lea sp, [bp - 10h]
+ pop esi
+ pop edx
+ pop ecx
+ pop ds
+ pop es
+ pop ebp
+ retf
+
+VGDrvOS2IDCService16_InvalidPointer:
+ mov ax, VERR_INVALID_POINTER
+ jmp VGDrvOS2IDCService16_Done
+ENDPROC VGDrvOS2IDCService16
+
+
+;;
+; The 16-bit IDC entry point, register based.
+;
+; This is just a wrapper around VGDrvOS2IDCService16 to simplify
+; calls from 16-bit assembly code.
+;
+; @returns ax: VBox status code; cx: The amount of data returned.
+;
+; @param u32Session eax - The above session handle.
+; @param iFunction dl - The requested function.
+; @param pvData es:bx - The input/output data buffer.
+; @param cbData cx - The size of the data buffer.
+;
+GLOBALNAME VGDrvOS2IDCService16Asm
+ push ebp ; bp - 0h
+ mov ebp, esp
+ push edx ; bp - 4h
+
+ push cx ; cbData
+ push es
+ xor dh, dh
+ push dx
+ push eax
+ call NAME(VGDrvOS2IDCService16)
+
+ mov cx, [es:bx + VBGLREQHDR.cbOut]
+
+ mov edx, [bp - 4]
+ mov esp, ebp
+ pop ebp
+ retf
+ENDPROC VGDrvOS2IDCService16Asm
+
+
+
+;;
+; The 16-bit interrupt service routine.
+;
+; OS/2 saves all registers according to the docs, although it doesn't say whether
+; this includes the 32-bit parts. Since it doesn't cost much to be careful, save
+; everything.
+;
+; @returns CF=0 if it's our interrupt, CF=1 it it isn't.
+;
+;
+GLOBALNAME vgdrvOS2ISR16
+ push ebp
+ mov ebp, esp
+ pushf ; bp - 02h
+ cli
+ push eax ; bp - 06h
+ push edx ; bp - 0ah
+ push ebx ; bp - 0eh
+ push ds ; bp - 10h
+ push es ; bp - 12h
+ push ecx ; bp - 16h
+ push esi ; bp - 1ah
+ push edi ; bp - 1eh
+
+ and sp, 0fff0h ; align the stack (16-bytes make GCC extremely happy).
+
+ JMP16TO32 vgdrvOS2ISR16_32
+segment TEXT32
+GLOBALNAME vgdrvOS2ISR16_32
+
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+
+ call KernThunkStackTo32
+
+ call NAME(vgdrvOS2ISR)
+ mov ebx, eax
+
+ call KernThunkStackTo16
+
+ JMP32TO16 vgdrvOS2ISR16_16
+segment CODE16
+GLOBALNAME vgdrvOS2ISR16_16
+
+ lea sp, [bp - 1eh]
+ pop edi
+ pop esi
+ pop ecx
+ pop es
+ pop ds
+ test bl, 0ffh
+ jnz .our
+ pop ebx
+ pop edx
+ pop eax
+ popf
+ pop ebp
+ stc
+ retf
+
+ ;
+ ; Do EIO.
+ ;
+.our:
+ mov al, [NAME(g_bInterruptLine)]
+ mov dl, DevHlp_EOI
+ call far [NAME(g_fpfnDevHlp)]
+
+ pop ebx
+ pop edx
+ pop eax
+ popf
+ pop ebp
+ clc
+ retf
+ENDPROC vgdrvOS2ISR16
+
+
+
+
+
+
+;
+; The 32-bit text segment.
+;
+segment TEXT32
+;;
+; 32-bit worker for registering the ISR.
+;
+; @returns 0 on success, some non-zero OS/2 error code on failure.
+; @param bIrq [ebp + 8] The IRQ number. (uint8_t)
+;
+GLOBALNAME vgdrvOS2DevHlpSetIRQ
+ push ebp
+ mov ebp, esp
+ push ebx
+ push ds
+
+ call KernThunkStackTo16
+
+ movzx ebx, byte [ebp + 8] ; load bIrq into BX.
+
+ JMP32TO16 vgdrvOS2DevHlpSetIRQ_16
+segment CODE16
+GLOBALNAME vgdrvOS2DevHlpSetIRQ_16
+
+ mov ax, DATA16 ; for g_fpfnDevHlp.
+ mov ds, ax
+ mov ax, NAME(vgdrvOS2ISR16) ; The devhlp assume it's relative to DS.
+ mov dh, 1 ; 1 = shared
+ mov dl, DevHlp_SetIRQ
+ call far [NAME(g_fpfnDevHlp)]
+ jnc .ok
+ movzx eax, ax
+ or eax, eax
+ jnz .go_back
+ or eax, 6
+ jmp .go_back
+.ok:
+ xor eax, eax
+
+.go_back:
+ JMP16TO32 vgdrvOS2DevHlpSetIRQ_32
+segment TEXT32
+GLOBALNAME vgdrvOS2DevHlpSetIRQ_32
+
+ pop ds ; KernThunkStackTo32 ASSUMES flat DS and ES.
+
+ mov ebx, eax
+ call KernThunkStackTo32
+ mov eax, ebx
+
+ pop ebx
+ pop ebp
+ ret
+ENDPROC vgdrvOS2DevHlpSetIRQ
+
+
+
+
+;
+; The 16-bit init code.
+;
+segment CODE16_INIT
+GLOBALNAME g_InitCodeStart
+
+;; The device name for DosOpen.
+g_szDeviceName:
+ db '\DEV\vboxgst$', 0
+
+; icsdebug can't see where stuff starts otherwise. (kDevTest)
+int3
+int3
+int3
+int3
+int3
+int3
+
+;;
+; The Ring-3 init code.
+;
+BEGINPROC vgdrvOS2InitEntrypointServiceInitReq
+ push ebp
+ mov ebp, esp
+ push es ; bp - 2
+ push sp ; bp - 4
+ push -1 ; bp - 6: hfOpen
+ push 0 ; bp - 8: usAction
+ and sp, 0fffch
+
+ ; check for the init package.
+ cmp word [es:bx + PKTHDR.cmd], 0
+ jne near .not_init
+
+ ; check that we found the VMMDev.
+ test byte [NAME(g_fFoundAdapter)], 1
+ jz near .done_err
+
+ ;
+ ; Copy the data out of the init packet.
+ ;
+ mov eax, [es:bx + PKTINITIN.fpfnDevHlp]
+ mov [NAME(g_fpfnDevHlp)], eax
+ mov edx, [es:bx + PKTINITIN.fpszArgs]
+ mov [g_fpszArgs], edx
+
+ ;
+ ; Open the first driver, close it, and check status.
+ ;
+
+ ; APIRET _Pascal DosOpen(PSZ pszFname, PHFILE phfOpen, PUSHORT pusAction,
+ ; ULONG ulFSize, USHORT usAttr, USHORT fsOpenFlags,
+ ; USHORT fsOpenMode, ULONG ulReserved);
+ push seg g_szDeviceName ; pszFname
+ push g_szDeviceName
+ push ss ; phfOpen
+ lea dx, [bp - 6]
+ push dx
+ push ss ; pusAction
+ lea dx, [bp - 8]
+ push dx
+ push dword 0 ; ulFSize
+ push 0 ; usAttr = FILE_NORMAL
+ push 1 ; fsOpenFlags = FILE_OPEN
+ push 00040h ; fsOpenMode = OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY
+ push dword 0 ; ulReserved
+ call far DOS16OPEN
+
+ push ax ; Quickly flush any text.
+ call NAME(vgdrvOS2InitFlushText)
+ pop ax
+
+ or ax, ax
+ jnz .done_err
+
+ ; APIRET APIENTRY DosClose(HFILE hf);
+ mov cx, [bp - 6]
+ push cx
+ call far DOS16CLOSE
+ or ax, ax
+ jnz .done_err ; This can't happen (I hope).
+
+ ;
+ ; Ok, we're good.
+ ;
+ mov word [es:bx + PKTHDR.status], 00100h ; done, ok.
+ mov byte [es:bx + PKTINITOUT.cUnits], 0
+ mov word [es:bx + PKTINITOUT.cbCode16], NAME(g_InitCodeStart) wrt CODE16
+ mov word [es:bx + PKTINITOUT.cbData16], NAME(g_InitDataStart) wrt DATA16
+ mov dword [es:bx + PKTINITOUT.fpaBPBs], 0
+ jmp .done
+
+ ;
+ ; Init failure.
+ ;
+.done_err:
+ mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed.
+ mov byte [es:bx + PKTINITOUT.cUnits], 0
+ mov word [es:bx + PKTINITOUT.cbCode16], 0
+ mov word [es:bx + PKTINITOUT.cbData16], 0
+ mov dword [es:bx + PKTINITOUT.fpaBPBs], 0
+ jmp .done
+
+ ;
+ ; Not init, return 'unknown command'.
+ ;
+.not_init:
+ mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command.
+ jmp .done
+
+ ;
+ ; Request done.
+ ;
+.done:
+ mov sp, bp
+ pop ebp
+ retf
+ENDPROC vgdrvOS2InitEntrypointServiceInitReq
+
+
+;;
+; The Ring-0 init code.
+;
+BEGINPROC vgdrvRing0Init
+ push es
+ push esi
+ push ebp
+ mov ebp, esp
+ and sp, 0fffch
+
+ ;
+ ; Thunk the argument string pointer first.
+ ;
+ movzx esi, word [g_fpszArgs] ; offset
+ mov ax, [g_fpszArgs + 2] ; selector
+ mov dl, DevHlp_VirtToLin
+ call far [NAME(g_fpfnDevHlp)]
+ jc near vgdrvRing0Init_done ; eax is non-zero on failure (can't happen)
+ push eax ; 00h - pszArgs (for vgdrvOS2Init).
+
+ ;
+ ; Do 16-bit init?
+ ;
+
+
+ ;
+ ; Do 32-bit init
+ ;
+ JMP16TO32 vgdrvRing0Init_32
+segment TEXT32
+GLOBALNAME vgdrvRing0Init_32
+
+ ; switch stack to 32-bit.
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov es, ax
+ call KernThunkStackTo32
+
+ ; call the C code.
+ call NAME(vgdrvOS2Init)
+
+ ; switch back the stack and reload ds.
+ push eax
+ call KernThunkStackTo16
+ pop eax
+
+ mov dx, seg NAME(g_fInitialized)
+ mov ds, dx
+
+ JMP32TO16 vgdrvRing0Init_16
+segment CODE16_INIT
+GLOBALNAME vgdrvRing0Init_16
+
+ ; check the result and set g_fInitialized on success.
+ or eax, eax
+ jnz vgdrvRing0Init_done
+ mov byte [NAME(g_fInitialized)], 1
+
+vgdrvRing0Init_done:
+ mov sp, bp
+ pop ebp
+ pop esi
+ pop es
+ ret
+ENDPROC vgdrvRing0Init
+
+
+;;
+; Flush any text in the text buffer.
+;
+BEGINPROC vgdrvOS2InitFlushText
+ push bp
+ mov bp, sp
+
+ ; Anything in the buffer?
+ mov ax, [NAME(g_cchInitText)]
+ or ax, ax
+ jz .done
+
+%if 1
+ ; Write it to STDOUT.
+ ; APIRET _Pascal DosWrite(HFILE hf, PVOID pvBuf, USHORT cbBuf, PUSHORT pcbBytesWritten);
+ push ax ; bp - 2 : cbBytesWritten
+ mov cx, sp
+ push 1 ; STDOUT
+ push seg NAME(g_szInitText) ; pvBuf
+ push NAME(g_szInitText)
+ push ax ; cbBuf
+ push ss ; pcbBytesWritten
+ push cx
+%if 0 ; wlink generates a non-aliased fixup here which results in 16-bit offset with the flat 32-bit selector.
+ call far DOS16WRITE
+%else
+ ; convert flat pointer to a far pointer using the tiled algorithm.
+ push ds
+ mov ax, DATA32 wrt FLAT
+ mov ds, ax
+ mov eax, g_pfnDos16Write wrt FLAT
+ movzx eax, word [eax + 2] ; High word of the flat address (in DATA32).
+ shl ax, 3
+ or ax, 0007h
+ pop ds
+ mov [NAME(g_fpfnDos16Write) + 2], ax ; Update the selector (in DATA16_INIT).
+ ; do the call
+ call far [NAME(g_fpfnDos16Write)]
+%endif
+
+%else ; alternative workaround for the wlink issue.
+ ; Use the save message devhlp.
+ push esi
+ push ebx
+ xor bx, bx
+ mov si, NAME(g_MsgTab)
+ mov dx, seg NAME(g_MsgTab)
+ mov ds, dx
+ mov dl, DevHlp_SAVE_MESSAGE
+ call far [NAME(g_fpfnDevHlp)]
+ pop ebx
+ pop esi
+%endif
+
+ ; Empty the buffer.
+ mov word [NAME(g_cchInitText)], 0
+ mov byte [NAME(g_szInitText)], 0
+
+.done:
+ mov sp, bp
+ pop bp
+ ret
+ENDPROC vgdrvOS2InitFlushText
+
+
+;; The device name for DosOpen.
+g_szOemHlpDevName:
+ db '\DEV\OEMHLP$', 0
+
+
+;;
+; Talks to OEMHLP$ about finding the VMMDev PCI adapter.
+;
+; On success g_fFoundAdapter is set to 1, and g_cbMMIO,
+; g_PhysMMIOBase and g_IOPortBase are initialized with
+; the PCI data.
+;
+; @returns 0 on success, non-zero on failure. (eax)
+;
+; @remark ASSUMES DS:DATA16.
+; @uses nothing.
+;
+BEGINPROC vgdrvFindAdapter
+ push ebx
+ push ecx
+ push edx
+ push esi
+ push edi
+ push ebp
+ mov ebp, esp
+ push -1 ; bp - 2: hfOpen
+%define hfOpen bp - 2
+ push 0 ; bp - 4: usAction
+%define usAction bp - 4
+ sub sp, 20h ; bp - 44: 32 byte parameter buffer.
+%define abParm bp - 24h
+ sub sp, 40h ; bp - c4: 32 byte data buffer.
+%define abData bp - 44h
+
+;; VBox stuff
+%define VBOX_PCI_VENDORID 080eeh
+%define VMMDEV_DEVICEID 0cafeh
+
+;; OEMHLP$ stuff.
+%define IOCTL_OEMHLP 80h
+%define OEMHLP_PCI 0bh
+%define PCI_FIND_DEVICE 1
+%define PCI_READ_CONFIG 3
+
+;; PCI stuff
+%define PCI_INTERRUPT_LINE 03ch ;;< 8-bit RW - Interrupt line.
+%define PCI_BASE_ADDRESS_0 010h ;;< 32-bit RW */
+%define PCI_BASE_ADDRESS_1 014h ;;< 32-bit RW */
+
+
+%macro CallIOCtl 2
+ ; APIRET _Pascal DosDevIOCtl2(PVOID pData, USHORT cbData, PVOID pParm,
+ ; USHORT cbParm, USHORT usFun, USHORT usCategory,
+ ; HFILE hDev);
+ push ss ; pData
+ lea dx, [abData]
+ push dx
+ push %2 ; cbData
+
+ push ss ; pParm
+ lea dx, [abParm]
+ push dx
+ push %1 ; cbParm
+ push OEMHLP_PCI ; usFun
+ push IOCTL_OEMHLP ; usCategory
+
+ mov ax, [hfOpen] ; hDev
+ push ax
+ call far DOS16DEVIOCTL2
+
+ ; check for error.
+ test ax, ax
+ jnz near .done_err_close
+ cmp [abData + 0], byte 0
+ jne near .done_err_close
+%endmacro
+
+
+ ;
+ ; Open the OEMHLP$ driver.
+ ;
+
+ ; APIRET _Pascal DosOpen(PSZ pszFname, PHFILE phfOpen, PUSHORT pusAction,
+ ; ULONG ulFSize, USHORT usAttr, USHORT fsOpenFlags,
+ ; USHORT fsOpenMode, ULONG ulReserved);
+ mov di, '0'
+ push seg g_szOemHlpDevName ; pszFname
+ push g_szOemHlpDevName
+ push ss ; phfOpen
+ lea dx, [hfOpen]
+ push dx
+ push ss ; pusAction
+ lea dx, [usAction]
+ push dx
+ push dword 0 ; ulFSize
+ push 0 ; usAttr = FILE_NORMAL
+ push 1 ; fsOpenFlags = FILE_OPEN
+ push 00040h ; fsOpenMode = OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY
+ push dword 0 ; ulReserved
+ call far DOS16OPEN
+ or ax, ax
+ jnz near .done
+
+
+ ;
+ ; Send a PCI_FIND_DEVICE request.
+ ;
+
+ ; Initialize the parameter packet.
+ mov [abParm + 0], byte PCI_FIND_DEVICE ; 0 - db - SubFunction Number
+ mov [abParm + 1], word VMMDEV_DEVICEID ; 1 - dw - Device ID
+ mov [abParm + 3], word VBOX_PCI_VENDORID ; 3 - dw - Vendor ID
+ mov [abParm + 5], byte 0 ; 5 - db - (Device) Index
+
+ ; Zero padd the data packet.
+ mov [abData + 0], dword 0
+
+ mov di, '1'
+ CallIOCtl 6, 3
+
+ mov al, [abData + 1] ; 1 - db - Bus Number.
+ mov [NAME(g_bPciBusNo)], al
+ mov al, [abData + 2] ; 2 - db - DevFunc Number.
+ mov [NAME(g_bPciDevFunNo)], al
+
+ ;
+ ; Read the interrupt register (byte).
+ ;
+ mov di, '2'
+ mov ax, PCI_INTERRUPT_LINE | 0100h
+ call .NestedReadReg
+ mov [NAME(g_bInterruptLine)], al
+
+ ;
+ ; Read the first base address (dword), this shall must be in I/O space.
+ ;
+ mov di, '3'
+ mov ax, PCI_BASE_ADDRESS_0 | 0400h
+ call .NestedReadReg
+ mov di, '4'
+ test al, 1h ; Test that it's an I/O space address.
+ jz .done_err_close
+ mov di, '5'
+ test eax, 0ffff0002h ; These shall all be 0 according to the specs.
+ jnz .done_err_close
+ and ax, 0fffeh
+ mov [NAME(g_IOPortBase)], ax
+
+ ;
+ ; Read the second base address (dword), this shall be in memory space if present.
+ ;
+ mov di, '6'
+ mov ax, PCI_BASE_ADDRESS_1 | 0400h
+ call .NestedReadReg
+ mov di, '7'
+ test al, 1h ; Test that it's a memory space address.
+ jnz .done_err_close
+ and eax, 0fffffff0h
+ mov [NAME(g_PhysMMIOBase)], eax
+
+ ;or eax, eax
+ ;jz .done_success ; No memory region.
+ ;; @todo If there is a simple way of determining the size do that, if
+ ; not we can easily handle it the code that does the actual mapping.
+
+
+ ;
+ ; Ok, we're good!
+ ;
+.done_success:
+ or [NAME(g_fFoundAdapter)], byte 1
+ jmp .done_close
+
+ ;
+ ; Close the OEMHLP$ driver.
+ ;
+.done_err_close:
+ or ax, 80h
+.done_close:
+ ; APIRET APIENTRY DosClose(HFILE hf);
+ push ax ; Save result
+ mov cx, [hfOpen]
+ push cx
+ call far DOS16CLOSE
+ or ax, ax
+ jnz .bitch ; This can't happen (I hope).
+ pop ax
+ or ax, ax
+ jnz .bitch
+
+ ;
+ ; Return to vgdrvOS2EP_Init.
+ ;
+.done:
+ mov esp, ebp
+ pop ebp
+ pop edi
+ pop esi
+ pop edx
+ pop ecx
+ pop ebx
+ ret
+
+
+ ;
+ ; Puts the reason for failure in message buffer.
+ ; The caller will flush this.
+ ;
+.bitch:
+ push es
+
+ mov ax, ds
+ mov es, ax
+ mov ax, di ; save the reason.
+ mov di, NAME(g_szInitText)
+ add di, [NAME(g_cchInitText)]
+
+ mov si, g_achLoadFailureMsg1
+ mov cx, g_cchLoadFailureMsg1
+ rep movsb
+
+ stosb
+
+ mov si, g_achLoadFailureMsg2
+ mov cx, g_cchLoadFailureMsg2
+ rep movsb
+
+ mov [di], byte 0
+ sub di, NAME(g_szInitText)
+ mov [NAME(g_cchInitText)], di
+
+ pop es
+ jmp .done
+
+
+ ;
+ ; Nested function which reads a PCI config register.
+ ; (This operates on the vgdrvFindAdapter stack frame.)
+ ;
+ ; Input:
+ ; al - register to read
+ ; ah - register size.
+ ;
+ ; Output:
+ ; eax - the value.
+ ;
+ ; Uses:
+ ; dx
+ ;
+.NestedReadReg:
+ ; Fill in the request packet.
+ mov [abParm + 0], byte PCI_READ_CONFIG ; 0 - db - SubFunction Number
+ mov dl, [NAME(g_bPciBusNo)]
+ mov [abParm + 1], dl ; 1 - db - Bus Number
+ mov dl, [NAME(g_bPciDevFunNo)]
+ mov [abParm + 2], dl ; 2 - db - DevFunc Number
+ mov [abParm + 3], al ; 3 - db - Configuration Register
+ mov [abParm + 4], ah ; 4 - db - (Register) Size
+
+ ; Pad the data packet.
+ mov [abData + 0], dword 0
+ mov [abData + 4], dword 0
+
+ CallIOCtl 5, 5
+
+ mov eax, [abData + 1] ; 1 - dd - Data
+
+ ret
+
+ENDPROC vgdrvFindAdapter
+
+
+
+;;
+; This must be present
+segment DATA32
+g_pfnDos16Write:
+ dd DOS16WRITE ; flat
+
diff --git a/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h b/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h
new file mode 100644
index 00000000..282c835f
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h
@@ -0,0 +1,415 @@
+/* $Id: VBoxGuestInternal.h $ */
+/** @file
+ * VBoxGuest - Guest Additions Driver, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuestInternal_h
+#define GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuestInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+#include <iprt/list.h>
+#include <iprt/semaphore.h>
+#include <iprt/spinlock.h>
+#include <iprt/timer.h>
+#include <VBox/VMMDev.h>
+#include <VBox/VBoxGuest.h>
+#include <VBox/VBoxGuestLib.h>
+
+/** @def VBOXGUEST_USE_DEFERRED_WAKE_UP
+ * Defer wake-up of waiting thread when defined. */
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
+# define VBOXGUEST_USE_DEFERRED_WAKE_UP
+#endif
+
+/** @def VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT
+ * The mouse notification callback can cause preemption and must not be invoked
+ * while holding a high-level spinlock.
+ */
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
+# define VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT
+#endif
+
+/** Pointer to the VBoxGuest per session data. */
+typedef struct VBOXGUESTSESSION *PVBOXGUESTSESSION;
+
+/** Pointer to a wait-for-event entry. */
+typedef struct VBOXGUESTWAIT *PVBOXGUESTWAIT;
+
+/**
+ * VBox guest wait for event entry.
+ *
+ * Each waiting thread allocates one of these items and adds
+ * it to the wait list before going to sleep on the event sem.
+ */
+typedef struct VBOXGUESTWAIT
+{
+ /** The list node. */
+ RTLISTNODE ListNode;
+ /** The events we are waiting on. */
+ uint32_t fReqEvents;
+ /** The events we received. */
+ uint32_t volatile fResEvents;
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ /** Set by VGDrvCommonWaitDoWakeUps before leaving the spinlock to call
+ * RTSemEventMultiSignal. */
+ bool volatile fPendingWakeUp;
+ /** Set by the requestor thread if it got the spinlock before the
+ * signaller. Deals with the race in VGDrvCommonWaitDoWakeUps. */
+ bool volatile fFreeMe;
+#endif
+ /** The event semaphore. */
+ RTSEMEVENTMULTI Event;
+ /** The session that's waiting. */
+ PVBOXGUESTSESSION pSession;
+#ifdef VBOX_WITH_HGCM
+ /** The HGCM request we're waiting for to complete. */
+ VMMDevHGCMRequestHeader volatile *pHGCMReq;
+#endif
+} VBOXGUESTWAIT;
+
+
+/**
+ * VBox guest memory balloon.
+ */
+typedef struct VBOXGUESTMEMBALLOON
+{
+ /** Mutex protecting the members below from concurrent access. */
+ RTSEMFASTMUTEX hMtx;
+ /** The current number of chunks in the balloon. */
+ uint32_t cChunks;
+ /** The maximum number of chunks in the balloon (typically the amount of guest
+ * memory / chunksize). */
+ uint32_t cMaxChunks;
+ /** This is true if we are using RTR0MemObjAllocPhysNC() / RTR0MemObjGetPagePhysAddr()
+ * and false otherwise. */
+ bool fUseKernelAPI;
+ /** The current owner of the balloon.
+ * This is automatically assigned to the first session using the ballooning
+ * API and first released when the session closes. */
+ PVBOXGUESTSESSION pOwner;
+ /** The pointer to the array of memory objects holding the chunks of the
+ * balloon. This array is cMaxChunks in size when present. */
+ PRTR0MEMOBJ paMemObj;
+} VBOXGUESTMEMBALLOON;
+/** Pointer to a memory balloon. */
+typedef VBOXGUESTMEMBALLOON *PVBOXGUESTMEMBALLOON;
+
+
+/**
+ * Per bit usage tracker for a uint32_t mask.
+ *
+ * Used for optimal handling of guest properties, mouse status and event filter.
+ */
+typedef struct VBOXGUESTBITUSAGETRACER
+{
+ /** Per bit usage counters. */
+ uint32_t acPerBitUsage[32];
+ /** The current mask according to acPerBitUsage. */
+ uint32_t fMask;
+} VBOXGUESTBITUSAGETRACER;
+/** Pointer to a per bit usage tracker. */
+typedef VBOXGUESTBITUSAGETRACER *PVBOXGUESTBITUSAGETRACER;
+/** Pointer to a const per bit usage tracker. */
+typedef VBOXGUESTBITUSAGETRACER const *PCVBOXGUESTBITUSAGETRACER;
+
+
+/**
+ * VBox guest device (data) extension.
+ */
+typedef struct VBOXGUESTDEVEXT
+{
+ /** VBOXGUESTDEVEXT_INIT_STATE_XXX. */
+ uint32_t uInitState;
+ /** The base of the adapter I/O ports. */
+ RTIOPORT IOPortBase;
+ /** Pointer to the mapping of the VMMDev adapter memory. */
+ VMMDevMemory volatile *pVMMDevMemory;
+ /** The memory object reserving space for the guest mappings. */
+ RTR0MEMOBJ hGuestMappings;
+ /** Spinlock protecting the signaling and resetting of the wait-for-event
+ * semaphores as well as the event acking in the ISR. */
+ RTSPINLOCK EventSpinlock;
+ /** Host feature flags (VMMDEV_HVF_XXX). */
+ uint32_t fHostFeatures;
+ /** Preallocated VMMDevEvents for the IRQ handler. */
+ VMMDevEvents *pIrqAckEvents;
+ /** The physical address of pIrqAckEvents. */
+ RTCCPHYS PhysIrqAckEvents;
+ /** Wait-for-event list for threads waiting for multiple events
+ * (VBOXGUESTWAIT). */
+ RTLISTANCHOR WaitList;
+#ifdef VBOX_WITH_HGCM
+ /** Wait-for-event list for threads waiting on HGCM async completion
+ * (VBOXGUESTWAIT).
+ *
+ * The entire list is evaluated upon the arrival of an HGCM event, unlike
+ * the other lists which are only evaluated till the first thread has
+ * been woken up. */
+ RTLISTANCHOR HGCMWaitList;
+#endif
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+ /** List of wait-for-event entries that needs waking up
+ * (VBOXGUESTWAIT). */
+ RTLISTANCHOR WakeUpList;
+#endif
+ /** List of wait-for-event entries that has been woken up
+ * (VBOXGUESTWAIT). */
+ RTLISTANCHOR WokenUpList;
+ /** List of free wait-for-event entries (VBOXGUESTWAIT). */
+ RTLISTANCHOR FreeList;
+ /** Mask of pending events. */
+ uint32_t volatile f32PendingEvents;
+ /** Current VMMDEV_EVENT_MOUSE_POSITION_CHANGED sequence number.
+ * Used to implement polling. */
+ uint32_t volatile u32MousePosChangedSeq;
+
+ /** Spinlock various items in the VBOXGUESTSESSION. */
+ RTSPINLOCK SessionSpinlock;
+ /** List of guest sessions (VBOXGUESTSESSION). We currently traverse this
+ * but do not search it, so a list data type should be fine. Use under the
+ * #SessionSpinlock lock. */
+ RTLISTANCHOR SessionList;
+ /** Number of session. */
+ uint32_t cSessions;
+ /** Flag indicating whether logging to the release log
+ * is enabled. */
+ bool fLoggingEnabled;
+ /** Memory balloon information for RTR0MemObjAllocPhysNC(). */
+ VBOXGUESTMEMBALLOON MemBalloon;
+ /** Mouse notification callback function. */
+ PFNVBOXGUESTMOUSENOTIFY pfnMouseNotifyCallback;
+ /** The callback argument for the mouse ntofication callback. */
+ void *pvMouseNotifyCallbackArg;
+
+ /** @name Host Event Filtering
+ * @{ */
+ /** Events we won't permit anyone to filter out. */
+ uint32_t fFixedEvents;
+ /** Usage counters for the host events. (Fixed events are not included.) */
+ VBOXGUESTBITUSAGETRACER EventFilterTracker;
+ /** The event filter last reported to the host (UINT32_MAX on failure). */
+ uint32_t fEventFilterHost;
+ /** @} */
+
+ /** @name Mouse Status
+ * @{ */
+ /** Usage counters for the mouse statuses (VMMDEV_MOUSE_XXX). */
+ VBOXGUESTBITUSAGETRACER MouseStatusTracker;
+ /** The mouse status last reported to the host (UINT32_MAX on failure). */
+ uint32_t fMouseStatusHost;
+ /** @} */
+
+ /** @name Guest Capabilities
+ * @{ */
+ /** Guest capabilities which have been set to "acquire" mode. This means
+ * that only one session can use them at a time, and that they will be
+ * automatically cleaned up if that session exits without doing so.
+ *
+ * Protected by VBOXGUESTDEVEXT::SessionSpinlock, but is unfortunately read
+ * without holding the lock in a couple of places. */
+ uint32_t volatile fAcquireModeGuestCaps;
+ /** Guest capabilities which have been set to "set" mode. This just means
+ * that they have been blocked from ever being set to "acquire" mode. */
+ uint32_t fSetModeGuestCaps;
+ /** Mask of all capabilities which are currently acquired by some session
+ * and as such reported to the host. */
+ uint32_t fAcquiredGuestCaps;
+ /** Usage counters for guest capabilities in "set" mode. Indexed by
+ * capability bit number, one count per session using a capability. */
+ VBOXGUESTBITUSAGETRACER SetGuestCapsTracker;
+ /** The guest capabilities last reported to the host (UINT32_MAX on failure). */
+ uint32_t fGuestCapsHost;
+ /** @} */
+
+ /** Heartbeat timer which fires with interval
+ * cNsHearbeatInterval and its handler sends
+ * VMMDevReq_GuestHeartbeat to VMMDev. */
+ PRTTIMER pHeartbeatTimer;
+ /** Heartbeat timer interval in nanoseconds. */
+ uint64_t cNsHeartbeatInterval;
+ /** Preallocated VMMDevReq_GuestHeartbeat request. */
+ VMMDevRequestHeader *pReqGuestHeartbeat;
+} VBOXGUESTDEVEXT;
+/** Pointer to the VBoxGuest driver data. */
+typedef VBOXGUESTDEVEXT *PVBOXGUESTDEVEXT;
+
+/** @name VBOXGUESTDEVEXT_INIT_STATE_XXX - magic values for validating init
+ * state of the device extension structur.
+ * @{ */
+#define VBOXGUESTDEVEXT_INIT_STATE_FUNDAMENT UINT32_C(0x0badcafe)
+#define VBOXGUESTDEVEXT_INIT_STATE_RESOURCES UINT32_C(0xcafebabe)
+#define VBOXGUESTDEVEXT_INIT_STATE_DELETED UINT32_C(0xdeadd0d0)
+/** @} */
+
+/**
+ * The VBoxGuest per session data.
+ */
+typedef struct VBOXGUESTSESSION
+{
+ /** The list node. */
+ RTLISTNODE ListNode;
+#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_OS2) || defined(RT_OS_SOLARIS)
+ /** Pointer to the next session with the same hash. */
+ PVBOXGUESTSESSION pNextHash;
+#endif
+#if defined(RT_OS_OS2)
+ /** The system file number of this session. */
+ uint16_t sfn;
+ uint16_t Alignment; /**< Alignment */
+#endif
+ /** The requestor information to pass to the host for this session.
+ * @sa VMMDevRequestHeader::fRequestor */
+ uint32_t fRequestor;
+ /** The process (id) of the session.
+ * This is NIL if it's a kernel session. */
+ RTPROCESS Process;
+ /** Which process this session is associated with.
+ * This is NIL if it's a kernel session. */
+ RTR0PROCESS R0Process;
+ /** Pointer to the device extension. */
+ PVBOXGUESTDEVEXT pDevExt;
+
+#ifdef VBOX_WITH_HGCM
+ /** Array containing HGCM client IDs associated with this session.
+ * This will be automatically disconnected when the session is closed. */
+ uint32_t volatile aHGCMClientIds[64];
+#endif
+ /** The last consumed VMMDEV_EVENT_MOUSE_POSITION_CHANGED sequence number.
+ * Used to implement polling. */
+ uint32_t volatile u32MousePosChangedSeq;
+ /** Host events requested by the session.
+ * An event type requested in any guest session will be added to the host
+ * filter. Protected by VBOXGUESTDEVEXT::SessionSpinlock. */
+ uint32_t fEventFilter;
+ /** Guest capabilities held in "acquired" by this session.
+ * Protected by VBOXGUESTDEVEXT::SessionSpinlock, but is unfortunately read
+ * without holding the lock in a couple of places. */
+ uint32_t volatile fAcquiredGuestCaps;
+ /** Guest capabilities in "set" mode for this session.
+ * These accumulated for sessions via VBOXGUESTDEVEXT::acGuestCapsSet and
+ * reported to the host. Protected by VBOXGUESTDEVEXT::SessionSpinlock. */
+ uint32_t fCapabilities;
+ /** Mouse features supported. A feature enabled in any guest session will
+ * be enabled for the host.
+ * @note We invert the VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR feature in this
+ * bitmap. The logic of this is that the real feature is when the host
+ * cursor is not needed, and we tell the host it is not needed if any
+ * session explicitly fails to assert it. Storing it inverted simplifies
+ * the checks.
+ * Use under the VBOXGUESTDEVEXT#SessionSpinlock lock. */
+ uint32_t fMouseStatus;
+#ifdef RT_OS_DARWIN
+ /** Pointer to the associated org_virtualbox_VBoxGuestClient object. */
+ void *pvVBoxGuestClient;
+ /** Whether this session has been opened or not. */
+ bool fOpened;
+#endif
+ /** Whether a CANCEL_ALL_WAITEVENTS is pending. This happens when
+ * CANCEL_ALL_WAITEVENTS is called, but no call to WAITEVENT is in process
+ * in the current session. In that case the next call will be interrupted
+ * at once. */
+ bool volatile fPendingCancelWaitEvents;
+ /** Does this session belong to a root process or a user one? */
+ bool fUserSession;
+} VBOXGUESTSESSION;
+
+RT_C_DECLS_BEGIN
+
+int VGDrvCommonInitDevExt(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase, void *pvMMIOBase, uint32_t cbMMIO,
+ VBOXOSTYPE enmOSType, uint32_t fEvents);
+void VGDrvCommonDeleteDevExt(PVBOXGUESTDEVEXT pDevExt);
+
+int VGDrvCommonInitLoggers(void);
+void VGDrvCommonDestroyLoggers(void);
+int VGDrvCommonInitDevExtFundament(PVBOXGUESTDEVEXT pDevExt);
+void VGDrvCommonDeleteDevExtFundament(PVBOXGUESTDEVEXT pDevExt);
+int VGDrvCommonInitDevExtResources(PVBOXGUESTDEVEXT pDevExt, uint16_t IOPortBase,
+ void *pvMMIOBase, uint32_t cbMMIO, VBOXOSTYPE enmOSType, uint32_t fFixedEvents);
+void VGDrvCommonDeleteDevExtResources(PVBOXGUESTDEVEXT pDevExt);
+int VGDrvCommonReinitDevExtAfterHibernation(PVBOXGUESTDEVEXT pDevExt, VBOXOSTYPE enmOSType);
+
+bool VBDrvCommonIsOptionValueTrue(const char *pszValue);
+void VGDrvCommonProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue);
+void VGDrvCommonProcessOptionsFromHost(PVBOXGUESTDEVEXT pDevExt);
+bool VGDrvCommonIsOurIRQ(PVBOXGUESTDEVEXT pDevExt);
+bool VGDrvCommonISR(PVBOXGUESTDEVEXT pDevExt);
+
+#ifdef VBOXGUEST_USE_DEFERRED_WAKE_UP
+void VGDrvCommonWaitDoWakeUps(PVBOXGUESTDEVEXT pDevExt);
+#endif
+
+int VGDrvCommonCreateUserSession(PVBOXGUESTDEVEXT pDevExt, uint32_t fRequestor, PVBOXGUESTSESSION *ppSession);
+int VGDrvCommonCreateKernelSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION *ppSession);
+void VGDrvCommonCloseSession(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession);
+
+int VGDrvCommonIoCtlFast(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession);
+int VGDrvCommonIoCtl(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession,
+ PVBGLREQHDR pReqHdr, size_t cbReq);
+
+/**
+ * ISR callback for notifying threads polling for mouse events.
+ *
+ * This is called at the end of the ISR, after leaving the event spinlock, if
+ * VMMDEV_EVENT_MOUSE_POSITION_CHANGED was raised by the host.
+ *
+ * @param pDevExt The device extension.
+ */
+void VGDrvNativeISRMousePollEvent(PVBOXGUESTDEVEXT pDevExt);
+
+/**
+ * Hook for handling OS specfic options from the host.
+ *
+ * @returns true if handled, false if not.
+ * @param pDevExt The device extension.
+ * @param pszName The option name.
+ * @param pszValue The option value.
+ */
+bool VGDrvNativeProcessOption(PVBOXGUESTDEVEXT pDevExt, const char *pszName, const char *pszValue);
+
+
+#ifdef VBOX_WITH_DPC_LATENCY_CHECKER
+int VGDrvNtIOCtl_DpcLatencyChecker(void);
+#endif
+
+#ifdef VBOXGUEST_MOUSE_NOTIFY_CAN_PREEMPT
+int VGDrvNativeSetMouseNotifyCallback(PVBOXGUESTDEVEXT pDevExt, PVBGLIOCSETMOUSENOTIFYCALLBACK pNotify);
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_VBoxGuestInternal_h */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/darwin/Info.plist b/src/VBox/Additions/common/VBoxGuest/darwin/Info.plist
new file mode 100644
index 00000000..f4384535
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/darwin/Info.plist
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key> <string>English</string>
+ <key>CFBundleExecutable</key> <string>VBoxGuest</string>
+ <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxGuest</string>
+ <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string>
+ <key>CFBundleName</key> <string>VBoxGuest</string>
+ <key>CFBundlePackageType</key> <string>KEXT</string>
+ <key>CFBundleSignature</key> <string>????</string>
+ <key>NSHumanReadableCopyright</key> <string>Copyright © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string>
+ <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string>
+ <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string>
+ <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string>
+ <key>OSBundleCompatibleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string>
+ <key>IOKitPersonalities</key>
+ <dict>
+ <key>VBoxGuest</key>
+ <dict>
+ <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxGuest</string>
+ <key>IOClass</key> <string>org_virtualbox_VBoxGuest</string>
+ <key>IOMatchCategory</key> <string>org_virtualbox_VBoxGuest</string>
+ <key>IOUserClientClass</key> <string>org_virtualbox_VBoxGuestClient</string>
+ <key>IOKitDebug</key> <integer>65535</integer>
+ <key>IOProviderClass</key> <string>IOPCIDevice</string>
+ <key>IOPCIPrimaryMatch</key> <string>0xcafe80ee</string>
+ <!-- <key>IONameMatch</key> <string>pci80ee,cafe</string> -->
+ </dict>
+ </dict>
+ <key>OSBundleLibraries</key>
+ <dict>
+ <key>com.apple.iokit.IOPCIFamily</key> <string>2.5</string> <!-- TODO: Figure the version in mac os x 10.4. -->
+ <key>com.apple.kpi.bsd</key> <string>8.0.0</string>
+ <key>com.apple.kpi.mach</key> <string>8.0.0</string>
+ <key>com.apple.kpi.libkern</key> <string>8.0.0</string>
+ <key>com.apple.kpi.unsupported</key> <string>8.0.0</string>
+ <key>com.apple.kpi.iokit</key> <string>8.0.0</string>
+ </dict>
+ <key>OSBundleLibraries_x86_64</key>
+ <dict>
+ <key>com.apple.iokit.IOPCIFamily</key> <string>2.6</string>
+ <key>com.apple.kpi.bsd</key> <string>10.0.0d4</string>
+ <key>com.apple.kpi.mach</key> <string>10.0.0d3</string>
+ <key>com.apple.kpi.libkern</key> <string>10.0.0d3</string>
+ <key>com.apple.kpi.iokit</key> <string>10.0.0d3</string>
+ <key>com.apple.kpi.unsupported</key> <string>10.0.0d3</string>
+ </dict>
+</dict>
+</plist>
+
diff --git a/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile
new file mode 100644
index 00000000..5344dbc0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile
@@ -0,0 +1,202 @@
+# $Id: Makefile $
+## @file
+# VirtualBox Guest Additions Module Makefile.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+KMOD = vboxguest
+
+CFLAGS += -DRT_OS_FREEBSD -DIN_RING0 -DIN_RT_R0 -DIN_SUP_R0 -DVBOX -DRT_WITH_VBOX -Iinclude -I. -Ir0drv -w -DVBGL_VBOXGUEST -DVBOX_WITH_HGCM -DVBOX_WITH_64_BITS_GUESTS
+
+.if (${MACHINE_ARCH} == "i386")
+ CFLAGS += -DRT_ARCH_X86
+.elif (${MACHINE_ARCH} == "amd64")
+ CFLAGS += -DRT_ARCH_AMD64
+.endif
+
+SRCS = \
+ VBoxGuest.c \
+ VBoxGuest-freebsd.c \
+ VBoxGuestR0LibGenericRequest.c \
+ VBoxGuestR0LibHGCMInternal.c \
+ VBoxGuestR0LibInit.c \
+ VBoxGuestR0LibPhysHeap.c \
+ VBoxGuestR0LibVMMDev.c
+
+# Include needed interface headers so they are created during build
+SRCS += \
+ device_if.h \
+ bus_if.h \
+ pci_if.h \
+
+.PATH: ${.CURDIR}/alloc
+SRCS += \
+ heapsimple.c
+
+.PATH: ${.CURDIR}/common/err
+SRCS += \
+ RTErrConvertFromErrno.c \
+ RTErrConvertToErrno.c \
+ errinfo.c
+
+.PATH: ${.CURDIR}/common/log
+SRCS += \
+ log.c \
+ logellipsis.c \
+ logrel.c \
+ logrelellipsis.c \
+ logcom.c \
+ logformat.c \
+ RTLogCreateEx.c
+
+.PATH: ${.CURDIR}/common/misc
+SRCS += \
+ RTAssertMsg1Weak.c \
+ RTAssertMsg2.c \
+ RTAssertMsg2Add.c \
+ RTAssertMsg2AddWeak.c \
+ RTAssertMsg2AddWeakV.c \
+ RTAssertMsg2Weak.c \
+ RTAssertMsg2WeakV.c \
+ assert.c \
+ handletable.c \
+ handletablectx.c \
+ once.c \
+ thread.c
+
+.PATH: ${.CURDIR}/common/string
+SRCS += \
+ RTStrCat.c \
+ RTStrCmp.c \
+ RTStrCopy.c \
+ RTStrCopyEx.c \
+ RTStrCopyP.c \
+ RTStrEnd.c \
+ RTStrICmpAscii.c \
+ RTStrNICmpAscii.c \
+ RTStrNCmp.c \
+ RTStrNLen.c \
+ stringalloc.c \
+ strformat.c \
+ RTStrFormat.c \
+ strformatnum.c \
+ strformatrt.c \
+ strformattype.c \
+ strprintf.c \
+ strprintf-ellipsis.c \
+ strprintf2.c \
+ strprintf2-ellipsis.c \
+ strtonum.c \
+ memchr.c \
+ utf-8.c
+
+.PATH: ${.CURDIR}/common/rand
+SRCS += \
+ rand.c \
+ randadv.c \
+ randparkmiller.c
+
+.PATH: ${.CURDIR}/common/path
+SRCS += \
+ RTPathStripFilename.c
+
+.PATH: ${.CURDIR}/common/checksum
+SRCS += \
+ crc32.c \
+ ipv4.c
+
+.PATH: ${.CURDIR}/common/table
+SRCS += \
+ avlpv.c
+
+.PATH: ${.CURDIR}/common/time
+SRCS += \
+ time.c
+
+.PATH: ${.CURDIR}/generic
+SRCS += \
+ uuid-generic.c \
+ RTAssertShouldPanic-generic.c \
+ RTLogWriteDebugger-generic.c \
+ RTLogWriteStdOut-stub-generic.c \
+ RTLogWriteStdErr-stub-generic.c \
+ RTRandAdvCreateSystemFaster-generic.c \
+ RTRandAdvCreateSystemTruer-generic.c \
+ RTSemEventWait-2-ex-generic.c \
+ RTSemEventWaitNoResume-2-ex-generic.c \
+ RTSemEventMultiWait-2-ex-generic.c \
+ RTSemEventMultiWaitNoResume-2-ex-generic.c \
+ RTTimerCreate-generic.c \
+ rtStrFormatKernelAddress-generic.c \
+ timer-generic.c \
+ errvars-generic.c \
+ mppresent-generic.c
+
+.PATH: ${.CURDIR}/r0drv
+SRCS += \
+ alloc-r0drv.c \
+ initterm-r0drv.c \
+ memobj-r0drv.c \
+ powernotification-r0drv.c
+
+.PATH: ${.CURDIR}/r0drv/freebsd
+SRCS += \
+ assert-r0drv-freebsd.c \
+ alloc-r0drv-freebsd.c \
+ initterm-r0drv-freebsd.c \
+ memobj-r0drv-freebsd.c \
+ memuserkernel-r0drv-freebsd.c \
+ mp-r0drv-freebsd.c \
+ process-r0drv-freebsd.c \
+ semevent-r0drv-freebsd.c \
+ semeventmulti-r0drv-freebsd.c \
+ semfastmutex-r0drv-freebsd.c \
+ semmutex-r0drv-freebsd.c \
+ spinlock-r0drv-freebsd.c \
+ thread-r0drv-freebsd.c \
+ thread2-r0drv-freebsd.c \
+ time-r0drv-freebsd.c
+
+.PATH: ${.CURDIR}/r0drv/generic
+SRCS += \
+ semspinmutex-r0drv-generic.c \
+ mpnotification-r0drv-generic.c \
+ RTMpIsCpuWorkPending-r0drv-generic.c
+
+.PATH: ${.CURDIR}/VBox
+SRCS += \
+ log-vbox.c \
+ logbackdoor.c \
+ RTLogWriteVmm-amd64-x86.
+
+.include <bsd.kmod.mk>
+
diff --git a/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile.kup b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile.kup
diff --git a/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest b/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest
new file mode 100755
index 00000000..3f8e8f94
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/freebsd/files_vboxguest
@@ -0,0 +1,240 @@
+#!/bin/sh
+# $Id: files_vboxguest $
+## @file
+# Shared file between Makefile.kmk and export_modules.sh.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+FILES_VBOXGUEST_NOBIN=" \
+ ${PATH_ROOT}/include/iprt/alloca.h=>include/iprt/alloca.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/avl.h=>include/iprt/avl.h \
+ ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \
+ ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \
+ ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.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/errno.h=>include/iprt/errno.h \
+ ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \
+ ${PATH_ROOT}/include/iprt/handletable.h=>include/iprt/handletable.h \
+ ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \
+ ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \
+ ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \
+ ${PATH_ROOT}/include/iprt/lockvalidator.h=>include/iprt/lockvalidator.h \
+ ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \
+ ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \
+ ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \
+ ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \
+ ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \
+ ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \
+ ${PATH_ROOT}/include/iprt/power.h=>include/iprt/power.h \
+ ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \
+ ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \
+ ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.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/thread.h=>include/iprt/thread.h \
+ ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \
+ ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \
+ ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \
+ ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \
+ ${PATH_ROOT}/include/iprt/uuid.h=>include/iprt/uuid.h \
+ ${PATH_ROOT}/include/iprt/crc.h=>include/iprt/crc.h \
+ ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.h \
+ ${PATH_ROOT}/include/iprt/rand.h=>include/iprt/rand.h \
+ ${PATH_ROOT}/include/iprt/path.h=>include/iprt/path.h \
+ ${PATH_ROOT}/include/iprt/once.h=>include/iprt/once.h \
+ ${PATH_ROOT}/include/iprt/critsect.h=>include/iprt/critsect.h \
+ ${PATH_ROOT}/include/iprt/x86.h=>include/iprt/x86.h \
+ ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \
+ ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.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/param.h=>include/VBox/param.h \
+ ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \
+ ${PATH_ROOT}/include/VBox/ostypes.h=>include/VBox/ostypes.h \
+ ${PATH_ROOT}/include/VBox/VMMDev.h=>include/VBox/VMMDev.h \
+ ${PATH_ROOT}/include/VBox/VMMDevCoreTypes.h=>include/VBox/VMMDevCoreTypes.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/VBoxGuestMangling.h=>include/VBox/VBoxGuestMangling.h \
+ ${PATH_ROOT}/include/VBox/version.h=>include/VBox/version.h \
+ ${PATH_ROOT}/include/VBox/HostServices/GuestPropertySvc.h=>include/VBox/HostServices/GuestPropertySvc.h \
+ ${PATH_ROOT}/include/VBox/vmm/cpuidcall.h=>include/VBox/vmm/cpuidcall.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp=>VBoxGuest.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest-freebsd.c=>VBoxGuest-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h=>VBoxGuestInternal.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/freebsd/Makefile=>Makefile \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h=>VBoxGuestR0LibInternal.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp=>VBoxGuestR0LibGenericRequest.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp=>VBoxGuestR0LibHGCMInternal.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp=>VBoxGuestR0LibInit.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp=>VBoxGuestR0LibPhysHeap.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp=>VBoxGuestR0LibVMMDev.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/alloc/heapsimple.cpp=>alloc/heapsimple.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp=>common/err/RTErrConvertFromErrno.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp=>common/err/RTErrConvertToErrno.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/err/errinfo.cpp=>common/err/errinfo.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/log.cpp=>common/log/log.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logellipsis.cpp=>common/log/logellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logrel.cpp=>common/log/logrel.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logrelellipsis.cpp=>common/log/logrelellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logcom.cpp=>common/log/logcom.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logformat.cpp=>common/log/logformat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/RTLogCreateEx.cpp=>common/log/RTLogCreateEx.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.cpp=>common/misc/handletable.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.h=>common/misc/handletable.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletablectx.cpp=>common/misc/handletablectx.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/once.cpp=>common/misc/once.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/thread.cpp=>common/misc/thread.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp=>common/misc/RTAssertMsg1Weak.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp=>common/misc/RTAssertMsg2.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp=>common/misc/RTAssertMsg2Add.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp=>common/misc/RTAssertMsg2AddWeak.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp=>common/misc/RTAssertMsg2AddWeakV.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp=>common/misc/RTAssertMsg2Weak.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp=>common/misc/RTAssertMsg2WeakV.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/assert.cpp=>common/misc/assert.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCat.cpp=>common/string/RTStrCat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCmp.cpp=>common/string/RTStrCmp.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopy.cpp=>common/string/RTStrCopy.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyEx.cpp=>common/string/RTStrCopyEx.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyP.cpp=>common/string/RTStrCopyP.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrEnd.cpp=>common/string/RTStrEnd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrICmpAscii.cpp=>common/string/RTStrICmpAscii.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp=>common/string/RTStrNICmpAscii.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNCmp.cpp=>common/string/RTStrNCmp.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2.cpp=>common/string/strprintf2.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp=>common/string/strprintf2-ellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/memchr.cpp=>common/string/memchr.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/utf-8.cpp=>common/string/utf-8.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/rand/rand.cpp=>common/rand/rand.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/rand/randadv.cpp=>common/rand/randadv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/rand/randparkmiller.cpp=>common/rand/randparkmiller.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/path/RTPathStripFilename.cpp=>common/path/RTPathStripFilename.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/checksum/crc32.cpp=>common/checksum/crc32.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/checksum/ipv4.cpp=>common/checksum/ipv4.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avlpv.cpp=>common/table/avlpv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Base.cpp.h=>common/table/avl_Base.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Get.cpp.h=>common/table/avl_Get.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h=>common/table/avl_GetBestFit.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h=>common/table/avl_RemoveBestFit.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h=>common/table/avl_DoWithAll.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Destroy.cpp.h=>common/table/avl_Destroy.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/time/time.cpp=>common/time/time.c \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/assert.h=>include/internal/assert.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/initterm.h=>include/internal/initterm.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/iprt.h=>include/internal/iprt.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/lockvalidator.h=>include/internal/lockvalidator.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/magics.h=>include/internal/magics.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/mem.h=>include/internal/mem.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/memobj.h=>include/internal/memobj.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/string.h=>include/internal/string.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/thread.h=>include/internal/thread.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/time.h=>include/internal/time.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/rand.h=>include/internal/rand.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/sched.h=>include/internal/sched.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/process.h=>include/internal/process.h \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTAssertShouldPanic-generic.cpp=>generic/RTAssertShouldPanic-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdErr-stub-generic.cpp=>generic/RTLogWriteStdErr-stub-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdOut-stub-generic.cpp=>generic/RTLogWriteStdOut-stub-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteDebugger-generic.cpp=>generic/RTLogWriteDebugger-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTRandAdvCreateSystemFaster-generic.cpp=>generic/RTRandAdvCreateSystemFaster-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTRandAdvCreateSystemTruer-generic.cpp=>generic/RTRandAdvCreateSystemTruer-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/uuid-generic.cpp=>generic/uuid-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWait-2-ex-generic.cpp=>generic/RTSemEventWait-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventWaitNoResume-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWait-2-ex-generic.cpp=>generic/RTSemEventMultiWait-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventMultiWaitNoResume-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTTimerCreate-generic.cpp=>generic/RTTimerCreate-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/rtStrFormatKernelAddress-generic.cpp=>generic/rtStrFormatKernelAddress-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/errvars-generic.cpp=>generic/errvars-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/timer-generic.cpp=>generic/timer-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/mppresent-generic.cpp=>generic/mppresent-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.cpp=>r0drv/alloc-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.h=>r0drv/alloc-r0drv.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/initterm-r0drv.cpp=>r0drv/initterm-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/mp-r0drv.h=>r0drv/mp-r0drv.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/power-r0drv.h=>r0drv/power-r0drv.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/powernotification-r0drv.c=>r0drv/powernotification-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/assert-r0drv-freebsd.c=>r0drv/freebsd/assert-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/alloc-r0drv-freebsd.c=>r0drv/freebsd/alloc-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/initterm-r0drv-freebsd.c=>r0drv/freebsd/initterm-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/memobj-r0drv-freebsd.c=>r0drv/freebsd/memobj-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/memuserkernel-r0drv-freebsd.c=>r0drv/freebsd/memuserkernel-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/mp-r0drv-freebsd.c=>r0drv/freebsd/mp-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/process-r0drv-freebsd.c=>r0drv/freebsd/process-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semevent-r0drv-freebsd.c=>r0drv/freebsd/semevent-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semeventmulti-r0drv-freebsd.c=>r0drv/freebsd/semeventmulti-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semfastmutex-r0drv-freebsd.c=>r0drv/freebsd/semfastmutex-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semmutex-r0drv-freebsd.c=>r0drv/freebsd/semmutex-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/spinlock-r0drv-freebsd.c=>r0drv/freebsd/spinlock-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/the-freebsd-kernel.h=>r0drv/freebsd/the-freebsd-kernel.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/thread-r0drv-freebsd.c=>r0drv/freebsd/thread-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/thread2-r0drv-freebsd.c=>r0drv/freebsd/thread2-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/time-r0drv-freebsd.c=>r0drv/freebsd/time-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/timer-r0drv-freebsd.c=>r0drv/freebsd/timer-r0drv-freebsd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/sleepqueue-r0drv-freebsd.h=>r0drv/freebsd/sleepqueue-r0drv-freebsd.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/semspinmutex-r0drv-generic.c=>r0drv/generic/semspinmutex-r0drv-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/mpnotification-r0drv-generic.cpp=>r0drv/generic/mpnotification-r0drv-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/RTMpIsCpuWorkPending-r0drv-generic.cpp=>r0drv/generic/RTMpIsCpuWorkPending-r0drv-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/memobj-r0drv.cpp=>r0drv/memobj-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/VBox/log-vbox.cpp=>VBox/log-vbox.c \
+ ${PATH_ROOT}/src/VBox/Runtime/VBox/logbackdoor.cpp=>VBox/logbackdoor.c \
+ ${PATH_ROOT}/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.cpp=>VBox/RTLogWriteVmm-amd64-x86.c \
+ ${PATH_OUT}/version-generated.h=>version-generated.h \
+ ${PATH_OUT}/product-generated.h=>product-generated.h \
+ ${PATH_OUT}/revision-generated.h=>revision-generated.h \
+"
+
+FILES_VBOXGUEST_BIN=" \
+"
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk
new file mode 100644
index 00000000..eead95e5
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk
@@ -0,0 +1,254 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the common guest addition code library.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+
+#
+# Target config.
+#
+if defined(VBOX_WITH_ADDITION_DRIVERS) && !defined(VBOX_ONLY_VALIDATIONKIT)
+ LIBRARIES += \
+ VBoxGuestR0Lib \
+ VBoxGuestR0LibBase
+endif
+LIBRARIES += \
+ VBoxGuestR3Lib \
+ VBoxGuestR3LibShared
+ifndef VBOX_ONLY_VALIDATIONKIT
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd)
+ ifndef VBOX_USE_SYSTEM_XORG_HEADERS
+ LIBRARIES += \
+ VBoxGuestR3LibXFree86
+ endif
+ endif
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris)
+ LIBRARIES += \
+ VBoxGuestR3LibXOrg
+ endif
+endif
+LIBRARIES.win.amd64 += VBoxGuestR3Lib-x86 VBoxGuestR3LibShared-x86
+
+
+#
+# VBoxGuestR0Lib
+#
+VBoxGuestR0Lib_TEMPLATE = VBoxGuestR0DrvLib
+VBoxGuestR0Lib_DEFS = VBOX_WITH_HGCM \
+ $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,)
+VBoxGuestR0Lib_INCS = \
+ $(VBoxGuestR0Lib_0_OUTDIR)
+VBoxGuestR0Lib_SOURCES = \
+ VBoxGuestR0LibInit.cpp \
+ VBoxGuestR0LibPhysHeap.cpp \
+ VBoxGuestR0LibGenericRequest.cpp \
+ VBoxGuestR0LibVMMDev.cpp \
+ VBoxGuestR0LibHGCM.cpp \
+ VbglR0CanUsePhysPageList.cpp \
+ \
+ VBoxGuestR0LibIdc.cpp \
+ VBoxGuestR0LibSharedFolders.c \
+ VBoxGuestR0LibCrOgl.cpp \
+ VBoxGuestR0LibMouse.cpp
+VBoxGuestR0Lib_SOURCES.os2 = VBoxGuestR0LibIdc-os2.cpp
+VBoxGuestR0Lib_SOURCES.solaris = VBoxGuestR0LibIdc-solaris.cpp
+VBoxGuestR0Lib_SOURCES.win = VBoxGuestR0LibIdc-win.cpp
+ifn1of ($(KBUILD_TARGET), os2 solaris win)
+ VBoxGuestR0Lib_SOURCES += VBoxGuestR0LibIdc-unix.cpp
+endif
+
+#
+# VBoxGuestR0LibBase
+#
+VBoxGuestR0LibBase_TEMPLATE = VBoxGuestR0DrvLib
+VBoxGuestR0LibBase_DEFS = VBOX_WITH_HGCM VBGL_VBOXGUEST \
+ $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,)
+VBoxGuestR0LibBase_INCS = $(VBoxGuestR0Lib_INCS)
+VBoxGuestR0LibBase_INCS.win = $(VBoxGuestR0Lib_INCS.win)
+VBoxGuestR0LibBase_SOURCES = \
+ VBoxGuestR0LibInit.cpp \
+ VBoxGuestR0LibPhysHeap.cpp \
+ VBoxGuestR0LibGenericRequest.cpp \
+ VBoxGuestR0LibVMMDev.cpp \
+ VBoxGuestR0LibHGCMInternal.cpp \
+ VbglR0CanUsePhysPageList.cpp
+
+#
+# VBoxGuestR3Lib
+#
+VBoxGuestR3Lib_TEMPLATE := VBoxGuestR3Lib
+VBoxGuestR3Lib_DEFS = \
+ VBOX_WITH_HGCM \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_WITH_SHARED_CLIPBOARD),VBOX_WITH_SHARED_CLIPBOARD,) \
+ $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,) \
+ $(if $(VBOX_WITH_SHARED_FOLDERS),VBOX_WITH_SHARED_FOLDERS,) \
+ $(if $(VBOX_WITH_GUEST_CONTROL),VBOX_WITH_GUEST_CONTROL,)
+VBoxGuestR3Lib_SOURCES = \
+ VBoxGuestR3Lib.cpp \
+ VBoxGuestR3LibAdditions.cpp \
+ VBoxGuestR3LibAutoLogon.cpp \
+ VBoxGuestR3LibBalloon.cpp \
+ VBoxGuestR3LibCoreDump.cpp \
+ VBoxGuestR3LibCpuHotPlug.cpp \
+ VBoxGuestR3LibCredentials.cpp \
+ VBoxGuestR3LibDrmClient.cpp \
+ VBoxGuestR3LibEvent.cpp \
+ VBoxGuestR3LibGuestUser.cpp \
+ VBoxGuestR3LibGR.cpp \
+ VBoxGuestR3LibHGCM.cpp \
+ VBoxGuestR3LibHostChannel.cpp \
+ VBoxGuestR3LibLog.cpp \
+ VBoxGuestR3LibMisc.cpp \
+ VBoxGuestR3LibStat.cpp \
+ VBoxGuestR3LibTime.cpp \
+ VBoxGuestR3LibModule.cpp \
+ VBoxGuestR3LibPidFile.cpp \
+ VBoxGuestR3LibVrdp.cpp \
+ VBoxGuestR3LibMouse.cpp \
+ VBoxGuestR3LibSeamless.cpp \
+ VBoxGuestR3LibVideo.cpp
+ifneq ($(KBUILD_TARGET),win)
+ VBoxGuestR3Lib_SOURCES += \
+ VBoxGuestR3LibDaemonize.cpp
+endif
+ifdef VBOX_WITH_GUEST_PROPS
+ VBoxGuestR3Lib_SOURCES += \
+ VBoxGuestR3LibGuestProp.cpp \
+ VBoxGuestR3LibHostVersion.cpp
+endif
+ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VBoxGuestR3Lib_DEFS += VBOX_WITH_SHARED_CLIPBOARD_GUEST
+ VBoxGuestR3Lib_SOURCES += \
+ VBoxGuestR3LibClipboard.cpp
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ VBoxGuestR3Lib_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp
+ endif
+endif
+ifdef VBOX_WITH_SHARED_FOLDERS
+ VBoxGuestR3Lib_SOURCES += \
+ VBoxGuestR3LibSharedFolders.cpp
+endif
+ifdef VBOX_WITH_GUEST_CONTROL
+ VBoxGuestR3Lib_SOURCES += \
+ VBoxGuestR3LibGuestCtrl.cpp
+endif
+ifdef VBOX_WITH_DRAG_AND_DROP
+ VBoxGuestR3Lib_DEFS += \
+ VBOX_WITH_DRAG_AND_DROP \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,)
+ VBoxGuestR3Lib_SOURCES += \
+ VBoxGuestR3LibDragAndDrop.cpp
+endif
+
+VBoxGuestR3LibAdditions.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV)
+
+#
+# VBoxGuestR3LibShared - a PIC variant of VBoxGuestR3Lib for linking into .so/.dll/.dylib.
+#
+VBoxGuestR3LibShared_TEMPLATE := VBoxGuestR3Dll
+VBoxGuestR3LibShared_DEFS := $(VBoxGuestR3Lib_DEFS)
+VBoxGuestR3LibShared_SOURCES := $(VBoxGuestR3Lib_SOURCES)
+VBoxGuestR3LibShared_INST := $(INST_ADDITIONS_LIB)
+
+
+#
+# VBoxGuestR3Lib-x86 - an x86 (32-bit) variant of VBoxGuestR3Lib for 64-bit Windows.
+#
+VBoxGuestR3Lib-x86_EXTENDS := VBoxGuestR3Lib
+VBoxGuestR3Lib-x86_BLD_TRG_ARCH := x86
+
+
+#
+# VBoxGuestR3LibShared-x86 - an x86 (32-bit) variant of VBoxGuestR3LibShared for 64-bit Windows.
+#
+VBoxGuestR3LibShared-x86_EXTENDS := VBoxGuestR3LibShared
+VBoxGuestR3LibShared-x86_BLD_TRG_ARCH := x86
+
+
+#
+# VBoxGuestR3LibXFree86 - a reduced version of the guest library which uses
+# the X server runtime instead of IPRT, for use with old servers where the
+# C library is not available.
+#
+VBoxGuestR3LibXFree86_TEMPLATE = VBoxGuestR3XFree86Lib
+VBoxGuestR3LibXFree86_DEFS = \
+ VBOX_WITH_HGCM \
+ VBOX_VBGLR3_XFREE86 \
+ RTMEM_NO_WRAP_TO_EF_APIS \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) \
+ $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,)
+VBoxGuestR3LibXFree86_SOURCES = \
+ VBoxGuestR3Lib.cpp \
+ VBoxGuestR3LibGR.cpp \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBoxGuestR3LibGuestProp.cpp,) \
+ VBoxGuestR3LibMouse.cpp \
+ VBoxGuestR3LibMisc.cpp \
+ VBoxGuestR3LibSeamless.cpp \
+ VBoxGuestR3LibVideo.cpp \
+ VBoxGuestR3LibRuntimeXF86.cpp
+VBoxGuestR3LibXFree86_INCS = \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/Xserver \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3 \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11
+
+#
+# VBoxGuestR3LibXOrg - a reduced version of the guest library which uses
+# the C server runtime instead of IPRT.
+#
+VBoxGuestR3LibXOrg_TEMPLATE = VBoxGuestR3XOrgLib
+VBoxGuestR3LibXOrg_DEFS = \
+ VBOX_WITH_HGCM \
+ VBOX_VBGLR3_XORG \
+ RTMEM_NO_WRAP_TO_EF_APIS \
+ IN_RT_STATIC \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP),VBOX_WITH_DRAG_AND_DROP,) \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) \
+ $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS,)
+VBoxGuestR3LibXOrg_SOURCES = $(VBoxGuestR3LibXFree86_SOURCES)
+
+VBoxGuestR3LibRuntimeXF86.cpp_CXXFLAGS = -Wno-shadow
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibCrOgl.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibCrOgl.cpp
new file mode 100644
index 00000000..a6638dc0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibCrOgl.cpp
@@ -0,0 +1,134 @@
+/* $Id: VBoxGuestR0LibCrOgl.cpp $ */
+/** @file
+ * VBoxGuestLib - Ring-3 Support Library for VirtualBox guest additions, Chromium OpenGL Service.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "VBoxGuestR0LibInternal.h"
+
+#ifdef VBGL_VBOXGUEST
+# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code."
+#endif
+
+
+DECLR0VBGL(int) VbglR0CrCtlCreate(VBGLCRCTLHANDLE *phCtl)
+{
+ int rc;
+
+ if (phCtl)
+ {
+ struct VBGLHGCMHANDLEDATA *pHandleData = vbglR0HGCMHandleAlloc();
+ if (pHandleData)
+ {
+ rc = VbglR0IdcOpen(&pHandleData->IdcHandle,
+ VBGL_IOC_VERSION /*uReqVersion*/,
+ VBGL_IOC_VERSION & UINT32_C(0xffff0000) /*uMinVersion*/,
+ NULL /*puSessionVersion*/, NULL /*puDriverVersion*/, NULL /*uDriverRevision*/);
+ if (RT_SUCCESS(rc))
+ {
+ *phCtl = pHandleData;
+ return VINF_SUCCESS;
+ }
+
+ vbglR0HGCMHandleFree(pHandleData);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ *phCtl = NULL;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+DECLR0VBGL(int) VbglR0CrCtlDestroy(VBGLCRCTLHANDLE hCtl)
+{
+ VbglR0IdcClose(&hCtl->IdcHandle);
+
+ vbglR0HGCMHandleFree(hCtl);
+
+ return VINF_SUCCESS;
+}
+
+DECLR0VBGL(int) VbglR0CrCtlConConnect(VBGLCRCTLHANDLE hCtl, HGCMCLIENTID *pidClient)
+{
+ VBGLIOCHGCMCONNECT info;
+ int rc;
+
+ if (!hCtl || !pidClient)
+ return VERR_INVALID_PARAMETER;
+
+ RT_ZERO(info);
+ VBGLREQHDR_INIT(&info.Hdr, HGCM_CONNECT);
+ info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing;
+ RTStrCopy(info.u.In.Loc.u.host.achName, sizeof(info.u.In.Loc.u.host.achName), "VBoxSharedCrOpenGL");
+ rc = VbglR0IdcCall(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CONNECT, &info.Hdr, sizeof(info));
+ if (RT_SUCCESS(rc))
+ {
+ Assert(info.u.Out.idClient);
+ *pidClient = info.u.Out.idClient;
+ return rc;
+ }
+
+ AssertRC(rc);
+ *pidClient = 0;
+ return rc;
+}
+
+DECLR0VBGL(int) VbglR0CrCtlConDisconnect(VBGLCRCTLHANDLE hCtl, HGCMCLIENTID idClient)
+{
+ VBGLIOCHGCMDISCONNECT info;
+ VBGLREQHDR_INIT(&info.Hdr, HGCM_DISCONNECT);
+ info.u.In.idClient = idClient;
+ return VbglR0IdcCall(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_DISCONNECT, &info.Hdr, sizeof(info));
+}
+
+DECLR0VBGL(int) VbglR0CrCtlConCallRaw(VBGLCRCTLHANDLE hCtl, PVBGLIOCHGCMCALL pCallInfo, int cbCallInfo)
+{
+ return VbglR0IdcCallRaw(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CALL(cbCallInfo), &pCallInfo->Hdr, cbCallInfo);
+}
+
+DECLR0VBGL(int) VbglR0CrCtlConCall(VBGLCRCTLHANDLE hCtl, PVBGLIOCHGCMCALL pCallInfo, int cbCallInfo)
+{
+ int rc = VbglR0IdcCallRaw(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CALL(cbCallInfo), &pCallInfo->Hdr, cbCallInfo);
+ if (RT_SUCCESS(rc))
+ rc = pCallInfo->Hdr.rc;
+ return rc;
+}
+
+DECLR0VBGL(int) VbglR0CrCtlConCallUserDataRaw(VBGLCRCTLHANDLE hCtl, PVBGLIOCHGCMCALL pCallInfo, int cbCallInfo)
+{
+ return VbglR0IdcCallRaw(&hCtl->IdcHandle, VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA(cbCallInfo), &pCallInfo->Hdr, cbCallInfo);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp
new file mode 100644
index 00000000..72518a3e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp
@@ -0,0 +1,183 @@
+/* $Id: VBoxGuestR0LibGenericRequest.cpp $ */
+/** @file
+ * VBoxGuestLibR0 - Generic VMMDev request management.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+#include <iprt/asm.h>
+#include <iprt/asm-amd64-x86.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+
+
+DECLR0VBGL(int) VbglGR0Verify(const VMMDevRequestHeader *pReq, size_t cbReq)
+{
+ size_t cbReqExpected;
+
+ if (RT_UNLIKELY(!pReq || cbReq < sizeof(VMMDevRequestHeader)))
+ {
+ dprintf(("VbglGR0Verify: Invalid parameter: pReq = %p, cbReq = %zu\n", pReq, cbReq));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_UNLIKELY(pReq->size > cbReq))
+ {
+ dprintf(("VbglGR0Verify: request size %u > buffer size %zu\n", pReq->size, cbReq));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* The request size must correspond to the request type. */
+ cbReqExpected = vmmdevGetRequestSize(pReq->requestType);
+ if (RT_UNLIKELY(cbReq < cbReqExpected))
+ {
+ dprintf(("VbglGR0Verify: buffer size %zu < expected size %zu\n", cbReq, cbReqExpected));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (cbReqExpected == cbReq)
+ {
+ /*
+ * This is most likely a fixed size request, and in this case the
+ * request size must be also equal to the expected size.
+ */
+ if (RT_UNLIKELY(pReq->size != cbReqExpected))
+ {
+ dprintf(("VbglGR0Verify: request size %u != expected size %zu\n", pReq->size, cbReqExpected));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * This can be a variable size request. Check the request type and limit the size
+ * to VMMDEV_MAX_VMMDEVREQ_SIZE, which is max size supported by the host.
+ *
+ * Note: Keep this list sorted for easier human lookup!
+ */
+ if ( pReq->requestType == VMMDevReq_ChangeMemBalloon
+ || pReq->requestType == VMMDevReq_GetDisplayChangeRequestMulti
+#ifdef VBOX_WITH_64_BITS_GUESTS
+ || pReq->requestType == VMMDevReq_HGCMCall64
+#endif
+ || pReq->requestType == VMMDevReq_HGCMCall32
+ || pReq->requestType == VMMDevReq_RegisterSharedModule
+ || pReq->requestType == VMMDevReq_ReportGuestUserState
+ || pReq->requestType == VMMDevReq_LogString
+ || pReq->requestType == VMMDevReq_SetPointerShape
+ || pReq->requestType == VMMDevReq_VideoSetVisibleRegion
+ || pReq->requestType == VMMDevReq_VideoUpdateMonitorPositions)
+ {
+ if (RT_UNLIKELY(cbReq > VMMDEV_MAX_VMMDEVREQ_SIZE))
+ {
+ dprintf(("VbglGR0Verify: VMMDevReq_LogString: buffer size %zu too big\n", cbReq));
+ return VERR_BUFFER_OVERFLOW; /** @todo is this error code ok? */
+ }
+ }
+ else
+ {
+ dprintf(("VbglGR0Verify: request size %u > buffer size %zu\n", pReq->size, cbReq));
+ return VERR_IO_BAD_LENGTH; /** @todo is this error code ok? */
+ }
+
+ return VINF_SUCCESS;
+}
+
+DECLR0VBGL(int) VbglR0GRAlloc(VMMDevRequestHeader **ppReq, size_t cbReq, VMMDevRequestType enmReqType)
+{
+ int rc = vbglR0Enter();
+ if (RT_SUCCESS(rc))
+ {
+ if ( ppReq
+ && cbReq >= sizeof(VMMDevRequestHeader)
+ && cbReq == (uint32_t)cbReq)
+ {
+ VMMDevRequestHeader *pReq = (VMMDevRequestHeader *)VbglR0PhysHeapAlloc((uint32_t)cbReq);
+ AssertMsgReturn(pReq, ("VbglR0GRAlloc: no memory (cbReq=%u)\n", cbReq), VERR_NO_MEMORY);
+ memset(pReq, 0xAA, cbReq);
+
+ pReq->size = (uint32_t)cbReq;
+ pReq->version = VMMDEV_REQUEST_HEADER_VERSION;
+ pReq->requestType = enmReqType;
+ pReq->rc = VERR_GENERAL_FAILURE;
+ pReq->reserved1 = 0;
+#ifdef VBGL_VBOXGUEST
+ pReq->fRequestor = VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV
+#else
+ pReq->fRequestor = VMMDEV_REQUESTOR_KERNEL | VMMDEV_REQUESTOR_USR_DRV_OTHER
+#endif
+
+ | VMMDEV_REQUESTOR_CON_DONT_KNOW | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
+ *ppReq = pReq;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ dprintf(("VbglR0GRAlloc: Invalid parameter: ppReq=%p cbReq=%u\n", ppReq, cbReq));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ return rc;
+}
+
+DECLR0VBGL(int) VbglR0GRPerform(VMMDevRequestHeader *pReq)
+{
+ int rc = vbglR0Enter();
+ if (RT_SUCCESS(rc))
+ {
+ if (pReq)
+ {
+ RTCCPHYS PhysAddr = VbglR0PhysHeapGetPhysAddr(pReq);
+ if ( PhysAddr != 0
+ && PhysAddr < _4G) /* Port IO is 32 bit. */
+ {
+ ASMOutU32(g_vbgldata.portVMMDev + VMMDEV_PORT_OFF_REQUEST, (uint32_t)PhysAddr);
+ /* Make the compiler aware that the host has changed memory. */
+ ASMCompilerBarrier();
+ rc = pReq->rc;
+ }
+ else
+ rc = VERR_VBGL_INVALID_ADDR;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ return rc;
+}
+
+DECLR0VBGL(void) VbglR0GRFree(VMMDevRequestHeader *pReq)
+{
+ int rc = vbglR0Enter();
+ if (RT_SUCCESS(rc))
+ VbglR0PhysHeapFree(pReq);
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp
new file mode 100644
index 00000000..e2e28ebb
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp
@@ -0,0 +1,240 @@
+/* $Id: VBoxGuestR0LibHGCM.cpp $ */
+/** @file
+ * VBoxGuestLib - Host-Guest Communication Manager, ring-0 client drivers.
+ *
+ * These public functions can be only used by other drivers. They all
+ * do an IOCTL to VBoxGuest via IDC.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+
+#include <iprt/assert.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+
+#ifdef VBGL_VBOXGUEST
+# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code."
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VBGL_HGCM_ASSERT_MSG AssertReleaseMsg
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Fast heap for HGCM handles data.
+ * @{
+ */
+static RTSEMFASTMUTEX g_hMtxHGCMHandleData;
+static struct VBGLHGCMHANDLEDATA g_aHGCMHandleData[64];
+/** @} */
+
+
+/**
+ * Initializes the HGCM VBGL bits.
+ *
+ * @return VBox status code.
+ */
+DECLR0VBGL(int) VbglR0HGCMInit(void)
+{
+ AssertReturn(g_hMtxHGCMHandleData == NIL_RTSEMFASTMUTEX, VINF_ALREADY_INITIALIZED);
+ return RTSemFastMutexCreate(&g_hMtxHGCMHandleData);
+}
+
+/**
+ * Initializes the HGCM VBGL bits.
+ *
+ * @return VBox status code.
+ */
+DECLR0VBGL(int) VbglR0HGCMTerminate(void)
+{
+ RTSemFastMutexDestroy(g_hMtxHGCMHandleData);
+ g_hMtxHGCMHandleData = NIL_RTSEMFASTMUTEX;
+
+ return VINF_SUCCESS;
+}
+
+DECLINLINE(int) vbglR0HandleHeapEnter(void)
+{
+ int rc = RTSemFastMutexRequest(g_hMtxHGCMHandleData);
+
+ VBGL_HGCM_ASSERT_MSG(RT_SUCCESS(rc), ("Failed to request handle heap mutex, rc = %Rrc\n", rc));
+
+ return rc;
+}
+
+DECLINLINE(void) vbglR0HandleHeapLeave(void)
+{
+ RTSemFastMutexRelease(g_hMtxHGCMHandleData);
+}
+
+struct VBGLHGCMHANDLEDATA *vbglR0HGCMHandleAlloc(void)
+{
+ struct VBGLHGCMHANDLEDATA *p = NULL;
+ int rc = vbglR0HandleHeapEnter();
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t i;
+
+ /* Simple linear search in array. This will be called not so often, only connect/disconnect. */
+ /** @todo bitmap for faster search and other obvious optimizations. */
+ for (i = 0; i < RT_ELEMENTS(g_aHGCMHandleData); i++)
+ {
+ if (!g_aHGCMHandleData[i].fAllocated)
+ {
+ p = &g_aHGCMHandleData[i];
+ p->fAllocated = 1;
+ break;
+ }
+ }
+
+ vbglR0HandleHeapLeave();
+
+ VBGL_HGCM_ASSERT_MSG(p != NULL, ("Not enough HGCM handles.\n"));
+ }
+ return p;
+}
+
+void vbglR0HGCMHandleFree(struct VBGLHGCMHANDLEDATA *pHandle)
+{
+ if (pHandle)
+ {
+ int rc = vbglR0HandleHeapEnter();
+ if (RT_SUCCESS(rc))
+ {
+ VBGL_HGCM_ASSERT_MSG(pHandle->fAllocated, ("Freeing not allocated handle.\n"));
+
+ RT_ZERO(*pHandle);
+ vbglR0HandleHeapLeave();
+ }
+ }
+}
+
+DECLR0VBGL(int) VbglR0HGCMConnect(VBGLHGCMHANDLE *pHandle, const char *pszServiceName, HGCMCLIENTID *pidClient)
+{
+ int rc;
+ if (pHandle && pszServiceName && pidClient)
+ {
+ struct VBGLHGCMHANDLEDATA *pHandleData = vbglR0HGCMHandleAlloc();
+ if (pHandleData)
+ {
+ rc = VbglR0IdcOpen(&pHandleData->IdcHandle,
+ VBGL_IOC_VERSION /*uReqVersion*/,
+ VBGL_IOC_VERSION & UINT32_C(0xffff0000) /*uMinVersion*/,
+ NULL /*puSessionVersion*/, NULL /*puDriverVersion*/, NULL /*uDriverRevision*/);
+ if (RT_SUCCESS(rc))
+ {
+ VBGLIOCHGCMCONNECT Info;
+ RT_ZERO(Info);
+ VBGLREQHDR_INIT(&Info.Hdr, HGCM_CONNECT);
+ Info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing;
+ rc = RTStrCopy(Info.u.In.Loc.u.host.achName, sizeof(Info.u.In.Loc.u.host.achName), pszServiceName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0IdcCall(&pHandleData->IdcHandle, VBGL_IOCTL_HGCM_CONNECT, &Info.Hdr, sizeof(Info));
+ if (RT_SUCCESS(rc))
+ {
+ *pidClient = Info.u.Out.idClient;
+ *pHandle = pHandleData;
+ return rc;
+ }
+ }
+
+ VbglR0IdcClose(&pHandleData->IdcHandle);
+ }
+
+ vbglR0HGCMHandleFree(pHandleData);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+}
+
+DECLR0VBGL(int) VbglR0HGCMDisconnect(VBGLHGCMHANDLE handle, HGCMCLIENTID idClient)
+{
+ int rc;
+ VBGLIOCHGCMDISCONNECT Info;
+
+ RT_ZERO(Info);
+ VBGLREQHDR_INIT(&Info.Hdr, HGCM_DISCONNECT);
+ Info.u.In.idClient = idClient;
+ rc = VbglR0IdcCall(&handle->IdcHandle, VBGL_IOCTL_HGCM_DISCONNECT, &Info.Hdr, sizeof(Info));
+
+ VbglR0IdcClose(&handle->IdcHandle);
+
+ vbglR0HGCMHandleFree(handle);
+
+ return rc;
+}
+
+DECLR0VBGL(int) VbglR0HGCMCallRaw(VBGLHGCMHANDLE handle, PVBGLIOCHGCMCALL pData, uint32_t cbData)
+{
+ VBGL_HGCM_ASSERT_MSG(cbData >= sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(HGCMFunctionParameter),
+ ("cbData = %d, cParms = %d (calculated size %d)\n", cbData, pData->cParms,
+ sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(VBGLIOCHGCMCALL)));
+
+ return VbglR0IdcCallRaw(&handle->IdcHandle, VBGL_IOCTL_HGCM_CALL(cbData), &pData->Hdr, cbData);
+}
+
+DECLR0VBGL(int) VbglR0HGCMCall(VBGLHGCMHANDLE handle, PVBGLIOCHGCMCALL pData, uint32_t cbData)
+{
+ int rc = VbglR0HGCMCallRaw(handle, pData, cbData);
+ if (RT_SUCCESS(rc))
+ rc = pData->Hdr.rc;
+ return rc;
+}
+
+DECLR0VBGL(int) VbglR0HGCMCallUserDataRaw(VBGLHGCMHANDLE handle, PVBGLIOCHGCMCALL pData, uint32_t cbData)
+{
+ VBGL_HGCM_ASSERT_MSG(cbData >= sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(HGCMFunctionParameter),
+ ("cbData = %d, cParms = %d (calculated size %d)\n", cbData, pData->cParms,
+ sizeof(VBGLIOCHGCMCALL) + pData->cParms * sizeof(VBGLIOCHGCMCALL)));
+
+ return VbglR0IdcCallRaw(&handle->IdcHandle, VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA(cbData), &pData->Hdr, cbData);
+}
+
+
+DECLR0VBGL(int) VbglR0HGCMFastCall(VBGLHGCMHANDLE hHandle, PVBGLIOCIDCHGCMFASTCALL pCallReq, uint32_t cbCallReq)
+{
+ /* pCallReq->Hdr.rc and pCallReq->HgcmCallReq.header.header.rc; are not used by this IDC. */
+ return VbglR0IdcCallRaw(&hHandle->IdcHandle, VBGL_IOCTL_IDC_HGCM_FAST_CALL, &pCallReq->Hdr, cbCallReq);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp
new file mode 100644
index 00000000..2a237db1
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp
@@ -0,0 +1,1175 @@
+/* $Id: VBoxGuestR0LibHGCMInternal.cpp $ */
+/** @file
+ * VBoxGuestLib - Host-Guest Communication Manager internal functions, implemented by VBoxGuest
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_HGCM
+
+#include "VBoxGuestR0LibInternal.h"
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/memobj.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <VBox/err.h>
+
+#ifndef VBGL_VBOXGUEST
+# error "This file should only be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest."
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max parameter buffer size for a user request. */
+#define VBGLR0_MAX_HGCM_USER_PARM (24*_1M)
+/** The max parameter buffer size for a kernel request. */
+#define VBGLR0_MAX_HGCM_KERNEL_PARM (16*_1M)
+/** The max embedded buffer size. */
+#define VBGLR0_MAX_HGCM_EMBEDDED_BUFFER _64K
+
+#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN)
+/** Linux needs to use bounce buffers since RTR0MemObjLockUser has unwanted
+ * side effects.
+ * Darwin 32bit & 64bit also needs this because of 4GB/4GB user/kernel space. */
+# define USE_BOUNCE_BUFFERS
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Lock info structure used by VbglR0HGCMInternalCall and its helpers.
+ */
+struct VbglR0ParmInfo
+{
+ uint32_t cLockBufs;
+ struct
+ {
+ uint32_t iParm;
+ RTR0MEMOBJ hObj;
+#ifdef USE_BOUNCE_BUFFERS
+ void *pvSmallBuf;
+#endif
+ } aLockBufs[10];
+};
+
+
+
+/* These functions can be only used by VBoxGuest. */
+
+DECLR0VBGL(int) VbglR0HGCMInternalConnect(HGCMServiceLocation const *pLoc, uint32_t fRequestor, HGCMCLIENTID *pidClient,
+ PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData)
+{
+ int rc;
+ if ( RT_VALID_PTR(pLoc)
+ && RT_VALID_PTR(pidClient)
+ && RT_VALID_PTR(pfnAsyncCallback))
+ {
+ /* Allocate request */
+ VMMDevHGCMConnect *pHGCMConnect = NULL;
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pHGCMConnect, sizeof(VMMDevHGCMConnect), VMMDevReq_HGCMConnect);
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize request memory */
+ pHGCMConnect->header.header.fRequestor = fRequestor;
+
+ pHGCMConnect->header.fu32Flags = 0;
+
+ memcpy(&pHGCMConnect->loc, pLoc, sizeof(pHGCMConnect->loc));
+ pHGCMConnect->u32ClientID = 0;
+
+ /* Issue request */
+ rc = VbglR0GRPerform (&pHGCMConnect->header.header);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check if host decides to process the request asynchronously. */
+ if (rc == VINF_HGCM_ASYNC_EXECUTE)
+ {
+ /* Wait for request completion interrupt notification from host */
+ pfnAsyncCallback(&pHGCMConnect->header, pvAsyncData, u32AsyncData);
+ }
+
+ rc = pHGCMConnect->header.result;
+ if (RT_SUCCESS(rc))
+ *pidClient = pHGCMConnect->u32ClientID;
+ }
+ VbglR0GRFree(&pHGCMConnect->header.header);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+}
+
+
+DECLR0VBGL(int) VbglR0HGCMInternalDisconnect(HGCMCLIENTID idClient, uint32_t fRequestor,
+ PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData)
+{
+ int rc;
+ if ( idClient != 0
+ && pfnAsyncCallback)
+ {
+ /* Allocate request */
+ VMMDevHGCMDisconnect *pHGCMDisconnect = NULL;
+ rc = VbglR0GRAlloc ((VMMDevRequestHeader **)&pHGCMDisconnect, sizeof (VMMDevHGCMDisconnect), VMMDevReq_HGCMDisconnect);
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize request memory */
+ pHGCMDisconnect->header.header.fRequestor = fRequestor;
+
+ pHGCMDisconnect->header.fu32Flags = 0;
+
+ pHGCMDisconnect->u32ClientID = idClient;
+
+ /* Issue request */
+ rc = VbglR0GRPerform(&pHGCMDisconnect->header.header);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check if host decides to process the request asynchronously. */
+ if (rc == VINF_HGCM_ASYNC_EXECUTE)
+ {
+ /* Wait for request completion interrupt notification from host */
+ pfnAsyncCallback(&pHGCMDisconnect->header, pvAsyncData, u32AsyncData);
+ }
+
+ rc = pHGCMDisconnect->header.result;
+ }
+
+ VbglR0GRFree(&pHGCMDisconnect->header.header);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+}
+
+
+/**
+ * Preprocesses the HGCM call, validating and locking/buffering parameters.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCallInfo The call info.
+ * @param cbCallInfo The size of the call info structure.
+ * @param fIsUser Is it a user request or kernel request.
+ * @param pcbExtra Where to return the extra request space needed for
+ * physical page lists.
+ */
+static int vbglR0HGCMInternalPreprocessCall(PCVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo,
+ bool fIsUser, struct VbglR0ParmInfo *pParmInfo, size_t *pcbExtra)
+{
+ HGCMFunctionParameter const *pSrcParm = VBGL_HGCM_GET_CALL_PARMS(pCallInfo);
+ uint32_t const cParms = pCallInfo->cParms;
+ uint32_t iParm;
+ uint32_t cb;
+
+ /*
+ * Lock down the any linear buffers so we can get their addresses
+ * and figure out how much extra storage we need for page lists.
+ *
+ * Note! With kernel mode users we can be assertive. For user mode users
+ * we should just (debug) log it and fail without any fanfare.
+ */
+ *pcbExtra = 0;
+ pParmInfo->cLockBufs = 0;
+ for (iParm = 0; iParm < cParms; iParm++, pSrcParm++)
+ {
+ switch (pSrcParm->type)
+ {
+ case VMMDevHGCMParmType_32bit:
+ Log4(("GstHGCMCall: parm=%u type=32bit: %#010x\n", iParm, pSrcParm->u.value32));
+ break;
+
+ case VMMDevHGCMParmType_64bit:
+ Log4(("GstHGCMCall: parm=%u type=64bit: %#018RX64\n", iParm, pSrcParm->u.value64));
+ break;
+
+ case VMMDevHGCMParmType_PageList:
+ case VMMDevHGCMParmType_ContiguousPageList:
+ if (fIsUser)
+ return VERR_INVALID_PARAMETER;
+ cb = pSrcParm->u.PageList.size;
+ if (cb)
+ {
+ uint32_t off = pSrcParm->u.PageList.offset;
+ HGCMPageListInfo *pPgLst;
+ uint32_t cPages;
+ uint32_t u32;
+
+ AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM),
+ VERR_OUT_OF_RANGE);
+ AssertMsgReturn( off >= cParms * sizeof(HGCMFunctionParameter)
+ && off <= cbCallInfo - sizeof(HGCMPageListInfo),
+ ("offset=%#x cParms=%#x cbCallInfo=%#x\n", off, cParms, cbCallInfo),
+ VERR_INVALID_PARAMETER);
+
+ pPgLst = (HGCMPageListInfo *)((uint8_t *)pCallInfo + off);
+ cPages = pPgLst->cPages;
+ u32 = RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]) + off;
+ AssertMsgReturn(u32 <= cbCallInfo,
+ ("u32=%#x (cPages=%#x offset=%#x) cbCallInfo=%#x\n", u32, cPages, off, cbCallInfo),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pPgLst->offFirstPage < PAGE_SIZE, ("#x\n", pPgLst->offFirstPage), VERR_INVALID_PARAMETER);
+ u32 = RT_ALIGN_32(pPgLst->offFirstPage + cb, PAGE_SIZE) >> PAGE_SHIFT;
+ AssertMsgReturn(cPages == u32, ("cPages=%#x u32=%#x\n", cPages, u32), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(VBOX_HGCM_F_PARM_ARE_VALID(pPgLst->flags), ("%#x\n", pPgLst->flags), VERR_INVALID_PARAMETER);
+ Log4(("GstHGCMCall: parm=%u type=pglst: cb=%#010x cPgs=%u offPg0=%#x flags=%#x\n",
+ iParm, cb, cPages, pPgLst->offFirstPage, pPgLst->flags));
+ u32 = cPages;
+ while (u32-- > 0)
+ {
+ Log4(("GstHGCMCall: pg#%u=%RHp\n", u32, pPgLst->aPages[u32]));
+ AssertMsgReturn(!(pPgLst->aPages[u32] & (PAGE_OFFSET_MASK | UINT64_C(0xfff0000000000000))),
+ ("pg#%u=%RHp\n", u32, pPgLst->aPages[u32]),
+ VERR_INVALID_PARAMETER);
+ }
+
+ *pcbExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[pPgLst->cPages]);
+ }
+ else
+ Log4(("GstHGCMCall: parm=%u type=pglst: cb=0\n", iParm));
+ break;
+
+ case VMMDevHGCMParmType_Embedded:
+ if (fIsUser) /// @todo relax this.
+ return VERR_INVALID_PARAMETER;
+ cb = pSrcParm->u.Embedded.cbData;
+ if (cb)
+ {
+ uint32_t off = pSrcParm->u.Embedded.offData;
+ AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_EMBEDDED_BUFFER, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_EMBEDDED_BUFFER),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cb <= cbCallInfo - cParms * sizeof(HGCMFunctionParameter),
+ ("cb=%#x cParms=%#x cbCallInfo=%3x\n", cb, cParms, cbCallInfo),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn( off >= cParms * sizeof(HGCMFunctionParameter)
+ && off <= cbCallInfo - cb,
+ ("offData=%#x cParms=%#x cbCallInfo=%#x\n", off, cParms, cbCallInfo),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(VBOX_HGCM_F_PARM_ARE_VALID(pSrcParm->u.Embedded.fFlags),
+ ("%#x\n", pSrcParm->u.Embedded.fFlags), VERR_INVALID_PARAMETER);
+
+ *pcbExtra += RT_ALIGN_32(cb, 8);
+ }
+ else
+ Log4(("GstHGCMCall: parm=%u type=embed: cb=0\n", iParm));
+ break;
+
+
+ case VMMDevHGCMParmType_LinAddr_Locked_In:
+ case VMMDevHGCMParmType_LinAddr_Locked_Out:
+ case VMMDevHGCMParmType_LinAddr_Locked:
+ if (fIsUser)
+ return VERR_INVALID_PARAMETER;
+ if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ true))
+ {
+ cb = pSrcParm->u.Pointer.size;
+ AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM),
+ VERR_OUT_OF_RANGE);
+ if (cb != 0)
+ Log4(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p\n",
+ iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr));
+ else
+ Log4(("GstHGCMCall: parm=%u type=%#x: cb=0\n", iParm, pSrcParm->type));
+ break;
+ }
+ RT_FALL_THRU();
+
+ case VMMDevHGCMParmType_LinAddr_In:
+ case VMMDevHGCMParmType_LinAddr_Out:
+ case VMMDevHGCMParmType_LinAddr:
+ cb = pSrcParm->u.Pointer.size;
+ if (cb != 0)
+ {
+#ifdef USE_BOUNCE_BUFFERS
+ void *pvSmallBuf = NULL;
+#endif
+ uint32_t iLockBuf = pParmInfo->cLockBufs;
+ RTR0MEMOBJ hObj;
+ int rc;
+ uint32_t fAccess = pSrcParm->type == VMMDevHGCMParmType_LinAddr_In
+ || pSrcParm->type == VMMDevHGCMParmType_LinAddr_Locked_In
+ ? RTMEM_PROT_READ
+ : RTMEM_PROT_READ | RTMEM_PROT_WRITE;
+
+ AssertReturn(iLockBuf < RT_ELEMENTS(pParmInfo->aLockBufs), VERR_INVALID_PARAMETER);
+ if (!fIsUser)
+ {
+ AssertMsgReturn(cb <= VBGLR0_MAX_HGCM_KERNEL_PARM, ("%#x > %#x\n", cb, VBGLR0_MAX_HGCM_KERNEL_PARM),
+ VERR_OUT_OF_RANGE);
+ rc = RTR0MemObjLockKernel(&hObj, (void *)pSrcParm->u.Pointer.u.linearAddr, cb, fAccess);
+ if (RT_FAILURE(rc))
+ {
+ Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemObjLockKernel(,%p,%#x) -> %Rrc\n",
+ pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, pSrcParm->u.Pointer.u.linearAddr, cb, rc));
+ return rc;
+ }
+ Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p locked kernel -> %p\n",
+ iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj));
+ }
+ else if (cb > VBGLR0_MAX_HGCM_USER_PARM)
+ {
+ Log(("GstHGCMCall: id=%#x fn=%u parm=%u pv=%p cb=%#x > %#x -> out of range\n",
+ pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, pSrcParm->u.Pointer.u.linearAddr,
+ cb, VBGLR0_MAX_HGCM_USER_PARM));
+ return VERR_OUT_OF_RANGE;
+ }
+ else
+ {
+#ifndef USE_BOUNCE_BUFFERS
+ rc = RTR0MemObjLockUser(&hObj, (RTR3PTR)pSrcParm->u.Pointer.u.linearAddr, cb, fAccess, NIL_RTR0PROCESS);
+ if (RT_FAILURE(rc))
+ {
+ Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemObjLockUser(,%p,%#x,nil) -> %Rrc\n",
+ pCallInfo->u32ClientID, pCallInfo->u32Function, iParm, pSrcParm->u.Pointer.u.linearAddr, cb, rc));
+ return rc;
+ }
+ Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p locked user -> %p\n",
+ iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj));
+
+#else /* USE_BOUNCE_BUFFERS */
+ /*
+ * This is a bit massive, but we don't want to waste a
+ * whole page for a 3 byte string buffer (guest props).
+ *
+ * The threshold is ASSUMING sizeof(RTMEMHDR) == 16 and
+ * the system is using some power of two allocator.
+ */
+ /** @todo A more efficient strategy would be to combine buffers. However it
+ * is probably going to be more massive than the current code, so
+ * it can wait till later. */
+ bool fCopyIn = pSrcParm->type != VMMDevHGCMParmType_LinAddr_Out
+ && pSrcParm->type != VMMDevHGCMParmType_LinAddr_Locked_Out;
+ if (cb <= PAGE_SIZE / 2 - 16)
+ {
+ pvSmallBuf = fCopyIn ? RTMemTmpAlloc(cb) : RTMemTmpAllocZ(cb);
+ if (RT_UNLIKELY(!pvSmallBuf))
+ return VERR_NO_MEMORY;
+ if (fCopyIn)
+ {
+ rc = RTR0MemUserCopyFrom(pvSmallBuf, pSrcParm->u.Pointer.u.linearAddr, cb);
+ if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(pvSmallBuf);
+ Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemUserCopyFrom(,%p,%#x) -> %Rrc\n",
+ pCallInfo->u32ClientID, pCallInfo->u32Function, iParm,
+ pSrcParm->u.Pointer.u.linearAddr, cb, rc));
+ return rc;
+ }
+ }
+ rc = RTR0MemObjLockKernel(&hObj, pvSmallBuf, cb, fAccess);
+ if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(pvSmallBuf);
+ Log(("GstHGCMCall: RTR0MemObjLockKernel failed for small buffer: rc=%Rrc pvSmallBuf=%p cb=%#x\n",
+ rc, pvSmallBuf, cb));
+ return rc;
+ }
+ Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p small buffer %p -> %p\n",
+ iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, pvSmallBuf, hObj));
+ }
+ else
+ {
+ rc = RTR0MemObjAllocPage(&hObj, cb, false /*fExecutable*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!fCopyIn)
+ memset(RTR0MemObjAddress(hObj), '\0', cb);
+ else
+ {
+ rc = RTR0MemUserCopyFrom(RTR0MemObjAddress(hObj), pSrcParm->u.Pointer.u.linearAddr, cb);
+ if (RT_FAILURE(rc))
+ {
+ RTR0MemObjFree(hObj, false /*fFreeMappings*/);
+ Log(("GstHGCMCall: id=%#x fn=%u parm=%u RTR0MemUserCopyFrom(,%p,%#x) -> %Rrc\n",
+ pCallInfo->u32ClientID, pCallInfo->u32Function, iParm,
+ pSrcParm->u.Pointer.u.linearAddr, cb, rc));
+ return rc;
+ }
+ }
+ Log3(("GstHGCMCall: parm=%u type=%#x: cb=%#010x pv=%p big buffer -> %p\n",
+ iParm, pSrcParm->type, cb, pSrcParm->u.Pointer.u.linearAddr, hObj));
+ }
+#endif /* USE_BOUNCE_BUFFERS */
+ }
+
+ pParmInfo->aLockBufs[iLockBuf].iParm = iParm;
+ pParmInfo->aLockBufs[iLockBuf].hObj = hObj;
+#ifdef USE_BOUNCE_BUFFERS
+ pParmInfo->aLockBufs[iLockBuf].pvSmallBuf = pvSmallBuf;
+#endif
+ pParmInfo->cLockBufs = iLockBuf + 1;
+
+ if (VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ false))
+ {
+ size_t const cPages = RTR0MemObjSize(hObj) >> PAGE_SHIFT;
+ *pcbExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]);
+ }
+ }
+ else
+ Log4(("GstHGCMCall: parm=%u type=%#x: cb=0\n", iParm, pSrcParm->type));
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Translates locked linear address to the normal type.
+ * The locked types are only for the guest side and not handled by the host.
+ *
+ * @returns normal linear address type.
+ * @param enmType The type.
+ */
+static HGCMFunctionParameterType vbglR0HGCMInternalConvertLinAddrType(HGCMFunctionParameterType enmType)
+{
+ switch (enmType)
+ {
+ case VMMDevHGCMParmType_LinAddr_Locked_In:
+ return VMMDevHGCMParmType_LinAddr_In;
+ case VMMDevHGCMParmType_LinAddr_Locked_Out:
+ return VMMDevHGCMParmType_LinAddr_Out;
+ case VMMDevHGCMParmType_LinAddr_Locked:
+ return VMMDevHGCMParmType_LinAddr;
+ default:
+ return enmType;
+ }
+}
+
+
+/**
+ * Translates linear address types to page list direction flags.
+ *
+ * @returns page list flags.
+ * @param enmType The type.
+ */
+static uint32_t vbglR0HGCMInternalLinAddrTypeToPageListFlags(HGCMFunctionParameterType enmType)
+{
+ switch (enmType)
+ {
+ case VMMDevHGCMParmType_LinAddr_In:
+ case VMMDevHGCMParmType_LinAddr_Locked_In:
+ return VBOX_HGCM_F_PARM_DIRECTION_TO_HOST;
+
+ case VMMDevHGCMParmType_LinAddr_Out:
+ case VMMDevHGCMParmType_LinAddr_Locked_Out:
+ return VBOX_HGCM_F_PARM_DIRECTION_FROM_HOST;
+
+ default: AssertFailed(); RT_FALL_THRU();
+ case VMMDevHGCMParmType_LinAddr:
+ case VMMDevHGCMParmType_LinAddr_Locked:
+ return VBOX_HGCM_F_PARM_DIRECTION_BOTH;
+ }
+}
+
+
+/**
+ * Initializes the call request that we're sending to the host.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCallInfo The call info.
+ * @param cbCallInfo The size of the call info structure.
+ * @param fRequestor VMMDEV_REQUESTOR_XXX.
+ * @param fIsUser Is it a user request or kernel request.
+ * @param pcbExtra Where to return the extra request space needed for
+ * physical page lists.
+ */
+static void vbglR0HGCMInternalInitCall(VMMDevHGCMCall *pHGCMCall, PCVBGLIOCHGCMCALL pCallInfo,
+ uint32_t cbCallInfo, uint32_t fRequestor, bool fIsUser, struct VbglR0ParmInfo *pParmInfo)
+{
+ HGCMFunctionParameter const *pSrcParm = VBGL_HGCM_GET_CALL_PARMS(pCallInfo);
+ HGCMFunctionParameter *pDstParm = VMMDEV_HGCM_CALL_PARMS(pHGCMCall);
+ uint32_t const cParms = pCallInfo->cParms;
+ uint32_t offExtra = (uint32_t)((uintptr_t)(pDstParm + cParms) - (uintptr_t)pHGCMCall);
+ uint32_t iLockBuf = 0;
+ uint32_t iParm;
+ RT_NOREF1(cbCallInfo);
+#ifndef USE_BOUNCE_BUFFERS
+ RT_NOREF1(fIsUser);
+#endif
+
+ /*
+ * The call request headers.
+ */
+ pHGCMCall->header.header.fRequestor = !fIsUser || (fRequestor & VMMDEV_REQUESTOR_USERMODE) ? fRequestor
+ : VMMDEV_REQUESTOR_USERMODE | VMMDEV_REQUESTOR_USR_NOT_GIVEN
+ | VMMDEV_REQUESTOR_TRUST_NOT_GIVEN | VMMDEV_REQUESTOR_CON_DONT_KNOW;
+
+ pHGCMCall->header.fu32Flags = 0;
+ pHGCMCall->header.result = VINF_SUCCESS;
+
+ pHGCMCall->u32ClientID = pCallInfo->u32ClientID;
+ pHGCMCall->u32Function = pCallInfo->u32Function;
+ pHGCMCall->cParms = cParms;
+
+ /*
+ * The parameters.
+ */
+ for (iParm = 0; iParm < cParms; iParm++, pSrcParm++, pDstParm++)
+ {
+ switch (pSrcParm->type)
+ {
+ case VMMDevHGCMParmType_32bit:
+ case VMMDevHGCMParmType_64bit:
+ *pDstParm = *pSrcParm;
+ break;
+
+ case VMMDevHGCMParmType_PageList:
+ case VMMDevHGCMParmType_ContiguousPageList:
+ pDstParm->type = pSrcParm->type;
+ pDstParm->u.PageList.size = pSrcParm->u.PageList.size;
+ if (pSrcParm->u.PageList.size)
+ {
+ HGCMPageListInfo const *pSrcPgLst = (HGCMPageListInfo *)((uint8_t *)pCallInfo + pSrcParm->u.PageList.offset);
+ HGCMPageListInfo *pDstPgLst = (HGCMPageListInfo *)((uint8_t *)pHGCMCall + offExtra);
+ uint32_t const cPages = pSrcPgLst->cPages;
+ uint32_t iPage;
+
+ pDstParm->u.PageList.offset = offExtra;
+ pDstPgLst->flags = pSrcPgLst->flags;
+ pDstPgLst->offFirstPage = pSrcPgLst->offFirstPage;
+ pDstPgLst->cPages = (uint16_t)cPages;
+ for (iPage = 0; iPage < cPages; iPage++)
+ pDstPgLst->aPages[iPage] = pSrcPgLst->aPages[iPage];
+
+ offExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]);
+ }
+ else
+ pDstParm->u.PageList.offset = 0; /** @todo will fail on the host side now */
+ break;
+
+ case VMMDevHGCMParmType_Embedded:
+ {
+ uint32_t const cb = pSrcParm->u.Embedded.cbData;
+ pDstParm->type = VMMDevHGCMParmType_Embedded;
+ pDstParm->u.Embedded.cbData = cb;
+ pDstParm->u.Embedded.offData = offExtra;
+ if (cb > 0)
+ {
+ uint8_t *pbDst = (uint8_t *)pHGCMCall + offExtra;
+ if (pSrcParm->u.Embedded.fFlags & VBOX_HGCM_F_PARM_DIRECTION_TO_HOST)
+ {
+ memcpy(pbDst, (uint8_t const *)pCallInfo + pSrcParm->u.Embedded.offData, cb);
+ if (RT_ALIGN(cb, 8) != cb)
+ memset(pbDst + cb, 0, RT_ALIGN(cb, 8) - cb);
+ }
+ else
+ RT_BZERO(pbDst, RT_ALIGN(cb, 8));
+ offExtra += RT_ALIGN(cb, 8);
+ }
+ break;
+ }
+
+ case VMMDevHGCMParmType_LinAddr_Locked_In:
+ case VMMDevHGCMParmType_LinAddr_Locked_Out:
+ case VMMDevHGCMParmType_LinAddr_Locked:
+ if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ true))
+ {
+ *pDstParm = *pSrcParm;
+ pDstParm->type = vbglR0HGCMInternalConvertLinAddrType(pSrcParm->type);
+ break;
+ }
+ RT_FALL_THRU();
+
+ case VMMDevHGCMParmType_LinAddr_In:
+ case VMMDevHGCMParmType_LinAddr_Out:
+ case VMMDevHGCMParmType_LinAddr:
+ if (pSrcParm->u.Pointer.size != 0)
+ {
+#ifdef USE_BOUNCE_BUFFERS
+ void *pvSmallBuf = pParmInfo->aLockBufs[iLockBuf].pvSmallBuf;
+#endif
+ RTR0MEMOBJ hObj = pParmInfo->aLockBufs[iLockBuf].hObj;
+ Assert(iParm == pParmInfo->aLockBufs[iLockBuf].iParm);
+
+ if (VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ false))
+ {
+ HGCMPageListInfo *pDstPgLst = (HGCMPageListInfo *)((uint8_t *)pHGCMCall + offExtra);
+ size_t const cPages = RTR0MemObjSize(hObj) >> PAGE_SHIFT;
+ size_t iPage;
+
+ pDstParm->type = VMMDevHGCMParmType_PageList;
+ pDstParm->u.PageList.size = pSrcParm->u.Pointer.size;
+ pDstParm->u.PageList.offset = offExtra;
+ pDstPgLst->flags = vbglR0HGCMInternalLinAddrTypeToPageListFlags(pSrcParm->type);
+#ifdef USE_BOUNCE_BUFFERS
+ if (fIsUser)
+ pDstPgLst->offFirstPage = (uintptr_t)pvSmallBuf & PAGE_OFFSET_MASK;
+ else
+#endif
+ pDstPgLst->offFirstPage = (uint16_t)(pSrcParm->u.Pointer.u.linearAddr & PAGE_OFFSET_MASK);
+ pDstPgLst->cPages = (uint16_t)cPages; Assert(pDstPgLst->cPages == cPages);
+ for (iPage = 0; iPage < cPages; iPage++)
+ {
+ pDstPgLst->aPages[iPage] = RTR0MemObjGetPagePhysAddr(hObj, iPage);
+ Assert(pDstPgLst->aPages[iPage] != NIL_RTHCPHYS);
+ }
+
+ offExtra += RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]);
+ }
+ else
+ {
+ pDstParm->type = vbglR0HGCMInternalConvertLinAddrType(pSrcParm->type);
+ pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size;
+#ifdef USE_BOUNCE_BUFFERS
+ if (fIsUser)
+ pDstParm->u.Pointer.u.linearAddr = pvSmallBuf
+ ? (uintptr_t)pvSmallBuf
+ : (uintptr_t)RTR0MemObjAddress(hObj);
+ else
+#endif
+ pDstParm->u.Pointer.u.linearAddr = pSrcParm->u.Pointer.u.linearAddr;
+ }
+ iLockBuf++;
+ }
+ else
+ {
+ pDstParm->type = vbglR0HGCMInternalConvertLinAddrType(pSrcParm->type);
+ pDstParm->u.Pointer.size = 0;
+ pDstParm->u.Pointer.u.linearAddr = 0;
+ }
+ break;
+
+ default:
+ AssertFailed();
+ pDstParm->type = VMMDevHGCMParmType_Invalid;
+ break;
+ }
+ }
+}
+
+
+/**
+ * Performs the call and completion wait.
+ *
+ * @returns VBox status code of this operation, not necessarily the call.
+ *
+ * @param pHGCMCall The HGCM call info.
+ * @param pfnAsyncCallback The async callback that will wait for the call
+ * to complete.
+ * @param pvAsyncData Argument for the callback.
+ * @param u32AsyncData Argument for the callback.
+ * @param pfLeakIt Where to return the leak it / free it,
+ * indicator. Cancellation fun.
+ */
+static int vbglR0HGCMInternalDoCall(VMMDevHGCMCall *pHGCMCall, PFNVBGLHGCMCALLBACK pfnAsyncCallback,
+ void *pvAsyncData, uint32_t u32AsyncData, bool *pfLeakIt)
+{
+ int rc;
+
+ Log(("calling VbglR0GRPerform\n"));
+ rc = VbglR0GRPerform(&pHGCMCall->header.header);
+ Log(("VbglR0GRPerform rc = %Rrc (header rc=%d)\n", rc, pHGCMCall->header.result));
+
+ /*
+ * If the call failed, but as a result of the request itself, then pretend
+ * success. Upper layers will interpret the result code in the packet.
+ */
+ if ( RT_FAILURE(rc)
+ && rc == pHGCMCall->header.result)
+ {
+ Assert(pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE);
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Check if host decides to process the request asynchronously,
+ * if so, we wait for it to complete using the caller supplied callback.
+ */
+ *pfLeakIt = false;
+ if (rc == VINF_HGCM_ASYNC_EXECUTE)
+ {
+ Log(("Processing HGCM call asynchronously\n"));
+ rc = pfnAsyncCallback(&pHGCMCall->header, pvAsyncData, u32AsyncData);
+ if (pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE)
+ {
+ Assert(!(pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_CANCELLED));
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /*
+ * The request didn't complete in time or the call was interrupted,
+ * the RC from the callback indicates which. Try cancel the request.
+ *
+ * This is a bit messy because we're racing request completion. Sorry.
+ */
+ /** @todo It would be nice if we could use the waiter callback to do further
+ * waiting in case of a completion race. If it wasn't for WINNT having its own
+ * version of all that stuff, I would've done it already. */
+ VMMDevHGCMCancel2 *pCancelReq;
+ int rc2 = VbglR0GRAlloc((VMMDevRequestHeader **)&pCancelReq, sizeof(*pCancelReq), VMMDevReq_HGCMCancel2);
+ if (RT_SUCCESS(rc2))
+ {
+ pCancelReq->physReqToCancel = VbglR0PhysHeapGetPhysAddr(pHGCMCall);
+ rc2 = VbglR0GRPerform(&pCancelReq->header);
+ VbglR0GRFree(&pCancelReq->header);
+ }
+#if 1 /** @todo ADDVER: Remove this on next minor version change. */
+ if (rc2 == VERR_NOT_IMPLEMENTED)
+ {
+ /* host is too old, or we're out of heap. */
+ pHGCMCall->header.fu32Flags |= VBOX_HGCM_REQ_CANCELLED;
+ pHGCMCall->header.header.requestType = VMMDevReq_HGCMCancel;
+ rc2 = VbglR0GRPerform(&pHGCMCall->header.header);
+ if (rc2 == VERR_INVALID_PARAMETER)
+ rc2 = VERR_NOT_FOUND;
+ else if (RT_SUCCESS(rc))
+ RTThreadSleep(1);
+ }
+#endif
+ if (RT_SUCCESS(rc)) rc = VERR_INTERRUPTED; /** @todo weed this out from the WINNT VBoxGuest code. */
+ if (RT_SUCCESS(rc2))
+ {
+ Log(("vbglR0HGCMInternalDoCall: successfully cancelled\n"));
+ pHGCMCall->header.fu32Flags |= VBOX_HGCM_REQ_CANCELLED;
+ }
+ else
+ {
+ /*
+ * Wait for a bit while the host (hopefully) completes it.
+ */
+ uint64_t u64Start = RTTimeSystemMilliTS();
+ uint32_t cMilliesToWait = rc2 == VERR_NOT_FOUND || rc2 == VERR_SEM_DESTROYED ? 500 : 2000;
+ uint64_t cElapsed = 0;
+ if (rc2 != VERR_NOT_FOUND)
+ {
+ static unsigned s_cErrors = 0;
+ if (s_cErrors++ < 32)
+ LogRel(("vbglR0HGCMInternalDoCall: Failed to cancel the HGCM call on %Rrc: rc2=%Rrc\n", rc, rc2));
+ }
+ else
+ Log(("vbglR0HGCMInternalDoCall: Cancel race rc=%Rrc rc2=%Rrc\n", rc, rc2));
+
+ do
+ {
+ ASMCompilerBarrier(); /* paranoia */
+ if (pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE)
+ break;
+ RTThreadSleep(1);
+ cElapsed = RTTimeSystemMilliTS() - u64Start;
+ } while (cElapsed < cMilliesToWait);
+
+ ASMCompilerBarrier(); /* paranoia^2 */
+ if (pHGCMCall->header.fu32Flags & VBOX_HGCM_REQ_DONE)
+ rc = VINF_SUCCESS;
+ else
+ {
+ LogRel(("vbglR0HGCMInternalDoCall: Leaking %u bytes. Pending call to %u with %u parms. (rc2=%Rrc)\n",
+ pHGCMCall->header.header.size, pHGCMCall->u32Function, pHGCMCall->cParms, rc2));
+ *pfLeakIt = true;
+ }
+ Log(("vbglR0HGCMInternalDoCall: Cancel race ended with rc=%Rrc (rc2=%Rrc) after %llu ms\n", rc, rc2, cElapsed));
+ }
+ }
+ }
+
+ Log(("GstHGCMCall: rc=%Rrc result=%Rrc fu32Flags=%#x fLeakIt=%d\n",
+ rc, pHGCMCall->header.result, pHGCMCall->header.fu32Flags, *pfLeakIt));
+ return rc;
+}
+
+
+/**
+ * Copies the result of the call back to the caller info structure and user
+ * buffers (if using bounce buffers).
+ *
+ * @returns rc, unless RTR0MemUserCopyTo fails.
+ * @param pCallInfo Call info structure to update.
+ * @param cbCallInfo The size of the client request.
+ * @param pHGCMCall HGCM call request.
+ * @param cbHGCMCall The size of the HGCM call request.
+ * @param pParmInfo Parameter locking/buffering info.
+ * @param fIsUser Is it a user (true) or kernel request.
+ * @param rc The current result code. Passed along to
+ * preserve informational status codes.
+ */
+static int vbglR0HGCMInternalCopyBackResult(PVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo,
+ VMMDevHGCMCall const *pHGCMCall, uint32_t cbHGCMCall,
+ struct VbglR0ParmInfo *pParmInfo, bool fIsUser, int rc)
+{
+ HGCMFunctionParameter const *pSrcParm = VMMDEV_HGCM_CALL_PARMS(pHGCMCall);
+ HGCMFunctionParameter *pDstParm = VBGL_HGCM_GET_CALL_PARMS(pCallInfo);
+ uint32_t const cParms = pCallInfo->cParms;
+#ifdef USE_BOUNCE_BUFFERS
+ uint32_t iLockBuf = 0;
+#endif
+ uint32_t iParm;
+ RT_NOREF1(pParmInfo);
+#ifndef USE_BOUNCE_BUFFERS
+ RT_NOREF1(fIsUser);
+#endif
+
+ /*
+ * The call result.
+ */
+ pCallInfo->Hdr.rc = pHGCMCall->header.result;
+
+ /*
+ * Copy back parameters.
+ */
+ /** @todo This is assuming user data (pDstParm) is buffered. Not true
+ * on OS/2, though I'm not sure we care... */
+ for (iParm = 0; iParm < cParms; iParm++, pSrcParm++, pDstParm++)
+ {
+ switch (pDstParm->type)
+ {
+ case VMMDevHGCMParmType_32bit:
+ case VMMDevHGCMParmType_64bit:
+ *pDstParm = *pSrcParm;
+ break;
+
+ case VMMDevHGCMParmType_PageList:
+ case VMMDevHGCMParmType_ContiguousPageList:
+ pDstParm->u.PageList.size = pSrcParm->u.PageList.size;
+ break;
+
+ case VMMDevHGCMParmType_Embedded:
+ {
+ uint32_t const cbDst = pDstParm->u.Embedded.cbData;
+ uint32_t cbSrc;
+ pDstParm->u.Embedded.cbData = cbSrc = pSrcParm->u.Embedded.cbData;
+ if ( cbSrc > 0
+ && (pDstParm->u.Embedded.fFlags & VBOX_HGCM_F_PARM_DIRECTION_FROM_HOST))
+ {
+ uint32_t const offDst = pDstParm->u.Embedded.offData;
+ uint32_t const offSrc = pSrcParm->u.Embedded.offData;
+
+ AssertReturn(offDst < cbCallInfo, VERR_INTERNAL_ERROR_2);
+ AssertReturn(offDst >= sizeof(*pCallInfo) + cParms * sizeof(*pDstParm), VERR_INTERNAL_ERROR_2);
+ AssertReturn(cbDst <= cbCallInfo - offDst , VERR_INTERNAL_ERROR_2);
+
+ AssertReturn(offSrc < cbCallInfo, VERR_INTERNAL_ERROR_2);
+ AssertReturn(offSrc >= sizeof(*pHGCMCall) + cParms * sizeof(*pSrcParm), VERR_INTERNAL_ERROR_2);
+ if (cbSrc <= cbHGCMCall - offSrc)
+ { /* likely */ }
+ else
+ {
+ /* Special case: Buffer overflow w/ correct size given. */
+ AssertReturn(RT_FAILURE_NP(rc), VERR_INTERNAL_ERROR_2);
+ cbSrc = cbHGCMCall - offSrc;
+ }
+ memcpy((uint8_t *)pCallInfo + offDst, (uint8_t const *)pHGCMCall + offSrc, RT_MIN(cbSrc, cbDst));
+ }
+ break;
+ }
+
+ case VMMDevHGCMParmType_LinAddr_Locked_In:
+ case VMMDevHGCMParmType_LinAddr_In:
+#ifdef USE_BOUNCE_BUFFERS
+ if ( fIsUser
+ && iLockBuf < pParmInfo->cLockBufs
+ && iParm == pParmInfo->aLockBufs[iLockBuf].iParm)
+ iLockBuf++;
+#endif
+ pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size;
+ break;
+
+ case VMMDevHGCMParmType_LinAddr_Locked_Out:
+ case VMMDevHGCMParmType_LinAddr_Locked:
+ if (!VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ true))
+ {
+ pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size;
+ break;
+ }
+ RT_FALL_THRU();
+
+ case VMMDevHGCMParmType_LinAddr_Out:
+ case VMMDevHGCMParmType_LinAddr:
+ {
+#ifdef USE_BOUNCE_BUFFERS
+ if (fIsUser)
+ {
+ size_t cbOut = RT_MIN(pSrcParm->u.Pointer.size, pDstParm->u.Pointer.size);
+ if (cbOut)
+ {
+ int rc2;
+ Assert(pParmInfo->aLockBufs[iLockBuf].iParm == iParm);
+ rc2 = RTR0MemUserCopyTo((RTR3PTR)pDstParm->u.Pointer.u.linearAddr,
+ pParmInfo->aLockBufs[iLockBuf].pvSmallBuf
+ ? pParmInfo->aLockBufs[iLockBuf].pvSmallBuf
+ : RTR0MemObjAddress(pParmInfo->aLockBufs[iLockBuf].hObj),
+ cbOut);
+ if (RT_FAILURE(rc2))
+ return rc2;
+ iLockBuf++;
+ }
+ else if ( iLockBuf < pParmInfo->cLockBufs
+ && iParm == pParmInfo->aLockBufs[iLockBuf].iParm)
+ iLockBuf++;
+ }
+#endif
+ pDstParm->u.Pointer.size = pSrcParm->u.Pointer.size;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ rc = VERR_INTERNAL_ERROR_4;
+ break;
+ }
+ }
+
+#ifdef USE_BOUNCE_BUFFERS
+ Assert(!fIsUser || pParmInfo->cLockBufs == iLockBuf);
+#endif
+ return rc;
+}
+
+
+DECLR0VBGL(int) VbglR0HGCMInternalCall(PVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo, uint32_t fFlags, uint32_t fRequestor,
+ PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData)
+{
+ bool fIsUser = (fFlags & VBGLR0_HGCMCALL_F_MODE_MASK) == VBGLR0_HGCMCALL_F_USER;
+ struct VbglR0ParmInfo ParmInfo;
+ size_t cbExtra;
+ int rc;
+
+ /*
+ * Basic validation.
+ */
+ AssertMsgReturn( !pCallInfo
+ || !pfnAsyncCallback
+ || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS
+ || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK),
+ ("pCallInfo=%p pfnAsyncCallback=%p fFlags=%#x\n", pCallInfo, pfnAsyncCallback, fFlags),
+ VERR_INVALID_PARAMETER);
+ AssertReturn( cbCallInfo >= sizeof(VBGLIOCHGCMCALL)
+ || cbCallInfo >= pCallInfo->cParms * sizeof(HGCMFunctionParameter),
+ VERR_INVALID_PARAMETER);
+
+ Log(("GstHGCMCall: u32ClientID=%#x u32Function=%u cParms=%u cbCallInfo=%#x fFlags=%#x\n",
+ pCallInfo->u32ClientID, pCallInfo->u32ClientID, pCallInfo->u32Function, pCallInfo->cParms, cbCallInfo, fFlags));
+
+ /*
+ * Validate, lock and buffer the parameters for the call.
+ * This will calculate the amount of extra space for physical page list.
+ */
+ rc = vbglR0HGCMInternalPreprocessCall(pCallInfo, cbCallInfo, fIsUser, &ParmInfo, &cbExtra);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate the request buffer and recreate the call request.
+ */
+ VMMDevHGCMCall *pHGCMCall;
+ uint32_t const cbHGCMCall = sizeof(VMMDevHGCMCall) + pCallInfo->cParms * sizeof(HGCMFunctionParameter) + (uint32_t)cbExtra;
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pHGCMCall, cbHGCMCall, VMMDevReq_HGCMCall);
+ if (RT_SUCCESS(rc))
+ {
+ bool fLeakIt;
+ vbglR0HGCMInternalInitCall(pHGCMCall, pCallInfo, cbCallInfo, fRequestor, fIsUser, &ParmInfo);
+
+ /*
+ * Perform the call.
+ */
+ rc = vbglR0HGCMInternalDoCall(pHGCMCall, pfnAsyncCallback, pvAsyncData, u32AsyncData, &fLeakIt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Copy back the result (parameters and buffers that changed).
+ */
+ rc = vbglR0HGCMInternalCopyBackResult(pCallInfo, cbCallInfo, pHGCMCall, cbHGCMCall, &ParmInfo, fIsUser, rc);
+ }
+ else
+ {
+ if ( rc != VERR_INTERRUPTED
+ && rc != VERR_TIMEOUT)
+ {
+ static unsigned s_cErrors = 0;
+ if (s_cErrors++ < 32)
+ LogRel(("VbglR0HGCMInternalCall: vbglR0HGCMInternalDoCall failed. rc=%Rrc\n", rc));
+ }
+ }
+
+ if (!fLeakIt)
+ VbglR0GRFree(&pHGCMCall->header.header);
+ }
+ }
+ else
+ LogRel(("VbglR0HGCMInternalCall: vbglR0HGCMInternalPreprocessCall failed. rc=%Rrc\n", rc));
+
+ /*
+ * Release locks and free bounce buffers.
+ */
+ if (ParmInfo.cLockBufs)
+ while (ParmInfo.cLockBufs-- > 0)
+ {
+ RTR0MemObjFree(ParmInfo.aLockBufs[ParmInfo.cLockBufs].hObj, false /*fFreeMappings*/);
+#ifdef USE_BOUNCE_BUFFERS
+ RTMemTmpFree(ParmInfo.aLockBufs[ParmInfo.cLockBufs].pvSmallBuf);
+#endif
+ }
+
+ return rc;
+}
+
+
+#if ARCH_BITS == 64
+DECLR0VBGL(int) VbglR0HGCMInternalCall32(PVBGLIOCHGCMCALL pCallInfo, uint32_t cbCallInfo, uint32_t fFlags, uint32_t fRequestor,
+ PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData)
+{
+ PVBGLIOCHGCMCALL pCallInfo64 = NULL;
+ HGCMFunctionParameter *pParm64 = NULL;
+ HGCMFunctionParameter32 *pParm32 = NULL;
+ uint32_t cParms = 0;
+ uint32_t iParm = 0;
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Input validation.
+ */
+ AssertMsgReturn( !pCallInfo
+ || !pfnAsyncCallback
+ || pCallInfo->cParms > VBOX_HGCM_MAX_PARMS
+ || !(fFlags & ~VBGLR0_HGCMCALL_F_MODE_MASK),
+ ("pCallInfo=%p pfnAsyncCallback=%p fFlags=%#x\n", pCallInfo, pfnAsyncCallback, fFlags),
+ VERR_INVALID_PARAMETER);
+ AssertReturn( cbCallInfo >= sizeof(VBGLIOCHGCMCALL)
+ || cbCallInfo >= pCallInfo->cParms * sizeof(HGCMFunctionParameter32),
+ VERR_INVALID_PARAMETER);
+
+ /* This Assert does not work on Solaris/Windows 64/32 mixed mode, not sure why, skipping for now */
+#if !defined(RT_OS_SOLARIS) && !defined(RT_OS_WINDOWS)
+ AssertReturn((fFlags & VBGLR0_HGCMCALL_F_MODE_MASK) == VBGLR0_HGCMCALL_F_KERNEL, VERR_WRONG_ORDER);
+#endif
+
+ cParms = pCallInfo->cParms;
+ Log(("VbglR0HGCMInternalCall32: cParms=%d, u32Function=%d, fFlags=%#x\n", cParms, pCallInfo->u32Function, fFlags));
+
+ /*
+ * The simple approach, allocate a temporary request and convert the parameters.
+ */
+ pCallInfo64 = (PVBGLIOCHGCMCALL)RTMemTmpAllocZ(sizeof(*pCallInfo64) + cParms * sizeof(HGCMFunctionParameter));
+ if (!pCallInfo64)
+ return VERR_NO_TMP_MEMORY;
+
+ *pCallInfo64 = *pCallInfo;
+ pParm32 = VBGL_HGCM_GET_CALL_PARMS32(pCallInfo);
+ pParm64 = VBGL_HGCM_GET_CALL_PARMS(pCallInfo64);
+ for (iParm = 0; iParm < cParms; iParm++, pParm32++, pParm64++)
+ {
+ switch (pParm32->type)
+ {
+ case VMMDevHGCMParmType_32bit:
+ pParm64->type = VMMDevHGCMParmType_32bit;
+ pParm64->u.value32 = pParm32->u.value32;
+ break;
+
+ case VMMDevHGCMParmType_64bit:
+ pParm64->type = VMMDevHGCMParmType_64bit;
+ pParm64->u.value64 = pParm32->u.value64;
+ break;
+
+ case VMMDevHGCMParmType_LinAddr_Out:
+ case VMMDevHGCMParmType_LinAddr:
+ case VMMDevHGCMParmType_LinAddr_In:
+ pParm64->type = pParm32->type;
+ pParm64->u.Pointer.size = pParm32->u.Pointer.size;
+ pParm64->u.Pointer.u.linearAddr = pParm32->u.Pointer.u.linearAddr;
+ break;
+
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ LogRel(("VbglR0HGCMInternalCall32: pParm32 type %#x invalid.\n", pParm32->type));
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0HGCMInternalCall(pCallInfo64, sizeof(*pCallInfo64) + cParms * sizeof(HGCMFunctionParameter), fFlags,
+ fRequestor, pfnAsyncCallback, pvAsyncData, u32AsyncData);
+
+ if (RT_SUCCESS(rc))
+ {
+ *pCallInfo = *pCallInfo64;
+
+ /*
+ * Copy back.
+ */
+ pParm32 = VBGL_HGCM_GET_CALL_PARMS32(pCallInfo);
+ pParm64 = VBGL_HGCM_GET_CALL_PARMS(pCallInfo64);
+ for (iParm = 0; iParm < cParms; iParm++, pParm32++, pParm64++)
+ {
+ switch (pParm64->type)
+ {
+ case VMMDevHGCMParmType_32bit:
+ pParm32->u.value32 = pParm64->u.value32;
+ break;
+
+ case VMMDevHGCMParmType_64bit:
+ pParm32->u.value64 = pParm64->u.value64;
+ break;
+
+ case VMMDevHGCMParmType_LinAddr_Out:
+ case VMMDevHGCMParmType_LinAddr:
+ case VMMDevHGCMParmType_LinAddr_In:
+ pParm32->u.Pointer.size = pParm64->u.Pointer.size;
+ break;
+
+ default:
+ LogRel(("VbglR0HGCMInternalCall32: failed invalid pParm32 type %d\n", pParm32->type));
+ rc = VERR_INTERNAL_ERROR_3;
+ break;
+ }
+ }
+ }
+ else
+ {
+ static unsigned s_cErrors = 0;
+ if (s_cErrors++ < 32)
+ LogRel(("VbglR0HGCMInternalCall32: VbglR0HGCMInternalCall failed. rc=%Rrc\n", rc));
+ }
+ }
+ else
+ {
+ static unsigned s_cErrors = 0;
+ if (s_cErrors++ < 32)
+ LogRel(("VbglR0HGCMInternalCall32: failed. rc=%Rrc\n", rc));
+ }
+
+ RTMemTmpFree(pCallInfo64);
+ return rc;
+}
+#endif /* ARCH_BITS == 64 */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-os2.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-os2.cpp
new file mode 100644
index 00000000..828dea2e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-os2.cpp
@@ -0,0 +1,85 @@
+/* $Id: VBoxGuestR0LibIdc-os2.cpp $ */
+/** @file
+ * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, OS/2 specific.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN /* for watcom */
+/* This is defined in some assembly file. The AttachDD operation
+ is done in the driver init code. */
+extern VBGLOS2ATTACHDD g_VBoxGuestIDC;
+RT_C_DECLS_END
+
+
+
+int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq)
+{
+ /*
+ * Just check whether the connection was made or not.
+ */
+ if ( g_VBoxGuestIDC.u32Version == VBGL_IOC_VERSION
+ && RT_VALID_PTR(g_VBoxGuestIDC.u32Session)
+ && RT_VALID_PTR(g_VBoxGuestIDC.pfnServiceEP))
+ return g_VBoxGuestIDC.pfnServiceEP(g_VBoxGuestIDC.u32Session, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr, sizeof(*pReq));
+ Log(("vbglDriverOpen: failed\n"));
+ RT_NOREF(pHandle);
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq)
+{
+ return g_VBoxGuestIDC.pfnServiceEP((uintptr_t)pHandle->s.pvSession, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr, sizeof(*pReq));
+}
+
+
+/**
+ * Makes an IDC call, returning only the I/O control status code.
+ *
+ * @returns VBox status code (the I/O control failure status).
+ * @param pHandle The IDC handle.
+ * @param uReq The request number.
+ * @param pReqHdr The request header.
+ * @param cbReq The request size.
+ */
+DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq)
+{
+ return g_VBoxGuestIDC.pfnServiceEP((uintptr_t)pHandle->s.pvSession, uReq, pReqHdr, cbReq);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-solaris.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-solaris.cpp
new file mode 100644
index 00000000..760f060c
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-solaris.cpp
@@ -0,0 +1,97 @@
+/* $Id: VBoxGuestR0LibIdc-solaris.cpp $ */
+/** @file
+ * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, Solaris specific.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/conf.h>
+#include <sys/sunldi.h>
+#include <sys/file.h>
+#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
+#include "VBoxGuestR0LibInternal.h"
+#include <VBox/err.h>
+
+
+int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq)
+{
+ ldi_handle_t hDev = NULL;
+ ldi_ident_t hIdent = ldi_ident_from_anon();
+ int rc = ldi_open_by_name((char *)VBOXGUEST_DEVICE_NAME, FREAD, kcred, &hDev, hIdent);
+ ldi_ident_release(hIdent);
+ if (rc == 0)
+ {
+ pHandle->s.hDev = hDev;
+ rc = VbglR0IdcCallRaw(pHandle, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr, sizeof(*pReq));
+ if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc))
+ return VINF_SUCCESS;
+ ldi_close(hDev, FREAD, kcred);
+ }
+ else
+ rc = VERR_OPEN_FAILED;
+ pHandle->s.hDev = NULL;
+ return rc;
+}
+
+
+int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq)
+{
+ int rc = VbglR0IdcCallRaw(pHandle, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr, sizeof(*pReq));
+ if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc))
+ {
+ ldi_close(pHandle->s.hDev, FREAD, kcred);
+ pHandle->s.hDev = NULL;
+ }
+ return rc;
+}
+
+
+/**
+ * Makes an IDC call, returning only the I/O control status code.
+ *
+ * @returns VBox status code (the I/O control failure status).
+ * @param pHandle The IDC handle.
+ * @param uReq The request number.
+ * @param pReqHdr The request header.
+ * @param cbReq The request size.
+ */
+DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq)
+{
+#if 0
+ return VBoxGuestIDC(pHandle->s.pvSession, uReq, pReqHdr, cbReq);
+#else
+ int iIgn;
+ int rc = ldi_ioctl(pHandle->s.hDev, uReq, (intptr_t)pReqHdr, FKIOCTL | FNATIVE, kcred, &iIgn);
+ if (rc == 0)
+ return VINF_SUCCESS;
+ return RTErrConvertFromErrno(rc);
+#endif
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp
new file mode 100644
index 00000000..8aefb725
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp
@@ -0,0 +1,64 @@
+/* $Id: VBoxGuestR0LibIdc-unix.cpp $ */
+/** @file
+ * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, UNIX-like OSes.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+
+
+int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq)
+{
+ RT_NOREF(pHandle);
+ return VBoxGuestIDC(NULL /*pvSession*/, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr, sizeof(*pReq));
+}
+
+
+int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq)
+{
+ return VBoxGuestIDC(pHandle->s.pvSession, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr, sizeof(*pReq));
+}
+
+
+/**
+ * Makes an IDC call, returning only the I/O control status code.
+ *
+ * @returns VBox status code (the I/O control failure status).
+ * @param pHandle The IDC handle.
+ * @param uReq The request number.
+ * @param pReqHdr The request header.
+ * @param cbReq The request size.
+ */
+DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq)
+{
+ return VBoxGuestIDC(pHandle->s.pvSession, uReq, pReqHdr, cbReq);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp
new file mode 100644
index 00000000..395667e0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-win.cpp
@@ -0,0 +1,208 @@
+/* $Id: VBoxGuestR0LibIdc-win.cpp $ */
+/** @file
+ * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC, Windows specific.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/nt/nt.h>
+#include "VBoxGuestR0LibInternal.h"
+#include <VBox/VBoxGuest.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+
+
+/**
+ * Internal I/O Control call worker.
+ *
+ * @returns VBox status code.
+ * @param pDeviceObject The device object to call.
+ * @param pFileObject The file object for the connection.
+ * @param uReq The request.
+ * @param pReq The request packet.
+ */
+static int vbglR0IdcNtCallInternal(PDEVICE_OBJECT pDeviceObject, PFILE_OBJECT pFileObject, uint32_t uReq, PVBGLREQHDR pReq)
+{
+ int rc;
+ NTSTATUS rcNt;
+
+ /*
+ * Build the request.
+ *
+ * We want to avoid double buffering of the request, therefore we don't
+ * specify any request pointers or sizes when asking the kernel to build
+ * the IRP for us, but instead do that part our selves.
+ *
+ * See https://www.osr.com/blog/2018/02/14/beware-iobuilddeviceiocontrolrequest/
+ * for how fun this is when we're not at IRQL PASSIVE (HACK ALERT futher down).
+ * Ran into this little issue when LoadLibraryEx on a .NET DLL using the
+ * LOAD_LIBRARY_AS_DATAFILE and LOAD_LIBRARY_AS_IMAGE_RESOURCE flags.
+ */
+ KEVENT Event;
+ KeInitializeEvent(&Event, NotificationEvent, FALSE);
+
+ IO_STATUS_BLOCK IoStatusBlock = RTNT_IO_STATUS_BLOCK_INITIALIZER;
+#if 0
+ PIRP pIrp = IoBuildDeviceIoControlRequest(uReq, /* IoControlCode */
+ pDeviceObject,
+ pReq, /* InputBuffer */
+ pReq->cbIn, /* InputBufferLength */
+ pReq, /* OutputBuffer */
+ pReq->cbOut, /* OutputBufferLength */
+ TRUE, /* InternalDeviceIoControl (=> IRP_MJ_INTERNAL_DEVICE_CONTROL) */
+ &Event, /* Event */
+ &IoStatusBlock); /* IoStatusBlock */
+#else
+ PIRP pIrp = IoBuildDeviceIoControlRequest(uReq, /* IoControlCode */
+ pDeviceObject,
+ NULL, /* InputBuffer */
+ 0, /* InputBufferLength */
+ NULL, /* OutputBuffer */
+ 0, /* OutputBufferLength */
+ TRUE, /* InternalDeviceIoControl (=> IRP_MJ_INTERNAL_DEVICE_CONTROL) */
+ &Event, /* Event */
+ &IoStatusBlock); /* IoStatusBlock */
+#endif
+ if (pIrp)
+ {
+#if 0
+ IoGetNextIrpStackLocation(pIrp)->FileObject = pFileObject;
+#else
+ //w2k3sp1+: if (!KeAreAllApcsDisabled())
+ //w2k3sp1+: pIrp->Flags |= IRP_SYNCHRONOUS_API;
+ //w2k3sp1+: else
+ {
+ /* HACK ALERT! Causes IoCompleteRequest to update UserIosb and free the IRP w/o any APC happening. */
+ pIrp->Flags |= IRP_SYNCHRONOUS_API | IRP_PAGING_IO | IRP_SYNCHRONOUS_PAGING_IO;
+ KIRQL bIrqlSaved;
+ KeRaiseIrql(APC_LEVEL, &bIrqlSaved);
+ RemoveEntryList(&pIrp->ThreadListEntry);
+ InitializeListHead(&pIrp->ThreadListEntry);
+ KeLowerIrql(bIrqlSaved);
+ }
+ pIrp->UserBuffer = pReq;
+ pIrp->AssociatedIrp.SystemBuffer = pReq;
+ PIO_STACK_LOCATION pStack = IoGetNextIrpStackLocation(pIrp);
+ pStack->FileObject = pFileObject;
+ pStack->Parameters.DeviceIoControl.OutputBufferLength = pReq->cbOut;
+ pStack->Parameters.DeviceIoControl.InputBufferLength = pReq->cbIn;
+#endif
+
+ /*
+ * Call the driver, wait for an async request to complete (should never happen).
+ */
+ rcNt = IoCallDriver(pDeviceObject, pIrp);
+ if (rcNt == STATUS_PENDING)
+ rcNt = KeWaitForSingleObject(&Event, /* Object */
+ Executive, /* WaitReason */
+ KernelMode, /* WaitMode */
+ FALSE, /* Alertable */
+ NULL); /* TimeOut */
+ if (NT_SUCCESS(rcNt))
+ rcNt = IoStatusBlock.Status;
+ if (NT_SUCCESS(rcNt))
+ rc = pReq->rc;
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq)
+{
+ PDEVICE_OBJECT pDeviceObject = NULL;
+ PFILE_OBJECT pFileObject = NULL;
+ UNICODE_STRING wszDeviceName;
+ NTSTATUS rcNt;
+ int rc;
+
+ /*
+ * Get the device object pointer.
+ */
+ RtlInitUnicodeString(&wszDeviceName, VBOXGUEST_DEVICE_NAME_NT);
+ rcNt = IoGetDeviceObjectPointer(&wszDeviceName, FILE_ALL_ACCESS, &pFileObject, &pDeviceObject);
+ if (NT_SUCCESS(rcNt))
+ {
+ /*
+ * Make the connection call.
+ */
+ rc = vbglR0IdcNtCallInternal(pDeviceObject, pFileObject, VBGL_IOCTL_IDC_CONNECT, &pReq->Hdr);
+ if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc))
+ {
+ pHandle->s.pDeviceObject = pDeviceObject;
+ pHandle->s.pFileObject = pFileObject;
+ return rc;
+ }
+
+ /* only the file object. */
+ ObDereferenceObject(pFileObject);
+ }
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+
+ pHandle->s.pDeviceObject = NULL;
+ pHandle->s.pFileObject = NULL;
+ return rc;
+}
+
+
+int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq)
+{
+ PFILE_OBJECT pFileObject = pHandle->s.pFileObject;
+ int rc = vbglR0IdcNtCallInternal(pHandle->s.pDeviceObject, pFileObject, VBGL_IOCTL_IDC_DISCONNECT, &pReq->Hdr);
+ if (RT_SUCCESS(rc) && RT_SUCCESS(pReq->Hdr.rc))
+ {
+ pHandle->s.pDeviceObject = NULL;
+ pHandle->s.pFileObject = NULL;
+ ObDereferenceObject(pFileObject);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Makes an IDC call, returning only the I/O control status code.
+ *
+ * @returns VBox status code (the I/O control failure status).
+ * @param pHandle The IDC handle.
+ * @param uReq The request number.
+ * @param pReqHdr The request header.
+ * @param cbReq The request size.
+ */
+DECLR0VBGL(int) VbglR0IdcCallRaw(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq)
+{
+ NOREF(cbReq);
+ return vbglR0IdcNtCallInternal(pHandle->s.pDeviceObject, pHandle->s.pFileObject, (uint32_t)uReq, pReqHdr);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp
new file mode 100644
index 00000000..c0b0ac53
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp
@@ -0,0 +1,205 @@
+/* $Id: VBoxGuestR0LibIdc.cpp $ */
+/** @file
+ * VBoxGuestLib - Ring-0 Support Library for VBoxGuest, IDC.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+#include <iprt/errcore.h>
+#include <VBox/VBoxGuest.h>
+/*#include <iprt/asm.h>*/
+
+#ifdef VBGL_VBOXGUEST
+# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code."
+#endif
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/*static PVBGLIDCHANDLE volatile g_pMainHandle = NULL;*/
+
+
+/**
+ * Opens the IDC interface of the support driver.
+ *
+ * This will perform basic version negotiations and fail if the
+ * minimum requirements aren't met.
+ *
+ * @returns VBox status code.
+ * @param pHandle The handle structure (output).
+ * @param uReqVersion The requested version. Pass 0 for default.
+ * @param uMinVersion The minimum required version. Pass 0 for default.
+ * @param puSessionVersion Where to store the session version. Optional.
+ * @param puDriverVersion Where to store the session version. Optional.
+ * @param puDriverRevision Where to store the SVN revision of the driver. Optional.
+ */
+DECLR0VBGL(int) VbglR0IdcOpen(PVBGLIDCHANDLE pHandle, uint32_t uReqVersion, uint32_t uMinVersion,
+ uint32_t *puSessionVersion, uint32_t *puDriverVersion, uint32_t *puDriverRevision)
+{
+ unsigned uDefaultMinVersion;
+ VBGLIOCIDCCONNECT Req;
+ int rc;
+
+ /*
+ * Validate and set failure return values.
+ */
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ pHandle->s.pvSession = NULL;
+
+ AssertPtrNullReturn(puSessionVersion, VERR_INVALID_POINTER);
+ if (puSessionVersion)
+ *puSessionVersion = 0;
+
+ AssertPtrNullReturn(puDriverVersion, VERR_INVALID_POINTER);
+ if (puDriverVersion)
+ *puDriverVersion = 0;
+
+ AssertPtrNullReturn(puDriverRevision, VERR_INVALID_POINTER);
+ if (puDriverRevision)
+ *puDriverRevision = 0;
+
+ AssertReturn(!uMinVersion || (uMinVersion & UINT32_C(0xffff0000)) == (VBGL_IOC_VERSION & UINT32_C(0xffff0000)), VERR_INVALID_PARAMETER);
+ AssertReturn(!uReqVersion || (uReqVersion & UINT32_C(0xffff0000)) == (VBGL_IOC_VERSION & UINT32_C(0xffff0000)), VERR_INVALID_PARAMETER);
+
+ /*
+ * Handle default version input and enforce minimum requirements made
+ * by this library.
+ *
+ * The clients will pass defaults (0), and only in the case that some
+ * special API feature was just added will they set an actual version.
+ * So, this is the place where can easily enforce a minimum IDC version
+ * on bugs and similar. It corresponds a bit to what SUPR3Init is
+ * responsible for.
+ */
+ uDefaultMinVersion = VBGL_IOC_VERSION & UINT32_C(0xffff0000);
+ if (!uMinVersion || uMinVersion < uDefaultMinVersion)
+ uMinVersion = uDefaultMinVersion;
+ if (!uReqVersion || uReqVersion < uDefaultMinVersion)
+ uReqVersion = uDefaultMinVersion;
+
+ /*
+ * Setup the connect request packet and call the OS specific function.
+ */
+ VBGLREQHDR_INIT(&Req.Hdr, IDC_CONNECT);
+ Req.u.In.u32MagicCookie = VBGL_IOCTL_IDC_CONNECT_MAGIC_COOKIE;
+ Req.u.In.uMinVersion = uMinVersion;
+ Req.u.In.uReqVersion = uReqVersion;
+ Req.u.In.uReserved = 0;
+ rc = vbglR0IdcNativeOpen(pHandle, &Req);
+ if (RT_SUCCESS(rc))
+ rc = Req.Hdr.rc;
+ if (RT_SUCCESS(rc))
+ {
+ pHandle->s.pvSession = Req.u.Out.pvSession;
+ if (puSessionVersion)
+ *puSessionVersion = Req.u.Out.uSessionVersion;
+ if (puDriverVersion)
+ *puDriverVersion = Req.u.Out.uDriverVersion;
+ if (puDriverRevision)
+ *puDriverRevision = Req.u.Out.uDriverRevision;
+
+ /*
+ * We don't really trust anyone, make sure the returned
+ * session and version values actually makes sense.
+ */
+ if ( RT_VALID_PTR(Req.u.Out.pvSession)
+ && Req.u.Out.uSessionVersion >= uMinVersion
+ && (Req.u.Out.uSessionVersion & UINT32_C(0xffff0000)) == (VBGL_IOC_VERSION & UINT32_C(0xffff0000)))
+ {
+ /*ASMAtomicCmpXchgPtr(&g_pMainHandle, pHandle, NULL);*/
+ return rc;
+ }
+
+ AssertMsgFailed(("pSession=%p uSessionVersion=0x%x (r%u)\n", Req.u.Out.pvSession, Req.u.Out.uSessionVersion, Req.u.Out.uDriverRevision));
+ rc = VERR_VERSION_MISMATCH;
+ VbglR0IdcClose(pHandle);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Closes a IDC connection established by VbglR0IdcOpen.
+ *
+ * @returns VBox status code.
+ * @param pHandle The IDC handle.
+ */
+DECLR0VBGL(int) VbglR0IdcClose(PVBGLIDCHANDLE pHandle)
+{
+ VBGLIOCIDCDISCONNECT Req;
+ int rc;
+
+ /*
+ * Catch closed handles and check that the session is valid.
+ */
+ AssertPtrReturn(pHandle, VERR_INVALID_POINTER);
+ if (!pHandle->s.pvSession)
+ return VERR_INVALID_HANDLE;
+ AssertPtrReturn(pHandle->s.pvSession, VERR_INVALID_HANDLE);
+
+ /*
+ * Create the request and hand it to the OS specific code.
+ */
+ VBGLREQHDR_INIT(&Req.Hdr, IDC_DISCONNECT);
+ Req.u.In.pvSession = pHandle->s.pvSession;
+ rc = vbglR0IdcNativeClose(pHandle, &Req);
+ if (RT_SUCCESS(rc))
+ rc = Req.Hdr.rc;
+ if (RT_SUCCESS(rc))
+ {
+ pHandle->s.pvSession = NULL;
+ /*ASMAtomicCmpXchgPtr(&g_pMainHandle, NULL, pHandle);*/
+ }
+ return rc;
+}
+
+
+/**
+ * Makes an IDC call, returning the request status.
+ *
+ * @returns VBox status code. Request status if the I/O control succeeds,
+ * otherwise the I/O control failure status.
+ * @param pHandle The IDC handle.
+ * @param uReq The request number.
+ * @param pReqHdr The request header.
+ * @param cbReq The request size.
+ */
+DECLR0VBGL(int) VbglR0IdcCall(PVBGLIDCHANDLE pHandle, uintptr_t uReq, PVBGLREQHDR pReqHdr, uint32_t cbReq)
+{
+ int rc = VbglR0IdcCallRaw(pHandle, uReq, pReqHdr, cbReq);
+ if (RT_SUCCESS(rc))
+ rc = pReqHdr->rc;
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp
new file mode 100644
index 00000000..0ed6c35f
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp
@@ -0,0 +1,333 @@
+/* $Id: VBoxGuestR0LibInit.cpp $ */
+/** @file
+ * VBoxGuestLibR0 - Library initialization.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/semaphore.h>
+#include <VBox/err.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The global VBGL instance data. */
+VBGLDATA g_vbgldata;
+
+
+/**
+ * Used by vbglR0QueryDriverInfo and VbglInit to try get the host feature mask
+ * and version information (g_vbgldata::hostVersion).
+ *
+ * This was first implemented by the host in 3.1 and we quietly ignore failures
+ * for that reason.
+ */
+static void vbglR0QueryHostVersion(void)
+{
+ VMMDevReqHostVersion *pReq;
+ int rc = VbglR0GRAlloc((VMMDevRequestHeader **) &pReq, sizeof (*pReq), VMMDevReq_GetHostVersion);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0GRPerform(&pReq->header);
+ if (RT_SUCCESS(rc))
+ {
+ g_vbgldata.hostVersion = *pReq;
+ Log(("vbglR0QueryHostVersion: %u.%u.%ur%u %#x\n",
+ pReq->major, pReq->minor, pReq->build, pReq->revision, pReq->features));
+ }
+
+ VbglR0GRFree(&pReq->header);
+ }
+}
+
+
+#ifndef VBGL_VBOXGUEST
+/**
+ * The guest library uses lazy initialization for VMMDev port and memory,
+ * because these values are provided by the VBoxGuest driver and it might
+ * be loaded later than other drivers.
+ *
+ * The VbglEnter checks the current library status, tries to retrieve these
+ * values and fails if they are unavailable.
+ */
+static int vbglR0QueryDriverInfo(void)
+{
+# ifdef VBGLDATA_USE_FAST_MUTEX
+ int rc = RTSemFastMutexRequest(g_vbgldata.hMtxIdcSetup);
+# else
+ int rc = RTSemMutexRequest(g_vbgldata.hMtxIdcSetup, RT_INDEFINITE_WAIT);
+# endif
+ if (RT_SUCCESS(rc))
+ {
+ if (g_vbgldata.status == VbglStatusReady)
+ { /* likely */ }
+ else
+ {
+ rc = VbglR0IdcOpen(&g_vbgldata.IdcHandle,
+ VBGL_IOC_VERSION /*uReqVersion*/,
+ VBGL_IOC_VERSION & UINT32_C(0xffff0000) /*uMinVersion*/,
+ NULL /*puSessionVersion*/, NULL /*puDriverVersion*/, NULL /*puDriverRevision*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Try query the port info.
+ */
+ VBGLIOCGETVMMDEVIOINFO PortInfo;
+ RT_ZERO(PortInfo);
+ VBGLREQHDR_INIT(&PortInfo.Hdr, GET_VMMDEV_IO_INFO);
+ rc = VbglR0IdcCall(&g_vbgldata.IdcHandle, VBGL_IOCTL_GET_VMMDEV_IO_INFO, &PortInfo.Hdr, sizeof(PortInfo));
+ if (RT_SUCCESS(rc))
+ {
+ dprintf(("Port I/O = 0x%04x, MMIO = %p\n", PortInfo.u.Out.IoPort, PortInfo.u.Out.pvVmmDevMapping));
+
+ g_vbgldata.portVMMDev = PortInfo.u.Out.IoPort;
+ g_vbgldata.pVMMDevMemory = (VMMDevMemory *)PortInfo.u.Out.pvVmmDevMapping;
+ g_vbgldata.status = VbglStatusReady;
+
+ vbglR0QueryHostVersion();
+ }
+ }
+
+ dprintf(("vbglQueryDriverInfo rc = %Rrc\n", rc));
+ }
+
+# ifdef VBGLDATA_USE_FAST_MUTEX
+ RTSemFastMutexRelease(g_vbgldata.hMtxIdcSetup);
+# else
+ RTSemMutexRelease(g_vbgldata.hMtxIdcSetup);
+# endif
+ }
+ return rc;
+}
+#endif /* !VBGL_VBOXGUEST */
+
+/**
+ * Checks if VBGL has been initialized.
+ *
+ * The client library, this will lazily complete the initialization.
+ *
+ * @return VINF_SUCCESS or VERR_VBGL_NOT_INITIALIZED.
+ */
+int vbglR0Enter(void)
+{
+ if (g_vbgldata.status == VbglStatusReady)
+ return VINF_SUCCESS;
+
+#ifndef VBGL_VBOXGUEST
+ if (g_vbgldata.status == VbglStatusInitializing)
+ {
+ vbglR0QueryDriverInfo();
+ if (g_vbgldata.status == VbglStatusReady)
+ return VINF_SUCCESS;
+ }
+#endif
+ return VERR_VBGL_NOT_INITIALIZED;
+}
+
+
+static int vbglR0InitCommon(void)
+{
+ int rc;
+
+ RT_ZERO(g_vbgldata);
+ g_vbgldata.status = VbglStatusInitializing;
+
+ rc = VbglR0PhysHeapInit();
+ if (RT_SUCCESS(rc))
+ {
+ dprintf(("vbglR0InitCommon: returns rc = %d\n", rc));
+ return rc;
+ }
+
+ LogRel(("vbglR0InitCommon: VbglR0PhysHeapInit failed: rc=%Rrc\n", rc));
+ g_vbgldata.status = VbglStatusNotInitialized;
+ return rc;
+}
+
+
+static void vbglR0TerminateCommon(void)
+{
+ VbglR0PhysHeapTerminate();
+ g_vbgldata.status = VbglStatusNotInitialized;
+}
+
+#ifdef VBGL_VBOXGUEST
+
+DECLR0VBGL(int) VbglR0InitPrimary(RTIOPORT portVMMDev, VMMDevMemory *pVMMDevMemory, uint32_t *pfFeatures)
+{
+ int rc;
+
+# ifdef RT_OS_WINDOWS /** @todo r=bird: this doesn't make sense. Is there something special going on on windows? */
+ dprintf(("vbglInit: starts g_vbgldata.status %d\n", g_vbgldata.status));
+
+ if ( g_vbgldata.status == VbglStatusInitializing
+ || g_vbgldata.status == VbglStatusReady)
+ {
+ /* Initialization is already in process. */
+ return VINF_SUCCESS;
+ }
+# else
+ dprintf(("vbglInit: starts\n"));
+# endif
+
+ rc = vbglR0InitCommon();
+ if (RT_SUCCESS(rc))
+ {
+ g_vbgldata.portVMMDev = portVMMDev;
+ g_vbgldata.pVMMDevMemory = pVMMDevMemory;
+ g_vbgldata.status = VbglStatusReady;
+
+ vbglR0QueryHostVersion();
+ *pfFeatures = g_vbgldata.hostVersion.features;
+ return VINF_SUCCESS;
+ }
+
+ g_vbgldata.status = VbglStatusNotInitialized;
+ return rc;
+}
+
+DECLR0VBGL(void) VbglR0TerminatePrimary(void)
+{
+ vbglR0TerminateCommon();
+}
+
+
+#else /* !VBGL_VBOXGUEST */
+
+DECLR0VBGL(int) VbglR0InitClient(void)
+{
+ int rc;
+
+ /** @todo r=bird: explain why we need to be doing this, please... */
+ if ( g_vbgldata.status == VbglStatusInitializing
+ || g_vbgldata.status == VbglStatusReady)
+ {
+ /* Initialization is already in process. */
+ return VINF_SUCCESS;
+ }
+
+ rc = vbglR0InitCommon();
+ if (RT_SUCCESS(rc))
+ {
+# ifdef VBGLDATA_USE_FAST_MUTEX
+ rc = RTSemFastMutexCreate(&g_vbgldata.hMtxIdcSetup);
+# else
+ rc = RTSemMutexCreate(&g_vbgldata.hMtxIdcSetup);
+# endif
+ if (RT_SUCCESS(rc))
+ {
+ /* Try to obtain VMMDev port via IOCTL to VBoxGuest main driver. */
+ vbglR0QueryDriverInfo();
+
+# ifdef VBOX_WITH_HGCM
+ rc = VbglR0HGCMInit();
+# endif
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+# ifdef VBGLDATA_USE_FAST_MUTEX
+ RTSemFastMutexDestroy(g_vbgldata.hMtxIdcSetup);
+ g_vbgldata.hMtxIdcSetup = NIL_RTSEMFASTMUTEX;
+# else
+ RTSemMutexDestroy(g_vbgldata.hMtxIdcSetup);
+ g_vbgldata.hMtxIdcSetup = NIL_RTSEMMUTEX;
+# endif
+ }
+ vbglR0TerminateCommon();
+ }
+
+ return rc;
+}
+
+DECLR0VBGL(void) VbglR0TerminateClient(void)
+{
+# ifdef VBOX_WITH_HGCM
+ VbglR0HGCMTerminate();
+# endif
+
+ /* driver open could fail, which does not prevent VbglInit from succeeding,
+ * close the driver only if it is opened */
+ VbglR0IdcClose(&g_vbgldata.IdcHandle);
+# ifdef VBGLDATA_USE_FAST_MUTEX
+ RTSemFastMutexDestroy(g_vbgldata.hMtxIdcSetup);
+ g_vbgldata.hMtxIdcSetup = NIL_RTSEMFASTMUTEX;
+# else
+ RTSemMutexDestroy(g_vbgldata.hMtxIdcSetup);
+ g_vbgldata.hMtxIdcSetup = NIL_RTSEMMUTEX;
+# endif
+
+ /* note: do vbglR0TerminateCommon as a last step since it zeroez up the g_vbgldata
+ * conceptually, doing vbglR0TerminateCommon last is correct
+ * since this is the reverse order to how init is done */
+ vbglR0TerminateCommon();
+}
+
+
+int VBOXCALL vbglR0QueryIdcHandle(PVBGLIDCHANDLE *ppIdcHandle)
+{
+ if (g_vbgldata.status == VbglStatusReady)
+ { /* likely */ }
+ else
+ {
+ vbglR0QueryDriverInfo();
+ if (g_vbgldata.status != VbglStatusReady)
+ {
+ *ppIdcHandle = NULL;
+ return VERR_TRY_AGAIN;
+ }
+ }
+
+ *ppIdcHandle = &g_vbgldata.IdcHandle;
+ return VINF_SUCCESS;
+}
+
+
+DECLR0VBGL(int) VbglR0QueryHostFeatures(uint32_t *pfHostFeatures)
+{
+ if (g_vbgldata.status == VbglStatusReady)
+ *pfHostFeatures = g_vbgldata.hostVersion.features;
+ else
+ {
+ int rc = vbglR0QueryDriverInfo();
+ if (g_vbgldata.status != VbglStatusReady)
+ return rc;
+ *pfHostFeatures = g_vbgldata.hostVersion.features;
+ }
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !VBGL_VBOXGUEST */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h
new file mode 100644
index 00000000..81657a8e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h
@@ -0,0 +1,212 @@
+/* $Id: VBoxGuestR0LibInternal.h $ */
+/** @file
+ * VBoxGuestLibR0 - Internal header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR0LibInternal_h
+#define GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR0LibInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/*
+ * Define the private IDC handle structure before we include the VBoxGuestLib.h header.
+ */
+#include <iprt/types.h>
+#include <iprt/assert.h>
+RT_C_DECLS_BEGIN
+
+# ifndef VBGL_VBOXGUEST
+/**
+ * The hidden part of VBGLIDCHANDLE.
+ */
+struct VBGLIDCHANDLEPRIVATE
+{
+ /** Pointer to the session handle. */
+ void *pvSession;
+# if defined(RT_OS_WINDOWS) && (defined(IPRT_INCLUDED_nt_ntddk_h) || defined(IPRT_INCLUDED_nt_nt_h))
+ /** Pointer to the NT device object. */
+ PDEVICE_OBJECT pDeviceObject;
+ /** Pointer to the NT file object. */
+ PFILE_OBJECT pFileObject;
+# elif defined(RT_OS_SOLARIS) && defined(_SYS_SUNLDI_H)
+ /** LDI device handle to keep the device attached. */
+ ldi_handle_t hDev;
+# endif
+};
+/** Indicate that the VBGLIDCHANDLEPRIVATE structure is present. */
+# define VBGLIDCHANDLEPRIVATE_DECLARED 1
+#endif
+
+#include <VBox/VMMDev.h>
+#include <VBox/VBoxGuest.h>
+#include <VBox/VBoxGuestLib.h>
+
+#ifdef VBGLIDCHANDLEPRIVATE_DECLARED
+AssertCompile(RT_SIZEOFMEMB(VBGLIDCHANDLE, apvPadding) >= sizeof(struct VBGLIDCHANDLEPRIVATE));
+#endif
+
+
+/*
+ * Native IDC functions.
+ */
+int VBOXCALL vbglR0IdcNativeOpen(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCCONNECT pReq);
+int VBOXCALL vbglR0IdcNativeClose(PVBGLIDCHANDLE pHandle, PVBGLIOCIDCDISCONNECT pReq);
+
+
+/*
+ * Deprecated logging macro
+ */
+#include <VBox/log.h>
+#ifdef RT_OS_WINDOWS /** @todo dprintf() -> Log() */
+# if (defined(DEBUG) && !defined(NO_LOGGING)) || defined(LOG_ENABLED)
+# define dprintf(a) RTLogBackdoorPrintf a
+# else
+# define dprintf(a) do {} while (0)
+# endif
+#else
+# define dprintf(a) Log(a)
+#endif
+
+/*
+ * Lazy bird: OS/2 doesn't currently implement the RTSemMutex API in ring-0, so
+ * use a fast mutex instead. Unlike Windows, the OS/2 implementation
+ * doesn't have any nasty side effects on IRQL-like context properties, so the
+ * fast mutexes on OS/2 are identical to normal mutexes except for the missing
+ * timeout aspec. Fortunately we don't need timeouts here.
+ */
+#ifdef RT_OS_OS2
+# define VBGLDATA_USE_FAST_MUTEX
+#endif
+
+struct VBGLPHYSHEAPBLOCK;
+typedef struct VBGLPHYSHEAPBLOCK VBGLPHYSHEAPBLOCK;
+struct VBGLPHYSHEAPFREEBLOCK;
+typedef struct VBGLPHYSHEAPFREEBLOCK VBGLPHYSHEAPFREEBLOCK;
+struct VBGLPHYSHEAPCHUNK;
+typedef struct VBGLPHYSHEAPCHUNK VBGLPHYSHEAPCHUNK;
+
+enum VbglLibStatus
+{
+ VbglStatusNotInitialized = 0,
+ VbglStatusInitializing,
+ VbglStatusReady
+};
+
+/**
+ * Global VBGL ring-0 data.
+ * Lives in VBoxGuestR0LibInit.cpp.
+ */
+typedef struct VBGLDATA
+{
+ enum VbglLibStatus status;
+
+ RTIOPORT portVMMDev;
+
+ VMMDevMemory *pVMMDevMemory;
+
+ /** Physical memory heap data.
+ * @{
+ */
+ RTSEMFASTMUTEX hMtxHeap;
+ /** Head of the block list (both free and allocated blocks).
+ * This runs parallel to the chunk list and is sorted by address within each
+ * chunk. This allows merging with blocks both after and before when
+ * freeing. */
+ VBGLPHYSHEAPBLOCK *pBlockHead;
+ /** Head of the free list.
+ * This is not sorted and more on the most recently freed approach. */
+ VBGLPHYSHEAPFREEBLOCK *pFreeHead;
+ /** Number of block of any kind. */
+ int32_t cBlocks;
+ /** Number of free blocks. */
+ int32_t cFreeBlocks;
+ /** Head of the chunk list. */
+ VBGLPHYSHEAPCHUNK *pChunkHead;
+ /** @} */
+
+ /**
+ * The host version data.
+ */
+ VMMDevReqHostVersion hostVersion;
+
+
+#ifndef VBGL_VBOXGUEST
+ /** The IDC handle. This is used for talking to the main driver. */
+ VBGLIDCHANDLE IdcHandle;
+ /** Mutex used to serialize IDC setup. */
+# ifdef VBGLDATA_USE_FAST_MUTEX
+ RTSEMFASTMUTEX hMtxIdcSetup;
+# else
+ RTSEMMUTEX hMtxIdcSetup;
+# endif
+#endif
+} VBGLDATA;
+
+
+extern VBGLDATA g_vbgldata;
+
+/**
+ * Internal macro for checking whether we can pass physical page lists to the
+ * host.
+ *
+ * ASSUMES that vbglR0Enter has been called already.
+ *
+ * @param a_fLocked For the windows shared folders workarounds.
+ *
+ * @remarks Disabled the PageList feature for locked memory on Windows,
+ * because a new MDL is created by VBGL to get the page addresses
+ * and the pages from the MDL are marked as dirty when they should not.
+ */
+#if defined(RT_OS_WINDOWS)
+# define VBGLR0_CAN_USE_PHYS_PAGE_LIST(a_fLocked) \
+ ( !(a_fLocked) && (g_vbgldata.hostVersion.features & VMMDEV_HVF_HGCM_PHYS_PAGE_LIST) )
+#else
+# define VBGLR0_CAN_USE_PHYS_PAGE_LIST(a_fLocked) \
+ ( !!(g_vbgldata.hostVersion.features & VMMDEV_HVF_HGCM_PHYS_PAGE_LIST) )
+#endif
+
+int vbglR0Enter (void);
+
+#ifdef VBOX_WITH_HGCM
+struct VBGLHGCMHANDLEDATA *vbglR0HGCMHandleAlloc(void);
+void vbglR0HGCMHandleFree(struct VBGLHGCMHANDLEDATA *pHandle);
+#endif /* VBOX_WITH_HGCM */
+
+#ifndef VBGL_VBOXGUEST
+/**
+ * Get the IDC handle to the main VBoxGuest driver.
+ * @returns VERR_TRY_AGAIN if the main driver has not yet been loaded.
+ */
+int VBOXCALL vbglR0QueryIdcHandle(PVBGLIDCHANDLE *ppIdcHandle);
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR0LibInternal_h */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibMouse.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibMouse.cpp
new file mode 100644
index 00000000..a0c9e1c5
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibMouse.cpp
@@ -0,0 +1,130 @@
+/* $Id: VBoxGuestR0LibMouse.cpp $ */
+/** @file
+ * VBoxGuestLibR0 - Mouse Integration.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+#ifdef VBGL_VBOXGUEST
+# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code."
+#endif
+
+
+/**
+ * Sets the function which is called back on each mouse pointer event. Only
+ * one callback can be active at once, so if you need several for any reason
+ * you must multiplex yourself. Call backs can be disabled by passing NULL
+ * as the function pointer.
+ *
+ * @remarks Ring-0.
+ * @returns iprt status code.
+ * @returns VERR_TRY_AGAIN if the main guest driver hasn't finished
+ * initialising.
+ *
+ * @param pfnNotify the function to call back. NULL to disable call backs.
+ * @param pvUser user supplied data/cookie to be passed to the function.
+ */
+DECLR0VBGL(int) VbglR0SetMouseNotifyCallback(PFNVBOXGUESTMOUSENOTIFY pfnNotify, void *pvUser)
+{
+ PVBGLIDCHANDLE pIdcHandle;
+ int rc = vbglR0QueryIdcHandle(&pIdcHandle);
+ if (RT_SUCCESS(rc))
+ {
+ VBGLIOCSETMOUSENOTIFYCALLBACK NotifyCallback;
+ VBGLREQHDR_INIT(&NotifyCallback.Hdr, SET_MOUSE_NOTIFY_CALLBACK);
+ NotifyCallback.u.In.pfnNotify = pfnNotify;
+ NotifyCallback.u.In.pvUser = pvUser;
+ rc = VbglR0IdcCall(pIdcHandle, VBGL_IOCTL_SET_MOUSE_NOTIFY_CALLBACK, &NotifyCallback.Hdr, sizeof(NotifyCallback));
+ }
+ return rc;
+}
+
+
+/**
+ * Retrieve mouse coordinates and features from the host.
+ *
+ * @remarks Ring-0.
+ * @returns VBox status code.
+ *
+ * @param pfFeatures Where to store the mouse features.
+ * @param px Where to store the X co-ordinate.
+ * @param py Where to store the Y co-ordinate.
+ */
+DECLR0VBGL(int) VbglR0GetMouseStatus(uint32_t *pfFeatures, uint32_t *px, uint32_t *py)
+{
+ PVBGLIDCHANDLE pIdcHandle;
+ int rc = vbglR0QueryIdcHandle(&pIdcHandle);
+ if (RT_SUCCESS(rc))
+ {
+ VMMDevReqMouseStatus Req;
+ VMMDEV_REQ_HDR_INIT(&Req.header, sizeof(Req), VMMDevReq_GetMouseStatus);
+ Req.mouseFeatures = 0;
+ Req.pointerXPos = 0;
+ Req.pointerYPos = 0;
+ rc = VbglR0IdcCall(pIdcHandle, VBGL_IOCTL_VMMDEV_REQUEST(sizeof(Req)), (PVBGLREQHDR)&Req.header, sizeof(Req));
+ if (RT_SUCCESS(rc))
+ {
+ if (pfFeatures)
+ *pfFeatures = Req.mouseFeatures;
+ if (px)
+ *px = Req.pointerXPos;
+ if (py)
+ *py = Req.pointerYPos;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Send mouse features to the host.
+ *
+ * @remarks Ring-0.
+ * @returns VBox status code.
+ *
+ * @param fFeatures Supported mouse pointer features. The main guest driver
+ * will mediate different callers and show the host any
+ * feature enabled by any guest caller.
+ */
+DECLR0VBGL(int) VbglR0SetMouseStatus(uint32_t fFeatures)
+{
+ PVBGLIDCHANDLE pIdcHandle;
+ int rc = vbglR0QueryIdcHandle(&pIdcHandle);
+ if (RT_SUCCESS(rc))
+ {
+ VBGLIOCSETMOUSESTATUS Req;
+ VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS);
+ Req.u.In.fStatus = fFeatures;
+ rc = VbglR0IdcCall(pIdcHandle, VBGL_IOCTL_SET_MOUSE_STATUS, &Req.Hdr, sizeof(Req));
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp
new file mode 100644
index 00000000..b99f51ad
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp
@@ -0,0 +1,1197 @@
+/* $Id: VBoxGuestR0LibPhysHeap.cpp $ */
+/** @file
+ * VBoxGuestLibR0 - Physical memory heap.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/** @page pg_vbglr0_phys_heap VBoxGuestLibR0 - Physical memory heap.
+ *
+ * Traditional heap implementation keeping all blocks in a ordered list and
+ * keeping free blocks on additional list via pointers in the user area. This
+ * is similar to @ref grp_rt_heap_simple "RTHeapSimple" and
+ * @ref grp_rt_heap_offset "RTHeapOffset" in IPRT, except that this code handles
+ * mutiple chunks and has a physical address associated with each chunk and
+ * block. The alignment is fixed (VBGL_PH_ALLOC_ALIGN).
+ *
+ * When allocating memory, a free block is found that satisfies the request,
+ * extending the heap with another chunk if needed. The block is split if it's
+ * too large, and the tail end is put on the free list.
+ *
+ * When freeing memory, the block being freed is put back on the free list and
+ * we use the block list to check whether it can be merged with adjacent blocks.
+ *
+ * @note The original code managed the blocks in two separate lists for free and
+ * allocated blocks, which had the disadvantage only allowing merging with
+ * the block after the block being freed. On the plus side, it had the
+ * potential for slightly better locality when examining the free list,
+ * since the next pointer and block size members were closer to one
+ * another.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/memobj.h>
+#include <iprt/semaphore.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Enables heap dumping. */
+#if defined(DOXYGEN_RUNNING) || 0
+# define VBGL_PH_DUMPHEAP
+#endif
+
+#ifdef VBGL_PH_DUMPHEAP
+# define VBGL_PH_DPRINTF(a) RTAssertMsg2Weak a
+#else
+# define VBGL_PH_DPRINTF(a) do { } while (0)
+#endif
+
+/** Heap chunk signature */
+#define VBGL_PH_CHUNKSIGNATURE UINT32_C(0xADDCCCCC)
+/** Heap chunk allocation unit */
+#define VBGL_PH_CHUNKSIZE (0x10000)
+
+/** Heap block signature */
+#define VBGL_PH_BLOCKSIGNATURE UINT32_C(0xADDBBBBB)
+
+/** The allocation block alignment.
+ *
+ * This cannot be larger than VBGLPHYSHEAPBLOCK.
+ */
+#define VBGL_PH_ALLOC_ALIGN (sizeof(void *))
+
+/** Max number of free nodes to search before just using the best fit.
+ *
+ * This is used to limit the free list walking during allocation and just get
+ * on with the job. A low number should reduce the cache trashing at the
+ * possible cost of heap fragmentation.
+ *
+ * Picked 16 after comparing the tstVbglR0PhysHeap-1 results w/ uRandSeed=42 for
+ * different max values.
+ */
+#define VBGL_PH_MAX_FREE_SEARCH 16
+
+/** Threshold to stop the block search if a free block is at least this much too big.
+ *
+ * May cause more fragmation (depending on usage pattern), but should speed up
+ * allocation and hopefully reduce cache trashing.
+ *
+ * Since we merge adjacent free blocks when we can, free blocks should typically
+ * be a lot larger that what's requested. So, it is probably a good idea to
+ * just chop up a large block rather than keep searching for a perfect-ish
+ * match.
+ *
+ * Undefine this to disable this trick.
+ */
+#if defined(DOXYGEN_RUNNING) || 1
+# define VBGL_PH_STOP_SEARCH_AT_EXCESS _4K
+#endif
+
+/** Threshold at which to split out a tail free block when allocating.
+ *
+ * The value gives the amount of user space, i.e. excluding the header.
+ *
+ * Using 32 bytes based on VMMDev.h request sizes. The smallest requests are 24
+ * bytes, i.e. only the header, at least 4 of these. There are at least 10 with
+ * size 28 bytes and at least 11 with size 32 bytes. So, 32 bytes would fit
+ * some 25 requests out of about 60, which is reasonable.
+ */
+#define VBGL_PH_MIN_SPLIT_FREE_BLOCK 32
+
+
+/** The smallest amount of user data that can be allocated.
+ *
+ * This is to ensure that the block can be converted into a
+ * VBGLPHYSHEAPFREEBLOCK structure when freed. This must be smaller or equal
+ * to VBGL_PH_MIN_SPLIT_FREE_BLOCK.
+ */
+#define VBGL_PH_SMALLEST_ALLOC_SIZE 16
+
+/** The maximum allocation request size. */
+#define VBGL_PH_LARGEST_ALLOC_SIZE RT_ALIGN_32( _128M \
+ - sizeof(VBGLPHYSHEAPBLOCK) \
+ - sizeof(VBGLPHYSHEAPCHUNK) \
+ - VBGL_PH_ALLOC_ALIGN, \
+ VBGL_PH_ALLOC_ALIGN)
+
+/**
+ * Whether to use the RTR0MemObjAllocCont API or RTMemContAlloc for
+ * allocating chunks.
+ *
+ * This can be enabled on hosts where RTMemContAlloc is more complicated than
+ * RTR0MemObjAllocCont. This can also be done if we wish to save code space, as
+ * the latter is typically always dragged into the link on guests where the
+ * linker cannot eliminiate functions within objects. Only drawback is that
+ * RTR0MemObjAllocCont requires another heap allocation for the handle.
+ */
+#if defined(DOXYGEN_RUNNING) || (!defined(IN_TESTCASE) && 0)
+# define VBGL_PH_USE_MEMOBJ
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * A heap block (within a chunk).
+ *
+ * This is used to track a part of a heap chunk that's either free or
+ * allocated. The VBGLPHYSHEAPBLOCK::fAllocated member indicates which it is.
+ */
+struct VBGLPHYSHEAPBLOCK
+{
+ /** Magic value (VBGL_PH_BLOCKSIGNATURE). */
+ uint32_t u32Signature;
+
+ /** Size of user data in the block. Does not include this block header. */
+ uint32_t cbUser : 31;
+ /** The top bit indicates whether it's allocated or free. */
+ uint32_t fAllocated : 1;
+
+ /** Pointer to the next block on the list. */
+ VBGLPHYSHEAPBLOCK *pNext;
+ /** Pointer to the previous block on the list. */
+ VBGLPHYSHEAPBLOCK *pPrev;
+ /** Pointer back to the chunk. */
+ VBGLPHYSHEAPCHUNK *pChunk;
+};
+
+/**
+ * A free block.
+ */
+struct VBGLPHYSHEAPFREEBLOCK
+{
+ /** Core block data. */
+ VBGLPHYSHEAPBLOCK Core;
+ /** Pointer to the next free list entry. */
+ VBGLPHYSHEAPFREEBLOCK *pNextFree;
+ /** Pointer to the previous free list entry. */
+ VBGLPHYSHEAPFREEBLOCK *pPrevFree;
+};
+AssertCompile(VBGL_PH_SMALLEST_ALLOC_SIZE >= sizeof(VBGLPHYSHEAPFREEBLOCK) - sizeof(VBGLPHYSHEAPBLOCK));
+AssertCompile(VBGL_PH_MIN_SPLIT_FREE_BLOCK >= sizeof(VBGLPHYSHEAPFREEBLOCK) - sizeof(VBGLPHYSHEAPBLOCK));
+AssertCompile(VBGL_PH_MIN_SPLIT_FREE_BLOCK >= VBGL_PH_SMALLEST_ALLOC_SIZE);
+
+/**
+ * A chunk of memory used by the heap for sub-allocations.
+ *
+ * There is a list of these.
+ */
+struct VBGLPHYSHEAPCHUNK
+{
+ /** Magic value (VBGL_PH_CHUNKSIGNATURE). */
+ uint32_t u32Signature;
+
+ /** Size of the chunk. Includes the chunk header. */
+ uint32_t cbChunk;
+
+ /** Physical address of the chunk (contiguous). */
+ uint32_t physAddr;
+
+#if !defined(VBGL_PH_USE_MEMOBJ) || ARCH_BITS != 32
+ uint32_t uPadding1;
+#endif
+
+ /** Number of block of any kind. */
+ int32_t cBlocks;
+ /** Number of free blocks. */
+ int32_t cFreeBlocks;
+
+ /** Pointer to the next chunk. */
+ VBGLPHYSHEAPCHUNK *pNext;
+ /** Pointer to the previous chunk. */
+ VBGLPHYSHEAPCHUNK *pPrev;
+
+#if defined(VBGL_PH_USE_MEMOBJ)
+ /** The allocation handle. */
+ RTR0MEMOBJ hMemObj;
+#endif
+
+#if ARCH_BITS == 64
+ /** Pad the size up to 64 bytes. */
+# ifdef VBGL_PH_USE_MEMOBJ
+ uintptr_t auPadding2[2];
+# else
+ uintptr_t auPadding2[3];
+# endif
+#endif
+};
+#if ARCH_BITS == 64
+AssertCompileSize(VBGLPHYSHEAPCHUNK, 64);
+#endif
+
+
+/**
+ * Debug function that dumps the heap.
+ */
+#ifndef VBGL_PH_DUMPHEAP
+# define dumpheap(pszWhere) do { } while (0)
+#else
+static void dumpheap(const char *pszWhere)
+{
+ VBGL_PH_DPRINTF(("VBGL_PH dump at '%s'\n", pszWhere));
+
+ VBGL_PH_DPRINTF(("Chunks:\n"));
+ for (VBGLPHYSHEAPCHUNK *pChunk = g_vbgldata.pChunkHead; pChunk; pChunk = pChunk->pNext)
+ VBGL_PH_DPRINTF(("%p: pNext = %p, pPrev = %p, sign = %08X, size = %8d, cBlocks = %8d, cFreeBlocks=%8d, phys = %08X\n",
+ pChunk, pChunk->pNext, pChunk->pPrev, pChunk->u32Signature, pChunk->cbChunk,
+ pChunk->cBlocks, pChunk->cFreeBlocks, pChunk->physAddr));
+
+ VBGL_PH_DPRINTF(("Allocated blocks:\n"));
+ for (VBGLPHYSHEAPBLOCK *pBlock = g_vbgldata.pBlockHead; pBlock; pBlock = pBlock->pNext)
+ VBGL_PH_DPRINTF(("%p: pNext = %p, pPrev = %p, size = %05x, sign = %08X, %s, pChunk = %p\n",
+ pBlock, pBlock->pNext, pBlock->pPrev, pBlock->cbUser,
+ pBlock->u32Signature, pBlock->fAllocated ? "allocated" : " free", pBlock->pChunk));
+
+ VBGL_PH_DPRINTF(("Free blocks:\n"));
+ for (VBGLPHYSHEAPFREEBLOCK *pBlock = g_vbgldata.pFreeHead; pBlock; pBlock = pBlock->pNextFree)
+ VBGL_PH_DPRINTF(("%p: pNextFree = %p, pPrevFree = %p, size = %05x, sign = %08X, pChunk = %p%s\n",
+ pBlock, pBlock->pNextFree, pBlock->pPrevFree, pBlock->Core.cbUser,
+ pBlock->Core.u32Signature, pBlock->Core.pChunk,
+ !pBlock->Core.fAllocated ? "" : " !!allocated-block-on-freelist!!"));
+
+ VBGL_PH_DPRINTF(("VBGL_PH dump at '%s' done\n", pszWhere));
+}
+#endif
+
+
+/**
+ * Initialize a free block
+ */
+static void vbglPhysHeapInitFreeBlock(VBGLPHYSHEAPFREEBLOCK *pBlock, VBGLPHYSHEAPCHUNK *pChunk, uint32_t cbUser)
+{
+ Assert(pBlock != NULL);
+ Assert(pChunk != NULL);
+
+ pBlock->Core.u32Signature = VBGL_PH_BLOCKSIGNATURE;
+ pBlock->Core.cbUser = cbUser;
+ pBlock->Core.fAllocated = false;
+ pBlock->Core.pNext = NULL;
+ pBlock->Core.pPrev = NULL;
+ pBlock->Core.pChunk = pChunk;
+ pBlock->pNextFree = NULL;
+ pBlock->pPrevFree = NULL;
+}
+
+
+/**
+ * Updates block statistics when a block is added.
+ */
+DECLINLINE(void) vbglPhysHeapStatsBlockAdded(VBGLPHYSHEAPBLOCK *pBlock)
+{
+ g_vbgldata.cBlocks += 1;
+ pBlock->pChunk->cBlocks += 1;
+ AssertMsg((uint32_t)pBlock->pChunk->cBlocks <= pBlock->pChunk->cbChunk / sizeof(VBGLPHYSHEAPFREEBLOCK),
+ ("pChunk=%p: cbChunk=%#x cBlocks=%d\n", pBlock->pChunk, pBlock->pChunk->cbChunk, pBlock->pChunk->cBlocks));
+}
+
+
+/**
+ * Links @a pBlock onto the head of block list.
+ *
+ * This also update the per-chunk block counts.
+ */
+static void vbglPhysHeapInsertBlock(VBGLPHYSHEAPBLOCK *pBlock)
+{
+ AssertMsg(pBlock->pNext == NULL, ("pBlock->pNext = %p\n", pBlock->pNext));
+ AssertMsg(pBlock->pPrev == NULL, ("pBlock->pPrev = %p\n", pBlock->pPrev));
+
+ /* inserting to head of list */
+ VBGLPHYSHEAPBLOCK *pOldHead = g_vbgldata.pBlockHead;
+
+ pBlock->pNext = pOldHead;
+ pBlock->pPrev = NULL;
+
+ if (pOldHead)
+ pOldHead->pPrev = pBlock;
+ g_vbgldata.pBlockHead = pBlock;
+
+ /* Update the stats: */
+ vbglPhysHeapStatsBlockAdded(pBlock);
+}
+
+
+/**
+ * Links @a pBlock onto the block list after @a pInsertAfter.
+ *
+ * This also update the per-chunk block counts.
+ */
+static void vbglPhysHeapInsertBlockAfter(VBGLPHYSHEAPBLOCK *pBlock, VBGLPHYSHEAPBLOCK *pInsertAfter)
+{
+ AssertMsg(pBlock->pNext == NULL, ("pBlock->pNext = %p\n", pBlock->pNext));
+ AssertMsg(pBlock->pPrev == NULL, ("pBlock->pPrev = %p\n", pBlock->pPrev));
+
+ pBlock->pNext = pInsertAfter->pNext;
+ pBlock->pPrev = pInsertAfter;
+
+ if (pInsertAfter->pNext)
+ pInsertAfter->pNext->pPrev = pBlock;
+
+ pInsertAfter->pNext = pBlock;
+
+ /* Update the stats: */
+ vbglPhysHeapStatsBlockAdded(pBlock);
+}
+
+
+/**
+ * Unlinks @a pBlock from the block list.
+ *
+ * This also update the per-chunk block counts.
+ */
+static void vbglPhysHeapUnlinkBlock(VBGLPHYSHEAPBLOCK *pBlock)
+{
+ VBGLPHYSHEAPBLOCK *pOtherBlock = pBlock->pNext;
+ if (pOtherBlock)
+ pOtherBlock->pPrev = pBlock->pPrev;
+ /* else: this is tail of list but we do not maintain tails of block lists. so nothing to do. */
+
+ pOtherBlock = pBlock->pPrev;
+ if (pOtherBlock)
+ pOtherBlock->pNext = pBlock->pNext;
+ else
+ {
+ Assert(g_vbgldata.pBlockHead == pBlock);
+ g_vbgldata.pBlockHead = pBlock->pNext;
+ }
+
+ pBlock->pNext = NULL;
+ pBlock->pPrev = NULL;
+
+ /* Update the stats: */
+ g_vbgldata.cBlocks -= 1;
+ pBlock->pChunk->cBlocks -= 1;
+ AssertMsg(pBlock->pChunk->cBlocks >= 0,
+ ("pChunk=%p: cbChunk=%#x cBlocks=%d\n", pBlock->pChunk, pBlock->pChunk->cbChunk, pBlock->pChunk->cBlocks));
+ Assert(g_vbgldata.cBlocks >= 0);
+}
+
+
+
+/**
+ * Updates statistics after adding a free block.
+ */
+DECLINLINE(void) vbglPhysHeapStatsFreeBlockAdded(VBGLPHYSHEAPFREEBLOCK *pBlock)
+{
+ g_vbgldata.cFreeBlocks += 1;
+ pBlock->Core.pChunk->cFreeBlocks += 1;
+}
+
+
+/**
+ * Links @a pBlock onto head of the free chain.
+ *
+ * This is used during block freeing and when adding a new chunk.
+ *
+ * This also update the per-chunk block counts.
+ */
+static void vbglPhysHeapInsertFreeBlock(VBGLPHYSHEAPFREEBLOCK *pBlock)
+{
+ Assert(!pBlock->Core.fAllocated);
+ AssertMsg(pBlock->pNextFree == NULL, ("pBlock->pNextFree = %p\n", pBlock->pNextFree));
+ AssertMsg(pBlock->pPrevFree == NULL, ("pBlock->pPrevFree = %p\n", pBlock->pPrevFree));
+
+ /* inserting to head of list */
+ VBGLPHYSHEAPFREEBLOCK *pOldHead = g_vbgldata.pFreeHead;
+
+ pBlock->pNextFree = pOldHead;
+ pBlock->pPrevFree = NULL;
+
+ if (pOldHead)
+ pOldHead->pPrevFree = pBlock;
+ g_vbgldata.pFreeHead = pBlock;
+
+ /* Update the stats: */
+ vbglPhysHeapStatsFreeBlockAdded(pBlock);
+}
+
+
+/**
+ * Links @a pBlock after @a pInsertAfter.
+ *
+ * This is used when splitting a free block during allocation to preserve the
+ * place in the free list.
+ *
+ * This also update the per-chunk block counts.
+ */
+static void vbglPhysHeapInsertFreeBlockAfter(VBGLPHYSHEAPFREEBLOCK *pBlock, VBGLPHYSHEAPFREEBLOCK *pInsertAfter)
+{
+ Assert(!pBlock->Core.fAllocated);
+ AssertMsg(pBlock->pNextFree == NULL, ("pBlock->pNextFree = %p\n", pBlock->pNextFree));
+ AssertMsg(pBlock->pPrevFree == NULL, ("pBlock->pPrevFree = %p\n", pBlock->pPrevFree));
+
+ /* inserting after the tiven node */
+ pBlock->pNextFree = pInsertAfter->pNextFree;
+ pBlock->pPrevFree = pInsertAfter;
+
+ if (pInsertAfter->pNextFree)
+ pInsertAfter->pNextFree->pPrevFree = pBlock;
+
+ pInsertAfter->pNextFree = pBlock;
+
+ /* Update the stats: */
+ vbglPhysHeapStatsFreeBlockAdded(pBlock);
+}
+
+
+/**
+ * Unlinks @a pBlock from the free list.
+ *
+ * This also update the per-chunk block counts.
+ */
+static void vbglPhysHeapUnlinkFreeBlock(VBGLPHYSHEAPFREEBLOCK *pBlock)
+{
+ Assert(!pBlock->Core.fAllocated);
+
+ VBGLPHYSHEAPFREEBLOCK *pOtherBlock = pBlock->pNextFree;
+ if (pOtherBlock)
+ pOtherBlock->pPrevFree = pBlock->pPrevFree;
+ /* else: this is tail of list but we do not maintain tails of block lists. so nothing to do. */
+
+ pOtherBlock = pBlock->pPrevFree;
+ if (pOtherBlock)
+ pOtherBlock->pNextFree = pBlock->pNextFree;
+ else
+ {
+ Assert(g_vbgldata.pFreeHead == pBlock);
+ g_vbgldata.pFreeHead = pBlock->pNextFree;
+ }
+
+ pBlock->pNextFree = NULL;
+ pBlock->pPrevFree = NULL;
+
+ /* Update the stats: */
+ g_vbgldata.cFreeBlocks -= 1;
+ pBlock->Core.pChunk->cFreeBlocks -= 1;
+ AssertMsg(pBlock->Core.pChunk->cFreeBlocks >= 0,
+ ("pChunk=%p: cbChunk=%#x cFreeBlocks=%d\n",
+ pBlock->Core.pChunk, pBlock->Core.pChunk->cbChunk, pBlock->Core.pChunk->cFreeBlocks));
+ Assert(g_vbgldata.cFreeBlocks >= 0);
+}
+
+
+/**
+ * Allocate another chunk and add it to the heap.
+ *
+ * @returns Pointer to the free block in the new chunk on success, NULL on
+ * allocation failure.
+ * @param cbMinBlock The size of the user block we need this chunk for.
+ */
+static VBGLPHYSHEAPFREEBLOCK *vbglPhysHeapChunkAlloc(uint32_t cbMinBlock)
+{
+ RTCCPHYS PhysAddr = NIL_RTHCPHYS;
+ VBGLPHYSHEAPCHUNK *pChunk;
+ uint32_t cbChunk;
+#ifdef VBGL_PH_USE_MEMOBJ
+ RTR0MEMOBJ hMemObj = NIL_RTR0MEMOBJ;
+ int rc;
+#endif
+
+ VBGL_PH_DPRINTF(("Allocating new chunk for %#x byte allocation\n", cbMinBlock));
+ AssertReturn(cbMinBlock <= VBGL_PH_LARGEST_ALLOC_SIZE, NULL); /* paranoia */
+
+ /*
+ * Compute the size of the new chunk, rounding up to next chunk size,
+ * which must be power of 2.
+ *
+ * Note! Using VBGLPHYSHEAPFREEBLOCK here means the minimum block size is
+ * 8 or 16 bytes too high, but safer this way since cbMinBlock is
+ * zero during the init code call.
+ */
+ Assert(RT_IS_POWER_OF_TWO(VBGL_PH_CHUNKSIZE));
+ cbChunk = cbMinBlock + sizeof(VBGLPHYSHEAPCHUNK) + sizeof(VBGLPHYSHEAPFREEBLOCK);
+ cbChunk = RT_ALIGN_32(cbChunk, VBGL_PH_CHUNKSIZE);
+
+ /*
+ * This function allocates physical contiguous memory below 4 GB. This 4GB
+ * limitation stems from using a 32-bit OUT instruction to pass a block
+ * physical address to the host.
+ */
+#ifdef VBGL_PH_USE_MEMOBJ
+ rc = RTR0MemObjAllocCont(&hMemObj, cbChunk, false /*fExecutable*/);
+ pChunk = (VBGLPHYSHEAPCHUNK *)(RT_SUCCESS(rc) ? RTR0MemObjAddress(hMemObj) : NULL);
+#else
+ pChunk = (VBGLPHYSHEAPCHUNK *)RTMemContAlloc(&PhysAddr, cbChunk);
+#endif
+ if (!pChunk)
+ {
+ /* If the allocation fail, halv the size till and try again. */
+ uint32_t cbMinChunk = RT_MAX(cbMinBlock, PAGE_SIZE / 2) + sizeof(VBGLPHYSHEAPCHUNK) + sizeof(VBGLPHYSHEAPFREEBLOCK);
+ cbMinChunk = RT_ALIGN_32(cbMinChunk, PAGE_SIZE);
+ if (cbChunk > cbMinChunk)
+ do
+ {
+ cbChunk >>= 2;
+ cbChunk = RT_ALIGN_32(cbChunk, PAGE_SIZE);
+#ifdef VBGL_PH_USE_MEMOBJ
+ rc = RTR0MemObjAllocCont(&hMemObj, cbChunk, false /*fExecutable*/);
+ pChunk = (VBGLPHYSHEAPCHUNK *)(RT_SUCCESS(rc) ? RTR0MemObjAddress(hMemObj) : NULL);
+#else
+ pChunk = (VBGLPHYSHEAPCHUNK *)RTMemContAlloc(&PhysAddr, cbChunk);
+#endif
+ } while (!pChunk && cbChunk > cbMinChunk);
+ }
+ if (pChunk)
+ {
+ VBGLPHYSHEAPCHUNK *pOldHeadChunk;
+ VBGLPHYSHEAPFREEBLOCK *pBlock;
+ AssertRelease(PhysAddr < _4G && PhysAddr + cbChunk <= _4G);
+
+ /*
+ * Init the new chunk.
+ */
+ pChunk->u32Signature = VBGL_PH_CHUNKSIGNATURE;
+ pChunk->cbChunk = cbChunk;
+ pChunk->physAddr = (uint32_t)PhysAddr;
+ pChunk->cBlocks = 0;
+ pChunk->cFreeBlocks = 0;
+ pChunk->pNext = NULL;
+ pChunk->pPrev = NULL;
+#ifdef VBGL_PH_USE_MEMOBJ
+ pChunk->hMemObj = hMemObj;
+#endif
+
+ /* Initialize the padding too: */
+#if !defined(VBGL_PH_USE_MEMOBJ) || ARCH_BITS != 32
+ pChunk->uPadding1 = UINT32_C(0xADDCAAA1);
+#endif
+#if ARCH_BITS == 64
+ pChunk->auPadding2[0] = UINT64_C(0xADDCAAA3ADDCAAA2);
+ pChunk->auPadding2[1] = UINT64_C(0xADDCAAA5ADDCAAA4);
+# ifndef VBGL_PH_USE_MEMOBJ
+ pChunk->auPadding2[2] = UINT64_C(0xADDCAAA7ADDCAAA6);
+# endif
+#endif
+
+ /*
+ * Initialize the free block, which now occupies entire chunk.
+ */
+ pBlock = (VBGLPHYSHEAPFREEBLOCK *)(pChunk + 1);
+ vbglPhysHeapInitFreeBlock(pBlock, pChunk, cbChunk - sizeof(VBGLPHYSHEAPCHUNK) - sizeof(VBGLPHYSHEAPBLOCK));
+ vbglPhysHeapInsertBlock(&pBlock->Core);
+ vbglPhysHeapInsertFreeBlock(pBlock);
+
+ /*
+ * Add the chunk to the list.
+ */
+ pOldHeadChunk = g_vbgldata.pChunkHead;
+ pChunk->pNext = pOldHeadChunk;
+ if (pOldHeadChunk)
+ pOldHeadChunk->pPrev = pChunk;
+ g_vbgldata.pChunkHead = pChunk;
+
+ VBGL_PH_DPRINTF(("Allocated chunk %p LB %#x, block %p LB %#x\n", pChunk, cbChunk, pBlock, pBlock->Core.cbUser));
+ return pBlock;
+ }
+ LogRel(("vbglPhysHeapChunkAlloc: failed to alloc %u (%#x) contiguous bytes.\n", cbChunk, cbChunk));
+ return NULL;
+}
+
+
+/**
+ * Deletes a chunk: Unlinking all its blocks and freeing its memory.
+ */
+static void vbglPhysHeapChunkDelete(VBGLPHYSHEAPCHUNK *pChunk)
+{
+ uintptr_t uEnd, uCur;
+ Assert(pChunk != NULL);
+ AssertMsg(pChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE, ("pChunk->u32Signature = %08X\n", pChunk->u32Signature));
+
+ VBGL_PH_DPRINTF(("Deleting chunk %p size %x\n", pChunk, pChunk->cbChunk));
+
+ /*
+ * First scan the chunk and unlink all blocks from the lists.
+ *
+ * Note! We could do this by finding the first and last block list entries
+ * and just drop the whole chain relating to this chunk, rather than
+ * doing it one by one. But doing it one by one is simpler and will
+ * continue to work if the block list ends in an unsorted state.
+ */
+ uEnd = (uintptr_t)pChunk + pChunk->cbChunk;
+ uCur = (uintptr_t)(pChunk + 1);
+
+ while (uCur < uEnd)
+ {
+ VBGLPHYSHEAPBLOCK *pBlock = (VBGLPHYSHEAPBLOCK *)uCur;
+ Assert(pBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE);
+ Assert(pBlock->pChunk == pChunk);
+
+ uCur += pBlock->cbUser + sizeof(VBGLPHYSHEAPBLOCK);
+ Assert(uCur == (uintptr_t)pBlock->pNext || uCur >= uEnd);
+
+ if (!pBlock->fAllocated)
+ vbglPhysHeapUnlinkFreeBlock((VBGLPHYSHEAPFREEBLOCK *)pBlock);
+ vbglPhysHeapUnlinkBlock(pBlock);
+ }
+
+ AssertMsg(uCur == uEnd, ("uCur = %p, uEnd = %p, pChunk->cbChunk = %08X\n", uCur, uEnd, pChunk->cbChunk));
+
+ /*
+ * Unlink the chunk from the chunk list.
+ */
+ if (pChunk->pNext)
+ pChunk->pNext->pPrev = pChunk->pPrev;
+ /* else: we do not maintain tail pointer. */
+
+ if (pChunk->pPrev)
+ pChunk->pPrev->pNext = pChunk->pNext;
+ else
+ {
+ Assert(g_vbgldata.pChunkHead == pChunk);
+ g_vbgldata.pChunkHead = pChunk->pNext;
+ }
+
+ /*
+ * Finally, free the chunk memory.
+ */
+#ifdef VBGL_PH_USE_MEMOBJ
+ RTR0MemObjFree(pChunk->hMemObj, true /*fFreeMappings*/);
+#else
+ RTMemContFree(pChunk, pChunk->cbChunk);
+#endif
+}
+
+
+DECLR0VBGL(void *) VbglR0PhysHeapAlloc(uint32_t cb)
+{
+ VBGLPHYSHEAPFREEBLOCK *pBlock;
+ VBGLPHYSHEAPFREEBLOCK *pIter;
+ int32_t cLeft;
+#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS
+ uint32_t cbAlwaysSplit;
+#endif
+ int rc;
+
+ /*
+ * Make sure we don't allocate anything too small to turn into a free node
+ * and align the size to prevent pointer misalignment and whatnot.
+ */
+ cb = RT_MAX(cb, VBGL_PH_SMALLEST_ALLOC_SIZE);
+ cb = RT_ALIGN_32(cb, VBGL_PH_ALLOC_ALIGN);
+ AssertCompile(VBGL_PH_ALLOC_ALIGN <= sizeof(pBlock->Core));
+
+ rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap);
+ AssertRCReturn(rc, NULL);
+
+ dumpheap("pre alloc");
+
+ /*
+ * Search the free list. We do this in linear fashion as we don't expect
+ * there to be many blocks in the heap.
+ */
+#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS
+ cbAlwaysSplit = cb + VBGL_PH_STOP_SEARCH_AT_EXCESS;
+#endif
+ cLeft = VBGL_PH_MAX_FREE_SEARCH;
+ pBlock = NULL;
+ if (cb <= PAGE_SIZE / 4 * 3)
+ {
+ /* Smaller than 3/4 page: Prefer a free block that can keep the request within a single page,
+ so HGCM processing in VMMDev can use page locks instead of several reads and writes. */
+ VBGLPHYSHEAPFREEBLOCK *pFallback = NULL;
+ for (pIter = g_vbgldata.pFreeHead; pIter != NULL; pIter = pIter->pNextFree, cLeft--)
+ {
+ AssertBreak(pIter->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE);
+ if (pIter->Core.cbUser >= cb)
+ {
+ if (pIter->Core.cbUser == cb)
+ {
+ if (PAGE_SIZE - ((uintptr_t)(pIter + 1) & PAGE_OFFSET_MASK) >= cb)
+ {
+ pBlock = pIter;
+ break;
+ }
+ pFallback = pIter;
+ }
+ else
+ {
+ if (!pFallback || pIter->Core.cbUser < pFallback->Core.cbUser)
+ pFallback = pIter;
+ if (PAGE_SIZE - ((uintptr_t)(pIter + 1) & PAGE_OFFSET_MASK) >= cb)
+ {
+ if (!pBlock || pIter->Core.cbUser < pBlock->Core.cbUser)
+ pBlock = pIter;
+#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS
+ else if (pIter->Core.cbUser >= cbAlwaysSplit)
+ {
+ pBlock = pIter;
+ break;
+ }
+#endif
+ }
+ }
+
+ if (cLeft > 0)
+ { /* likely */ }
+ else
+ break;
+ }
+ }
+
+ if (!pBlock)
+ pBlock = pFallback;
+ }
+ else
+ {
+ /* Large than 3/4 page: Find closest free list match. */
+ for (pIter = g_vbgldata.pFreeHead; pIter != NULL; pIter = pIter->pNextFree, cLeft--)
+ {
+ AssertBreak(pIter->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE);
+ if (pIter->Core.cbUser >= cb)
+ {
+ if (pIter->Core.cbUser == cb)
+ {
+ /* Exact match - we're done! */
+ pBlock = pIter;
+ break;
+ }
+
+#ifdef VBGL_PH_STOP_SEARCH_AT_EXCESS
+ if (pIter->Core.cbUser >= cbAlwaysSplit)
+ {
+ /* Really big block - no point continue searching! */
+ pBlock = pIter;
+ break;
+ }
+#endif
+ /* Looking for a free block with nearest size. */
+ if (!pBlock || pIter->Core.cbUser < pBlock->Core.cbUser)
+ pBlock = pIter;
+
+ if (cLeft > 0)
+ { /* likely */ }
+ else
+ break;
+ }
+ }
+ }
+
+ if (!pBlock)
+ {
+ /* No free blocks, allocate a new chunk, the only free block of the
+ chunk will be returned. */
+ pBlock = vbglPhysHeapChunkAlloc(cb);
+ }
+
+ if (pBlock)
+ {
+ /* We have a free block, either found or allocated. */
+ AssertMsg(pBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE,
+ ("pBlock = %p, pBlock->u32Signature = %08X\n", pBlock, pBlock->Core.u32Signature));
+ AssertMsg(!pBlock->Core.fAllocated, ("pBlock = %p\n", pBlock));
+
+ /*
+ * If the block is too large, split off a free block with the unused space.
+ *
+ * We do this before unlinking the block so we can preserve the location
+ * in the free list.
+ *
+ * Note! We cannot split off and return the tail end here, because that may
+ * violate the same page requirements for requests smaller than 3/4 page.
+ */
+ AssertCompile(VBGL_PH_MIN_SPLIT_FREE_BLOCK >= sizeof(*pBlock) - sizeof(pBlock->Core));
+ if (pBlock->Core.cbUser >= sizeof(VBGLPHYSHEAPBLOCK) * 2 + VBGL_PH_MIN_SPLIT_FREE_BLOCK + cb)
+ {
+ pIter = (VBGLPHYSHEAPFREEBLOCK *)((uintptr_t)(&pBlock->Core + 1) + cb);
+ vbglPhysHeapInitFreeBlock(pIter, pBlock->Core.pChunk, pBlock->Core.cbUser - cb - sizeof(VBGLPHYSHEAPBLOCK));
+
+ pBlock->Core.cbUser = cb;
+
+ /* Insert the new 'pIter' block after the 'pBlock' in the block list
+ and in the free list. */
+ vbglPhysHeapInsertBlockAfter(&pIter->Core, &pBlock->Core);
+ vbglPhysHeapInsertFreeBlockAfter(pIter, pBlock);
+ }
+
+ /*
+ * Unlink the block from the free list and mark it as allocated.
+ */
+ vbglPhysHeapUnlinkFreeBlock(pBlock);
+ pBlock->Core.fAllocated = true;
+
+ dumpheap("post alloc");
+
+ /*
+ * Return success.
+ */
+ rc = RTSemFastMutexRelease(g_vbgldata.hMtxHeap);
+
+ VBGL_PH_DPRINTF(("VbglR0PhysHeapAlloc: returns %p size %x\n", pBlock + 1, pBlock->Core.cbUser));
+ return &pBlock->Core + 1;
+ }
+
+ /*
+ * Return failure.
+ */
+ rc = RTSemFastMutexRelease(g_vbgldata.hMtxHeap);
+ AssertRC(rc);
+
+ VBGL_PH_DPRINTF(("VbglR0PhysHeapAlloc: returns NULL (requested %#x bytes)\n", cb));
+ return NULL;
+}
+
+
+DECLR0VBGL(uint32_t) VbglR0PhysHeapGetPhysAddr(void *pv)
+{
+ /*
+ * Validate the incoming pointer.
+ */
+ if (pv != NULL)
+ {
+ VBGLPHYSHEAPBLOCK *pBlock = (VBGLPHYSHEAPBLOCK *)pv - 1;
+ if ( pBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE
+ && pBlock->fAllocated)
+ {
+ /*
+ * Calculate and return its physical address.
+ */
+ VBGLPHYSHEAPCHUNK *pChunk = pBlock->pChunk;
+ return pChunk->physAddr + (uint32_t)((uintptr_t)pv - (uintptr_t)pChunk);
+ }
+
+ AssertMsgFailed(("Use after free or corrupt pointer variable: pv=%p pBlock=%p: u32Signature=%#x cb=%#x fAllocated=%d\n",
+ pv, pBlock, pBlock->u32Signature, pBlock->cbUser, pBlock->fAllocated));
+ }
+ else
+ AssertMsgFailed(("Unexpected NULL pointer\n"));
+ return 0;
+}
+
+
+DECLR0VBGL(void) VbglR0PhysHeapFree(void *pv)
+{
+ if (pv != NULL)
+ {
+ VBGLPHYSHEAPFREEBLOCK *pBlock;
+
+ int rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap);
+ AssertRCReturnVoid(rc);
+
+ dumpheap("pre free");
+
+ /*
+ * Validate the block header.
+ */
+ pBlock = (VBGLPHYSHEAPFREEBLOCK *)((VBGLPHYSHEAPBLOCK *)pv - 1);
+ if ( pBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE
+ && pBlock->Core.fAllocated
+ && pBlock->Core.cbUser >= VBGL_PH_SMALLEST_ALLOC_SIZE)
+ {
+ VBGLPHYSHEAPCHUNK *pChunk;
+ VBGLPHYSHEAPBLOCK *pNeighbour;
+
+ /*
+ * Change the block status to freeed.
+ */
+ VBGL_PH_DPRINTF(("VbglR0PhysHeapFree: %p size %#x\n", pv, pBlock->Core.cbUser));
+
+ pBlock->Core.fAllocated = false;
+ pBlock->pNextFree = pBlock->pPrevFree = NULL;
+ vbglPhysHeapInsertFreeBlock(pBlock);
+
+ dumpheap("post insert");
+
+ /*
+ * Check if the block after this one is also free and we can merge it into this one.
+ */
+ pChunk = pBlock->Core.pChunk;
+
+ pNeighbour = pBlock->Core.pNext;
+ if ( pNeighbour
+ && !pNeighbour->fAllocated
+ && pNeighbour->pChunk == pChunk)
+ {
+ Assert((uintptr_t)pBlock + sizeof(pBlock->Core) + pBlock->Core.cbUser == (uintptr_t)pNeighbour);
+
+ /* Adjust size of current memory block */
+ pBlock->Core.cbUser += pNeighbour->cbUser + sizeof(VBGLPHYSHEAPBLOCK);
+
+ /* Unlink the following node and invalid it. */
+ vbglPhysHeapUnlinkFreeBlock((VBGLPHYSHEAPFREEBLOCK *)pNeighbour);
+ vbglPhysHeapUnlinkBlock(pNeighbour);
+
+ pNeighbour->u32Signature = ~VBGL_PH_BLOCKSIGNATURE;
+ pNeighbour->cbUser = UINT32_MAX / 4;
+
+ dumpheap("post merge after");
+ }
+
+ /*
+ * Same check for the block before us. This invalidates pBlock.
+ */
+ pNeighbour = pBlock->Core.pPrev;
+ if ( pNeighbour
+ && !pNeighbour->fAllocated
+ && pNeighbour->pChunk == pChunk)
+ {
+ Assert((uintptr_t)pNeighbour + sizeof(*pNeighbour) + pNeighbour->cbUser == (uintptr_t)pBlock);
+
+ /* Adjust size of the block before us */
+ pNeighbour->cbUser += pBlock->Core.cbUser + sizeof(VBGLPHYSHEAPBLOCK);
+
+ /* Unlink this node and invalid it. */
+ vbglPhysHeapUnlinkFreeBlock(pBlock);
+ vbglPhysHeapUnlinkBlock(&pBlock->Core);
+
+ pBlock->Core.u32Signature = ~VBGL_PH_BLOCKSIGNATURE;
+ pBlock->Core.cbUser = UINT32_MAX / 8;
+
+ pBlock = NULL; /* invalid */
+
+ dumpheap("post merge before");
+ }
+
+ /*
+ * If this chunk is now completely unused, delete it if there are
+ * more completely free ones.
+ */
+ if ( pChunk->cFreeBlocks == pChunk->cBlocks
+ && (pChunk->pPrev || pChunk->pNext))
+ {
+ VBGLPHYSHEAPCHUNK *pCurChunk;
+ uint32_t cUnusedChunks = 0;
+ for (pCurChunk = g_vbgldata.pChunkHead; pCurChunk; pCurChunk = pCurChunk->pNext)
+ {
+ AssertBreak(pCurChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE);
+ if (pCurChunk->cFreeBlocks == pCurChunk->cBlocks)
+ {
+ cUnusedChunks++;
+ if (cUnusedChunks > 1)
+ {
+ /* Delete current chunk, it will also unlink all free blocks
+ * remaining in the chunk from the free list, so the pBlock
+ * will also be invalid after this.
+ */
+ vbglPhysHeapChunkDelete(pChunk);
+ pBlock = NULL; /* invalid */
+ pChunk = NULL;
+ pNeighbour = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ dumpheap("post free");
+ }
+ else
+ AssertMsgFailed(("pBlock: %p: u32Signature=%#x cb=%#x fAllocated=%d - double free?\n",
+ pBlock, pBlock->Core.u32Signature, pBlock->Core.cbUser, pBlock->Core.fAllocated));
+
+ rc = RTSemFastMutexRelease(g_vbgldata.hMtxHeap);
+ AssertRC(rc);
+ }
+}
+
+#ifdef IN_TESTCASE /* For the testcase only */
+
+/**
+ * Returns the sum of all free heap blocks.
+ *
+ * This is the amount of memory you can theoretically allocate if you do
+ * allocations exactly matching the free blocks.
+ *
+ * @returns The size of the free blocks.
+ * @returns 0 if heap was safely detected as being bad.
+ */
+DECLVBGL(size_t) VbglR0PhysHeapGetFreeSize(void)
+{
+ int rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap);
+ AssertRCReturn(rc, 0);
+
+ size_t cbTotal = 0;
+ for (VBGLPHYSHEAPFREEBLOCK *pCurBlock = g_vbgldata.pFreeHead; pCurBlock; pCurBlock = pCurBlock->pNextFree)
+ {
+ Assert(pCurBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE);
+ Assert(!pCurBlock->Core.fAllocated);
+ cbTotal += pCurBlock->Core.cbUser;
+ }
+
+ RTSemFastMutexRelease(g_vbgldata.hMtxHeap);
+ return cbTotal;
+}
+
+
+/**
+ * Checks the heap, caller responsible for locking.
+ *
+ * @returns VINF_SUCCESS if okay, error status if not.
+ * @param pErrInfo Where to return more error details, optional.
+ */
+static int vbglR0PhysHeapCheckLocked(PRTERRINFO pErrInfo)
+{
+ /*
+ * Scan the blocks in each chunk, walking the block list in parallel.
+ */
+ const VBGLPHYSHEAPBLOCK *pPrevBlockListEntry = NULL;
+ const VBGLPHYSHEAPBLOCK *pCurBlockListEntry = g_vbgldata.pBlockHead;
+ unsigned acTotalBlocks[2] = { 0, 0 };
+ for (VBGLPHYSHEAPCHUNK *pCurChunk = g_vbgldata.pChunkHead, *pPrevChunk = NULL; pCurChunk; pCurChunk = pCurChunk->pNext)
+ {
+ AssertReturn(pCurChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE,
+ RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC, "pCurChunk=%p: magic=%#x", pCurChunk, pCurChunk->u32Signature));
+ AssertReturn(pCurChunk->pPrev == pPrevChunk,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_2,
+ "pCurChunk=%p: pPrev=%p, expected %p", pCurChunk, pCurChunk->pPrev, pPrevChunk));
+
+ const VBGLPHYSHEAPBLOCK *pCurBlock = (const VBGLPHYSHEAPBLOCK *)(pCurChunk + 1);
+ uintptr_t const uEnd = (uintptr_t)pCurChunk + pCurChunk->cbChunk;
+ unsigned acBlocks[2] = { 0, 0 };
+ while ((uintptr_t)pCurBlock < uEnd)
+ {
+ AssertReturn(pCurBlock->u32Signature == VBGL_PH_BLOCKSIGNATURE,
+ RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC,
+ "pCurBlock=%p: magic=%#x", pCurBlock, pCurBlock->u32Signature));
+ AssertReturn(pCurBlock->pChunk == pCurChunk,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_2,
+ "pCurBlock=%p: pChunk=%p, expected %p", pCurBlock, pCurBlock->pChunk, pCurChunk));
+ AssertReturn( pCurBlock->cbUser >= VBGL_PH_SMALLEST_ALLOC_SIZE
+ && pCurBlock->cbUser <= VBGL_PH_LARGEST_ALLOC_SIZE
+ && RT_ALIGN_32(pCurBlock->cbUser, VBGL_PH_ALLOC_ALIGN) == pCurBlock->cbUser,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_3,
+ "pCurBlock=%p: cbUser=%#x", pCurBlock, pCurBlock->cbUser));
+ AssertReturn(pCurBlock == pCurBlockListEntry,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_4,
+ "pCurChunk=%p: pCurBlock=%p, pCurBlockListEntry=%p\n",
+ pCurChunk, pCurBlock, pCurBlockListEntry));
+ AssertReturn(pCurBlock->pPrev == pPrevBlockListEntry,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_5,
+ "pCurChunk=%p: pCurBlock->pPrev=%p, pPrevBlockListEntry=%p\n",
+ pCurChunk, pCurBlock->pPrev, pPrevBlockListEntry));
+
+ acBlocks[pCurBlock->fAllocated] += 1;
+
+ /* advance */
+ pPrevBlockListEntry = pCurBlock;
+ pCurBlockListEntry = pCurBlock->pNext;
+ pCurBlock = (const VBGLPHYSHEAPBLOCK *)((uintptr_t)(pCurBlock + 1) + pCurBlock->cbUser);
+ }
+ AssertReturn((uintptr_t)pCurBlock == uEnd,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_4,
+ "pCurBlock=%p uEnd=%p", pCurBlock, uEnd));
+
+ acTotalBlocks[1] += acBlocks[1];
+ AssertReturn(acBlocks[0] + acBlocks[1] == (uint32_t)pCurChunk->cBlocks,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_4,
+ "pCurChunk=%p: cBlocks=%u, expected %u",
+ pCurChunk, pCurChunk->cBlocks, acBlocks[0] + acBlocks[1]));
+
+ acTotalBlocks[0] += acBlocks[0];
+ AssertReturn(acBlocks[0] == (uint32_t)pCurChunk->cFreeBlocks,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_5,
+ "pCurChunk=%p: cFreeBlocks=%u, expected %u",
+ pCurChunk, pCurChunk->cFreeBlocks, acBlocks[0]));
+
+ pPrevChunk = pCurChunk;
+ }
+
+ AssertReturn(acTotalBlocks[0] == (uint32_t)g_vbgldata.cFreeBlocks,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR,
+ "g_vbgldata.cFreeBlocks=%u, expected %u", g_vbgldata.cFreeBlocks, acTotalBlocks[0]));
+ AssertReturn(acTotalBlocks[0] + acTotalBlocks[1] == (uint32_t)g_vbgldata.cBlocks,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR,
+ "g_vbgldata.cBlocks=%u, expected %u", g_vbgldata.cBlocks, acTotalBlocks[0] + acTotalBlocks[1]));
+
+ /*
+ * Check that the free list contains the same number of blocks as we
+ * encountered during the above scan.
+ */
+ {
+ unsigned cFreeListBlocks = 0;
+ for (const VBGLPHYSHEAPFREEBLOCK *pCurBlock = g_vbgldata.pFreeHead, *pPrevBlock = NULL;
+ pCurBlock;
+ pCurBlock = pCurBlock->pNextFree)
+ {
+ AssertReturn(pCurBlock->Core.u32Signature == VBGL_PH_BLOCKSIGNATURE,
+ RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC,
+ "pCurBlock=%p/free: magic=%#x", pCurBlock, pCurBlock->Core.u32Signature));
+ AssertReturn(pCurBlock->pPrevFree == pPrevBlock,
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_2,
+ "pCurBlock=%p/free: pPrev=%p, expected %p", pCurBlock, pCurBlock->pPrevFree, pPrevBlock));
+ AssertReturn(pCurBlock->Core.pChunk->u32Signature == VBGL_PH_CHUNKSIGNATURE,
+ RTErrInfoSetF(pErrInfo, VERR_INVALID_MAGIC, "pCurBlock=%p/free: chunk (%p) magic=%#x",
+ pCurBlock, pCurBlock->Core.pChunk, pCurBlock->Core.pChunk->u32Signature));
+ cFreeListBlocks++;
+ pPrevBlock = pCurBlock;
+ }
+
+ AssertReturn(cFreeListBlocks == acTotalBlocks[0],
+ RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_3,
+ "Found %u in free list, expected %u", cFreeListBlocks, acTotalBlocks[0]));
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Performs a heap check.
+ *
+ * @returns Problem description on failure, NULL on success.
+ * @param pErrInfo Where to return more error details, optional.
+ */
+DECLVBGL(int) VbglR0PhysHeapCheck(PRTERRINFO pErrInfo)
+{
+ int rc = RTSemFastMutexRequest(g_vbgldata.hMtxHeap);
+ AssertRCReturn(rc, 0);
+
+ rc = vbglR0PhysHeapCheckLocked(pErrInfo);
+
+ RTSemFastMutexRelease(g_vbgldata.hMtxHeap);
+ return rc;
+}
+
+#endif /* IN_TESTCASE */
+
+DECLR0VBGL(int) VbglR0PhysHeapInit(void)
+{
+ g_vbgldata.hMtxHeap = NIL_RTSEMFASTMUTEX;
+
+ /* Allocate the first chunk of the heap. */
+ VBGLPHYSHEAPFREEBLOCK *pBlock = vbglPhysHeapChunkAlloc(0);
+ if (pBlock)
+ return RTSemFastMutexCreate(&g_vbgldata.hMtxHeap);
+ return VERR_NO_CONT_MEMORY;
+}
+
+DECLR0VBGL(void) VbglR0PhysHeapTerminate(void)
+{
+ while (g_vbgldata.pChunkHead)
+ vbglPhysHeapChunkDelete(g_vbgldata.pChunkHead);
+
+ RTSemFastMutexDestroy(g_vbgldata.hMtxHeap);
+ g_vbgldata.hMtxHeap = NIL_RTSEMFASTMUTEX;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c
new file mode 100644
index 00000000..02a5c470
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c
@@ -0,0 +1,716 @@
+/* $Id: VBoxGuestR0LibSharedFolders.c $ */
+/** @file
+ * VBoxGuestR0LibSharedFolders - Ring 0 Shared Folders calls.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#include "VBoxGuestR0LibInternal.h"
+#include <VBox/VBoxGuestLibSharedFolders.h>
+#include <VBox/log.h>
+#include <iprt/err.h>
+#include <iprt/time.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#ifdef VBGL_VBOXGUEST
+# error "This file shouldn't be part of the VBoxGuestR0LibBase library that is linked into VBoxGuest. It's client code."
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VBOX_INIT_CALL(a, b, c) \
+ LogFunc(("%s, idClient=%d\n", "SHFL_FN_" # b, (c)->idClient)); \
+ VBGL_HGCM_HDR_INIT(a, (c)->idClient, SHFL_FN_##b, SHFL_CPARMS_##b); \
+ (a)->fInterruptible = false /* Currently we do like nfs with -o hard (default). */
+
+#define VBOX_INIT_CALL_EX(a, b, c, a_cbReq) \
+ LogFunc(("%s, idClient=%d\n", "SHFL_FN_" # b, (c)->idClient)); \
+ VBGL_HGCM_HDR_INIT_EX(a, (c)->idClient, SHFL_FN_##b, SHFL_CPARMS_##b, a_cbReq); \
+ (a)->fInterruptible = false /* Currently we do like nfs with -o hard (default). */
+
+
+
+DECLVBGL(int) VbglR0SfInit(void)
+{
+ return VbglR0InitClient();
+}
+
+DECLVBGL(void) VbglR0SfTerm(void)
+{
+ VbglR0TerminateClient();
+}
+
+DECLVBGL(int) VbglR0SfConnect(PVBGLSFCLIENT pClient)
+{
+ int rc = VbglR0HGCMConnect(&pClient->handle, "VBoxSharedFolders", &pClient->idClient);
+ if (RT_SUCCESS(rc))
+ LogFunc(("idClient=%d\n", pClient->idClient));
+ else
+ LogFunc(("VbglR0HGCMConnect failed -> rc=%Rrc\n", rc));
+ return rc;
+}
+
+DECLVBGL(void) VbglR0SfDisconnect(PVBGLSFCLIENT pClient)
+{
+ int rc;
+ LogFunc(("u32ClientID=%d\n", pClient->idClient));
+ if (pClient->handle == NULL)
+ return; /* not connected */
+
+ rc = VbglR0HGCMDisconnect(pClient->handle, pClient->idClient);
+ NOREF(rc);
+/* Log(("VBOXSF: VbglR0SfDisconnect: VbglR0HGCMDisconnect -> %#x\n", rc)); */
+ pClient->idClient = 0;
+ pClient->handle = NULL;
+ return;
+}
+
+#if !defined(RT_OS_LINUX)
+
+# ifndef RT_OS_WINDOWS
+
+DECLVBGL(int) VbglR0SfSetUtf8(PVBGLSFCLIENT pClient)
+{
+ int rc;
+ VBGLIOCHGCMCALL callInfo;
+
+ VBOX_INIT_CALL(&callInfo, SET_UTF8, pClient);
+ rc = VbglR0HGCMCall(pClient->handle, &callInfo, sizeof(callInfo));
+/* Log(("VBOXSF: VbglR0SfSetUtf8: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+# endif /* !RT_OS_WINDOWS */
+
+/** @name Deprecated VBGL shared folder helpers.
+ *
+ * @deprecated These are all use the slow VbglR0HGCMCall interface, that
+ * basically treat ring-0 and user land callers much the same.
+ * Since 6.0 there is VbglR0HGCMFastCall() that does not bother with
+ * repacking the request and locking/duplicating parameter buffers,
+ * but just passes it along to the host and handles the waiting.
+ * Also new in 6.0 is embedded buffers which saves a bit time on
+ * guest and host by embedding parameter buffers into the request.
+ *
+ * @{
+ */
+
+DECLVBGL(int) VbglR0SfQueryMappings(PVBGLSFCLIENT pClient, SHFLMAPPING paMappings[], uint32_t *pcMappings)
+{
+ int rc;
+ VBoxSFQueryMappings data;
+
+ VBOX_INIT_CALL(&data.callInfo, QUERY_MAPPINGS, pClient);
+
+ data.flags.type = VMMDevHGCMParmType_32bit;
+ data.flags.u.value32 = SHFL_MF_UCS2;
+
+ data.numberOfMappings.type = VMMDevHGCMParmType_32bit;
+ data.numberOfMappings.u.value32 = *pcMappings;
+
+ data.mappings.type = VMMDevHGCMParmType_LinAddr;
+ data.mappings.u.Pointer.size = sizeof(SHFLMAPPING) * *pcMappings;
+ data.mappings.u.Pointer.u.linearAddr = (uintptr_t)&paMappings[0];
+
+/* Log(("VBOXSF: in ifs difference %d\n", (char *)&data.flags.type - (char *)&data.callInfo.cParms)); */
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfQueryMappings: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.result)); */
+ if (RT_SUCCESS(rc))
+ *pcMappings = data.numberOfMappings.u.value32;
+
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfQueryMapName(PVBGLSFCLIENT pClient, SHFLROOT root, SHFLSTRING *pString, uint32_t size)
+{
+ int rc;
+ VBoxSFQueryMapName data;
+
+ VBOX_INIT_CALL(&data.callInfo, QUERY_MAP_NAME, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = root;
+
+ data.name.type = VMMDevHGCMParmType_LinAddr;
+ data.name.u.Pointer.size = size;
+ data.name.u.Pointer.u.linearAddr = (uintptr_t)pString;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfQueryMapName: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfMapFolder(PVBGLSFCLIENT pClient, PSHFLSTRING szFolderName, PVBGLSFMAP pMap)
+{
+ int rc;
+ VBoxSFMapFolder data;
+
+ VBOX_INIT_CALL(&data.callInfo, MAP_FOLDER, pClient);
+
+ data.path.type = VMMDevHGCMParmType_LinAddr;
+ data.path.u.Pointer.size = ShflStringSizeOfBuffer(szFolderName);
+ data.path.u.Pointer.u.linearAddr = (uintptr_t)szFolderName;
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = 0;
+
+ data.delimiter.type = VMMDevHGCMParmType_32bit;
+ data.delimiter.u.value32 = RTPATH_DELIMITER;
+
+ data.fCaseSensitive.type = VMMDevHGCMParmType_32bit;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ data.fCaseSensitive.u.value32 = 0;
+#else
+ data.fCaseSensitive.u.value32 = 1;
+#endif
+
+ rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfMapFolder: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ if (RT_SUCCESS(rc))
+ {
+ pMap->root = data.root.u.value32;
+ rc = data.callInfo.Hdr.rc;
+ }
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfUnmapFolder(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap)
+{
+ int rc;
+ VBoxSFUnmapFolder data;
+
+ VBOX_INIT_CALL(&data.callInfo, UNMAP_FOLDER, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfUnmapFolder: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+# if !defined(RT_OS_WINDOWS)
+
+DECLVBGL(int) VbglR0SfCreate(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pParsedPath, PSHFLCREATEPARMS pCreateParms)
+{
+ /** @todo copy buffers to physical or mapped memory. */
+ int rc;
+ VBoxSFCreate data;
+
+ VBOX_INIT_CALL(&data.callInfo, CREATE, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.path.type = VMMDevHGCMParmType_LinAddr;
+ data.path.u.Pointer.size = ShflStringSizeOfBuffer (pParsedPath);
+ data.path.u.Pointer.u.linearAddr = (uintptr_t)pParsedPath;
+
+ data.parms.type = VMMDevHGCMParmType_LinAddr;
+ data.parms.u.Pointer.size = sizeof(SHFLCREATEPARMS);
+ data.parms.u.Pointer.u.linearAddr = (uintptr_t)pCreateParms;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfCreate: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfClose(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE Handle)
+{
+ int rc;
+ VBoxSFClose data;
+
+ VBOX_INIT_CALL(&data.callInfo, CLOSE, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.handle.type = VMMDevHGCMParmType_64bit;
+ data.handle.u.value64 = Handle;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfClose: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+
+DECLVBGL(int) VbglR0SfRemove(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pParsedPath, uint32_t flags)
+{
+ int rc = VINF_SUCCESS;
+
+ VBoxSFRemove data;
+
+ VBOX_INIT_CALL(&data.callInfo, REMOVE, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.path.type = VMMDevHGCMParmType_LinAddr_In;
+ data.path.u.Pointer.size = ShflStringSizeOfBuffer(pParsedPath);
+ data.path.u.Pointer.u.linearAddr = (uintptr_t)pParsedPath;
+
+ data.flags.type = VMMDevHGCMParmType_32bit;
+ data.flags.u.value32 = flags;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfRemove: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfRename(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pSrcPath, PSHFLSTRING pDestPath, uint32_t flags)
+{
+ int rc;
+ VBoxSFRename data;
+
+ VBOX_INIT_CALL(&data.callInfo, RENAME, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.src.type = VMMDevHGCMParmType_LinAddr_In;
+ data.src.u.Pointer.size = ShflStringSizeOfBuffer(pSrcPath);
+ data.src.u.Pointer.u.linearAddr = (uintptr_t)pSrcPath;
+
+ data.dest.type = VMMDevHGCMParmType_LinAddr_In;
+ data.dest.u.Pointer.size = ShflStringSizeOfBuffer(pDestPath);
+ data.dest.u.Pointer.u.linearAddr = (uintptr_t)pDestPath;
+
+ data.flags.type = VMMDevHGCMParmType_32bit;
+ data.flags.u.value32 = flags;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfRename: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfRead(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile,
+ uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer, bool fLocked)
+{
+ int rc;
+ VBoxSFRead data;
+
+ VBOX_INIT_CALL(&data.callInfo, READ, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.handle.type = VMMDevHGCMParmType_64bit;
+ data.handle.u.value64 = hFile;
+ data.offset.type = VMMDevHGCMParmType_64bit;
+ data.offset.u.value64 = offset;
+ data.cb.type = VMMDevHGCMParmType_32bit;
+ data.cb.u.value32 = *pcbBuffer;
+ data.buffer.type = (fLocked) ? VMMDevHGCMParmType_LinAddr_Locked_Out : VMMDevHGCMParmType_LinAddr_Out;
+ data.buffer.u.Pointer.size = *pcbBuffer;
+ data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer;
+
+ rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfRead: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ if (RT_SUCCESS(rc))
+ {
+ rc = data.callInfo.Hdr.rc;
+ *pcbBuffer = data.cb.u.value32;
+ }
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfReadPageList(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, uint64_t offset, uint32_t *pcbBuffer,
+ uint16_t offFirstPage, uint16_t cPages, RTGCPHYS64 *paPages)
+{
+ uint32_t cbToRead = *pcbBuffer;
+ uint32_t cbData = (uint32_t)(sizeof(VBoxSFRead) + RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]));
+ VBoxSFRead *pData = (VBoxSFRead *)RTMemTmpAlloc(cbData);
+ HGCMPageListInfo *pPgLst = (HGCMPageListInfo *)(pData + 1);
+ uint16_t iPage;
+ int rc;
+
+ if (RT_UNLIKELY(!pData))
+ return VERR_NO_TMP_MEMORY;
+
+ VBOX_INIT_CALL_EX(&pData->callInfo, READ, pClient, cbData);
+
+ pData->root.type = VMMDevHGCMParmType_32bit;
+ pData->root.u.value32 = pMap->root;
+
+ pData->handle.type = VMMDevHGCMParmType_64bit;
+ pData->handle.u.value64 = hFile;
+ pData->offset.type = VMMDevHGCMParmType_64bit;
+ pData->offset.u.value64 = offset;
+ pData->cb.type = VMMDevHGCMParmType_32bit;
+ pData->cb.u.value32 = cbToRead;
+ pData->buffer.type = VMMDevHGCMParmType_PageList;
+ pData->buffer.u.PageList.size = cbToRead;
+ pData->buffer.u.PageList.offset = sizeof(VBoxSFRead);
+
+ pPgLst->flags = VBOX_HGCM_F_PARM_DIRECTION_FROM_HOST;
+ pPgLst->offFirstPage = offFirstPage;
+ pPgLst->cPages = cPages;
+ for (iPage = 0; iPage < cPages; iPage++)
+ pPgLst->aPages[iPage] = paPages[iPage];
+
+ rc = VbglR0HGCMCallRaw(pClient->handle, &pData->callInfo, cbData);
+/* Log(("VBOXSF: VbglR0SfReadPageList: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ if (RT_SUCCESS(rc))
+ {
+ rc = pData->callInfo.Hdr.rc;
+ *pcbBuffer = pData->cb.u.value32;
+ }
+
+ RTMemTmpFree(pData);
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfWrite(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile,
+ uint64_t offset, uint32_t *pcbBuffer, uint8_t *pBuffer, bool fLocked)
+{
+ int rc;
+ VBoxSFWrite data;
+
+ VBOX_INIT_CALL(&data.callInfo, WRITE, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.handle.type = VMMDevHGCMParmType_64bit;
+ data.handle.u.value64 = hFile;
+ data.offset.type = VMMDevHGCMParmType_64bit;
+ data.offset.u.value64 = offset;
+ data.cb.type = VMMDevHGCMParmType_32bit;
+ data.cb.u.value32 = *pcbBuffer;
+ data.buffer.type = fLocked ? VMMDevHGCMParmType_LinAddr_Locked_In : VMMDevHGCMParmType_LinAddr_In;
+ data.buffer.u.Pointer.size = *pcbBuffer;
+ data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer;
+
+ rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfWrite: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ if (RT_SUCCESS(rc))
+ {
+ rc = data.callInfo.Hdr.rc;
+ *pcbBuffer = data.cb.u.value32;
+ }
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfWritePhysCont(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, uint64_t offset,
+ uint32_t *pcbBuffer, RTCCPHYS PhysBuffer)
+{
+ uint32_t cbToWrite = *pcbBuffer;
+ uint32_t cPages = RT_ALIGN_32((PhysBuffer & PAGE_OFFSET_MASK) + cbToWrite, PAGE_SIZE) >> PAGE_SHIFT;
+ uint32_t cbData = (uint32_t)(sizeof(VBoxSFWrite) + RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]));
+ VBoxSFWrite *pData = (VBoxSFWrite *)RTMemTmpAlloc(cbData);
+ HGCMPageListInfo *pPgLst = (HGCMPageListInfo *)(pData + 1);
+ uint32_t iPage;
+ int rc;
+
+ if (RT_UNLIKELY(!pData))
+ return VERR_NO_TMP_MEMORY;
+
+ VBOX_INIT_CALL_EX(&pData->callInfo, WRITE, pClient, cbData);
+
+ pData->root.type = VMMDevHGCMParmType_32bit;
+ pData->root.u.value32 = pMap->root;
+
+ pData->handle.type = VMMDevHGCMParmType_64bit;
+ pData->handle.u.value64 = hFile;
+ pData->offset.type = VMMDevHGCMParmType_64bit;
+ pData->offset.u.value64 = offset;
+ pData->cb.type = VMMDevHGCMParmType_32bit;
+ pData->cb.u.value32 = cbToWrite;
+ pData->buffer.type = VMMDevHGCMParmType_PageList;
+ pData->buffer.u.PageList.size = cbToWrite;
+ pData->buffer.u.PageList.offset = sizeof(VBoxSFWrite);
+
+ pPgLst->flags = VBOX_HGCM_F_PARM_DIRECTION_TO_HOST;
+ pPgLst->offFirstPage = (uint16_t)(PhysBuffer & PAGE_OFFSET_MASK);
+ pPgLst->cPages = cPages;
+ PhysBuffer &= ~(RTCCPHYS)PAGE_OFFSET_MASK;
+ for (iPage = 0; iPage < cPages; iPage++, PhysBuffer += PAGE_SIZE)
+ pPgLst->aPages[iPage] = PhysBuffer;
+
+ rc = VbglR0HGCMCallRaw(pClient->handle, &pData->callInfo, cbData);
+/* Log(("VBOXSF: VbglR0SfWritePhysCont: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ if (RT_SUCCESS(rc))
+ {
+ rc = pData->callInfo.Hdr.rc;
+ *pcbBuffer = pData->cb.u.value32;
+ }
+
+ RTMemTmpFree(pData);
+ return rc;
+
+}
+
+DECLVBGL(int) VbglR0SfWritePageList(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile, uint64_t offset, uint32_t *pcbBuffer,
+ uint16_t offFirstPage, uint16_t cPages, RTGCPHYS64 *paPages)
+{
+ uint32_t cbToWrite = *pcbBuffer;
+ uint32_t cbData = (uint32_t)(sizeof(VBoxSFWrite) + RT_UOFFSETOF_DYN(HGCMPageListInfo, aPages[cPages]));
+ VBoxSFWrite *pData = (VBoxSFWrite *)RTMemTmpAlloc(cbData);
+ HGCMPageListInfo *pPgLst = (HGCMPageListInfo *)(pData + 1);
+ uint16_t iPage;
+ int rc;
+
+ if (RT_UNLIKELY(!pData))
+ return VERR_NO_TMP_MEMORY;
+
+ VBOX_INIT_CALL_EX(&pData->callInfo, WRITE, pClient, cbData);
+
+ pData->root.type = VMMDevHGCMParmType_32bit;
+ pData->root.u.value32 = pMap->root;
+
+ pData->handle.type = VMMDevHGCMParmType_64bit;
+ pData->handle.u.value64 = hFile;
+ pData->offset.type = VMMDevHGCMParmType_64bit;
+ pData->offset.u.value64 = offset;
+ pData->cb.type = VMMDevHGCMParmType_32bit;
+ pData->cb.u.value32 = cbToWrite;
+ pData->buffer.type = VMMDevHGCMParmType_PageList;
+ pData->buffer.u.PageList.size = cbToWrite;
+ pData->buffer.u.PageList.offset = sizeof(VBoxSFWrite);
+
+ pPgLst->flags = VBOX_HGCM_F_PARM_DIRECTION_TO_HOST;
+ pPgLst->offFirstPage = offFirstPage;
+ pPgLst->cPages = cPages;
+ for (iPage = 0; iPage < cPages; iPage++)
+ pPgLst->aPages[iPage] = paPages[iPage];
+
+ rc = VbglR0HGCMCallRaw(pClient->handle, &pData->callInfo, cbData);
+/* Log(("VBOXSF: VbglR0SfWritePageList: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ if (RT_SUCCESS(rc))
+ {
+ rc = pData->callInfo.Hdr.rc;
+ *pcbBuffer = pData->cb.u.value32;
+ }
+
+ RTMemTmpFree(pData);
+ return rc;
+}
+
+# endif /* !RT_OS_WINDOWS */
+
+DECLVBGL(int) VbglR0SfFlush(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile)
+{
+ int rc;
+ VBoxSFFlush data;
+
+ VBOX_INIT_CALL(&data.callInfo, FLUSH, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.handle.type = VMMDevHGCMParmType_64bit;
+ data.handle.u.value64 = hFile;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfFlush: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfDirInfo(
+ PVBGLSFCLIENT pClient,
+ PVBGLSFMAP pMap,
+ SHFLHANDLE hFile,
+ PSHFLSTRING ParsedPath,
+ uint32_t flags,
+ uint32_t index,
+ uint32_t *pcbBuffer,
+ PSHFLDIRINFO pBuffer,
+ uint32_t *pcFiles)
+{
+ int rc;
+ VBoxSFList data;
+
+ VBOX_INIT_CALL(&data.callInfo, LIST, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.handle.type = VMMDevHGCMParmType_64bit;
+ data.handle.u.value64 = hFile;
+ data.flags.type = VMMDevHGCMParmType_32bit;
+ data.flags.u.value32 = flags;
+ data.cb.type = VMMDevHGCMParmType_32bit;
+ data.cb.u.value32 = *pcbBuffer;
+ data.path.type = VMMDevHGCMParmType_LinAddr_In;
+ data.path.u.Pointer.size = ParsedPath ? ShflStringSizeOfBuffer(ParsedPath) : 0;
+ data.path.u.Pointer.u.linearAddr = (uintptr_t) ParsedPath;
+
+ data.buffer.type = VMMDevHGCMParmType_LinAddr_Out;
+ data.buffer.u.Pointer.size = *pcbBuffer;
+ data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer;
+
+ data.resumePoint.type = VMMDevHGCMParmType_32bit;
+ data.resumePoint.u.value32 = index;
+ data.cFiles.type = VMMDevHGCMParmType_32bit;
+ data.cFiles.u.value32 = 0; /* out parameters only */
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfDirInfo: rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ *pcbBuffer = data.cb.u.value32;
+ *pcFiles = data.cFiles.u.value32;
+ return rc;
+}
+
+# ifndef RT_OS_WINDOWS
+
+DECLVBGL(int) VbglR0SfFsInfo(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile,
+ uint32_t flags, uint32_t *pcbBuffer, PSHFLDIRINFO pBuffer)
+{
+ int rc;
+ VBoxSFInformation data;
+
+ VBOX_INIT_CALL(&data.callInfo, INFORMATION, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.handle.type = VMMDevHGCMParmType_64bit;
+ data.handle.u.value64 = hFile;
+ data.flags.type = VMMDevHGCMParmType_32bit;
+ data.flags.u.value32 = flags;
+ data.cb.type = VMMDevHGCMParmType_32bit;
+ data.cb.u.value32 = *pcbBuffer;
+ data.info.type = VMMDevHGCMParmType_LinAddr;
+ data.info.u.Pointer.size = *pcbBuffer;
+ data.info.u.Pointer.u.linearAddr = (uintptr_t)pBuffer;
+
+ rc = VbglR0HGCMCallRaw(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfFsInfo: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ if (RT_SUCCESS(rc))
+ {
+ rc = data.callInfo.Hdr.rc;
+ *pcbBuffer = data.cb.u.value32;
+ }
+ return rc;
+}
+
+# endif /* !RT_OS_WINDOWS */
+
+DECLVBGL(int) VbglR0SfLock(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, SHFLHANDLE hFile,
+ uint64_t offset, uint64_t cbSize, uint32_t fLock)
+{
+ int rc;
+ VBoxSFLock data;
+
+ VBOX_INIT_CALL(&data.callInfo, LOCK, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.handle.type = VMMDevHGCMParmType_64bit;
+ data.handle.u.value64 = hFile;
+ data.offset.type = VMMDevHGCMParmType_64bit;
+ data.offset.u.value64 = offset;
+ data.length.type = VMMDevHGCMParmType_64bit;
+ data.length.u.value64 = cbSize;
+
+ data.flags.type = VMMDevHGCMParmType_32bit;
+ data.flags.u.value32 = fLock;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfLock: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+# ifndef RT_OS_WINDOWS
+
+DECLVBGL(int) VbglR0SfReadLink(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pParsedPath, uint32_t cbBuffer, uint8_t *pBuffer)
+{
+ int rc;
+ VBoxSFReadLink data;
+
+ VBOX_INIT_CALL(&data.callInfo, READLINK, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.path.type = VMMDevHGCMParmType_LinAddr_In;
+ data.path.u.Pointer.size = ShflStringSizeOfBuffer (pParsedPath);
+ data.path.u.Pointer.u.linearAddr = (uintptr_t)pParsedPath;
+
+ data.buffer.type = VMMDevHGCMParmType_LinAddr_Out;
+ data.buffer.u.Pointer.size = cbBuffer;
+ data.buffer.u.Pointer.u.linearAddr = (uintptr_t)pBuffer;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfReadLink: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfSymlink(PVBGLSFCLIENT pClient, PVBGLSFMAP pMap, PSHFLSTRING pNewPath, PSHFLSTRING pOldPath,
+ PSHFLFSOBJINFO pBuffer)
+{
+ int rc;
+ VBoxSFSymlink data;
+
+ VBOX_INIT_CALL(&data.callInfo, SYMLINK, pClient);
+
+ data.root.type = VMMDevHGCMParmType_32bit;
+ data.root.u.value32 = pMap->root;
+
+ data.newPath.type = VMMDevHGCMParmType_LinAddr_In;
+ data.newPath.u.Pointer.size = ShflStringSizeOfBuffer (pNewPath);
+ data.newPath.u.Pointer.u.linearAddr = (uintptr_t)pNewPath;
+
+ data.oldPath.type = VMMDevHGCMParmType_LinAddr_In;
+ data.oldPath.u.Pointer.size = ShflStringSizeOfBuffer (pOldPath);
+ data.oldPath.u.Pointer.u.linearAddr = (uintptr_t)pOldPath;
+
+ data.info.type = VMMDevHGCMParmType_LinAddr_Out;
+ data.info.u.Pointer.size = sizeof(SHFLFSOBJINFO);
+ data.info.u.Pointer.u.linearAddr = (uintptr_t)pBuffer;
+
+ rc = VbglR0HGCMCall(pClient->handle, &data.callInfo, sizeof(data));
+/* Log(("VBOXSF: VbglR0SfSymlink: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+DECLVBGL(int) VbglR0SfSetSymlinks(PVBGLSFCLIENT pClient)
+{
+ int rc;
+ VBGLIOCHGCMCALL callInfo;
+
+ VBOX_INIT_CALL(&callInfo, SET_SYMLINKS, pClient);
+ rc = VbglR0HGCMCall(pClient->handle, &callInfo, sizeof(callInfo));
+/* Log(("VBOXSF: VbglR0SfSetSymlinks: VbglR0HGCMCall rc = %#x, result = %#x\n", rc, data.callInfo.Hdr.rc)); */
+ return rc;
+}
+
+# endif /* !RT_OS_WINDOWS */
+
+#endif /* !RT_OS_LINUX */
+
+/** @} */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp
new file mode 100644
index 00000000..5909fd2c
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp
@@ -0,0 +1,51 @@
+/* $Id: VBoxGuestR0LibVMMDev.cpp $ */
+/** @file
+ * VBoxGuestLibR0 - VMMDev device related functions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+
+
+DECLVBGL(int) VbglR0QueryVMMDevMemory(VMMDevMemory **ppVMMDevMemory)
+{
+ int rc = vbglR0Enter();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* If the memory was not found, return an error. */
+ if (!g_vbgldata.pVMMDevMemory)
+ return VERR_NOT_SUPPORTED;
+
+ *ppVMMDevMemory = g_vbgldata.pVMMDevMemory;
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp
new file mode 100644
index 00000000..4298b742
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3Lib.cpp
@@ -0,0 +1,485 @@
+/* $Id: VBoxGuestR3Lib.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Core.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#if defined(RT_OS_WINDOWS)
+# include <iprt/nt/nt-and-windows.h>
+
+#elif defined(RT_OS_OS2)
+# define INCL_BASE
+# define INCL_ERRORS
+# include <os2.h>
+
+#elif defined(RT_OS_DARWIN) \
+ || defined(RT_OS_FREEBSD) \
+ || defined(RT_OS_HAIKU) \
+ || defined(RT_OS_LINUX) \
+ || defined(RT_OS_NETBSD) \
+ || defined(RT_OS_SOLARIS)
+# include <sys/types.h>
+# include <sys/stat.h>
+# if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) || defined(RT_OS_NETBSD)
+ /** @todo check this on solaris+freebsd as well. */
+# include <sys/ioctl.h>
+# endif
+# if defined(RT_OS_DARWIN)
+# include <mach/mach_port.h>
+# include <IOKit/IOKitLib.h>
+# endif
+# include <errno.h>
+# include <unistd.h>
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/time.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <VBox/log.h>
+#include "VBoxGuestR3LibInternal.h"
+
+#ifdef VBOX_VBGLR3_XFREE86
+/* Rather than try to resolve all the header file conflicts, I will just
+ prototype what we need here. */
+# define XF86_O_RDWR 0x0002
+typedef void *pointer;
+extern "C" int xf86open(const char *, int, ...);
+extern "C" int xf86close(int);
+extern "C" int xf86ioctl(int, unsigned long, pointer);
+# define VBOX_VBGLR3_XSERVER
+#elif defined(VBOX_VBGLR3_XORG)
+# include <sys/stat.h>
+# include <fcntl.h>
+# include <unistd.h>
+# include <sys/ioctl.h>
+# define xf86open open
+# define xf86close close
+# define xf86ioctl ioctl
+# define XF86_O_RDWR O_RDWR
+# define VBOX_VBGLR3_XSERVER
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The VBoxGuest device handle. */
+#ifdef VBOX_VBGLR3_XSERVER
+static int g_File = -1;
+#elif defined(RT_OS_WINDOWS)
+static HANDLE g_hFile = INVALID_HANDLE_VALUE;
+#else
+static RTFILE g_File = NIL_RTFILE;
+#endif
+/** User counter.
+ * A counter of the number of times the library has been initialised, for use with
+ * X.org drivers, where the library may be shared by multiple independent modules
+ * inside a single process space.
+ */
+static uint32_t volatile g_cInits = 0;
+#ifdef RT_OS_DARWIN
+/** I/O Kit connection handle. */
+static io_connect_t g_uConnection = 0;
+#endif
+
+
+
+/**
+ * Implementation of VbglR3Init and VbglR3InitUser
+ */
+static int vbglR3Init(const char *pszDeviceName)
+{
+ int rc2;
+ uint32_t cInits = ASMAtomicIncU32(&g_cInits);
+ Assert(cInits > 0);
+ if (cInits > 1)
+ {
+ /*
+ * This will fail if two (or more) threads race each other calling VbglR3Init.
+ * However it will work fine for single threaded or otherwise serialized
+ * processed calling us more than once.
+ */
+#ifdef RT_OS_WINDOWS
+ if (g_hFile == INVALID_HANDLE_VALUE)
+#elif !defined (VBOX_VBGLR3_XSERVER)
+ if (g_File == NIL_RTFILE)
+#else
+ if (g_File == -1)
+#endif
+ return VERR_INTERNAL_ERROR;
+ return VINF_SUCCESS;
+ }
+#if defined(RT_OS_WINDOWS)
+ if (g_hFile != INVALID_HANDLE_VALUE)
+#elif !defined(VBOX_VBGLR3_XSERVER)
+ if (g_File != NIL_RTFILE)
+#else
+ if (g_File != -1)
+#endif
+ return VERR_INTERNAL_ERROR;
+
+#if defined(RT_OS_WINDOWS)
+ /*
+ * Have to use CreateFile here as we want to specify FILE_FLAG_OVERLAPPED
+ * and possible some other bits not available thru iprt/file.h.
+ */
+ HANDLE hFile = CreateFile(pszDeviceName,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
+ NULL);
+
+ if (hFile == INVALID_HANDLE_VALUE)
+ return VERR_OPEN_FAILED;
+ g_hFile = hFile;
+
+#elif defined(RT_OS_OS2)
+ /*
+ * We might wish to compile this with Watcom, so stick to
+ * the OS/2 APIs all the way. And in any case we have to use
+ * DosDevIOCtl for the requests, why not use Dos* for everything.
+ */
+ HFILE hf = NULLHANDLE;
+ ULONG ulAction = 0;
+ APIRET rc = DosOpen((PCSZ)pszDeviceName, &hf, &ulAction, 0, FILE_NORMAL,
+ OPEN_ACTION_OPEN_IF_EXISTS,
+ OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE,
+ NULL);
+ if (rc)
+ return RTErrConvertFromOS2(rc);
+
+ if (hf < 16)
+ {
+ HFILE ahfs[16];
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(ahfs); i++)
+ {
+ ahfs[i] = 0xffffffff;
+ rc = DosDupHandle(hf, &ahfs[i]);
+ if (rc)
+ break;
+ }
+
+ if (i-- > 1)
+ {
+ ULONG fulState = 0;
+ rc = DosQueryFHState(ahfs[i], &fulState);
+ if (!rc)
+ {
+ fulState |= OPEN_FLAGS_NOINHERIT;
+ fulState &= OPEN_FLAGS_WRITE_THROUGH | OPEN_FLAGS_FAIL_ON_ERROR | OPEN_FLAGS_NO_CACHE | OPEN_FLAGS_NOINHERIT; /* Turn off non-participating bits. */
+ rc = DosSetFHState(ahfs[i], fulState);
+ }
+ if (!rc)
+ {
+ rc = DosClose(hf);
+ AssertMsg(!rc, ("%ld\n", rc));
+ hf = ahfs[i];
+ }
+ else
+ i++;
+ while (i-- > 0)
+ DosClose(ahfs[i]);
+ }
+ }
+ g_File = (RTFILE)hf;
+
+#elif defined(RT_OS_DARWIN)
+ /*
+ * Darwin is kind of special we need to engage the device via I/O first
+ * before we open it via the BSD device node.
+ */
+ /* IOKit */
+ mach_port_t MasterPort;
+ kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &MasterPort);
+ if (kr != kIOReturnSuccess)
+ {
+ LogRel(("IOMasterPort -> %d\n", kr));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ CFDictionaryRef ClassToMatch = IOServiceMatching("org_virtualbox_VBoxGuest");
+ if (!ClassToMatch)
+ {
+ LogRel(("IOServiceMatching(\"org_virtualbox_VBoxGuest\") failed.\n"));
+ return VERR_GENERAL_FAILURE;
+ }
+
+ io_service_t ServiceObject = IOServiceGetMatchingService(kIOMasterPortDefault, ClassToMatch);
+ if (!ServiceObject)
+ {
+ LogRel(("IOServiceGetMatchingService returned NULL\n"));
+ return VERR_NOT_FOUND;
+ }
+
+ io_connect_t uConnection;
+ kr = IOServiceOpen(ServiceObject, mach_task_self(), VBOXGUEST_DARWIN_IOSERVICE_COOKIE, &uConnection);
+ IOObjectRelease(ServiceObject);
+ if (kr != kIOReturnSuccess)
+ {
+ LogRel(("IOServiceOpen returned %d. Driver open failed.\n", kr));
+ return VERR_OPEN_FAILED;
+ }
+
+ /* Regular unix FD. */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszDeviceName, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("RTFileOpen(%s) returned %Rrc. Driver open failed.\n", pszDeviceName, rc));
+ IOServiceClose(uConnection);
+ return rc;
+ }
+ g_File = hFile;
+ g_uConnection = uConnection;
+
+#elif defined(VBOX_VBGLR3_XSERVER)
+ int File = xf86open(pszDeviceName, XF86_O_RDWR);
+ if (File == -1)
+ return VERR_OPEN_FAILED;
+ g_File = File;
+
+#else
+
+ /* The default implementation. (linux, solaris, freebsd, netbsd, haiku) */
+ RTFILE File;
+ int rc = RTFileOpen(&File, pszDeviceName, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ return rc;
+ g_File = File;
+
+#endif
+
+ /*
+ * Adjust the I/O control interface version.
+ */
+ {
+ VBGLIOCDRIVERVERSIONINFO VerInfo;
+ VBGLREQHDR_INIT(&VerInfo.Hdr, DRIVER_VERSION_INFO);
+ VerInfo.u.In.uMinVersion = VBGL_IOC_VERSION & UINT32_C(0xffff0000);
+ VerInfo.u.In.uReqVersion = VBGL_IOC_VERSION;
+ VerInfo.u.In.uReserved1 = 0;
+ VerInfo.u.In.uReserved2 = 0;
+ rc2 = vbglR3DoIOCtl(VBGL_IOCTL_DRIVER_VERSION_INFO, &VerInfo.Hdr, sizeof(VerInfo));
+#ifndef VBOX_VBGLR3_XSERVER
+ AssertRC(rc2); /* otherwise ignored for now*/
+#endif
+ }
+
+
+#ifndef VBOX_VBGLR3_XSERVER
+ /*
+ * Create release logger
+ */
+ PRTLOGGER pReleaseLogger;
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ rc2 = RTLogCreate(&pReleaseLogger, 0, "all", "VBOX_RELEASE_LOG",
+ RT_ELEMENTS(s_apszGroups), &s_apszGroups[0], RTLOGDEST_USER, NULL);
+ /* This may legitimately fail if we are using the mini-runtime. */
+ if (RT_SUCCESS(rc2))
+ RTLogRelSetDefaultInstance(pReleaseLogger);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Open the VBox R3 Guest Library. This should be called by system daemons
+ * and processes.
+ */
+VBGLR3DECL(int) VbglR3Init(void)
+{
+ return vbglR3Init(VBOXGUEST_DEVICE_NAME);
+}
+
+
+/**
+ * Open the VBox R3 Guest Library. Equivalent to VbglR3Init, but for user
+ * session processes.
+ */
+VBGLR3DECL(int) VbglR3InitUser(void)
+{
+ return vbglR3Init(VBOXGUEST_USER_DEVICE_NAME);
+}
+
+
+VBGLR3DECL(void) VbglR3Term(void)
+{
+ /*
+ * Decrement the reference count and see if we're the last one out.
+ */
+ uint32_t cInits = ASMAtomicDecU32(&g_cInits);
+ if (cInits > 0)
+ return;
+#if !defined(VBOX_VBGLR3_XSERVER)
+ AssertReturnVoid(!cInits);
+
+# if defined(RT_OS_WINDOWS)
+ HANDLE hFile = g_hFile;
+ g_hFile = INVALID_HANDLE_VALUE;
+ AssertReturnVoid(hFile != INVALID_HANDLE_VALUE);
+ BOOL fRc = CloseHandle(hFile);
+ Assert(fRc); NOREF(fRc);
+
+# elif defined(RT_OS_OS2)
+ RTFILE File = g_File;
+ g_File = NIL_RTFILE;
+ AssertReturnVoid(File != NIL_RTFILE);
+ APIRET rc = DosClose((uintptr_t)File);
+ AssertMsg(!rc, ("%ld\n", rc));
+
+#elif defined(RT_OS_DARWIN)
+ io_connect_t uConnection = g_uConnection;
+ RTFILE hFile = g_File;
+ g_uConnection = 0;
+ g_File = NIL_RTFILE;
+ kern_return_t kr = IOServiceClose(uConnection);
+ AssertMsg(kr == kIOReturnSuccess, ("%#x (%d)\n", kr, kr)); NOREF(kr);
+ int rc = RTFileClose(hFile);
+ AssertRC(rc);
+
+# else /* The IPRT case. */
+ RTFILE File = g_File;
+ g_File = NIL_RTFILE;
+ AssertReturnVoid(File != NIL_RTFILE);
+ int rc = RTFileClose(File);
+ AssertRC(rc);
+# endif
+
+#else /* VBOX_VBGLR3_XSERVER */
+ int File = g_File;
+ g_File = -1;
+ if (File == -1)
+ return;
+ xf86close(File);
+#endif /* VBOX_VBGLR3_XSERVER */
+}
+
+
+/**
+ * Internal wrapper around various OS specific ioctl implementations.
+ *
+ * @returns VBox status code as returned by VBoxGuestCommonIOCtl, or
+ * an failure returned by the OS specific ioctl APIs.
+ *
+ * @param uFunction The requested function.
+ * @param pHdr The input and output request buffer.
+ * @param cbReq The size of the request buffer.
+ */
+int vbglR3DoIOCtlRaw(uintptr_t uFunction, PVBGLREQHDR pHdr, size_t cbReq)
+{
+ Assert(cbReq == RT_MAX(pHdr->cbIn, pHdr->cbOut)); RT_NOREF1(cbReq);
+ Assert(pHdr->cbOut != 0);
+
+#if defined(RT_OS_WINDOWS)
+# if 0 /*def USE_NT_DEVICE_IO_CONTROL_FILE*/
+ IO_STATUS_BLOCK Ios;
+ Ios.Status = -1;
+ Ios.Information = 0;
+ NTSTATUS rcNt = NtDeviceIoControlFile(g_hFile, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, &Ios,
+ (ULONG)uFunction,
+ pHdr /*pvInput */, pHdr->cbIn /* cbInput */,
+ pHdr /*pvOutput*/, pHdr->cbOut /* cbOutput */);
+ if (NT_SUCCESS(rcNt))
+ {
+ if (NT_SUCCESS(Ios.Status))
+ return VINF_SUCCESS;
+ rcNt = Ios.Status;
+ }
+ return RTErrConvertFromNtStatus(rcNt);
+
+# else
+ DWORD cbReturned = (ULONG)pHdr->cbOut;
+ if (DeviceIoControl(g_hFile, uFunction, pHdr, pHdr->cbIn, pHdr, cbReturned, &cbReturned, NULL))
+ return 0;
+ return RTErrConvertFromWin32(GetLastError());
+# endif
+
+#elif defined(RT_OS_OS2)
+ ULONG cbOS2Parm = cbReq;
+ APIRET rc = DosDevIOCtl((uintptr_t)g_File, VBGL_IOCTL_CATEGORY, uFunction, pHdr, cbReq, &cbOS2Parm, NULL, 0, NULL);
+ if (RT_LIKELY(rc == NO_ERROR))
+ return VINF_SUCCESS;
+ return RTErrConvertFromOS2(rc);
+
+#elif defined(VBOX_VBGLR3_XSERVER)
+ if (g_File != -1)
+ {
+ if (RT_LIKELY(xf86ioctl((int)g_File, uFunction, pHdr) >= 0))
+ return VINF_SUCCESS;
+ return VERR_FILE_IO_ERROR;
+ }
+ return VERR_INVALID_HANDLE;
+
+#else
+ if (g_File != NIL_RTFILE)
+ {
+ if (RT_LIKELY(ioctl((int)(intptr_t)g_File, uFunction, pHdr) >= 0))
+ return VINF_SUCCESS;
+ return RTErrConvertFromErrno(errno);
+ }
+ return VERR_INVALID_HANDLE;
+#endif
+}
+
+
+/**
+ * Internal wrapper around various OS specific ioctl implementations, that
+ * returns the status from the header.
+ *
+ * @returns VBox status code as returned by VBoxGuestCommonIOCtl, or
+ * an failure returned by the OS specific ioctl APIs.
+ *
+ * @param uFunction The requested function.
+ * @param pHdr The input and output request buffer.
+ * @param cbReq The size of the request buffer.
+ */
+int vbglR3DoIOCtl(uintptr_t uFunction, PVBGLREQHDR pHdr, size_t cbReq)
+{
+ int rc = vbglR3DoIOCtlRaw(uFunction, pHdr, cbReq);
+ if (RT_SUCCESS(rc))
+ rc = pHdr->rc;
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp
new file mode 100644
index 00000000..2ee88509
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAdditions.cpp
@@ -0,0 +1,363 @@
+/* $Id: VBoxGuestR3LibAdditions.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Additions Info.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#ifdef RT_OS_WINDOWS
+# include <iprt/utf16.h>
+#endif
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include "VBoxGuestR3LibInternal.h"
+
+
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Opens the "VirtualBox Guest Additions" registry key.
+ *
+ * @returns IPRT status code
+ * @param phKey Receives key handle on success. The returned handle must
+ * be closed by calling vbglR3WinCloseRegKey.
+ */
+static int vbglR3WinOpenAdditionRegisterKey(PHKEY phKey)
+{
+ /*
+ * Current vendor first. We keep the older ones just for the case that
+ * the caller isn't actually installed yet (no real use case AFAIK).
+ */
+ static PCRTUTF16 s_apwszKeys[] =
+ {
+ L"SOFTWARE\\" RT_LSTR(VBOX_VENDOR_SHORT) L"\\VirtualBox Guest Additions",
+#ifdef RT_ARCH_AMD64
+ L"SOFTWARE\\Wow6432Node\\" RT_LSTR(VBOX_VENDOR_SHORT) L"\\VirtualBox Guest Additions",
+#endif
+ L"SOFTWARE\\Sun\\VirtualBox Guest Additions",
+#ifdef RT_ARCH_AMD64
+ L"SOFTWARE\\Wow6432Node\\Sun\\VirtualBox Guest Additions",
+#endif
+ L"SOFTWARE\\Sun\\xVM VirtualBox Guest Additions",
+#ifdef RT_ARCH_AMD64
+ L"SOFTWARE\\Wow6432Node\\Sun\\xVM VirtualBox Guest Additions",
+#endif
+ };
+ int rc = VERR_NOT_FOUND;
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apwszKeys); i++)
+ {
+ LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, s_apwszKeys[i], 0 /* ulOptions*/, KEY_READ, phKey);
+ if (lrc == ERROR_SUCCESS)
+ return VINF_SUCCESS;
+ if (i == 0)
+ rc = RTErrConvertFromWin32(lrc);
+ }
+ return rc;
+}
+
+
+/**
+ * Closes the registry handle returned by vbglR3WinOpenAdditionRegisterKey().
+ *
+ * @returns @a rc or IPRT failure status.
+ * @param hKey Handle to close.
+ * @param rc The current IPRT status of the operation. Error
+ * condition takes precedence over errors from this call.
+ */
+static int vbglR3WinCloseRegKey(HKEY hKey, int rc)
+{
+ LSTATUS lrc = RegCloseKey(hKey);
+ if ( lrc == ERROR_SUCCESS
+ || RT_FAILURE(rc))
+ return rc;
+ return RTErrConvertFromWin32(lrc);
+}
+
+
+/**
+ * Queries a string value from a specified registry key.
+ *
+ * @return IPRT status code.
+ * @param hKey Handle of registry key to use.
+ * @param pwszValueName The name of the value to query.
+ * @param cbHint Size hint.
+ * @param ppszValue Where to return value string on success. Free
+ * with RTStrFree.
+ */
+static int vbglR3QueryRegistryString(HKEY hKey, PCRTUTF16 pwszValueName, uint32_t cbHint, char **ppszValue)
+{
+ AssertPtr(pwszValueName);
+ AssertPtrReturn(ppszValue, VERR_INVALID_POINTER);
+
+ /*
+ * First try.
+ */
+ int rc;
+ DWORD dwType;
+ DWORD cbTmp = cbHint;
+ PRTUTF16 pwszTmp = (PRTUTF16)RTMemTmpAllocZ(cbTmp + sizeof(RTUTF16));
+ if (pwszTmp)
+ {
+ LSTATUS lrc = RegQueryValueExW(hKey, pwszValueName, NULL, &dwType, (BYTE *)pwszTmp, &cbTmp);
+ if (lrc == ERROR_MORE_DATA)
+ {
+ /*
+ * Allocate larger buffer and try again.
+ */
+ RTMemTmpFree(pwszTmp);
+ cbTmp += 16;
+ pwszTmp = (PRTUTF16)RTMemTmpAllocZ(cbTmp + sizeof(RTUTF16));
+ if (!pwszTmp)
+ {
+ *ppszValue = NULL;
+ return VERR_NO_TMP_MEMORY;
+ }
+ lrc = RegQueryValueExW(hKey, pwszValueName, NULL, &dwType, (BYTE *)pwszTmp, &cbTmp);
+ }
+ if (lrc == ERROR_SUCCESS)
+ {
+ /*
+ * Check the type and convert to UTF-8.
+ */
+ if (dwType == REG_SZ)
+ rc = RTUtf16ToUtf8(pwszTmp, ppszValue);
+ else
+ rc = VERR_WRONG_TYPE;
+ }
+ else
+ rc = RTErrConvertFromWin32(lrc);
+ RTMemTmpFree(pwszTmp);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ if (RT_SUCCESS(rc))
+ return rc;
+ *ppszValue = NULL;
+ return rc;
+}
+
+#endif /* RT_OS_WINDOWS */
+
+
+/**
+ * Fallback for VbglR3GetAdditionsVersion.
+ *
+ * @copydoc VbglR3GetAdditionsVersion
+ */
+static int vbglR3GetAdditionsCompileTimeVersion(char **ppszVer, char **ppszVerExt, char **ppszRev)
+{
+ int rc = VINF_SUCCESS;
+ if (ppszVer)
+ rc = RTStrDupEx(ppszVer, VBOX_VERSION_STRING_RAW);
+ if (RT_SUCCESS(rc))
+ {
+ if (ppszVerExt)
+ rc = RTStrDupEx(ppszVerExt, VBOX_VERSION_STRING);
+ if (RT_SUCCESS(rc))
+ {
+ if (ppszRev)
+ rc = RTStrDupEx(ppszRev, RT_XSTR(VBOX_SVN_REV));
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ /* bail out: */
+ }
+ if (ppszVerExt)
+ {
+ RTStrFree(*ppszVerExt);
+ *ppszVerExt = NULL;
+ }
+ }
+ if (ppszVer)
+ {
+ RTStrFree(*ppszVer);
+ *ppszVer = NULL;
+ }
+ return rc;
+}
+
+
+/**
+ * Retrieves the installed Guest Additions version and/or revision.
+ *
+ * @returns IPRT status code
+ * @param ppszVer Receives pointer of allocated raw version string
+ * (major.minor.build). NULL is accepted. The returned
+ * pointer must be freed using RTStrFree().
+ * @param ppszVerExt Receives pointer of allocated full version string
+ * (raw version + vendor suffix(es)). NULL is
+ * accepted. The returned pointer must be freed using
+ * RTStrFree().
+ * @param ppszRev Receives pointer of allocated revision string. NULL is
+ * accepted. The returned pointer must be freed using
+ * RTStrFree().
+ */
+VBGLR3DECL(int) VbglR3GetAdditionsVersion(char **ppszVer, char **ppszVerExt, char **ppszRev)
+{
+ /*
+ * Zap the return value up front.
+ */
+ if (ppszVer)
+ *ppszVer = NULL;
+ if (ppszVerExt)
+ *ppszVerExt = NULL;
+ if (ppszRev)
+ *ppszRev = NULL;
+
+#ifdef RT_OS_WINDOWS
+ HKEY hKey;
+ int rc = vbglR3WinOpenAdditionRegisterKey(&hKey);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Version.
+ */
+ if (ppszVer)
+ rc = vbglR3QueryRegistryString(hKey, L"Version", 64, ppszVer);
+
+ if ( RT_SUCCESS(rc)
+ && ppszVerExt)
+ rc = vbglR3QueryRegistryString(hKey, L"VersionExt", 128, ppszVerExt);
+
+ /*
+ * Revision.
+ */
+ if ( RT_SUCCESS(rc)
+ && ppszRev)
+ rc = vbglR3QueryRegistryString(hKey, L"Revision", 64, ppszRev);
+
+ rc = vbglR3WinCloseRegKey(hKey, rc);
+
+ /* Clean up allocated strings on error. */
+ if (RT_FAILURE(rc))
+ {
+ if (ppszVer)
+ {
+ RTStrFree(*ppszVer);
+ *ppszVer = NULL;
+ }
+ if (ppszVerExt)
+ {
+ RTStrFree(*ppszVerExt);
+ *ppszVerExt = NULL;
+ }
+ if (ppszRev)
+ {
+ RTStrFree(*ppszRev);
+ *ppszRev = NULL;
+ }
+ }
+ }
+ /*
+ * No registry entries found, return the version string compiled into this binary.
+ */
+ else
+ rc = vbglR3GetAdditionsCompileTimeVersion(ppszVer, ppszVerExt, ppszRev);
+ return rc;
+
+#else /* !RT_OS_WINDOWS */
+ /*
+ * On non-Windows platforms just return the compile-time version string.
+ */
+ return vbglR3GetAdditionsCompileTimeVersion(ppszVer, ppszVerExt, ppszRev);
+#endif /* !RT_OS_WINDOWS */
+}
+
+
+/**
+ * Retrieves the installation path of Guest Additions.
+ *
+ * @returns IPRT status code
+ * @param ppszPath Receives pointer of allocated installation path string.
+ * The returned pointer must be freed using
+ * RTStrFree().
+ */
+VBGLR3DECL(int) VbglR3GetAdditionsInstallationPath(char **ppszPath)
+{
+ int rc;
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Get it from the registry.
+ */
+ HKEY hKey;
+ rc = vbglR3WinOpenAdditionRegisterKey(&hKey);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbglR3QueryRegistryString(hKey, L"InstallDir", MAX_PATH * sizeof(RTUTF16), ppszPath);
+ if (RT_SUCCESS(rc))
+ RTPathChangeToUnixSlashes(*ppszPath, true /*fForce*/);
+ rc = vbglR3WinCloseRegKey(hKey, rc);
+ }
+#else
+ /** @todo implement me */
+ rc = VERR_NOT_IMPLEMENTED;
+ RT_NOREF1(ppszPath);
+#endif
+ return rc;
+}
+
+
+/**
+ * Reports the Guest Additions status of a certain facility to the host.
+ *
+ * @returns IPRT status code
+ * @param enmFacility The facility to report the status on.
+ * @param enmStatus The new status of the facility.
+ * @param fReserved Flags reserved for future hacks.
+ */
+VBGLR3DECL(int) VbglR3ReportAdditionsStatus(VBoxGuestFacilityType enmFacility, VBoxGuestFacilityStatus enmStatus,
+ uint32_t fReserved)
+{
+ VMMDevReportGuestStatus Report;
+ RT_ZERO(Report);
+ int rc = vmmdevInitRequest(&Report.header, VMMDevReq_ReportGuestStatus);
+ if (RT_SUCCESS(rc))
+ {
+ Report.guestStatus.facility = enmFacility;
+ Report.guestStatus.status = enmStatus;
+ Report.guestStatus.flags = fReserved;
+
+ rc = vbglR3GRPerform(&Report.header);
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp
new file mode 100644
index 00000000..680e6a5c
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibAutoLogon.cpp
@@ -0,0 +1,130 @@
+/* $Id: VBoxGuestR3LibAutoLogon.cpp $ */
+/** @file
+ * VBoxGuestR3LibAutoLogon - Ring-3 utility functions for auto-logon modules
+ * (VBoxGINA / VBoxCredProv / pam_vbox).
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+#include "VBoxGuestR3LibInternal.h"
+#include <iprt/errcore.h>
+
+
+/**
+ * Reports the current auto-logon status to the host.
+ *
+ * This makes sure that the Failed state is sticky.
+ *
+ * @return IPRT status code.
+ * @param enmStatus Status to report to the host.
+ */
+VBGLR3DECL(int) VbglR3AutoLogonReportStatus(VBoxGuestFacilityStatus enmStatus)
+{
+ /*
+ * VBoxGuestFacilityStatus_Failed is sticky.
+ */
+ static VBoxGuestFacilityStatus s_enmLastStatus = VBoxGuestFacilityStatus_Inactive;
+ if (s_enmLastStatus != VBoxGuestFacilityStatus_Failed)
+ {
+ int rc = VbglR3ReportAdditionsStatus(VBoxGuestFacilityType_AutoLogon, enmStatus, 0 /* Flags */);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ /*
+ * To maintain backwards compatibility to older hosts which don't have
+ * VMMDevReportGuestStatus implemented we set the appropriate status via
+ * guest property to have at least something.
+ */
+#ifdef VBOX_WITH_GUEST_PROPS
+ HGCMCLIENTID idClient = 0;
+ rc = VbglR3GuestPropConnect(&idClient);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszStatus;
+ switch (enmStatus)
+ {
+ case VBoxGuestFacilityStatus_Inactive: pszStatus = "Inactive"; break;
+ case VBoxGuestFacilityStatus_Paused: pszStatus = "Disabled"; break;
+ case VBoxGuestFacilityStatus_PreInit: pszStatus = "PreInit"; break;
+ case VBoxGuestFacilityStatus_Init: pszStatus = "Init"; break;
+ case VBoxGuestFacilityStatus_Active: pszStatus = "Active"; break;
+ case VBoxGuestFacilityStatus_Terminating: pszStatus = "Terminating"; break;
+ case VBoxGuestFacilityStatus_Terminated: pszStatus = "Terminated"; break;
+ case VBoxGuestFacilityStatus_Failed: pszStatus = "Failed"; break;
+ default: pszStatus = NULL;
+ }
+ if (pszStatus)
+ {
+ /*
+ * Use TRANSRESET when possible, fall back to TRANSIENT
+ * (generally sufficient unless the guest misbehaves).
+ */
+ static const char s_szPath[] = "/VirtualBox/GuestInfo/OS/AutoLogonStatus";
+ rc = VbglR3GuestPropWrite(idClient, s_szPath, pszStatus, "TRANSRESET");
+ if (rc == VERR_PARSE_ERROR)
+ rc = VbglR3GuestPropWrite(idClient, s_szPath, pszStatus, "TRANSIENT");
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ VbglR3GuestPropDisconnect(idClient);
+ }
+#endif
+ }
+
+ s_enmLastStatus = enmStatus;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Detects whether our process is running in a remote session or not.
+ *
+ * @return bool true if running in a remote session, false if not.
+ */
+VBGLR3DECL(bool) VbglR3AutoLogonIsRemoteSession(void)
+{
+#ifdef RT_OS_WINDOWS
+ return GetSystemMetrics(SM_REMOTESESSION) != 0 ? true : false;
+#else
+ return false; /* Not implemented. */
+#endif
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp
new file mode 100644
index 00000000..b7b2a8f8
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibBalloon.cpp
@@ -0,0 +1,83 @@
+/* $Id: VBoxGuestR3LibBalloon.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Ballooning.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+#include <iprt/string.h>
+
+
+/**
+ * Refresh the memory balloon after a change.
+ *
+ * @returns IPRT status code.
+ * @param pcChunks The size of the balloon in chunks of 1MB (out).
+ * @param pfHandleInR3 Allocating of memory in R3 required (out).
+ */
+VBGLR3DECL(int) VbglR3MemBalloonRefresh(uint32_t *pcChunks, bool *pfHandleInR3)
+{
+ VBGLIOCCHECKBALLOON Info;
+ VBGLREQHDR_INIT(&Info.Hdr, CHECK_BALLOON);
+ int rc = vbglR3DoIOCtl(VBGL_IOCTL_CHECK_BALLOON, &Info.Hdr, sizeof(Info));
+ if (RT_SUCCESS(rc))
+ {
+ *pcChunks = Info.u.Out.cBalloonChunks;
+ *pfHandleInR3 = Info.u.Out.fHandleInR3 != false;
+ }
+ return rc;
+}
+
+
+/**
+ * Change the memory by granting/reclaiming memory to/from R0.
+ *
+ * @returns IPRT status code.
+ * @param pv Memory chunk (1MB).
+ * @param fInflate true = inflate balloon (grant memory).
+ * false = deflate balloon (reclaim memory).
+ */
+VBGLR3DECL(int) VbglR3MemBalloonChange(void *pv, bool fInflate)
+{
+ VBGLIOCCHANGEBALLOON Info;
+ VBGLREQHDR_INIT(&Info.Hdr, CHANGE_BALLOON);
+ Info.u.In.pvChunk = pv;
+ Info.u.In.fInflate = fInflate;
+ RT_ZERO(Info.u.In.abPadding);
+ return vbglR3DoIOCtl(VBGL_IOCTL_CHANGE_BALLOON, &Info.Hdr, sizeof(Info));
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp
new file mode 100644
index 00000000..f44b923a
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp
@@ -0,0 +1,2609 @@
+/* $Id: VBoxGuestR3LibClipboard.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <VBox/GuestHost/SharedClipboard-transfers.h>
+#endif
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/err.h>
+#include <iprt/assert.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <iprt/dir.h>
+# include <iprt/file.h>
+# include <iprt/path.h>
+#endif
+#include <iprt/string.h>
+#include <iprt/cpp/ministring.h>
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+/*
+ * Function naming convention:
+ *
+ * FunctionNameRecv = Receives a host message (request).
+ * FunctionNameReply = Replies to a host message (request).
+ * FunctionNameSend = Sends a guest message to the host.
+ */
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+
+
+/**
+ * Connects to the Shared Clipboard service, legacy version, do not use anymore.
+ *
+ * @returns VBox status code
+ * @param pidClient Where to put the client id on success. The client id
+ * must be passed to all the other clipboard calls.
+ */
+VBGLR3DECL(int) VbglR3ClipboardConnect(HGCMCLIENTID *pidClient)
+{
+ int rc = VbglR3HGCMConnect("VBoxSharedClipboard", pidClient);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND)
+ LogRel(("Shared Clipboard: Unabled to connect, as host service was not found, skipping\n"));
+ else
+ LogRel(("Shared Clipboard: Unabled to connect to host service, rc=%Rrc\n", rc));
+ }
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Connects to the Shared Clipboard service, extended version.
+ *
+ * @returns VBox status code.
+ * @param pCtx Command context. This will be initialized by this
+ * call.
+ * @param fGuestFeatures The guest features supported by this client,
+ * VBOX_SHCL_GF_0_XXX.
+ */
+VBGLR3DECL(int) VbglR3ClipboardConnectEx(PVBGLR3SHCLCMDCTX pCtx, uint64_t fGuestFeatures)
+{
+ /*
+ * Intialize the context structure.
+ */
+ pCtx->idClient = 0;
+ pCtx->fGuestFeatures = fGuestFeatures;
+ pCtx->fHostFeatures = 0;
+ pCtx->fUseLegacyProtocol = true;
+ pCtx->cParmsRecived = 0;
+ pCtx->idContext = 0;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /* Init callback table. */
+ RT_ZERO(pCtx->Transfers.Callbacks);
+ /* Indicate that this guest supports Shared Clipboard file transfers. */
+ pCtx->fGuestFeatures |= VBOX_SHCL_GF_0_TRANSFERS;
+# ifdef RT_OS_WINDOWS
+ /* Indicate that on Windows guest OSes we have our own IDataObject implementation which
+ * integrates nicely into the guest's Windows Explorer showing / handling the Shared Clipboard file transfers. */
+ pCtx->fGuestFeatures |= VBOX_SHCL_GF_0_TRANSFERS_FRONTEND;
+# endif
+ pCtx->Transfers.cbChunkSize = VBOX_SHCL_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */
+ pCtx->Transfers.cbMaxChunkSize = VBOX_SHCL_MAX_CHUNK_SIZE; /** @todo Ditto. */
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
+ /*
+ * First step is connecting to the HGCM service.
+ */
+ int rc = VbglR3ClipboardConnect(&pCtx->idClient);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Next is reporting our features. If this fails, assume older host.
+ */
+ rc = VbglR3ClipboardReportFeatures(pCtx->idClient, pCtx->fGuestFeatures, &pCtx->fHostFeatures);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("Shared Clipboard: Guest features: %#RX64 - Host features: %#RX64\n",
+ pCtx->fGuestFeatures, pCtx->fHostFeatures));
+
+ if ( (pCtx->fHostFeatures & VBOX_SHCL_HF_0_CONTEXT_ID)
+ && (pCtx->fGuestFeatures & VBOX_SHCL_GF_0_CONTEXT_ID) )
+ {
+ pCtx->fUseLegacyProtocol = false;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ if ( (pCtx->fHostFeatures & VBOX_SHCL_HF_0_TRANSFERS)
+ && (pCtx->fGuestFeatures & VBOX_SHCL_GF_0_TRANSFERS) )
+ {
+ VBoxShClParmNegotiateChunkSize MsgChunkSize;
+ do
+ {
+ VBGL_HGCM_HDR_INIT(&MsgChunkSize.hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE,
+ VBOX_SHCL_CPARMS_NEGOTIATE_CHUNK_SIZE);
+ MsgChunkSize.cb32MaxChunkSize.SetUInt32(pCtx->Transfers.cbMaxChunkSize);
+ MsgChunkSize.cb32ChunkSize.SetUInt32(0); /* If set to 0, let the host choose. */
+ rc = VbglR3HGCMCall(&MsgChunkSize.hdr, sizeof(MsgChunkSize));
+ } while (rc == VERR_INTERRUPTED);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(MsgChunkSize.cb32ChunkSize.type == VMMDevHGCMParmType_32bit);
+ pCtx->Transfers.cbChunkSize = RT_MIN(MsgChunkSize.cb32ChunkSize.u.value32, pCtx->Transfers.cbChunkSize);
+ Assert(MsgChunkSize.cb32MaxChunkSize.type == VMMDevHGCMParmType_32bit);
+ pCtx->Transfers.cbMaxChunkSize = RT_MIN(MsgChunkSize.cb32MaxChunkSize.u.value32, pCtx->Transfers.cbMaxChunkSize);
+
+ LogRel2(("Shared Clipboard: Using chunk size %RU32 (maximum is %RU32)\n",
+ pCtx->Transfers.cbChunkSize, pCtx->Transfers.cbMaxChunkSize));
+ }
+ }
+ else
+ {
+ if (!(pCtx->fHostFeatures & VBOX_SHCL_HF_0_TRANSFERS))
+ LogRel2(("Shared Clipboard: Host does not support transfers\n"));
+ }
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+ }
+ else
+ {
+ if (!(pCtx->fHostFeatures & VBOX_SHCL_HF_0_CONTEXT_ID))
+ LogRel(("Shared Clipboard: Host does not support context IDs, using legacy protocol\n"));
+
+ pCtx->fUseLegacyProtocol = true;
+ }
+ }
+ else
+ {
+ AssertLogRelMsg(rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_IMPLEMENTED,
+ ("Reporting features failed: %Rrc\n", rc));
+ pCtx->fUseLegacyProtocol = true;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Reports features to the host and retrieve host feature set.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3ClipboardConnect().
+ * @param fGuestFeatures Features to report, VBOX_SHCL_GF_XXX.
+ * @param pfHostFeatures Where to store the features VBOX_SHCL_HF_XXX.
+ */
+VBGLR3DECL(int) VbglR3ClipboardReportFeatures(uint32_t idClient, uint64_t fGuestFeatures, uint64_t *pfHostFeatures)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter f64Features0;
+ HGCMFunctionParameter f64Features1;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, 2);
+ VbglHGCMParmUInt64Set(&Msg.f64Features0, fGuestFeatures);
+ VbglHGCMParmUInt64Set(&Msg.f64Features1, VBOX_SHCL_GF_1_MUST_BE_ONE);
+
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit);
+ Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit);
+ if (Msg.f64Features1.u.value64 & VBOX_SHCL_GF_1_MUST_BE_ONE)
+ rc = VERR_NOT_SUPPORTED;
+ else if (pfHostFeatures)
+ *pfHostFeatures = Msg.f64Features0.u.value64;
+ break;
+ }
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+
+}
+
+
+/**
+ * Disconnects from the Shared Clipboard service, legacy version, do not use anymore.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3ClipboardConnect().
+ */
+VBGLR3DECL(int) VbglR3ClipboardDisconnect(HGCMCLIENTID idClient)
+{
+ return VbglR3HGCMDisconnect(idClient);
+}
+
+
+/**
+ * Disconnects from the Shared Clipboard service, extended version.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ */
+VBGLR3DECL(int) VbglR3ClipboardDisconnectEx(PVBGLR3SHCLCMDCTX pCtx)
+{
+ int rc = VbglR3ClipboardDisconnect(pCtx->idClient);
+ if (RT_SUCCESS(rc))
+ {
+ pCtx->idClient = 0;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Receives reported formats from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the
+ * connection.
+ * @param pfFormats Where to store the received formats from the host.
+ */
+static int vbglR3ClipboardFormatsReportRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLFORMATS pfFormats)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFormats, VERR_INVALID_POINTER);
+
+ *pfFormats = 0;
+
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter id64Context;
+ HGCMFunctionParameter f32Formats;
+ } Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_GET, 2);
+ Msg.id64Context.SetUInt32(VBOX_SHCL_HOST_MSG_FORMATS_REPORT);
+ Msg.f32Formats.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.f32Formats.GetUInt32(pfFormats);
+ AssertRC(rc);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Fetches a VBOX_SHCL_HOST_MSG_READ_DATA_CID message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pfFormat Where to return the requested format.
+ */
+static int vbglR3ClipboardFetchReadDataCid(PVBGLR3SHCLCMDCTX pCtx, PSHCLFORMAT pfFormat)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFormat, VERR_INVALID_POINTER);
+
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter id64Context;
+ HGCMFunctionParameter f32Format;
+ } Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_GET, 2);
+ Msg.id64Context.SetUInt64(VBOX_SHCL_HOST_MSG_READ_DATA_CID);
+ Msg.f32Format.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.id64Context.GetUInt64(&pCtx->idContext);
+ AssertRC(rc);
+ int rc2 = Msg.f32Format.GetUInt32(pfFormat);
+ AssertRCStmt(rc2, rc = rc2);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Fetches a VBOX_SHCL_HOST_MSG_READ_DATA message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pfFormat Where to return the requested format.
+ */
+static int vbglR3ClipboardFetchReadData(PVBGLR3SHCLCMDCTX pCtx, PSHCLFORMAT pfFormat)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFormat, VERR_INVALID_POINTER);
+
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter id32Msg;
+ HGCMFunctionParameter f32Format;
+ } Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_GET, 2);
+ Msg.id32Msg.SetUInt32(VBOX_SHCL_HOST_MSG_READ_DATA);
+ Msg.f32Format.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.f32Format.GetUInt32(pfFormat);
+ AssertRC(rc);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Get a host message, legacy version (which does not have VBOX_SHCL_GUEST_FN_MSG_GET). Do not use anymore.
+ *
+ * Note: This is the old message which still is being used for the non-URI Shared Clipboard transfers,
+ * to not break compatibility with older additions / VBox versions.
+ *
+ * This will block until a message becomes available.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3ClipboardConnect().
+ * @param pidMsg Where to store the message id.
+ * @param pfFormats Where to store the format(s) the message applies to.
+ */
+VBGLR3DECL(int) VbglR3ClipboardGetHostMsgOld(HGCMCLIENTID idClient, uint32_t *pidMsg, uint32_t *pfFormats)
+{
+ VBoxShClGetHostMsgOld Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, VBOX_SHCL_CPARMS_GET_HOST_MSG_OLD);
+ VbglHGCMParmUInt32Set(&Msg.msg, 0);
+ VbglHGCMParmUInt32Set(&Msg.formats, 0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = VbglHGCMParmUInt32Get(&Msg.msg, pidMsg);
+ if (RT_SUCCESS(rc))
+ {
+ rc2 = VbglHGCMParmUInt32Get(&Msg.formats, pfFormats);
+ if (RT_SUCCESS(rc2))
+ return rc;
+ }
+ rc = rc2;
+ }
+ *pidMsg = UINT32_MAX - 1;
+ *pfFormats = UINT32_MAX;
+ return rc;
+}
+
+
+/**
+ * Reads data from the host clipboard.
+ *
+ * Legacy function, do not use anymore.
+ *
+ * @returns VBox status code.
+ * @retval VINF_BUFFER_OVERFLOW If there is more data available than the caller provided buffer space for.
+ *
+ * @param idClient The client id returned by VbglR3ClipboardConnect().
+ * @param fFormat The format we're requesting the data in.
+ * @param pvData Where to store the data.
+ * @param cbData The size of the buffer pointed to by \a pvData.
+ * @param pcbRead The actual size of the host clipboard data. May be larger than \a cbData.
+ */
+VBGLR3DECL(int) VbglR3ClipboardReadData(HGCMCLIENTID idClient, uint32_t fFormat, void *pvData, uint32_t cbData,
+ uint32_t *pcbRead)
+{
+ LogFlowFuncEnter();
+
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ VBoxShClParmDataRead Parms;
+ } Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_DATA_READ, VBOX_SHCL_CPARMS_DATA_READ);
+ VbglHGCMParmUInt32Set(&Msg.Parms.f32Format, fFormat);
+ VbglHGCMParmPtrSet( &Msg.Parms.pData, pvData, cbData);
+ VbglHGCMParmUInt32Set(&Msg.Parms.cb32Needed, 0);
+
+ int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbRead;
+ rc = VbglHGCMParmUInt32Get(&Msg.Parms.cb32Needed, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("cbRead=%RU32\n", cbRead));
+
+ if (cbRead > cbData)
+ rc = VINF_BUFFER_OVERFLOW;
+
+ *pcbRead = cbRead;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Reads clipboard data from the host clipboard.
+ *
+ * @returns VBox status code.
+ * @retval VINF_BUFFER_OVERFLOW If there is more data available than the caller provided buffer space for.
+ *
+ * @param pCtx The command context returned by VbglR3ClipboardConnectEx().
+ * @param uFormat Clipboard format of clipboard data to be read.
+ * @param pvData Buffer where to store the read data.
+ * @param cbData Size (in bytes) of data buffer where to store the read data.
+ * @param pcbRead The actual size of the host clipboard data.
+ */
+VBGLR3DECL(int) VbglR3ClipboardReadDataEx(PVBGLR3SHCLCMDCTX pCtx,
+ SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ return VbglR3ClipboardReadData(pCtx->idClient, uFormat, pvData, cbData, pcbRead);
+}
+
+
+/**
+ * Query the host features.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3ClipboardConnect().
+ * @param pfHostFeatures Where to store the host feature, VBOX_SHCL_HF_XXX.
+ */
+VBGLR3DECL(int) VbglR3ClipboardQueryFeatures(uint32_t idClient, uint64_t *pfHostFeatures)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter f64Features0;
+ HGCMFunctionParameter f64Features1;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_QUERY_FEATURES, 2);
+ VbglHGCMParmUInt64Set(&Msg.f64Features0, 0);
+ VbglHGCMParmUInt64Set(&Msg.f64Features1, RT_BIT_64(63));
+
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit);
+ Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit);
+ if (Msg.f64Features1.u.value64 & RT_BIT_64(63))
+ rc = VERR_NOT_SUPPORTED;
+ else if (pfHostFeatures)
+ *pfHostFeatures = Msg.f64Features0.u.value64;
+ break;
+ }
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+
+}
+
+/**
+ * Peeks at the next host message, waiting for one to turn up.
+ *
+ * This glosses over the difference between new (6.1) and old (1.3.2) host
+ * service versions, however it does so by abusing @a pcParameters, so don't use
+ * it directly when in legacy mode, always pass it on to
+ * VbglR3ClipboardEventGetNext() or VbglR3ClipboardEventGetNextEx().
+ *
+ * @returns VBox status code.
+ * @retval VERR_INTERRUPTED if interrupted. Does the necessary cleanup, so
+ * caller just have to repeat this call.
+ * @retval VERR_VM_RESTORED if the VM has been restored (idRestoreCheck).
+ *
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pidMsg Where to store the message id.
+ * @param pcParameters Where to store the number of parameters which will
+ * be received in a second call to the host.
+ * @param pidRestoreCheck Pointer to the VbglR3GetSessionId() variable to use
+ * for the VM restore check. Optional.
+ *
+ * @note Restore check is only performed optimally with a 6.0 host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardMsgPeekWait(PVBGLR3SHCLCMDCTX pCtx, uint32_t *pidMsg,
+ uint32_t *pcParameters, uint64_t *pidRestoreCheck)
+{
+ AssertPtrReturn(pidMsg, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcParameters, VERR_INVALID_POINTER);
+
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter idMsg; /* Doubles as restore check on input. */
+ HGCMFunctionParameter cParameters;
+ } Msg;
+ int rc;
+ if (!pCtx->fUseLegacyProtocol)
+ {
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT, 2);
+ VbglHGCMParmUInt64Set(&Msg.idMsg, pidRestoreCheck ? *pidRestoreCheck : 0);
+ VbglHGCMParmUInt32Set(&Msg.cParameters, 0);
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ LogFlowFunc(("VbglR3HGCMCall -> %Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsgReturn( Msg.idMsg.type == VMMDevHGCMParmType_64bit
+ && Msg.cParameters.type == VMMDevHGCMParmType_32bit,
+ ("msg.type=%d num_parms.type=%d\n", Msg.idMsg.type, Msg.cParameters.type),
+ VERR_INTERNAL_ERROR_3);
+
+ *pidMsg = (uint32_t)Msg.idMsg.u.value64;
+ *pcParameters = Msg.cParameters.u.value32;
+ return rc;
+ }
+
+ /*
+ * If restored, update pidRestoreCheck.
+ */
+ if (rc == VERR_VM_RESTORED && pidRestoreCheck)
+ *pidRestoreCheck = Msg.idMsg.u.value64;
+ }
+ else
+ {
+ /*
+ * We do some crude stuff here by putting the 2nd parameter (foramts) in the parameter count,
+ * however it's supposed to be passed directly to VbglR3ClipboardEventGetNext or
+ * VbglR3ClipboardEventGetNextEx, so that's fine...
+ */
+ rc = VbglR3ClipboardGetHostMsgOld(pCtx->idClient, pidMsg, pcParameters);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+
+ /*
+ * If interrupted we must cancel the call so it doesn't prevent us from making another one.
+ */
+ if (rc == VERR_INTERRUPTED)
+ {
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_MSG_CANCEL, 0);
+ int rc2 = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg.Hdr));
+ AssertRC(rc2);
+ }
+
+ *pidMsg = UINT32_MAX - 1;
+ *pcParameters = UINT32_MAX - 2;
+ return rc;
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+
+/**
+ * Reads a root list header from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pRootListHdr Where to store the received root list header.
+ */
+static int vbglR3ClipboardRootListHdrRead(PVBGLR3SHCLCMDCTX pCtx, PSHCLROOTLISTHDR pRootListHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pRootListHdr, VERR_INVALID_POINTER);
+
+ VBoxShClRootListHdrMsg Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ, VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ);
+
+ Msg.ReqParms.uContext.SetUInt64(pCtx->idContext);
+ Msg.ReqParms.fRoots.SetUInt32(0);
+
+ Msg.cRoots.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.fRoots.GetUInt32(&pRootListHdr->fRoots); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.cRoots.GetUInt32(&pRootListHdr->cRoots);
+ AssertRC(rc);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Reads a root list entry from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param uIndex Index of root list entry to read.
+ * @param pRootListEntry Where to store the root list entry read from the host.
+ */
+static int vbglR3ClipboardRootListEntryRead(PVBGLR3SHCLCMDCTX pCtx, uint32_t uIndex, PSHCLROOTLISTENTRY pRootListEntry)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pRootListEntry, VERR_INVALID_POINTER);
+
+ VBoxShClRootListEntryMsg Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ, VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ);
+
+ Msg.Parms.uContext.SetUInt64(pCtx->idContext);
+ Msg.Parms.fInfo.SetUInt32(pRootListEntry->fInfo);
+ Msg.Parms.uIndex.SetUInt32(uIndex);
+
+ Msg.szName.SetPtr(pRootListEntry->pszName, pRootListEntry->cbName);
+ Msg.cbInfo.SetUInt32(pRootListEntry->cbInfo);
+ Msg.pvInfo.SetPtr(pRootListEntry->pvInfo, pRootListEntry->cbInfo);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.Parms.fInfo.GetUInt32(&pRootListEntry->fInfo); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbInfo = 0;
+ rc = Msg.cbInfo.GetUInt32(&cbInfo); AssertRC(rc);
+ if (pRootListEntry->cbInfo != cbInfo)
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Reads the root list from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param ppRootList Where to store the (allocated) root list. Must be free'd by the caller with
+ * SharedClipboardTransferRootListFree().
+ */
+VBGLR3DECL(int) VbglR3ClipboardRootListRead(PVBGLR3SHCLCMDCTX pCtx, PSHCLROOTLIST *ppRootList)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppRootList, VERR_INVALID_POINTER);
+
+ int rc;
+
+ PSHCLROOTLIST pRootList = ShClTransferRootListAlloc();
+ if (pRootList)
+ {
+ SHCLROOTLISTHDR srcRootListHdr;
+ rc = vbglR3ClipboardRootListHdrRead(pCtx, &srcRootListHdr);
+ if (RT_SUCCESS(rc))
+ {
+ pRootList->Hdr.cRoots = srcRootListHdr.cRoots;
+ pRootList->Hdr.fRoots = 0; /** @todo Implement this. */
+
+ if (srcRootListHdr.cRoots)
+ {
+ pRootList->paEntries =
+ (PSHCLROOTLISTENTRY)RTMemAllocZ(srcRootListHdr.cRoots * sizeof(SHCLROOTLISTENTRY));
+ if (pRootList->paEntries)
+ {
+ for (uint32_t i = 0; i < srcRootListHdr.cRoots; i++)
+ {
+ SHCLROOTLISTENTRY *pEntry = &pRootList->paEntries[i];
+ AssertPtr(pEntry);
+
+ rc = ShClTransferRootListEntryInit(pEntry);
+ if (RT_SUCCESS(rc))
+ rc = vbglR3ClipboardRootListEntryRead(pCtx, i, pEntry);
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppRootList = pRootList;
+ }
+ else
+ ShClTransferRootListFree(pRootList);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a transfer status from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pEnmDir Where to store the transfer direction for the reported transfer.
+ * @param pReport Where to store the transfer (status) report.
+ */
+VBGLR3DECL(int) VbglR3ClipboarTransferStatusRecv(PVBGLR3SHCLCMDCTX pCtx,
+ PSHCLTRANSFERDIR pEnmDir, PSHCLTRANSFERREPORT pReport)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pReport, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEnmDir, VERR_INVALID_POINTER);
+
+ VBoxShClTransferStatusMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_TRANSFER_STATUS);
+
+ Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS);
+ Msg.enmDir.SetUInt32(0);
+ Msg.enmStatus.SetUInt32(0);
+ Msg.rc.SetUInt32(0);
+ Msg.fFlags.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.enmDir.GetUInt32((uint32_t *)pEnmDir);
+ AssertRC(rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.enmStatus.GetUInt32(&pReport->uStatus);
+ AssertRC(rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.rc.GetUInt32((uint32_t *)&pReport->rc);
+ AssertRC(rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.fFlags.GetUInt32(&pReport->fFlags);
+ AssertRC(rc);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Replies to a transfer report from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pTransfer Transfer of report to reply to.
+ * @param uStatus Tranfer status to reply.
+ * @param rcTransfer Result code (rc) to reply.
+ */
+VBGLR3DECL(int) VbglR3ClipboardTransferStatusReply(PVBGLR3SHCLCMDCTX pCtx, PSHCLTRANSFER pTransfer,
+ SHCLTRANSFERSTATUS uStatus, int rcTransfer)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ RT_NOREF(pTransfer);
+
+ VBoxShClReplyMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_TRANSFER_STATUS);
+ Msg.rc.SetUInt32((uint32_t )rcTransfer); /* int vs. uint32_t */
+ Msg.pvPayload.SetPtr(NULL, 0);
+
+ Msg.u.TransferStatus.enmStatus.SetUInt32((uint32_t)uStatus);
+
+ LogFlowFunc(("%s\n", ShClTransferStatusToStr(uStatus)));
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to read a root list header from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pfRoots Where to store the root list header flags to use, requested by the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardRootListHdrReadReq(PVBGLR3SHCLCMDCTX pCtx, uint32_t *pfRoots)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfRoots, VERR_INVALID_POINTER);
+
+ VBoxShClRootListReadReqMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ_REQ);
+
+ Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ);
+ Msg.ReqParms.fRoots.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.fRoots.GetUInt32(pfRoots);
+ AssertRC(rc);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Replies to a root list header request.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pRootListHdr Root lsit header to reply to the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardRootListHdrReadReply(PVBGLR3SHCLCMDCTX pCtx, PSHCLROOTLISTHDR pRootListHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pRootListHdr, VERR_INVALID_POINTER);
+
+ VBoxShClRootListHdrMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE, VBOX_SHCL_CPARMS_ROOT_LIST_HDR_WRITE);
+
+ Msg.ReqParms.uContext.SetUInt64(pCtx->idContext);
+ Msg.ReqParms.fRoots.SetUInt32(pRootListHdr->fRoots);
+
+ Msg.cRoots.SetUInt32(pRootListHdr->cRoots);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to read a root list entry from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param puIndex Where to return the index of the root list entry the host wants to read.
+ * @param pfInfo Where to return the read flags the host wants to use.
+ */
+VBGLR3DECL(int) VbglR3ClipboardRootListEntryReadReq(PVBGLR3SHCLCMDCTX pCtx, uint32_t *puIndex, uint32_t *pfInfo)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(puIndex, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfInfo, VERR_INVALID_POINTER);
+
+ VBoxShClRootListEntryReadReqMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ_REQ);
+
+ Msg.Parms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ);
+ Msg.Parms.fInfo.SetUInt32(0);
+ Msg.Parms.uIndex.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.Parms.uContext.GetUInt64(&pCtx->idContext); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.Parms.fInfo.GetUInt32(pfInfo);
+ AssertRC(rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.Parms.uIndex.GetUInt32(puIndex);
+ AssertRC(rc);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Replies to a root list entry read request from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param uIndex Index of root list entry to reply.
+ * @param pEntry Actual root list entry to reply.
+ */
+VBGLR3DECL(int) VbglR3ClipboardRootListEntryReadReply(PVBGLR3SHCLCMDCTX pCtx, uint32_t uIndex, PSHCLROOTLISTENTRY pEntry)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEntry, VERR_INVALID_POINTER);
+
+ VBoxShClRootListEntryMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE, VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_WRITE);
+
+ Msg.Parms.uContext.SetUInt64(pCtx->idContext);
+ Msg.Parms.fInfo.SetUInt32(0);
+ Msg.Parms.uIndex.SetUInt32(uIndex);
+
+ Msg.szName.SetPtr(pEntry->pszName, pEntry->cbName);
+ Msg.cbInfo.SetUInt32(pEntry->cbInfo);
+ Msg.pvInfo.SetPtr(pEntry->pvInfo, pEntry->cbInfo);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a request to open a list handle to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pOpenParms List open parameters to use for the open request.
+ * @param phList Where to return the list handle received from the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListOpenSend(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTOPENPARMS pOpenParms,
+ PSHCLLISTHANDLE phList)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOpenParms, VERR_INVALID_POINTER);
+ AssertPtrReturn(phList, VERR_INVALID_POINTER);
+
+ VBoxShClListOpenMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_LIST_OPEN, VBOX_SHCL_CPARMS_LIST_OPEN);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.fList.SetUInt32(0);
+ Msg.pvFilter.SetPtr(pOpenParms->pszFilter, pOpenParms->cbFilter);
+ Msg.pvPath.SetPtr(pOpenParms->pszPath, pOpenParms->cbPath);
+ Msg.uHandle.SetUInt64(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uHandle.GetUInt64(phList); AssertRC(rc);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to open a list handle on the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pOpenParms Where to store the open parameters the host wants to use for opening the list handle.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListOpenRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTOPENPARMS pOpenParms)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOpenParms, VERR_INVALID_POINTER);
+
+ VBoxShClListOpenMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_OPEN);
+
+ Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN);
+ Msg.fList.SetUInt32(0);
+ Msg.pvPath.SetPtr(pOpenParms->pszPath, pOpenParms->cbPath);
+ Msg.pvFilter.SetPtr(pOpenParms->pszFilter, pOpenParms->cbFilter);
+ Msg.uHandle.SetUInt64(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uContext.GetUInt64(&pCtx->idContext);
+ if (RT_SUCCESS(rc))
+ rc = Msg.fList.GetUInt32(&pOpenParms->fList);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Replies to a list open request from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param rcReply Return code to reply to the host.
+ * @param hList List handle of (guest) list to reply to the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListOpenReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLLISTHANDLE hList)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ VBoxShClReplyMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_LIST_OPEN);
+ Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */
+ Msg.pvPayload.SetPtr(NULL, 0);
+
+ Msg.u.ListOpen.uHandle.SetUInt64(hList);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to close a list handle on the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param phList Where to store the list handle to close, received from the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListCloseRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTHANDLE phList)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(phList, VERR_INVALID_POINTER);
+
+ VBoxShClListCloseMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_CLOSE);
+
+ Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE);
+ Msg.uHandle.SetUInt64(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uContext.GetUInt64(&pCtx->idContext);
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uHandle.GetUInt64(phList);
+ AssertRC(rc);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Replies to a list handle close request from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param rcReply Return code to reply to the host.
+ * @param hList List handle the send the close reply for.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListCloseReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLLISTHANDLE hList)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ VBoxShClReplyMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_LIST_CLOSE);
+ Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */
+ Msg.pvPayload.SetPtr(NULL, 0);
+
+ Msg.u.ListOpen.uHandle.SetUInt64(hList);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a request to close a list handle to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hList List handle to request for closing on the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListCloseSend(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ VBoxShClListCloseMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_LIST_CLOSE, VBOX_SHCL_CPARMS_LIST_CLOSE);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.uHandle.SetUInt64(hList);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a request to read a list header to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hList List handle to read list header for.
+ * @param fFlags List header read flags to use.
+ * @param pListHdr Where to return the list header received from the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListHdrRead(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList, uint32_t fFlags,
+ PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pListHdr, VERR_INVALID_POINTER);
+
+ VBoxShClListHdrMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_LIST_HDR_READ, VBOX_SHCL_CPARMS_LIST_HDR);
+
+ Msg.ReqParms.uContext.SetUInt64(pCtx->idContext);
+ Msg.ReqParms.uHandle.SetUInt64(hList);
+ Msg.ReqParms.fFlags.SetUInt32(fFlags);
+
+ Msg.fFeatures.SetUInt32(0);
+ Msg.cbTotalSize.SetUInt32(0);
+ Msg.cTotalObjects.SetUInt64(0);
+ Msg.cbTotalSize.SetUInt64(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.fFeatures.GetUInt32(&pListHdr->fFeatures);
+ if (RT_SUCCESS(rc))
+ rc = Msg.cTotalObjects.GetUInt64(&pListHdr->cTotalObjects);
+ if (RT_SUCCESS(rc))
+ rc = Msg.cbTotalSize.GetUInt64(&pListHdr->cbTotalSize);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to read a list header on the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param phList Where to return the list handle to read list header for.
+ * @param pfFlags Where to return the List header read flags to use.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListHdrReadRecvReq(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTHANDLE phList, uint32_t *pfFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(phList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFlags, VERR_INVALID_POINTER);
+
+ VBoxShClListHdrReadReqMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_HDR_READ_REQ);
+
+ Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ);
+ Msg.ReqParms.uHandle.SetUInt64(0);
+ Msg.ReqParms.fFlags.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext);
+ if (RT_SUCCESS(rc))
+ rc = Msg.ReqParms.uHandle.GetUInt64(phList);
+ if (RT_SUCCESS(rc))
+ rc = Msg.ReqParms.fFlags.GetUInt32(pfFlags);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends (writes) a list header to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hList List handle to write list header for.
+ * @param pListHdr List header to write.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListHdrWrite(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList,
+ PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pListHdr, VERR_INVALID_POINTER);
+
+ VBoxShClListHdrMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE, VBOX_SHCL_CPARMS_LIST_HDR);
+
+ Msg.ReqParms.uContext.SetUInt64(pCtx->idContext);
+ Msg.ReqParms.uHandle.SetUInt64(hList);
+ Msg.ReqParms.fFlags.SetUInt32(0);
+
+ Msg.fFeatures.SetUInt32(0);
+ Msg.cbTotalSize.SetUInt32(pListHdr->fFeatures);
+ Msg.cTotalObjects.SetUInt64(pListHdr->cTotalObjects);
+ Msg.cbTotalSize.SetUInt64(pListHdr->cbTotalSize);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a request to read a list entry from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hList List handle to request to read a list entry for.
+ * @param pListEntry Where to return the list entry read from the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListEntryRead(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList,
+ PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pListEntry, VERR_INVALID_POINTER);
+
+ VBoxShClListEntryMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ, VBOX_SHCL_CPARMS_LIST_ENTRY);
+
+ Msg.ReqParms.uContext.SetUInt64(pCtx->idContext);
+ Msg.ReqParms.uHandle.SetUInt64(hList);
+ Msg.ReqParms.fInfo.SetUInt32(0);
+
+ Msg.szName.SetPtr(pListEntry->pszName, pListEntry->cbName);
+ Msg.cbInfo.SetUInt32(pListEntry->cbInfo);
+ Msg.pvInfo.SetPtr(pListEntry->pvInfo, pListEntry->cbInfo);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.cbInfo.GetUInt32(&pListEntry->cbInfo); AssertRC(rc);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to read a list entry from the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param phList Where to return the list handle to read a list entry for.
+ * @param pfInfo Where to return the list read flags.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListEntryReadRecvReq(PVBGLR3SHCLCMDCTX pCtx, PSHCLLISTHANDLE phList, uint32_t *pfInfo)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(phList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfInfo, VERR_INVALID_POINTER);
+
+ VBoxShClListEntryReadReqMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_LIST_ENTRY_READ);
+
+ Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ);
+ Msg.ReqParms.uHandle.SetUInt64(0);
+ Msg.ReqParms.fInfo.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext);
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.uHandle.GetUInt64(phList);
+ AssertRC(rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.fInfo.GetUInt32(pfInfo);
+ AssertRC(rc);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends (writes) a list entry to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hList List handle to write a list etnry for.
+ * @param pListEntry List entry to write.
+ */
+VBGLR3DECL(int) VbglR3ClipboardListEntryWrite(PVBGLR3SHCLCMDCTX pCtx, SHCLLISTHANDLE hList,
+ PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pListEntry, VERR_INVALID_POINTER);
+
+ VBoxShClListEntryMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE, VBOX_SHCL_CPARMS_LIST_ENTRY);
+
+ Msg.ReqParms.uContext.SetUInt64(pCtx->idContext);
+ Msg.ReqParms.uHandle.SetUInt64(hList);
+ Msg.ReqParms.fInfo.SetUInt32(pListEntry->fInfo);
+
+ Msg.szName.SetPtr(pListEntry->pszName, pListEntry->cbName);
+ Msg.cbInfo.SetUInt32(pListEntry->cbInfo);
+ Msg.pvInfo.SetPtr(pListEntry->pvInfo, pListEntry->cbInfo);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to open an object on the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pCreateParms Where to store the object open/create parameters received from the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjOpenRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCreateParms, VERR_INVALID_POINTER);
+
+ VBoxShClObjOpenMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_OBJ_OPEN);
+
+ Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN);
+ Msg.uHandle.SetUInt64(0);
+ Msg.szPath.SetPtr(pCreateParms->pszPath, pCreateParms->cbPath);
+ Msg.fCreate.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uContext.GetUInt64(&pCtx->idContext);
+ if (RT_SUCCESS(rc))
+ rc = Msg.fCreate.GetUInt32(&pCreateParms->fCreate);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Replies a host request to open an object.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param rcReply Return code to reply to the host.
+ * @param hObj Object handle of opened object to reply to the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjOpenReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLOBJHANDLE hObj)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ VBoxShClReplyMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_OBJ_OPEN);
+ Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */
+ Msg.pvPayload.SetPtr(NULL, 0);
+
+ Msg.u.ObjOpen.uHandle.SetUInt64(hObj);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends an object open request to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param pCreateParms Object open/create parameters to use for opening the object on the host.
+ * @param phObj Where to return the object handle from the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjOpenSend(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms,
+ PSHCLOBJHANDLE phObj)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCreateParms, VERR_INVALID_POINTER);
+ AssertPtrReturn(phObj, VERR_INVALID_POINTER);
+
+ VBoxShClObjOpenMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_OBJ_OPEN, VBOX_SHCL_CPARMS_OBJ_OPEN);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.uHandle.SetUInt64(0);
+ Msg.szPath.SetPtr((void *)pCreateParms->pszPath, pCreateParms->cbPath);
+ Msg.fCreate.SetUInt32(pCreateParms->fCreate);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.uHandle.GetUInt64(phObj);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to close an object on the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param phObj Where to return the object handle to close from the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjCloseRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJHANDLE phObj)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(phObj, VERR_INVALID_POINTER);
+
+ VBoxShClObjCloseMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_OBJ_CLOSE);
+
+ Msg.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE);
+ Msg.uHandle.SetUInt64(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uContext.GetUInt64(&pCtx->idContext);
+ if (RT_SUCCESS(rc))
+ rc = Msg.uHandle.GetUInt64(phObj);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Replies to an object open request from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param rcReply Return code to reply to the host.
+ * @param hObj Object handle to reply to the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjCloseReply(PVBGLR3SHCLCMDCTX pCtx, int rcReply, SHCLOBJHANDLE hObj)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ VBoxShClReplyMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN + 1);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.enmType.SetUInt32(VBOX_SHCL_REPLYMSGTYPE_OBJ_CLOSE);
+ Msg.rc.SetUInt32((uint32_t)rcReply); /** int vs. uint32_t */
+ Msg.pvPayload.SetPtr(NULL, 0);
+
+ Msg.u.ObjClose.uHandle.SetUInt64(hObj);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a request to close an object to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hObj Object handle to close on the host.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjCloseSend(PVBGLR3SHCLCMDCTX pCtx, SHCLOBJHANDLE hObj)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ VBoxShClObjCloseMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_OBJ_CLOSE, VBOX_SHCL_CPARMS_OBJ_CLOSE);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.uHandle.SetUInt64(hObj);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Receives a host request to read from an object on the guest.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param phObj Where to return the object handle to read from.
+ * @param pcbToRead Where to return the amount (in bytes) to read.
+ * @param pfFlags Where to return the read flags.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjReadRecv(PVBGLR3SHCLCMDCTX pCtx, PSHCLOBJHANDLE phObj, uint32_t *pcbToRead,
+ uint32_t *pfFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(phObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbToRead, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFlags, VERR_INVALID_POINTER);
+
+ VBoxShClObjReadReqMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_MSG_GET, VBOX_SHCL_CPARMS_OBJ_READ_REQ);
+
+ Msg.ReqParms.uContext.SetUInt64(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ);
+ Msg.ReqParms.uHandle.SetUInt64(0);
+ Msg.ReqParms.cbToRead.SetUInt32(0);
+ Msg.ReqParms.fRead.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.ReqParms.uContext.GetUInt64(&pCtx->idContext);
+ if (RT_SUCCESS(rc))
+ rc = Msg.ReqParms.uHandle.GetUInt64(phObj);
+ if (RT_SUCCESS(rc))
+ rc = Msg.ReqParms.cbToRead.GetUInt32(pcbToRead);
+ if (RT_SUCCESS(rc))
+ rc = Msg.ReqParms.fRead.GetUInt32(pfFlags);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a request to read from an object to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hObj Object handle of object to read from.
+ * @param pvData Buffer where to store the read object data.
+ * @param cbData Size (in bytes) of buffer.
+ * @param pcbRead Where to store the amount (in bytes) read from the object.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjReadSend(PVBGLR3SHCLCMDCTX pCtx, SHCLOBJHANDLE hObj,
+ void *pvData, uint32_t cbData, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+
+ VBoxShClObjReadWriteMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_OBJ_READ, VBOX_SHCL_CPARMS_OBJ_READ);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.uHandle.SetUInt64(hObj);
+ Msg.cbData.SetUInt32(cbData);
+ Msg.pvData.SetPtr(pvData, cbData);
+ Msg.cbChecksum.SetUInt32(0);
+ Msg.pvChecksum.SetPtr(NULL, 0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Add checksum support. */
+
+ if (pcbRead)
+ {
+ rc = Msg.cbData.GetUInt32(pcbRead); AssertRC(rc);
+ AssertReturn(cbData >= *pcbRead, VERR_TOO_MUCH_DATA);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends a request to write to an object to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Shared Clipboard command context to use for the connection.
+ * @param hObj Object handle of object to write to.
+ * @param pvData Buffer of data to write to object.
+ * @param cbData Size (in bytes) of buffer.
+ * @param pcbWritten Where to store the amount (in bytes) written to the object.
+ */
+VBGLR3DECL(int) VbglR3ClipboardObjWriteSend(PVBGLR3SHCLCMDCTX pCtx, SHCLOBJHANDLE hObj,
+ void *pvData, uint32_t cbData, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ /* cbData can be 0. */
+ /* pcbWritten is optional. */
+
+ VBoxShClObjReadWriteMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->idClient,
+ VBOX_SHCL_GUEST_FN_OBJ_WRITE, VBOX_SHCL_CPARMS_OBJ_WRITE);
+
+ Msg.uContext.SetUInt64(pCtx->idContext);
+ Msg.uHandle.SetUInt64(hObj);
+ Msg.pvData.SetPtr(pvData, cbData);
+ Msg.pvChecksum.SetPtr(NULL, 0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Add checksum support. */
+
+ if (pcbWritten)
+ *pcbWritten = cbData; /** @todo For now return all as being written. */
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Transfer interface implementations *
+*********************************************************************************************************************************/
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceGetRoots(PSHCLTXPROVIDERCTX pCtx, PSHCLROOTLIST *ppRootList)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ int rc = VbglR3ClipboardRootListRead(pCmdCtx, ppRootList);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLLISTOPENPARMS pOpenParms,
+ PSHCLLISTHANDLE phList)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ int rc = VbglR3ClipboardListOpenSend(pCmdCtx, pOpenParms, phList);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListClose(PSHCLTXPROVIDERCTX pCtx, SHCLLISTHANDLE hList)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ int rc = VbglR3ClipboardListCloseSend(pCmdCtx, hList);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListHdrRead(PSHCLTXPROVIDERCTX pCtx,
+ SHCLLISTHANDLE hList, PSHCLLISTHDR pListHdr)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ int rc = ShClTransferListHdrInit(pListHdr);
+ if (RT_SUCCESS(rc))
+ {
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardListHdrRead(pCmdCtx, hList, 0 /* fFlags */, pListHdr);
+ }
+ else
+ ShClTransferListHdrDestroy(pListHdr);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceListEntryRead(PSHCLTXPROVIDERCTX pCtx,
+ SHCLLISTHANDLE hList, PSHCLLISTENTRY pEntry)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ int rc = VbglR3ClipboardListEntryRead(pCmdCtx, hList, pEntry);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceObjOpen(PSHCLTXPROVIDERCTX pCtx,
+ PSHCLOBJOPENCREATEPARMS pCreateParms, PSHCLOBJHANDLE phObj)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ int rc = VbglR3ClipboardObjOpenSend(pCmdCtx, pCreateParms, phObj);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ int rc = VbglR3ClipboardObjCloseSend(pCmdCtx, hObj);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceObjRead(PSHCLTXPROVIDERCTX pCtx,
+ SHCLOBJHANDLE hObj, void *pvData, uint32_t cbData,
+ uint32_t fFlags, uint32_t *pcbRead)
+{
+ LogFlowFuncEnter();
+
+ PVBGLR3SHCLCMDCTX pCmdCtx = (PVBGLR3SHCLCMDCTX)pCtx->pvUser;
+ AssertPtr(pCmdCtx);
+
+ RT_NOREF(fFlags); /* Not used yet. */
+
+ int rc = VbglR3ClipboardObjReadSend(pCmdCtx, hObj, pvData, cbData, pcbRead);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Starts a transfer on the guest side.
+ *
+ * @returns VBox status code.
+ * @param pCmdCtx Command context to use.
+ * @param pTransferCtx Transfer context to create transfer for.
+ * @param uTransferID ID to use for transfer to start.
+ * @param enmDir Direction of transfer to start.
+ * @param enmSource Source of transfer to start.
+ * @param ppTransfer Where to return the transfer object on success. Optional.
+ */
+static int vbglR3ClipboardTransferStart(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCTX pTransferCtx,
+ SHCLTRANSFERID uTransferID, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource,
+ PSHCLTRANSFER *ppTransfer)
+{
+ PSHCLTRANSFER pTransfer;
+ int rc = ShClTransferCreate(&pTransfer);
+ if (RT_SUCCESS(rc))
+ {
+ ShClTransferSetCallbacks(pTransfer, &pCmdCtx->Transfers.Callbacks);
+
+ rc = ShClTransferInit(pTransfer, enmDir, enmSource);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferCtxTransferRegisterById(pTransferCtx, pTransfer, uTransferID);
+ if (RT_SUCCESS(rc))
+ {
+ /* If this is a read transfer (reading data from host), set the interface to use
+ * our VbglR3 routines here. */
+ if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE)
+ {
+ SHCLTXPROVIDERCREATIONCTX creationCtx;
+ RT_ZERO(creationCtx);
+
+ creationCtx.Interface.pfnRootsGet = vbglR3ClipboardTransferIfaceGetRoots;
+
+ creationCtx.Interface.pfnListOpen = vbglR3ClipboardTransferIfaceListOpen;
+ creationCtx.Interface.pfnListClose = vbglR3ClipboardTransferIfaceListClose;
+ creationCtx.Interface.pfnListHdrRead = vbglR3ClipboardTransferIfaceListHdrRead;
+ creationCtx.Interface.pfnListEntryRead = vbglR3ClipboardTransferIfaceListEntryRead;
+
+ creationCtx.Interface.pfnObjOpen = vbglR3ClipboardTransferIfaceObjOpen;
+ creationCtx.Interface.pfnObjClose = vbglR3ClipboardTransferIfaceObjClose;
+ creationCtx.Interface.pfnObjRead = vbglR3ClipboardTransferIfaceObjRead;
+
+ creationCtx.pvUser = pCmdCtx;
+
+ rc = ShClTransferSetProviderIface(pTransfer, &creationCtx);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = ShClTransferStart(pTransfer);
+ }
+
+ if (RT_FAILURE(rc))
+ ShClTransferCtxTransferUnregister(pTransferCtx, uTransferID);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (ppTransfer)
+ *ppTransfer = pTransfer;
+
+ LogRel2(("Shared Clipboard: Transfer ID=%RU32 (%s %s) successfully started\n",
+ uTransferID,
+ enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "reading from" : "writing to",
+ enmSource == SHCLSOURCE_LOCAL ? "local" : "remote"));
+ }
+ else
+ LogRel(("Shared Clipboard: Unable to start transfer ID=%RU32, rc=%Rrc\n", uTransferID, rc));
+
+ /* Send a reply in any case. */
+ int rc2 = VbglR3ClipboardTransferStatusReply(pCmdCtx, pTransfer,
+ RT_SUCCESS(rc)
+ ? SHCLTRANSFERSTATUS_STARTED : SHCLTRANSFERSTATUS_ERROR, rc);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferDestroy(pTransfer);
+ pTransfer = NULL;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Stops a transfer on the guest side.
+ *
+ * @returns VBox status code, or VERR_NOT_FOUND if transfer has not been found.
+ * @param pCmdCtx Command context to use.
+ * @param pTransferCtx Transfer context to stop transfer for.
+ * @param uTransferID ID of transfer to stop.
+ */
+static int vbglR3ClipboardTransferStop(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCTX pTransferCtx,
+ SHCLTRANSFERID uTransferID)
+{
+ int rc;
+
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, uTransferID);
+ if (pTransfer)
+ {
+ rc = ShClTransferCtxTransferUnregister(pTransferCtx, uTransferID);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("Shared Clipboard: Transfer ID=%RU32 successfully stopped\n", uTransferID));
+ }
+ else
+ LogRel(("Shared Clipboard: Unable to stop transfer ID=%RU32, rc=%Rrc\n", uTransferID, rc));
+
+ /* Send a reply in any case. */
+ int rc2 = VbglR3ClipboardTransferStatusReply(pCmdCtx, pTransfer,
+ RT_SUCCESS(rc)
+ ? SHCLTRANSFERSTATUS_STOPPED : SHCLTRANSFERSTATUS_ERROR, rc);
+ AssertRC(rc2);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sets transfer callbacks of a Shared Clipboard command context.
+ *
+ * @param pCmdCtx Command context to set callbacks for.
+ * @param pCallbacks Pointer to callback table to set.
+ */
+VBGLR3DECL(void) VbglR3ClipboardTransferSetCallbacks(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCALLBACKTABLE pCallbacks)
+{
+ AssertPtrReturnVoid(pCmdCtx);
+ AssertPtrReturnVoid(pCallbacks);
+
+ ShClTransferCopyCallbacks(&pCmdCtx->Transfers.Callbacks, pCallbacks);
+}
+
+VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms,
+ PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFERCTX pTransferCtx,
+ PVBGLR3CLIPBOARDEVENT pEvent)
+{
+ AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ LogFunc(("Handling idMsg=%RU32 (%s), cParms=%RU32\n", idMsg, ShClHostMsgToStr(idMsg), cParms));
+
+ int rc;
+ if (!pCmdCtx->fUseLegacyProtocol)
+ {
+ bool fErrorSent = false; /* Whether an error has been reported back to the host already. */
+
+ switch (idMsg)
+ {
+ case VBOX_SHCL_HOST_MSG_TRANSFER_STATUS:
+ {
+ SHCLTRANSFERDIR enmDir;
+ SHCLTRANSFERREPORT transferReport;
+ rc = VbglR3ClipboarTransferStatusRecv(pCmdCtx, &enmDir, &transferReport);
+ if (RT_SUCCESS(rc))
+ {
+ const SHCLTRANSFERID uTransferID = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext);
+
+ LogFlowFunc(("[Transfer %RU32] enmDir=%RU32, status=%s\n",
+ uTransferID, enmDir, ShClTransferStatusToStr(transferReport.uStatus)));
+
+ switch (transferReport.uStatus)
+ {
+ case SHCLTRANSFERSTATUS_INITIALIZED:
+ RT_FALL_THROUGH();
+ case SHCLTRANSFERSTATUS_STARTED:
+ {
+ SHCLSOURCE enmSource = SHCLSOURCE_INVALID;
+
+ /* The host announces the transfer direction from its point of view, so inverse the direction here. */
+ if (enmDir == SHCLTRANSFERDIR_TO_REMOTE)
+ {
+ enmDir = SHCLTRANSFERDIR_FROM_REMOTE;
+ enmSource = SHCLSOURCE_REMOTE;
+ }
+ else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE)
+ {
+ enmDir = SHCLTRANSFERDIR_TO_REMOTE;
+ enmSource = SHCLSOURCE_LOCAL;
+ }
+ else
+ AssertFailedBreakStmt(rc = VERR_INVALID_PARAMETER);
+
+ rc = vbglR3ClipboardTransferStart(pCmdCtx, pTransferCtx, uTransferID,
+ enmDir, enmSource, NULL /* ppTransfer */);
+ break;
+ }
+
+ case SHCLTRANSFERSTATUS_STOPPED:
+ RT_FALL_THROUGH();
+ case SHCLTRANSFERSTATUS_CANCELED:
+ RT_FALL_THROUGH();
+ case SHCLTRANSFERSTATUS_KILLED:
+ RT_FALL_THROUGH();
+ case SHCLTRANSFERSTATUS_ERROR:
+ {
+ rc = vbglR3ClipboardTransferStop(pCmdCtx, pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pEvent->u.TransferStatus.enmDir = enmDir;
+ pEvent->u.TransferStatus.Report = transferReport;
+ pEvent->u.TransferStatus.uID = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext);
+
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS;
+
+ LogRel2(("Shared Clipboard: Received status %s (rc=%Rrc) for transfer ID=%RU32\n",
+ ShClTransferStatusToStr(pEvent->u.TransferStatus.Report.uStatus), pEvent->u.TransferStatus.Report.rc,
+ pEvent->u.TransferStatus.uID));
+ }
+ }
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ:
+ {
+ uint32_t fRoots;
+ rc = VbglR3ClipboardRootListHdrReadReq(pCmdCtx, &fRoots);
+
+ /** @todo Validate / handle fRoots. */
+
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ SHCLROOTLISTHDR rootListHdr;
+ RT_ZERO(rootListHdr);
+
+ rootListHdr.cRoots = ShClTransferRootsCount(pTransfer);
+
+ LogFlowFunc(("cRoots=%RU32\n", rootListHdr.cRoots));
+
+ rc = VbglR3ClipboardRootListHdrReadReply(pCmdCtx, &rootListHdr);
+ }
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ:
+ {
+ uint32_t uIndex;
+ uint32_t fInfo;
+ rc = VbglR3ClipboardRootListEntryReadReq(pCmdCtx, &uIndex, &fInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ SHCLROOTLISTENTRY rootListEntry;
+ rc = ShClTransferRootsEntry(pTransfer, uIndex, &rootListEntry);
+ if (RT_SUCCESS(rc))
+ rc = VbglR3ClipboardRootListEntryReadReply(pCmdCtx, uIndex, &rootListEntry);
+ }
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN:
+ {
+ SHCLLISTOPENPARMS openParmsList;
+ rc = ShClTransferListOpenParmsInit(&openParmsList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardListOpenRecv(pCmdCtx, &openParmsList);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ LogFlowFunc(("pszPath=%s\n", openParmsList.pszPath));
+
+ SHCLLISTHANDLE hList = SHCLLISTHANDLE_INVALID;
+ rc = ShClTransferListOpen(pTransfer, &openParmsList, &hList);
+
+ /* Reply in any case. */
+ int rc2 = VbglR3ClipboardListOpenReply(pCmdCtx, rc, hList);
+ AssertRC(rc2);
+ }
+
+ ShClTransferListOpenParmsDestroy(&openParmsList);
+ }
+
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE:
+ {
+ SHCLLISTHANDLE hList;
+ rc = VbglR3ClipboardListCloseRecv(pCmdCtx, &hList);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ rc = ShClTransferListClose(pTransfer, hList);
+
+ /* Reply in any case. */
+ int rc2 = VbglR3ClipboardListCloseReply(pCmdCtx, rc, hList);
+ AssertRC(rc2);
+ }
+
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ:
+ {
+ /** @todo Handle filter + list features. */
+
+ SHCLLISTHANDLE hList = SHCLLISTHANDLE_INVALID;
+ uint32_t fFlags = 0;
+ rc = VbglR3ClipboardListHdrReadRecvReq(pCmdCtx, &hList, &fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ SHCLLISTHDR hdrList;
+ rc = ShClTransferListGetHeader(pTransfer, hList, &hdrList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardListHdrWrite(pCmdCtx, hList, &hdrList);
+
+ ShClTransferListHdrDestroy(&hdrList);
+ }
+ }
+
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ:
+ {
+ LogFlowFunc(("VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ\n"));
+
+ SHCLLISTENTRY entryList;
+ rc = ShClTransferListEntryInit(&entryList);
+ if (RT_SUCCESS(rc))
+ {
+ SHCLLISTHANDLE hList;
+ uint32_t fInfo;
+ rc = VbglR3ClipboardListEntryReadRecvReq(pCmdCtx, &hList, &fInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ rc = ShClTransferListRead(pTransfer, hList, &entryList);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLFSOBJINFO pObjInfo = (PSHCLFSOBJINFO)entryList.pvInfo;
+ Assert(entryList.cbInfo == sizeof(SHCLFSOBJINFO));
+
+ RT_NOREF(pObjInfo);
+
+ LogFlowFunc(("\t%s (%RU64 bytes)\n", entryList.pszName, pObjInfo->cbObject));
+
+ rc = VbglR3ClipboardListEntryWrite(pCmdCtx, hList, &entryList);
+ }
+ }
+
+ ShClTransferListEntryDestroy(&entryList);
+ }
+
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN:
+ {
+ SHCLOBJOPENCREATEPARMS openParms;
+ rc = ShClTransferObjOpenParmsInit(&openParms);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardObjOpenRecv(pCmdCtx, &openParms);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ SHCLOBJHANDLE hObj;
+ rc = ShClTransferObjOpen(pTransfer, &openParms, &hObj);
+
+ /* Reply in any case. */
+ int rc2 = VbglR3ClipboardObjOpenReply(pCmdCtx, rc, hObj);
+ AssertRC(rc2);
+ }
+
+ ShClTransferObjOpenParmsDestroy(&openParms);
+ }
+
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE:
+ {
+ SHCLOBJHANDLE hObj;
+ rc = VbglR3ClipboardObjCloseRecv(pCmdCtx, &hObj);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ rc = ShClTransferObjClose(pTransfer, hObj);
+
+ /* Reply in any case. */
+ int rc2 = VbglR3ClipboardObjCloseReply(pCmdCtx, rc, hObj);
+ AssertRC(rc2);
+ }
+
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ:
+ {
+ SHCLOBJHANDLE hObj;
+ uint32_t cbBuf;
+ uint32_t fFlags;
+ rc = VbglR3ClipboardObjReadRecv(pCmdCtx, &hObj, &cbBuf, &fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx,
+ VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext));
+ AssertPtrBreakStmt(pTransfer, rc = VERR_NOT_FOUND);
+
+ AssertBreakStmt(pCmdCtx->Transfers.cbChunkSize, rc = VERR_INVALID_PARAMETER);
+
+ const uint32_t cbToRead = RT_MIN(cbBuf, pCmdCtx->Transfers.cbChunkSize);
+
+ LogFlowFunc(("hObj=%RU64, cbBuf=%RU32, fFlags=0x%x -> cbChunkSize=%RU32, cbToRead=%RU32\n",
+ hObj, cbBuf, fFlags, pCmdCtx->Transfers.cbChunkSize, cbToRead));
+
+ void *pvBuf = RTMemAlloc(cbToRead);
+ if (pvBuf)
+ {
+ uint32_t cbRead;
+ rc = ShClTransferObjRead(pTransfer, hObj, pvBuf, cbToRead, fFlags, &cbRead);
+ if (RT_SUCCESS(rc))
+ rc = VbglR3ClipboardObjWriteSend(pCmdCtx, hObj, pvBuf, cbRead, NULL /* pcbWritten */);
+
+ RTMemFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ break;
+ }
+
+ default:
+ {
+ rc = VbglR3ClipboardEventGetNext(idMsg, cParms, pCmdCtx, pEvent);
+ if (RT_FAILURE(rc))
+ fErrorSent = true;
+ break;
+ }
+ }
+
+ if ( !fErrorSent
+ && RT_FAILURE(rc))
+ {
+ /* Report error back to the host. */
+ int rc2 = VbglR3ClipboardWriteError(pCmdCtx->idClient, rc);
+ AssertRC(rc2);
+ }
+ }
+ else
+ {
+ /*
+ * This builds on what we did in VbglR3ClipboardMsgPeekWait, so
+ * !HACK ALERT! cParms is the format flag or flags.
+ */
+ rc = VINF_SUCCESS;
+ switch (idMsg)
+ {
+ case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS;
+ pEvent->u.fReportedFormats = cParms;
+ break;
+
+ case VBOX_SHCL_HOST_MSG_READ_DATA:
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA;
+ pEvent->u.fReadData = cParms;
+ break;
+
+ case VBOX_SHCL_HOST_MSG_QUIT:
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_QUIT;
+ break;
+
+ default:
+ AssertMsgFailed(("%u (%#x)\n", idMsg, idMsg));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
+VBGLR3DECL(int) VbglR3ClipboardEventGetNext(uint32_t idMsg, uint32_t cParms, PVBGLR3SHCLCMDCTX pCtx, PVBGLR3CLIPBOARDEVENT pEvent)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ RT_NOREF(cParms);
+
+ int rc;
+ if (!pCtx->fUseLegacyProtocol)
+ {
+ LogFunc(("Handling idMsg=%RU32 (%s)\n", idMsg, ShClHostMsgToStr(idMsg)));
+ switch (idMsg)
+ {
+ case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ {
+ rc = vbglR3ClipboardFormatsReportRecv(pCtx, &pEvent->u.fReportedFormats);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS;
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_READ_DATA_CID:
+ {
+ rc = vbglR3ClipboardFetchReadDataCid(pCtx, &pEvent->u.fReadData);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA;
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_READ_DATA:
+ {
+ rc = vbglR3ClipboardFetchReadData(pCtx, &pEvent->u.fReadData);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA;
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_QUIT:
+ {
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_QUIT;
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ /** @todo r=bird: BUGBUG - need a skip command here! */
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Copy over our command context to the event. */
+ pEvent->cmdCtx = *pCtx;
+ }
+ else
+ {
+ /* Report error back to the host. */
+ int rc2 = VbglR3ClipboardWriteError(pCtx->idClient, rc);
+ AssertRC(rc2);
+ }
+ }
+ else
+ {
+ /*
+ * This builds on what we did in VbglR3ClipboardMsgPeekWait, so
+ * !HACK ALERT! cParms is the format flag or flags.
+ */
+ rc = VINF_SUCCESS;
+ switch (idMsg)
+ {
+ case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS;
+ pEvent->u.fReportedFormats = cParms;
+ break;
+
+ case VBOX_SHCL_HOST_MSG_READ_DATA:
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_READ_DATA;
+ pEvent->u.fReadData = cParms;
+ break;
+
+ case VBOX_SHCL_HOST_MSG_QUIT:
+ pEvent->enmType = VBGLR3CLIPBOARDEVENTTYPE_QUIT;
+ break;
+
+ default:
+ AssertMsgFailed(("%u (%#x)\n", idMsg, idMsg));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ pEvent->cmdCtx = *pCtx;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Frees (destroys) a formerly allocated Shared Clipboard event.
+ *
+ * @returns IPRT status code.
+ * @param pEvent Event to free (destroy).
+ */
+VBGLR3DECL(void) VbglR3ClipboardEventFree(PVBGLR3CLIPBOARDEVENT pEvent)
+{
+ if (!pEvent)
+ return;
+
+ /* Some messages require additional cleanup. */
+ switch (pEvent->enmType)
+ {
+ default:
+ break;
+ }
+
+ RTMemFree(pEvent);
+ pEvent = NULL;
+}
+
+/**
+ * Reports (advertises) guest clipboard formats to the host.
+ *
+ * Legacy function, do not use anymore.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3ClipboardConnect().
+ * @param fFormats The formats to report.
+ */
+VBGLR3DECL(int) VbglR3ClipboardReportFormats(HGCMCLIENTID idClient, uint32_t fFormats)
+{
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ VBoxShClParmReportFormats Parms;
+ } Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, VBOX_SHCL_CPARMS_REPORT_FORMATS);
+ VbglHGCMParmUInt32Set(&Msg.Parms.f32Formats, fFormats);
+
+ int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends guest clipboard data to the host.
+ *
+ * Legacy function kept for compatibility, do not use anymore.
+ *
+ * This is usually called in reply to a VBOX_SHCL_HOST_MSG_READ_DATA message
+ * from the host.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3ClipboardConnect().
+ * @param fFormat The format of the data.
+ * @param pvData Pointer to the data to send. Can be NULL if @a cbData
+ * is zero.
+ * @param cbData Number of bytes of data to send. Zero is valid.
+ */
+VBGLR3DECL(int) VbglR3ClipboardWriteData(HGCMCLIENTID idClient, uint32_t fFormat, void *pv, uint32_t cb)
+{
+ LogFlowFuncEnter();
+
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ VBoxShClParmDataWriteOld Parms;
+ } Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, VBOX_SHCL_CPARMS_DATA_WRITE_OLD);
+ VbglHGCMParmUInt32Set(&Msg.Parms.f32Format, fFormat);
+ VbglHGCMParmPtrSet(&Msg.Parms.pData, pv, cb);
+
+ int rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sends guest clipboard data to the host.
+ *
+ * This is usually called in reply to a VBOX_SHCL_HOST_MSG_READ_DATA message
+ * from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx The command context returned by VbglR3ClipboardConnectEx().
+ * @param fFormat Clipboard format to send.
+ * @param pvData Pointer to the data to send. Can be NULL if @a cbData
+ * is zero.
+ * @param cbData Number of bytes of data to send. Zero is valid.
+ */
+VBGLR3DECL(int) VbglR3ClipboardWriteDataEx(PVBGLR3SHCLCMDCTX pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData)
+{
+ LogFlowFunc(("ENTER: fFormat=%#x pvData=%p cbData=%#x\n", fFormat, pvData, cbData));
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ if (cbData > 0)
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+
+ int rc;
+ if (pCtx->fUseLegacyProtocol)
+ rc = VbglR3ClipboardWriteData(pCtx->idClient, fFormat, pvData, cbData);
+ else
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ VBoxShClParmDataWrite Parms;
+ } Msg;
+
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->idClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, VBOX_SHCL_CPARMS_DATA_WRITE);
+ Msg.Parms.id64Context.SetUInt64(pCtx->idContext);
+ Msg.Parms.f32Format.SetUInt32(fFormat);
+ Msg.Parms.pData.SetPtr(pvData, cbData);
+
+ LogFlowFunc(("CID=%RU32\n", pCtx->idContext));
+
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Writes an error to the host.
+ *
+ * @returns IPRT status code.
+ * @param idClient The client id returned by VbglR3ClipboardConnect().
+ * @param rcErr Error (IPRT-style) to send.
+ */
+VBGLR3DECL(int) VbglR3ClipboardWriteError(HGCMCLIENTID idClient, int rcErr)
+{
+ AssertReturn(idClient, VERR_INVALID_PARAMETER);
+
+ VBoxShClWriteErrorMsg Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, VBOX_SHCL_GUEST_FN_ERROR, VBOX_SHCL_CPARMS_ERROR);
+
+ /** @todo Context ID not used yet. */
+ Msg.uContext.SetUInt64(0);
+ Msg.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("Sending error %Rrc failed with rc=%Rrc\n", rcErr, rc));
+ if (rc == VERR_NOT_SUPPORTED)
+ rc = VINF_SUCCESS;
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Reporting error %Rrc to the host failed with %Rrc\n", rcErr, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp
new file mode 100644
index 00000000..35971006
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCoreDump.cpp
@@ -0,0 +1,56 @@
+/* $Id: VBoxGuestR3LibCoreDump.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Core Dumps.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Write guest core dump.
+ *
+ * @returns IPRT status code.
+ */
+VBGLR3DECL(int) VbglR3WriteCoreDump(void)
+{
+ VBGLIOCWRITECOREDUMP Req;
+ VBGLREQHDR_INIT(&Req.Hdr, WRITE_CORE_DUMP);
+ Req.u.In.fFlags = 0;
+ return vbglR3DoIOCtl(VBGL_IOCTL_WRITE_CORE_DUMP, &Req.Hdr, sizeof(Req));
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp
new file mode 100644
index 00000000..00aa0257
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCpuHotPlug.cpp
@@ -0,0 +1,133 @@
+/* $Id: VBoxGuestR3LibCpuHotPlug.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, CPU Hot Plugging.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+
+/**
+ * Initialize CPU hot plugging.
+ *
+ * This will enable the CPU hot plugging events.
+ *
+ * @returns VBox status code.
+ */
+VBGLR3DECL(int) VbglR3CpuHotPlugInit(void)
+{
+ int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_CPU_HOTPLUG, 0);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ VMMDevCpuHotPlugStatusRequest Req;
+ vmmdevInitRequest(&Req.header, VMMDevReq_SetCpuHotPlugStatus);
+ Req.enmStatusType = VMMDevCpuStatusType_Enable;
+ rc = vbglR3GRPerform(&Req.header);
+ if (RT_FAILURE(rc))
+ VbglR3CtlFilterMask(0, VMMDEV_EVENT_CPU_HOTPLUG);
+
+ return rc;
+}
+
+
+/**
+ * Terminate CPU hot plugging.
+ *
+ * This will disable the CPU hot plugging events.
+ *
+ * @returns VBox status code.
+ */
+VBGLR3DECL(int) VbglR3CpuHotPlugTerm(void)
+{
+ /* Clear the events. */
+ VbglR3CtlFilterMask(0, VMMDEV_EVENT_CPU_HOTPLUG);
+
+ VMMDevCpuHotPlugStatusRequest Req;
+ vmmdevInitRequest(&Req.header, VMMDevReq_SetCpuHotPlugStatus);
+ Req.enmStatusType = VMMDevCpuStatusType_Disable;
+ return vbglR3GRPerform(&Req.header);
+}
+
+
+/**
+ * Waits for a CPU hot plugging event and retrieve the data associated with it.
+ *
+ * @returns VBox status code.
+ * @param penmEventType Where to store the event type on success.
+ * @param pidCpuCore Where to store the CPU core ID on success.
+ * @param pidCpuPackage Where to store the CPU package ID on success.
+ */
+VBGLR3DECL(int) VbglR3CpuHotPlugWaitForEvent(VMMDevCpuEventType *penmEventType, uint32_t *pidCpuCore, uint32_t *pidCpuPackage)
+{
+ AssertPtrReturn(penmEventType, VERR_INVALID_POINTER);
+ AssertPtrReturn(pidCpuCore, VERR_INVALID_POINTER);
+ AssertPtrReturn(pidCpuPackage, VERR_INVALID_POINTER);
+
+ uint32_t fEvents = 0;
+ int rc = VbglR3WaitEvent(VMMDEV_EVENT_CPU_HOTPLUG, RT_INDEFINITE_WAIT, &fEvents);
+ if (RT_SUCCESS(rc))
+ {
+ /* did we get the right event? */
+ if (fEvents & VMMDEV_EVENT_CPU_HOTPLUG)
+ {
+ VMMDevGetCpuHotPlugRequest Req;
+
+ /* get the CPU hot plugging request */
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetCpuHotPlugRequest);
+ Req.idCpuCore = UINT32_MAX;
+ Req.idCpuPackage = UINT32_MAX;
+ Req.enmEventType = VMMDevCpuEventType_None;
+ rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ {
+ *penmEventType = Req.enmEventType;
+ *pidCpuCore = Req.idCpuCore;
+ *pidCpuPackage = Req.idCpuPackage;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_TRY_AGAIN;
+ }
+ else if (rc == VERR_TIMEOUT) /* just in case */
+ rc = VERR_TRY_AGAIN;
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp
new file mode 100644
index 00000000..b4d795f7
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibCredentials.cpp
@@ -0,0 +1,222 @@
+/* $Id: VBoxGuestR3LibCredentials.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, user credentials.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <VBox/log.h>
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Checks whether user credentials are available to the guest or not.
+ *
+ * @returns IPRT status value; VINF_SUCCESS if credentials are available,
+ * VERR_NOT_FOUND if not. Otherwise an error is occurred.
+ */
+VBGLR3DECL(int) VbglR3CredentialsQueryAvailability(void)
+{
+ VMMDevCredentials Req;
+ RT_ZERO(Req);
+ vmmdevInitRequest((VMMDevRequestHeader*)&Req, VMMDevReq_QueryCredentials);
+ Req.u32Flags |= VMMDEV_CREDENTIALS_QUERYPRESENCE;
+
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ {
+ if ((Req.u32Flags & VMMDEV_CREDENTIALS_PRESENT) == 0)
+ rc = VERR_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+/**
+ * Retrieves and clears the user credentials for logging into the guest OS.
+ *
+ * @returns IPRT status value
+ * @param ppszUser Receives pointer of allocated user name string.
+ * The returned pointer must be freed using VbglR3CredentialsDestroy().
+ * @param ppszPassword Receives pointer of allocated user password string.
+ * The returned pointer must be freed using VbglR3CredentialsDestroy().
+ * @param ppszDomain Receives pointer of allocated domain name string.
+ * The returned pointer must be freed using VbglR3CredentialsDestroy().
+ */
+VBGLR3DECL(int) VbglR3CredentialsRetrieve(char **ppszUser, char **ppszPassword, char **ppszDomain)
+{
+ AssertPtrReturn(ppszUser, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszPassword, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszDomain, VERR_INVALID_POINTER);
+
+ VMMDevCredentials Req;
+ RT_ZERO(Req);
+ vmmdevInitRequest((VMMDevRequestHeader*)&Req, VMMDevReq_QueryCredentials);
+ Req.u32Flags |= VMMDEV_CREDENTIALS_READ | VMMDEV_CREDENTIALS_CLEAR;
+
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrDupEx(ppszUser, Req.szUserName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrDupEx(ppszPassword, Req.szPassword);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrDupEx(ppszDomain, Req.szDomain);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTStrFree(*ppszPassword);
+ }
+ RTStrFree(*ppszUser);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Retrieves and clears the user credentials for logging into the guest OS.
+ * UTF-16 version.
+ *
+ * @returns IPRT status value
+ * @param ppwszUser Receives pointer of allocated user name string.
+ * The returned pointer must be freed using VbglR3CredentialsDestroyUtf16().
+ * @param ppwszPassword Receives pointer of allocated user password string.
+ * The returned pointer must be freed using VbglR3CredentialsDestroyUtf16().
+ * @param ppwszDomain Receives pointer of allocated domain name string.
+ * The returned pointer must be freed using VbglR3CredentialsDestroyUtf16().
+ */
+VBGLR3DECL(int) VbglR3CredentialsRetrieveUtf16(PRTUTF16 *ppwszUser, PRTUTF16 *ppwszPassword, PRTUTF16 *ppwszDomain)
+{
+ AssertPtrReturn(ppwszUser, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppwszPassword, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppwszDomain, VERR_INVALID_POINTER);
+
+ char *pszUser, *pszPassword, *pszDomain;
+ int rc = VbglR3CredentialsRetrieve(&pszUser, &pszPassword, &pszDomain);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 pwszUser = NULL;
+ PRTUTF16 pwszPassword = NULL;
+ PRTUTF16 pwszDomain = NULL;
+
+ rc = RTStrToUtf16(pszUser, &pwszUser);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrToUtf16(pszPassword, &pwszPassword);
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUtf16(pszDomain, &pwszDomain);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppwszUser = pwszUser;
+ *ppwszPassword = pwszPassword;
+ *ppwszDomain = pwszDomain;
+ }
+ else
+ VbglR3CredentialsDestroyUtf16(pwszUser, pwszPassword, pwszDomain, 3 /* Passes */);
+ VbglR3CredentialsDestroy(pszUser, pszPassword, pszDomain, 3 /* Passes */);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Clears and frees the three strings.
+ *
+ * @param pszUser Receives pointer of the user name string to destroy.
+ * Optional.
+ * @param pszPassword Receives pointer of the password string to destroy.
+ * Optional.
+ * @param pszDomain Receives pointer of allocated domain name string.
+ * Optional.
+ * @param cPasses Number of wipe passes. The more the better + slower.
+ */
+VBGLR3DECL(void) VbglR3CredentialsDestroy(char *pszUser, char *pszPassword, char *pszDomain, uint32_t cPasses)
+{
+ /* wipe first */
+ if (pszUser)
+ RTMemWipeThoroughly(pszUser, strlen(pszUser) + 1, cPasses);
+ if (pszPassword)
+ RTMemWipeThoroughly(pszPassword, strlen(pszPassword) + 1, cPasses);
+ if (pszDomain)
+ RTMemWipeThoroughly(pszDomain, strlen(pszDomain) + 1, cPasses);
+
+ /* then free. */
+ RTStrFree(pszUser);
+ RTStrFree(pszPassword);
+ RTStrFree(pszDomain);
+}
+
+
+/**
+ * Clears and frees the three strings. UTF-16 version.
+ *
+ * @param pwszUser Receives pointer of the user name string to destroy.
+ * Optional.
+ * @param pwszPassword Receives pointer of the password string to destroy.
+ * Optional.
+ * @param pwszDomain Receives pointer of allocated domain name string.
+ * Optional.
+ * @param cPasses Number of wipe passes. The more the better + slower.
+ */
+VBGLR3DECL(void) VbglR3CredentialsDestroyUtf16(PRTUTF16 pwszUser, PRTUTF16 pwszPassword, PRTUTF16 pwszDomain,
+ uint32_t cPasses)
+{
+ /* wipe first */
+ if (pwszUser)
+ RTMemWipeThoroughly(pwszUser, (RTUtf16Len(pwszUser) + 1) * sizeof(RTUTF16), cPasses);
+ if (pwszPassword)
+ RTMemWipeThoroughly(pwszPassword, (RTUtf16Len(pwszPassword) + 1) * sizeof(RTUTF16), cPasses);
+ if (pwszDomain)
+ RTMemWipeThoroughly(pwszDomain, (RTUtf16Len(pwszDomain) + 1) * sizeof(RTUTF16), cPasses);
+
+ /* then free. */
+ RTUtf16Free(pwszUser);
+ RTUtf16Free(pwszPassword);
+ RTUtf16Free(pwszDomain);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp
new file mode 100644
index 00000000..2aad8c5c
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDaemonize.cpp
@@ -0,0 +1,326 @@
+/** $Id: VBoxGuestR3LibDaemonize.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, daemonize a process.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#if defined(RT_OS_OS2)
+# define INCL_BASE
+# define INCL_ERRORS
+# include <os2.h>
+
+# include <iprt/alloca.h>
+# include <iprt/string.h>
+
+#elif defined(RT_OS_WINDOWS)
+# error "PORTME"
+
+#else /* the unices */
+# include <sys/types.h>
+# include <sys/stat.h>
+# include <sys/wait.h>
+# include <stdio.h>
+# include <fcntl.h>
+# include <stdlib.h>
+# include <unistd.h>
+# include <signal.h>
+# include <errno.h>
+#endif
+
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Daemonize the process for running in the background.
+ *
+ * This is supposed to do the same job as the BSD daemon() call.
+ *
+ * @returns 0 on success
+ *
+ * @param fNoChDir Pass false to change working directory to root.
+ * @param fNoClose Pass false to redirect standard file streams to /dev/null.
+ * @param fRespawn Restart the daemonised process after five seconds if it
+ * terminates abnormally.
+ * @param pcRespawn Where to store a count of how often we have respawned,
+ * intended for avoiding error spamming. Optional.
+ * @param fReturnOnUpdate If True, this function will return control to caller when
+ * child process will terminate with exit code of VBGLR3EXITCODERELOAD,
+ * indicating that Guest Additions update has been started and this running
+ * process will be asked to be restarted by arrival of the next SIGUSR1
+ * signal (caller should wait for SIGUSR1). If False, this functions will
+ * never return, but rather exit() when child process terminates with
+ * exit code 0.
+ * @param pfUpdateStarted A flag which passed to caller if fReturnOnUpdate is True (can be NULL).
+ * @param szPidfile Optional path to parent process' pidfile (can be NULL).
+ * @param phPidfile Optional path to parent process' pidfile handle (can not be NULL if
+ * szPidfile was specified).
+ *
+ * @todo Use RTProcDaemonize instead of this.
+ * @todo Implement fRespawn on OS/2.
+ * @todo Make the respawn interval configurable. But not until someone
+ * actually needs that.
+ */
+VBGLR3DECL(int) VbglR3DaemonizeEx(bool fNoChDir, bool fNoClose, bool fRespawn, unsigned *pcRespawn,
+ bool fReturnOnUpdate, bool *pfUpdateStarted, const char *szPidfile,
+ RTFILE *phPidfile)
+{
+#if defined(RT_OS_OS2)
+ PPIB pPib;
+ PTIB pTib;
+ DosGetInfoBlocks(&pTib, &pPib);
+
+ RT_NOREF(fReturnOnUpdate);
+ RT_NOREF(pfUpdateStarted);
+ RT_NOREF(szPidfile);
+ RT_NOREF(phPidfile);
+
+ AssertRelease(!fRespawn);
+ /* Get the full path to the executable. */
+ char szExe[CCHMAXPATH];
+ APIRET rc = DosQueryModuleName(pPib->pib_hmte, sizeof(szExe), szExe);
+ if (rc)
+ return RTErrConvertFromOS2(rc);
+
+ /* calc the length of the command line. */
+ char *pch = pPib->pib_pchcmd;
+ size_t cch0 = strlen(pch);
+ pch += cch0 + 1;
+ size_t cch1 = strlen(pch);
+ pch += cch1 + 1;
+ char *pchArgs;
+ if (cch1 && *pch)
+ {
+ do pch = strchr(pch, '\0') + 1;
+ while (*pch);
+
+ size_t cchTotal = pch - pPib->pib_pchcmd;
+ pchArgs = (char *)alloca(cchTotal + sizeof("--daemonized\0\0"));
+ memcpy(pchArgs, pPib->pib_pchcmd, cchTotal - 1);
+ memcpy(pchArgs + cchTotal - 1, "--daemonized\0\0", sizeof("--daemonized\0\0"));
+ }
+ else
+ {
+ size_t cchTotal = pch - pPib->pib_pchcmd + 1;
+ pchArgs = (char *)alloca(cchTotal + sizeof(" --daemonized "));
+ memcpy(pchArgs, pPib->pib_pchcmd, cch0 + 1);
+ pch = pchArgs + cch0 + 1;
+ memcpy(pch, " --daemonized ", sizeof(" --daemonized ") - 1);
+ pch += sizeof(" --daemonized ") - 1;
+ if (cch1)
+ memcpy(pch, pPib->pib_pchcmd + cch0 + 1, cch1 + 2);
+ else
+ pch[0] = pch[1] = '\0';
+ }
+
+ /* spawn a detach process */
+ char szObj[128];
+ RESULTCODES ResCodes = { 0, 0 };
+ szObj[0] = '\0';
+ rc = DosExecPgm(szObj, sizeof(szObj), EXEC_BACKGROUND, (PCSZ)pchArgs, NULL, &ResCodes, (PCSZ)szExe);
+ if (rc)
+ {
+ /** @todo Change this to some standard log/print error?? */
+ /* VBoxServiceError("DosExecPgm failed with rc=%d and szObj='%s'\n", rc, szObj); */
+ return RTErrConvertFromOS2(rc);
+ }
+ DosExit(EXIT_PROCESS, 0);
+ return VERR_GENERAL_FAILURE;
+
+#elif defined(RT_OS_WINDOWS)
+# error "PORTME"
+
+#else /* the unices */
+ /*
+ * Fork the child process in a new session and quit the parent.
+ *
+ * - fork once and create a new session (setsid). This will detach us
+ * from the controlling tty meaning that we won't receive the SIGHUP
+ * (or any other signal) sent to that session.
+ * - The SIGHUP signal is ignored because the session/parent may throw
+ * us one before we get to the setsid.
+ * - When the parent exit(0) we will become an orphan and re-parented to
+ * the init process.
+ * - Because of the Linux / System V semantics of assigning the controlling
+ * tty automagically when a session leader first opens a tty, we will
+ * fork() once more on Linux to get rid of the session leadership role.
+ */
+
+ struct sigaction OldSigAct;
+ struct sigaction SigAct;
+ RT_ZERO(SigAct);
+ SigAct.sa_handler = SIG_IGN;
+ int rcSigAct = sigaction(SIGHUP, &SigAct, &OldSigAct);
+
+ pid_t pid = fork();
+ if (pid == -1)
+ return RTErrConvertFromErrno(errno);
+ if (pid != 0)
+ exit(0);
+
+ /*
+ * The orphaned child becomes is reparented to the init process.
+ * We create a new session for it (setsid), point the standard
+ * file descriptors to /dev/null, and change to the root directory.
+ */
+ pid_t newpgid = setsid();
+ int SavedErrno = errno;
+ if (rcSigAct != -1)
+ sigaction(SIGHUP, &OldSigAct, NULL);
+ if (newpgid == -1)
+ return RTErrConvertFromErrno(SavedErrno);
+
+ if (!fNoClose)
+ {
+ /* Open stdin(0), stdout(1) and stderr(2) as /dev/null. */
+ int fd = open("/dev/null", O_RDWR);
+ if (fd == -1) /* paranoia */
+ {
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ fd = open("/dev/null", O_RDWR);
+ }
+ if (fd != -1)
+ {
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if (fd > 2)
+ close(fd);
+ }
+ }
+
+ if (!fNoChDir)
+ {
+ int rcShutUpGcc = chdir("/");
+ RT_NOREF_PV(rcShutUpGcc);
+ }
+
+ /*
+ * Change the umask - this is non-standard daemon() behavior.
+ */
+ umask(027);
+
+# ifdef RT_OS_LINUX
+ /*
+ * And fork again to lose session leader status (non-standard daemon()
+ * behaviour).
+ */
+ pid = fork();
+ if (pid == -1)
+ return RTErrConvertFromErrno(errno);
+ if (pid != 0)
+ exit(0);
+# endif /* RT_OS_LINUX */
+
+ /* Check if another instance is already running. */
+ if (szPidfile != NULL)
+ {
+ if (phPidfile != NULL)
+ {
+ int rc = VbglR3PidfileWait(szPidfile, phPidfile, 5000);
+
+ /* Another instance of process is already running. */
+ if (rc == VERR_FILE_LOCK_VIOLATION)
+ {
+ LogRel(("cannot aquire pidfile %s, exitting\n", szPidfile));
+ exit(1);
+ }
+
+ /* Unable to lock on pidfile. */
+ if (RT_FAILURE(rc))
+ exit(1);
+ }
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (fRespawn)
+ {
+ /* We implement re-spawning as a third fork(), with the parent process
+ * monitoring the child and re-starting it after a delay if it exits
+ * abnormally. */
+ unsigned cRespawn = 0;
+ for (;;)
+ {
+ int iStatus, rcWait;
+
+ if (pcRespawn != NULL)
+ *pcRespawn = cRespawn;
+ pid = fork();
+ if (pid == -1)
+ return RTErrConvertFromErrno(errno);
+ if (pid == 0)
+ return VINF_SUCCESS;
+ do
+ rcWait = waitpid(pid, &iStatus, 0);
+ while (rcWait == -1 && errno == EINTR);
+ if (rcWait == -1)
+ exit(1);
+ if (WIFEXITED(iStatus))
+ {
+ if (WEXITSTATUS(iStatus) == 0)
+ exit(0);
+ else if (fReturnOnUpdate && WEXITSTATUS(iStatus) == VBGLR3EXITCODERELOAD)
+ {
+ /* Tell caller that update has been started. */
+ if (pfUpdateStarted != NULL)
+ *pfUpdateStarted = true;
+
+ return VINF_SUCCESS;
+ }
+ }
+ sleep(5);
+ ++cRespawn;
+ }
+ }
+ return VINF_SUCCESS;
+#endif
+}
+
+/**
+ * A wrapper function for VbglR3DaemonizeEx.
+ */
+VBGLR3DECL(int) VbglR3Daemonize(bool fNoChDir, bool fNoClose, bool fRespawn, unsigned *pcRespawn)
+{
+ return VbglR3DaemonizeEx(fNoChDir, fNoClose, fRespawn, pcRespawn, false, NULL, NULL, NULL);
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp
new file mode 100644
index 00000000..68ba038b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDragAndDrop.cpp
@@ -0,0 +1,1948 @@
+/* $Id: VBoxGuestR3LibDragAndDrop.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Drag & Drop.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/path.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/uri.h>
+#include <iprt/thread.h>
+
+#include <iprt/cpp/list.h>
+#include <iprt/cpp/ministring.h>
+
+#ifdef LOG_GROUP
+ #undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include <VBox/log.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/DragAndDrop.h>
+#include <VBox/HostServices/DragAndDropSvc.h>
+
+using namespace DragAndDropSvc;
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+/*********************************************************************************************************************************
+* Private internal functions *
+*********************************************************************************************************************************/
+
+/**
+ * Receives the next upcoming message for a given DnD context.
+ *
+ * @returns IPRT status code.
+ * Will return VERR_CANCELLED (implemented by the host service) if we need to bail out.
+ * @param pCtx DnD context to use.
+ * @param puMsg Where to store the message type.
+ * @param pcParms Where to store the number of parameters required for receiving the message.
+ * @param fWait Whether to wait (block) for a new message to arrive or not.
+ */
+static int vbglR3DnDGetNextMsgType(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t *puMsg, uint32_t *pcParms, bool fWait)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(puMsg, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcParms, VERR_INVALID_POINTER);
+
+ int rc;
+
+ do
+ {
+ HGCMMsgGetNext Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GET_NEXT_HOST_MSG, 3);
+ Msg.uMsg.SetUInt32(0);
+ Msg.cParms.SetUInt32(0);
+ Msg.fBlock.SetUInt32(fWait ? 1 : 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ rc = Msg.uMsg.GetUInt32(puMsg); AssertRC(rc);
+ rc = Msg.cParms.GetUInt32(pcParms); AssertRC(rc);
+ }
+
+ LogRel(("DnD: Received message %s (%#x) from host\n", DnDHostMsgToStr(*puMsg), *puMsg));
+
+ } while (rc == VERR_INTERRUPTED);
+
+ return rc;
+}
+
+
+/**
+ * Sends a DnD error back to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param rcErr Error (IPRT-style) to send.
+ */
+VBGLR3DECL(int) VbglR3DnDSendError(PVBGLR3GUESTDNDCMDCTX pCtx, int rcErr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgGHError Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_EVT_ERROR, 2);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ /*
+ * Never return an error if the host did not accept the error at the current
+ * time. This can be due to the host not having any appropriate callbacks
+ * set which would handle that error.
+ *
+ * bird: Looks like VERR_NOT_SUPPORTED is what the host will return if it
+ * doesn't an appropriate callback. The code used to ignore ALL errors
+ * the host would return, also relevant ones.
+ */
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("Sending error %Rrc failed with rc=%Rrc\n", rcErr, rc));
+ if (rc == VERR_NOT_SUPPORTED)
+ rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive a so-called "action message" from the host.
+ * Certain DnD messages use the same amount / sort of parameters and grouped as "action messages".
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param uMsg Which kind of message to receive.
+ * @param puScreenID Where to store the host screen ID the message is bound to. Optional.
+ * @param puX Where to store the absolute X coordinates. Optional.
+ * @param puY Where to store the absolute Y coordinates. Optional.
+ * @param puDefAction Where to store the default action to perform. Optional.
+ * @param puAllActions Where to store the available actions. Optional.
+ * @param ppszFormats Where to store List of formats. Optional.
+ * @param pcbFormats Size (in bytes) of where to store the list of formats. Optional.
+ *
+ * @todo r=andy Get rid of this function as soon as we resolved the protocol TODO #1.
+ * This was part of the initial protocol and needs to go.
+ */
+static int vbglR3DnDHGRecvAction(PVBGLR3GUESTDNDCMDCTX pCtx,
+ uint32_t uMsg,
+ uint32_t *puScreenID,
+ uint32_t *puX,
+ uint32_t *puY,
+ uint32_t *puDefAction,
+ uint32_t *puAllActions,
+ char **ppszFormats,
+ uint32_t *pcbFormats)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ /* The rest is optional. */
+
+ const uint32_t cbFormatsTmp = pCtx->cbMaxChunkSize;
+
+ char *pszFormatsTmp = static_cast<char *>(RTMemAlloc(cbFormatsTmp));
+ if (!pszFormatsTmp)
+ return VERR_NO_MEMORY;
+
+ HGCMMsgHGAction Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, uMsg, 8);
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.uScreenId.SetUInt32(0);
+ Msg.u.v3.uX.SetUInt32(0);
+ Msg.u.v3.uY.SetUInt32(0);
+ Msg.u.v3.uDefAction.SetUInt32(0);
+ Msg.u.v3.uAllActions.SetUInt32(0);
+ Msg.u.v3.pvFormats.SetPtr(pszFormatsTmp, cbFormatsTmp);
+ Msg.u.v3.cbFormats.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Context ID not used yet. */
+ if (RT_SUCCESS(rc) && puScreenID)
+ rc = Msg.u.v3.uScreenId.GetUInt32(puScreenID);
+ if (RT_SUCCESS(rc) && puX)
+ rc = Msg.u.v3.uX.GetUInt32(puX);
+ if (RT_SUCCESS(rc) && puY)
+ rc = Msg.u.v3.uY.GetUInt32(puY);
+ if (RT_SUCCESS(rc) && puDefAction)
+ rc = Msg.u.v3.uDefAction.GetUInt32(puDefAction);
+ if (RT_SUCCESS(rc) && puAllActions)
+ rc = Msg.u.v3.uAllActions.GetUInt32(puAllActions);
+ if (RT_SUCCESS(rc) && pcbFormats)
+ rc = Msg.u.v3.cbFormats.GetUInt32(pcbFormats);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (ppszFormats)
+ {
+ *ppszFormats = RTStrDup(pszFormatsTmp);
+ if (!*ppszFormats)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ RTStrFree(pszFormatsTmp);
+
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive a HOST_DND_FN_HG_EVT_LEAVE message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ */
+static int vbglR3DnDHGRecvLeave(PVBGLR3GUESTDNDCMDCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgHGLeave Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_EVT_LEAVE, 1);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive a HOST_DND_FN_HG_EVT_CANCEL message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ */
+static int vbglR3DnDHGRecvCancel(PVBGLR3GUESTDNDCMDCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgHGCancel Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_CANCEL, 1);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive a HOST_DND_FN_HG_SND_DIR message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pszDirname Where to store the directory name of the directory being created.
+ * @param cbDirname Size (in bytes) of where to store the directory name of the directory being created.
+ * @param pcbDirnameRecv Size (in bytes) of the actual directory name received.
+ * @param pfMode Where to store the directory creation mode.
+ */
+static int vbglR3DnDHGRecvDir(PVBGLR3GUESTDNDCMDCTX pCtx,
+ char *pszDirname,
+ uint32_t cbDirname,
+ uint32_t *pcbDirnameRecv,
+ uint32_t *pfMode)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDirname, VERR_INVALID_POINTER);
+ AssertReturn(cbDirname, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbDirnameRecv, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfMode, VERR_INVALID_POINTER);
+
+ HGCMMsgHGSendDir Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_DIR, 4);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.pvName.SetPtr(pszDirname, cbDirname);
+ Msg.u.v3.cbName.SetUInt32(cbDirname);
+ Msg.u.v3.fMode.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Context ID not used yet. */
+ rc = Msg.u.v3.cbName.GetUInt32(pcbDirnameRecv); AssertRC(rc);
+ rc = Msg.u.v3.fMode.GetUInt32(pfMode); AssertRC(rc);
+
+ AssertReturn(cbDirname >= *pcbDirnameRecv, VERR_TOO_MUCH_DATA);
+ }
+
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive a HOST_DND_FN_HG_SND_FILE_DATA message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pvData Where to store the file data chunk.
+ * @param cbData Size (in bytes) of where to store the data chunk.
+ * @param pcbDataRecv Size (in bytes) of the actual data chunk size received.
+ */
+static int vbglR3DnDHGRecvFileData(PVBGLR3GUESTDNDCMDCTX pCtx,
+ void *pvData,
+ uint32_t cbData,
+ uint32_t *pcbDataRecv)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbDataRecv, VERR_INVALID_POINTER);
+
+ HGCMMsgHGSendFileData Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_FILE_DATA, 5);
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.pvData.SetPtr(pvData, cbData);
+ Msg.u.v3.cbData.SetUInt32(0);
+ Msg.u.v3.pvChecksum.SetPtr(NULL, 0);
+ Msg.u.v3.cbChecksum.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Context ID not used yet. */
+ rc = Msg.u.v3.cbData.GetUInt32(pcbDataRecv); AssertRC(rc);
+ AssertReturn(cbData >= *pcbDataRecv, VERR_TOO_MUCH_DATA);
+ /** @todo Add checksum support. */
+ }
+
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive the HOST_DND_FN_HG_SND_FILE_HDR message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pszFilename Where to store the file name of the file being transferred.
+ * @param cbFilename Size (in bytes) of where to store the file name of the file being transferred.
+ * @param puFlags File transfer flags. Currently not being used.
+ * @param pfMode Where to store the file creation mode.
+ * @param pcbTotal Where to store the file size (in bytes).
+ */
+static int vbglR3DnDHGRecvFileHdr(PVBGLR3GUESTDNDCMDCTX pCtx,
+ char *pszFilename,
+ uint32_t cbFilename,
+ uint32_t *puFlags,
+ uint32_t *pfMode,
+ uint64_t *pcbTotal)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(cbFilename, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puFlags, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfMode, VERR_INVALID_POINTER);
+ AssertReturn(pcbTotal, VERR_INVALID_POINTER);
+
+ HGCMMsgHGSendFileHdr Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_FILE_HDR, 6);
+ Msg.uContext.SetUInt32(0); /** @todo Not used yet. */
+ Msg.pvName.SetPtr(pszFilename, cbFilename);
+ Msg.cbName.SetUInt32(cbFilename);
+ Msg.uFlags.SetUInt32(0);
+ Msg.fMode.SetUInt32(0);
+ Msg.cbTotal.SetUInt64(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Get context ID. */
+ rc = Msg.uFlags.GetUInt32(puFlags); AssertRC(rc);
+ rc = Msg.fMode.GetUInt32(pfMode); AssertRC(rc);
+ rc = Msg.cbTotal.GetUInt64(pcbTotal); AssertRC(rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Helper function for receiving URI data from the host. Do not call directly.
+ * This function also will take care of the file creation / locking on the guest.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_CANCELLED if the transfer was cancelled by the host.
+ * @param pCtx DnD context to use.
+ * @param pDataHdr DnD data header to use. Needed for accounting.
+ * @param pDroppedFiles Dropped files object to use for maintaining the file creation / locking.
+ */
+static int vbglR3DnDHGRecvURIData(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr, PDNDDROPPEDFILES pDroppedFiles)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDroppedFiles, VERR_INVALID_POINTER);
+
+ /* Only count the raw data minus the already received meta data. */
+ Assert(pDataHdr->cbTotal >= pDataHdr->cbMeta);
+ uint64_t cbToRecvBytes = pDataHdr->cbTotal - pDataHdr->cbMeta;
+ uint64_t cToRecvObjs = pDataHdr->cObjects;
+
+ LogFlowFunc(("cbToRecvBytes=%RU64, cToRecvObjs=%RU64, (cbTotal=%RU64, cbMeta=%RU32)\n",
+ cbToRecvBytes, cToRecvObjs, pDataHdr->cbTotal, pDataHdr->cbMeta));
+
+ /* Anything to do at all? */
+ /* Note: Do not check for cbToRecvBytes == 0 here, as this might be just
+ * a bunch of 0-byte files to be transferred. */
+ if (!cToRecvObjs)
+ return VINF_SUCCESS;
+
+ LogRel2(("DnD: Receiving URI data started\n"));
+
+ /*
+ * Allocate temporary chunk buffer.
+ */
+ uint32_t cbChunkMax = pCtx->cbMaxChunkSize;
+ void *pvChunk = RTMemAlloc(cbChunkMax);
+ if (!pvChunk)
+ return VERR_NO_MEMORY;
+ uint32_t cbChunkRead = 0;
+
+ uint64_t cbFileSize = 0; /* Total file size (in bytes). */
+ uint64_t cbFileWritten = 0; /* Written bytes. */
+
+ const char *pszDropDir = DnDDroppedFilesGetDirAbs(pDroppedFiles);
+ AssertPtr(pszDropDir);
+
+ int rc;
+
+ /*
+ * Enter the main loop of retieving files + directories.
+ */
+ DNDTRANSFEROBJECT objCur;
+ RT_ZERO(objCur);
+
+ char szPathName[RTPATH_MAX] = { 0 };
+ uint32_t cbPathName = 0;
+ uint32_t fFlags = 0;
+ uint32_t fMode = 0;
+
+ do
+ {
+ LogFlowFunc(("Waiting for new message ...\n"));
+
+ uint32_t uNextMsg;
+ uint32_t cNextParms;
+ rc = vbglR3DnDGetNextMsgType(pCtx, &uNextMsg, &cNextParms, true /* fWait */);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("uNextMsg=%RU32, cNextParms=%RU32\n", uNextMsg, cNextParms));
+
+ switch (uNextMsg)
+ {
+ case HOST_DND_FN_HG_SND_DIR:
+ {
+ rc = vbglR3DnDHGRecvDir(pCtx,
+ szPathName,
+ sizeof(szPathName),
+ &cbPathName,
+ &fMode);
+ LogFlowFunc(("HOST_DND_FN_HG_SND_DIR: "
+ "pszPathName=%s, cbPathName=%RU32, fMode=0x%x, rc=%Rrc\n",
+ szPathName, cbPathName, fMode, rc));
+
+ char *pszPathAbs = RTPathJoinA(pszDropDir, szPathName);
+ if (pszPathAbs)
+ {
+#ifdef RT_OS_WINDOWS
+ uint32_t fCreationMode = (fMode & RTFS_DOS_MASK) | RTFS_DOS_NT_NORMAL;
+#else
+ uint32_t fCreationMode = (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRWXU;
+#endif
+ rc = RTDirCreate(pszPathAbs, fCreationMode, 0);
+ if (RT_SUCCESS(rc))
+ rc = DnDDroppedFilesAddDir(pDroppedFiles, pszPathAbs);
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cToRecvObjs);
+ cToRecvObjs--;
+ }
+
+ RTStrFree(pszPathAbs);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ case HOST_DND_FN_HG_SND_FILE_HDR:
+ RT_FALL_THROUGH();
+ case HOST_DND_FN_HG_SND_FILE_DATA:
+ {
+ if (uNextMsg == HOST_DND_FN_HG_SND_FILE_HDR)
+ {
+ rc = vbglR3DnDHGRecvFileHdr(pCtx,
+ szPathName,
+ sizeof(szPathName),
+ &fFlags,
+ &fMode,
+ &cbFileSize);
+ LogFlowFunc(("HOST_DND_FN_HG_SND_FILE_HDR: "
+ "szPathName=%s, fFlags=0x%x, fMode=0x%x, cbFileSize=%RU64, rc=%Rrc\n",
+ szPathName, fFlags, fMode, cbFileSize, rc));
+ }
+ else
+ {
+ rc = vbglR3DnDHGRecvFileData(pCtx,
+ pvChunk,
+ cbChunkMax,
+ &cbChunkRead);
+ LogFlowFunc(("HOST_DND_FN_HG_SND_FILE_DATA: "
+ "cbChunkRead=%RU32, rc=%Rrc\n", cbChunkRead, rc));
+ }
+
+ if ( RT_SUCCESS(rc)
+ && uNextMsg == HOST_DND_FN_HG_SND_FILE_HDR)
+ {
+ char *pszPathAbs = RTPathJoinA(pszDropDir, szPathName);
+ if (pszPathAbs)
+ {
+ LogFlowFunc(("Opening pszPathName=%s, cbPathName=%RU32, fMode=0x%x, cbFileSize=%zu\n",
+ szPathName, cbPathName, fMode, cbFileSize));
+
+ uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE
+ | RTFILE_O_CREATE_REPLACE;
+
+ /* Is there already a file open, e.g. in transfer? */
+ if (!DnDTransferObjectIsOpen(&objCur))
+ {
+#ifdef RT_OS_WINDOWS
+ uint32_t fCreationMode = (fMode & RTFS_DOS_MASK) | RTFS_DOS_NT_NORMAL;
+#else
+ uint32_t fCreationMode = (fMode & RTFS_UNIX_MASK) | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR;
+#endif
+ rc = DnDTransferObjectInitEx(&objCur, DNDTRANSFEROBJTYPE_FILE,
+ pszDropDir /* Source (base) path */, szPathName /* Destination path */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DnDTransferObjectOpen(&objCur, fOpen, fCreationMode, DNDTRANSFEROBJECT_FLAGS_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DnDDroppedFilesAddFile(pDroppedFiles, pszPathAbs);
+ if (RT_SUCCESS(rc))
+ {
+ cbFileWritten = 0;
+ DnDTransferObjectSetSize(&objCur, cbFileSize);
+ }
+ }
+ }
+ }
+ else
+ {
+ AssertMsgFailed(("ObjType=%RU32\n", DnDTransferObjectGetType(&objCur)));
+ rc = VERR_WRONG_ORDER;
+ }
+
+ RTStrFree(pszPathAbs);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && uNextMsg == HOST_DND_FN_HG_SND_FILE_DATA
+ && cbChunkRead)
+ {
+ uint32_t cbChunkWritten;
+ rc = DnDTransferObjectWrite(&objCur, pvChunk, cbChunkRead, &cbChunkWritten);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("HOST_DND_FN_HG_SND_FILE_DATA: "
+ "cbChunkRead=%RU32, cbChunkWritten=%RU32, cbFileWritten=%RU64 cbFileSize=%RU64\n",
+ cbChunkRead, cbChunkWritten, cbFileWritten + cbChunkWritten, cbFileSize));
+
+ cbFileWritten += cbChunkWritten;
+
+ Assert(cbChunkRead <= cbToRecvBytes);
+ cbToRecvBytes -= cbChunkRead;
+ }
+ }
+
+ /* Data transfer complete? Close the file. */
+ bool fClose = DnDTransferObjectIsComplete(&objCur);
+ if (fClose)
+ {
+ Assert(cToRecvObjs);
+ cToRecvObjs--;
+ }
+
+ /* Only since protocol v2 we know the file size upfront. */
+ Assert(cbFileWritten <= cbFileSize);
+
+ if (fClose)
+ {
+ LogFlowFunc(("Closing file\n"));
+ DnDTransferObjectDestroy(&objCur);
+ }
+
+ break;
+ }
+ case HOST_DND_FN_CANCEL:
+ {
+ rc = vbglR3DnDHGRecvCancel(pCtx);
+ if (RT_SUCCESS(rc))
+ rc = VERR_CANCELLED;
+ break;
+ }
+ default:
+ {
+ LogRel(("DnD: Warning: Message %s (%#x) from host not supported or in wrong order\n", DnDHostMsgToStr(uNextMsg), uNextMsg));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ LogFlowFunc(("cbToRecvBytes=%RU64, cToRecvObjs=%RU64\n", cbToRecvBytes, cToRecvObjs));
+ if ( !cbToRecvBytes
+ && !cToRecvObjs)
+ {
+ break;
+ }
+
+ } while (RT_SUCCESS(rc));
+
+ LogFlowFunc(("Loop ended with %Rrc\n", rc));
+
+ /* All URI data processed? */
+ if (rc == VERR_NO_DATA)
+ rc = VINF_SUCCESS;
+
+ /* Delete temp buffer again. */
+ if (pvChunk)
+ RTMemFree(pvChunk);
+
+ /* Cleanup on failure or if the user has canceled the operation or
+ * something else went wrong. */
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_CANCELLED)
+ LogRel2(("DnD: Receiving URI data was cancelled by the host\n"));
+ else
+ LogRel(("DnD: Receiving URI data failed with %Rrc\n", rc));
+
+ DnDTransferObjectDestroy(&objCur);
+ DnDDroppedFilesRollback(pDroppedFiles);
+ }
+ else
+ {
+ LogRel2(("DnD: Receiving URI data finished\n"));
+
+ /** @todo Compare the transfer list with the dirs/files we really transferred. */
+ /** @todo Implement checksum verification, if any. */
+ }
+
+ /*
+ * Close the dropped files directory.
+ * Don't try to remove it here, however, as the files are being needed
+ * by the client's drag'n drop operation lateron.
+ */
+ int rc2 = DnDDroppedFilesReset(pDroppedFiles, false /* fRemoveDropDir */);
+ if (RT_FAILURE(rc2)) /* Not fatal, don't report back to host. */
+ LogFlowFunc(("Closing dropped files directory failed with %Rrc\n", rc2));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive the HOST_DND_FN_HG_SND_DATA message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pDataHdr DnD data header to use. Need for accounting and stuff.
+ * @param pvData Where to store the received data from the host.
+ * @param cbData Size (in bytes) of where to store the received data.
+ * @param pcbDataRecv Where to store the received amount of data (in bytes).
+ */
+static int vbglR3DnDHGRecvDataRaw(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr,
+ void *pvData, uint32_t cbData, uint32_t *pcbDataRecv)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pcbDataRecv, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pvDate=%p, cbData=%RU32\n", pvData, cbData));
+
+ HGCMMsgHGSendData Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_DATA, 5);
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.pvData.SetPtr(pvData, cbData);
+ Msg.u.v3.cbData.SetUInt32(0);
+ Msg.u.v3.pvChecksum.SetPtr(NULL, 0);
+ Msg.u.v3.cbChecksum.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbDataRecv;
+ rc = Msg.u.v3.cbData.GetUInt32(&cbDataRecv);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Use checksum for validating the received data. */
+ if (pcbDataRecv)
+ *pcbDataRecv = cbDataRecv;
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+ }
+ }
+
+ /* failure */
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Utility function to receive the HOST_DND_FN_HG_SND_DATA_HDR message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pDataHdr Where to store the receivd DnD data header.
+ */
+static int vbglR3DnDHGRecvDataHdr(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
+
+ Assert(pCtx->uProtocolDeprecated >= 3); /* Only for protocol v3 and up. */
+
+ HGCMMsgHGSendDataHdr Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_HG_SND_DATA_HDR, 12);
+ Msg.uContext.SetUInt32(0);
+ Msg.uFlags.SetUInt32(0);
+ Msg.uScreenId.SetUInt32(0);
+ Msg.cbTotal.SetUInt64(0);
+ Msg.cbMeta.SetUInt32(0);
+ Msg.pvMetaFmt.SetPtr(pDataHdr->pvMetaFmt, pDataHdr->cbMetaFmt);
+ Msg.cbMetaFmt.SetUInt32(0);
+ Msg.cObjects.SetUInt64(0);
+ Msg.enmCompression.SetUInt32(0);
+ Msg.enmChecksumType.SetUInt32(0);
+ Msg.pvChecksum.SetPtr(pDataHdr->pvChecksum, pDataHdr->cbChecksum);
+ Msg.cbChecksum.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /* Msg.uContext not needed here. */
+ Msg.uFlags.GetUInt32(&pDataHdr->uFlags);
+ Msg.uScreenId.GetUInt32(&pDataHdr->uScreenId);
+ Msg.cbTotal.GetUInt64(&pDataHdr->cbTotal);
+ Msg.cbMeta.GetUInt32(&pDataHdr->cbMeta);
+ Msg.cbMetaFmt.GetUInt32(&pDataHdr->cbMetaFmt);
+ Msg.cObjects.GetUInt64(&pDataHdr->cObjects);
+ Msg.enmCompression.GetUInt32(&pDataHdr->enmCompression);
+ Msg.enmChecksumType.GetUInt32((uint32_t *)&pDataHdr->enmChecksumType);
+ Msg.cbChecksum.GetUInt32(&pDataHdr->cbChecksum);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Helper function for receiving the actual DnD data from the host. Do not call directly.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pDataHdr Where to store the data header data.
+ * @param ppvData Returns the received meta data. Needs to be free'd by the caller.
+ * @param pcbData Where to store the size (in bytes) of the received meta data.
+ */
+static int vbglR3DnDHGRecvDataLoop(PVBGLR3GUESTDNDCMDCTX pCtx, PVBOXDNDSNDDATAHDR pDataHdr,
+ void **ppvData, uint64_t *pcbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppvData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbData, VERR_INVALID_POINTER);
+
+ int rc;
+ uint32_t cbDataRecv;
+
+ LogFlowFuncEnter();
+
+ rc = vbglR3DnDHGRecvDataHdr(pCtx, pDataHdr);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ LogFlowFunc(("cbTotal=%RU64, cbMeta=%RU32, cObjects=%RU32\n", pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects));
+ if (pDataHdr->cbMeta)
+ {
+ uint64_t cbDataTmp = 0;
+ void *pvDataTmp = RTMemAlloc(pDataHdr->cbMeta);
+ if (!pvDataTmp)
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t *pvDataOff = (uint8_t *)pvDataTmp;
+ while (cbDataTmp < pDataHdr->cbMeta)
+ {
+ rc = vbglR3DnDHGRecvDataRaw(pCtx, pDataHdr,
+ pvDataOff, RT_MIN(pDataHdr->cbMeta - cbDataTmp, pCtx->cbMaxChunkSize),
+ &cbDataRecv);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("cbDataRecv=%RU32, cbDataTmp=%RU64\n", cbDataRecv, cbDataTmp));
+ Assert(cbDataTmp + cbDataRecv <= pDataHdr->cbMeta);
+ cbDataTmp += cbDataRecv;
+ pvDataOff += cbDataRecv;
+ }
+ else
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbDataTmp == pDataHdr->cbMeta);
+
+ LogFlowFunc(("Received %RU64 bytes of data\n", cbDataTmp));
+
+ *ppvData = pvDataTmp;
+ *pcbData = cbDataTmp;
+ }
+ else
+ RTMemFree(pvDataTmp);
+ }
+ }
+ else
+ {
+ *ppvData = NULL;
+ *pcbData = 0;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest
+ * Main function for receiving the actual DnD data from the host.
+ *
+ * @returns VBox status code.
+ * @retval VERR_CANCELLED if cancelled by the host.
+ * @param pCtx DnD context to use.
+ * @param pMeta Where to store the actual meta data received from the host.
+ */
+static int vbglR3DnDHGRecvDataMain(PVBGLR3GUESTDNDCMDCTX pCtx,
+ PVBGLR3GUESTDNDMETADATA pMeta)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pMeta, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(pCtx->cbMaxChunkSize, ("Maximum chunk size must not be 0\n"), VERR_INVALID_PARAMETER);
+
+ VBOXDNDDATAHDR dataHdr;
+ RT_ZERO(dataHdr);
+ dataHdr.cbMetaFmt = pCtx->cbMaxChunkSize;
+ dataHdr.pvMetaFmt = RTMemAlloc(dataHdr.cbMetaFmt);
+ if (!dataHdr.pvMetaFmt)
+ return VERR_NO_MEMORY;
+
+ void *pvData = NULL;
+ uint64_t cbData = 0;
+ int rc = vbglR3DnDHGRecvDataLoop(pCtx, &dataHdr, &pvData, &cbData);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel2(("DnD: Received %RU64 bytes meta data in format '%s'\n", cbData, (char *)dataHdr.pvMetaFmt));
+
+ /**
+ * Check if this is an URI event. If so, let VbglR3 do all the actual
+ * data transfer + file/directory creation internally without letting
+ * the caller know.
+ *
+ * This keeps the actual (guest OS-)dependent client (like VBoxClient /
+ * VBoxTray) small by not having too much redundant code.
+ */
+ Assert(dataHdr.cbMetaFmt);
+ AssertPtr(dataHdr.pvMetaFmt);
+ if (DnDMIMEHasFileURLs((char *)dataHdr.pvMetaFmt, dataHdr.cbMetaFmt)) /* URI data. */
+ {
+ DNDDROPPEDFILES droppedFiles;
+ RT_ZERO(droppedFiles);
+
+ rc = DnDDroppedFilesInit(&droppedFiles);
+ if (RT_SUCCESS(rc))
+ rc = DnDDroppedFilesOpenTemp(&droppedFiles, DNDURIDROPPEDFILE_FLAGS_NONE);
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DnD: Initializing dropped files directory failed with %Rrc\n", rc));
+ }
+ else
+ {
+ AssertPtr(pvData);
+ Assert(cbData);
+
+ /* Use the dropped files directory as the root directory for the current transfer. */
+ rc = DnDTransferListInitEx(&pMeta->u.URI.Transfer, DnDDroppedFilesGetDirAbs(&droppedFiles),
+ DNDTRANSFERLISTFMT_NATIVE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DnDTransferListAppendRootsFromBuffer(&pMeta->u.URI.Transfer, DNDTRANSFERLISTFMT_URI, (const char *)pvData, cbData,
+ DND_PATH_SEPARATOR_STR, 0 /* fFlags */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbglR3DnDHGRecvURIData(pCtx, &dataHdr, &droppedFiles);
+ if (RT_SUCCESS(rc))
+ {
+ pMeta->enmType = VBGLR3GUESTDNDMETADATATYPE_URI_LIST;
+ }
+ }
+ }
+ }
+ }
+ else /* Raw data. */
+ {
+ pMeta->u.Raw.cbMeta = cbData;
+ pMeta->u.Raw.pvMeta = pvData;
+
+ pMeta->enmType = VBGLR3GUESTDNDMETADATATYPE_RAW;
+ }
+
+ if (pvData)
+ RTMemFree(pvData);
+ }
+
+ if (dataHdr.pvMetaFmt)
+ RTMemFree(dataHdr.pvMetaFmt);
+
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_CANCELLED)
+ {
+ LogRel(("DnD: Receiving data failed with %Rrc\n", rc));
+
+ int rc2 = VbglR3DnDHGSendProgress(pCtx, DND_PROGRESS_ERROR, 100 /* Percent */, rc);
+ if (RT_FAILURE(rc2))
+ LogRel(("DnD: Unable to send progress error %Rrc to host: %Rrc\n", rc, rc2));
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+/**
+ * Guest -> Host
+ * Utility function to receive the HOST_DND_FN_GH_REQ_PENDING message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param puScreenID For which screen on the host the request is for. Optional.
+ */
+static int vbglR3DnDGHRecvPending(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t *puScreenID)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ /* pScreenID is optional. */
+
+ HGCMMsgGHReqPending Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_GH_REQ_PENDING, 2);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.uScreenId.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Context ID not used yet. */
+ if (puScreenID)
+ rc = Msg.u.v3.uContext.GetUInt32(puScreenID);
+ }
+
+ return rc;
+}
+
+/**
+ * Guest -> Host
+ * Utility function to receive the HOST_DND_FN_GH_EVT_DROPPED message from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param ppszFormat Requested data format from the host. Optional.
+ * @param pcbFormat Size of requested data format (in bytes). Optional.
+ * @param puAction Requested action from the host. Optional.
+ */
+static int vbglR3DnDGHRecvDropped(PVBGLR3GUESTDNDCMDCTX pCtx,
+ char **ppszFormat,
+ uint32_t *pcbFormat,
+ uint32_t *puAction)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ /* The rest is optional. */
+
+ const uint32_t cbFormatTmp = pCtx->cbMaxChunkSize;
+
+ char *pszFormatTmp = static_cast<char *>(RTMemAlloc(cbFormatTmp));
+ if (!pszFormatTmp)
+ return VERR_NO_MEMORY;
+
+ HGCMMsgGHDropped Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, HOST_DND_FN_GH_EVT_DROPPED, 4);
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.pvFormat.SetPtr(pszFormatTmp, cbFormatTmp);
+ Msg.u.v3.cbFormat.SetUInt32(0);
+ Msg.u.v3.uAction.SetUInt32(0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Context ID not used yet. */
+ if (pcbFormat)
+ rc = Msg.u.v3.cbFormat.GetUInt32(pcbFormat);
+ if (RT_SUCCESS(rc) && puAction)
+ rc = Msg.u.v3.uAction.GetUInt32(puAction);
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszFormat = RTStrDup(pszFormatTmp);
+ if (!*ppszFormat)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ RTMemFree(pszFormatTmp);
+
+ return rc;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
+
+/*********************************************************************************************************************************
+* Public functions *
+*********************************************************************************************************************************/
+
+/**
+ * Connects a DnD context to the DnD host service.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to connect.
+ */
+VBGLR3DECL(int) VbglR3DnDConnect(PVBGLR3GUESTDNDCMDCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ /* Initialize header */
+ int rc = VbglR3HGCMConnect("VBoxDragAndDropSvc", &pCtx->uClientID);
+ if (RT_FAILURE(rc))
+ return rc;
+ Assert(pCtx->uClientID);
+
+ /* Set the default protocol version we would like to use.
+ * Deprecated since VBox 6.1.x, but let this set to 3 to (hopefully) not break things. */
+ pCtx->uProtocolDeprecated = 3;
+
+ pCtx->fHostFeatures = VBOX_DND_HF_NONE;
+ pCtx->fGuestFeatures = VBOX_DND_GF_NONE;
+
+ /*
+ * Get the VM's session ID.
+ * This is not fatal in case we're running with an ancient VBox version.
+ */
+ pCtx->uSessionID = 0;
+ int rc2 = VbglR3GetSessionId(&pCtx->uSessionID); RT_NOREF(rc2);
+ LogFlowFunc(("uSessionID=%RU64, rc=%Rrc\n", pCtx->uSessionID, rc2));
+
+ /*
+ * Try sending the connect message to tell the protocol version to use.
+ * Note: This might fail when the Guest Additions run on an older VBox host (< VBox 5.0) which
+ * does not implement this command.
+ */
+ HGCMMsgConnect Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_CONNECT, 3);
+ Msg.u.v3.uContext.SetUInt32(0); /** @todo Context ID not used yet. */
+ Msg.u.v3.uProtocol.SetUInt32(pCtx->uProtocolDeprecated); /* Deprecated since VBox 6.1.x. */
+ Msg.u.v3.uFlags.SetUInt32(0); /* Unused at the moment. */
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /* Set the protocol version we're going to use as told by the host. */
+ rc = Msg.u.v3.uProtocol.GetUInt32(&pCtx->uProtocolDeprecated); AssertRC(rc);
+
+ /*
+ * Next is reporting our features. If this fails, assume older host.
+ */
+ rc2 = VbglR3DnDReportFeatures(pCtx->uClientID, pCtx->fGuestFeatures, &pCtx->fHostFeatures);
+ if (RT_SUCCESS(rc2))
+ {
+ LogRel2(("DnD: Guest features: %#RX64 - Host features: %#RX64\n",
+ pCtx->fGuestFeatures, pCtx->fHostFeatures));
+ }
+ else /* Failing here is not fatal; might be running with an older host. */
+ {
+ AssertLogRelMsg(rc2 == VERR_NOT_SUPPORTED || rc2 == VERR_NOT_IMPLEMENTED,
+ ("Reporting features failed: %Rrc\n", rc2));
+ }
+
+ pCtx->cbMaxChunkSize = DND_DEFAULT_CHUNK_SIZE; /** @todo Use a scratch buffer on the heap? */
+ }
+ else
+ pCtx->uProtocolDeprecated = 0; /* We're using protocol v0 (initial draft) as a fallback. */
+
+ LogFlowFunc(("uClient=%RU32, uProtocol=%RU32, rc=%Rrc\n", pCtx->uClientID, pCtx->uProtocolDeprecated, rc));
+ return rc;
+}
+
+/**
+ * Disconnects a given DnD context from the DnD host service.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to disconnect.
+ * The context is invalid afterwards on successful disconnection.
+ */
+VBGLR3DECL(int) VbglR3DnDDisconnect(PVBGLR3GUESTDNDCMDCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ if (!pCtx->uClientID) /* Already disconnected? Bail out early. */
+ return VINF_SUCCESS;
+
+ int rc = VbglR3HGCMDisconnect(pCtx->uClientID);
+ if (RT_SUCCESS(rc))
+ pCtx->uClientID = 0;
+
+ return rc;
+}
+
+/**
+ * Reports features to the host and retrieve host feature set.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3DnDConnect().
+ * @param fGuestFeatures Features to report, VBOX_DND_GF_XXX.
+ * @param pfHostFeatures Where to store the features VBOX_DND_HF_XXX.
+ */
+VBGLR3DECL(int) VbglR3DnDReportFeatures(uint32_t idClient, uint64_t fGuestFeatures, uint64_t *pfHostFeatures)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter f64Features0;
+ HGCMFunctionParameter f64Features1;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_DND_FN_REPORT_FEATURES, 2);
+ VbglHGCMParmUInt64Set(&Msg.f64Features0, fGuestFeatures);
+ VbglHGCMParmUInt64Set(&Msg.f64Features1, VBOX_DND_GF_1_MUST_BE_ONE);
+
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit);
+ Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit);
+ if (Msg.f64Features1.u.value64 & VBOX_DND_GF_1_MUST_BE_ONE)
+ rc = VERR_NOT_SUPPORTED;
+ else if (pfHostFeatures)
+ *pfHostFeatures = Msg.f64Features0.u.value64;
+ break;
+ }
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+
+}
+
+/**
+ * Receives the next upcoming DnD event.
+ *
+ * This is the main function DnD clients call in order to implement any DnD functionality.
+ * The purpose of it is to abstract the actual DnD protocol handling as much as possible from
+ * the clients -- those only need to react to certain events, regardless of how the underlying
+ * protocol actually is working.
+ *
+ * @returns VBox status code.
+ * @param pCtx DnD context to work with.
+ * @param ppEvent Next DnD event received on success; needs to be free'd by the client calling
+ * VbglR3DnDEventFree() when done.
+ */
+VBGLR3DECL(int) VbglR3DnDEventGetNext(PVBGLR3GUESTDNDCMDCTX pCtx, PVBGLR3DNDEVENT *ppEvent)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppEvent, VERR_INVALID_POINTER);
+
+ PVBGLR3DNDEVENT pEvent = (PVBGLR3DNDEVENT)RTMemAllocZ(sizeof(VBGLR3DNDEVENT));
+ if (!pEvent)
+ return VERR_NO_MEMORY;
+
+ uint32_t uMsg = 0;
+ uint32_t cParms = 0;
+ int rc = vbglR3DnDGetNextMsgType(pCtx, &uMsg, &cParms, true /* fWait */);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check for VM session change. */
+ uint64_t uSessionID;
+ int rc2 = VbglR3GetSessionId(&uSessionID);
+ if ( RT_SUCCESS(rc2)
+ && (uSessionID != pCtx->uSessionID))
+ {
+ LogRel2(("DnD: VM session ID changed to %RU64\n", uSessionID));
+ rc = VbglR3DnDDisconnect(pCtx);
+ if (RT_SUCCESS(rc))
+ rc = VbglR3DnDConnect(pCtx);
+ }
+ }
+
+ if (rc == VERR_CANCELLED) /* Host service told us that we have to bail out. */
+ {
+ LogRel2(("DnD: Host service requested termination\n"));
+
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_QUIT;
+ *ppEvent = pEvent;
+
+ return VINF_SUCCESS;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("Handling uMsg=%RU32\n", uMsg));
+
+ switch(uMsg)
+ {
+ case HOST_DND_FN_HG_EVT_ENTER:
+ {
+ rc = vbglR3DnDHGRecvAction(pCtx,
+ uMsg,
+ &pEvent->u.HG_Enter.uScreenID,
+ NULL /* puXPos */,
+ NULL /* puYPos */,
+ NULL /* uDefAction */,
+ &pEvent->u.HG_Enter.dndLstActionsAllowed,
+ &pEvent->u.HG_Enter.pszFormats,
+ &pEvent->u.HG_Enter.cbFormats);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_ENTER;
+ break;
+ }
+ case HOST_DND_FN_HG_EVT_MOVE:
+ {
+ rc = vbglR3DnDHGRecvAction(pCtx,
+ uMsg,
+ NULL /* puScreenId */,
+ &pEvent->u.HG_Move.uXpos,
+ &pEvent->u.HG_Move.uYpos,
+ &pEvent->u.HG_Move.dndActionDefault,
+ NULL /* puAllActions */,
+ NULL /* pszFormats */,
+ NULL /* pcbFormats */);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_MOVE;
+ break;
+ }
+ case HOST_DND_FN_HG_EVT_DROPPED:
+ {
+ rc = vbglR3DnDHGRecvAction(pCtx,
+ uMsg,
+ NULL /* puScreenId */,
+ &pEvent->u.HG_Drop.uXpos,
+ &pEvent->u.HG_Drop.uYpos,
+ &pEvent->u.HG_Drop.dndActionDefault,
+ NULL /* puAllActions */,
+ NULL /* pszFormats */,
+ NULL /* pcbFormats */);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_DROP;
+ break;
+ }
+ case HOST_DND_FN_HG_EVT_LEAVE:
+ {
+ rc = vbglR3DnDHGRecvLeave(pCtx);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_LEAVE;
+ break;
+ }
+ case HOST_DND_FN_HG_SND_DATA_HDR:
+ {
+ rc = vbglR3DnDHGRecvDataMain(pCtx, &pEvent->u.HG_Received.Meta);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_HG_RECEIVE;
+ break;
+ }
+ case HOST_DND_FN_HG_SND_DIR:
+ RT_FALL_THROUGH();
+ case HOST_DND_FN_HG_SND_FILE_HDR:
+ RT_FALL_THROUGH();
+ case HOST_DND_FN_HG_SND_FILE_DATA:
+ {
+ /*
+ * All messages for this block are handled internally
+ * by vbglR3DnDHGRecvDataMain(), see above.
+ *
+ * So if we land here our code is buggy.
+ */
+ rc = VERR_WRONG_ORDER;
+ break;
+ }
+ case HOST_DND_FN_CANCEL:
+ {
+ rc = vbglR3DnDHGRecvCancel(pCtx);
+ if (RT_SUCCESS(rc))
+ rc = VERR_CANCELLED; /* Will emit a cancel event below. */
+ break;
+ }
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case HOST_DND_FN_GH_REQ_PENDING:
+ {
+ rc = vbglR3DnDGHRecvPending(pCtx, &pEvent->u.GH_IsPending.uScreenID);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_GH_REQ_PENDING;
+ break;
+ }
+ case HOST_DND_FN_GH_EVT_DROPPED:
+ {
+ rc = vbglR3DnDGHRecvDropped(pCtx,
+ &pEvent->u.GH_Drop.pszFormat,
+ &pEvent->u.GH_Drop.cbFormat,
+ &pEvent->u.GH_Drop.dndActionRequested);
+ if (RT_SUCCESS(rc))
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_GH_DROP;
+ break;
+ }
+#endif
+ default:
+ {
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ /* Current operation cancelled? Set / overwrite event type and tell the caller. */
+ if (rc == VERR_CANCELLED)
+ {
+ pEvent->enmType = VBGLR3DNDEVENTTYPE_CANCEL;
+ rc = VINF_SUCCESS; /* Deliver the event to the caller. */
+ }
+ else
+ {
+ VbglR3DnDEventFree(pEvent);
+ LogRel(("DnD: Handling message %s (%#x) failed with %Rrc\n", DnDHostMsgToStr(uMsg), uMsg, rc));
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppEvent = pEvent;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Frees (destroys) a formerly allocated DnD event.
+ *
+ * @returns IPRT status code.
+ * @param pEvent Event to free (destroy).
+ */
+VBGLR3DECL(void) VbglR3DnDEventFree(PVBGLR3DNDEVENT pEvent)
+{
+ if (!pEvent)
+ return;
+
+ /* Some messages require additional cleanup. */
+ switch (pEvent->enmType)
+ {
+ case VBGLR3DNDEVENTTYPE_HG_ENTER:
+ {
+ if (pEvent->u.HG_Enter.pszFormats)
+ RTStrFree(pEvent->u.HG_Enter.pszFormats);
+ break;
+ }
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case VBGLR3DNDEVENTTYPE_GH_DROP:
+ {
+ if (pEvent->u.GH_Drop.pszFormat)
+ RTStrFree(pEvent->u.GH_Drop.pszFormat);
+ break;
+ }
+#endif
+ case VBGLR3DNDEVENTTYPE_HG_RECEIVE:
+ {
+ PVBGLR3GUESTDNDMETADATA pMeta = &pEvent->u.HG_Received.Meta;
+ switch (pMeta->enmType)
+ {
+ case VBGLR3GUESTDNDMETADATATYPE_RAW:
+ {
+ if (pMeta->u.Raw.pvMeta)
+ {
+ Assert(pMeta->u.Raw.cbMeta);
+ RTMemFree(pMeta->u.Raw.pvMeta);
+ pMeta->u.Raw.cbMeta = 0;
+ }
+ break;
+ }
+
+ case VBGLR3GUESTDNDMETADATATYPE_URI_LIST:
+ {
+ DnDTransferListDestroy(&pMeta->u.URI.Transfer);
+ break;
+ }
+
+ default:
+ break;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ RTMemFree(pEvent);
+ pEvent = NULL;
+}
+
+VBGLR3DECL(int) VbglR3DnDHGSendAckOp(PVBGLR3GUESTDNDCMDCTX pCtx, VBOXDNDACTION dndAction)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgHGAck Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_HG_ACK_OP, 2);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.uAction.SetUInt32(dndAction);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Host -> Guest
+ * Requests the actual DnD data to be sent from the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pcszFormat Format to request the data from the host in.
+ */
+VBGLR3DECL(int) VbglR3DnDHGSendReqData(PVBGLR3GUESTDNDCMDCTX pCtx, const char* pcszFormat)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszFormat, VERR_INVALID_POINTER);
+ if (!RTStrIsValidEncoding(pcszFormat))
+ return VERR_INVALID_PARAMETER;
+
+ const uint32_t cbFormat = (uint32_t)strlen(pcszFormat) + 1; /* Include termination */
+
+ HGCMMsgHGReqData Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_HG_REQ_DATA, 3);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.pvFormat.SetPtr((void*)pcszFormat, cbFormat);
+ Msg.u.v3.cbFormat.SetUInt32(cbFormat);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Host -> Guest
+ * Reports back its progress back to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param uStatus DnD status to report.
+ * @param uPercent Overall progress (in percent) to report.
+ * @param rcErr Error code (IPRT-style) to report.
+ */
+VBGLR3DECL(int) VbglR3DnDHGSendProgress(PVBGLR3GUESTDNDCMDCTX pCtx, uint32_t uStatus, uint8_t uPercent, int rcErr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(uStatus > DND_PROGRESS_UNKNOWN, VERR_INVALID_PARAMETER);
+
+ HGCMMsgHGProgress Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_HG_EVT_PROGRESS, 4);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.uStatus.SetUInt32(uStatus);
+ Msg.u.v3.uPercent.SetUInt32(uPercent);
+ Msg.u.v3.rc.SetUInt32((uint32_t)rcErr); /* uint32_t vs. int. */
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+/**
+ * Guest -> Host
+ * Acknowledges that there currently is a drag'n drop operation in progress on the guest,
+ * which eventually could be dragged over to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param dndActionDefault Default action for the operation to report.
+ * @param dndLstActionsAllowed All available actions for the operation to report.
+ * @param pcszFormats Available formats for the operation to report.
+ * @param cbFormats Size (in bytes) of formats to report.
+ */
+VBGLR3DECL(int) VbglR3DnDGHSendAckPending(PVBGLR3GUESTDNDCMDCTX pCtx,
+ VBOXDNDACTION dndActionDefault, VBOXDNDACTIONLIST dndLstActionsAllowed,
+ const char* pcszFormats, uint32_t cbFormats)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcszFormats, VERR_INVALID_POINTER);
+ AssertReturn(cbFormats, VERR_INVALID_PARAMETER);
+
+ if (!RTStrIsValidEncoding(pcszFormats))
+ return VERR_INVALID_UTF8_ENCODING;
+
+ HGCMMsgGHAckPending Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GH_ACK_PENDING, 5);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.uDefAction.SetUInt32(dndActionDefault);
+ Msg.u.v3.uAllActions.SetUInt32(dndLstActionsAllowed);
+ Msg.u.v3.pvFormats.SetPtr((void*)pcszFormats, cbFormats);
+ Msg.u.v3.cbFormats.SetUInt32(cbFormats);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Guest -> Host
+ * Utility function to send DnD data from guest to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pvData Data block to send.
+ * @param cbData Size (in bytes) of data block to send.
+ * @param pDataHdr Data header to use -- needed for accounting.
+ */
+static int vbglR3DnDGHSendDataInternal(PVBGLR3GUESTDNDCMDCTX pCtx,
+ void *pvData, uint64_t cbData, PVBOXDNDSNDDATAHDR pDataHdr)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pDataHdr, VERR_INVALID_POINTER);
+
+ HGCMMsgGHSendDataHdr MsgHdr;
+ VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_DATA_HDR, 12);
+ MsgHdr.uContext.SetUInt32(0); /** @todo Not used yet. */
+ MsgHdr.uFlags.SetUInt32(0); /** @todo Not used yet. */
+ MsgHdr.uScreenId.SetUInt32(0); /** @todo Not used for guest->host (yet). */
+ MsgHdr.cbTotal.SetUInt64(pDataHdr->cbTotal);
+ MsgHdr.cbMeta.SetUInt32(pDataHdr->cbMeta);
+ MsgHdr.pvMetaFmt.SetPtr(pDataHdr->pvMetaFmt, pDataHdr->cbMetaFmt);
+ MsgHdr.cbMetaFmt.SetUInt32(pDataHdr->cbMetaFmt);
+ MsgHdr.cObjects.SetUInt64(pDataHdr->cObjects);
+ MsgHdr.enmCompression.SetUInt32(0); /** @todo Not used yet. */
+ MsgHdr.enmChecksumType.SetUInt32(RTDIGESTTYPE_INVALID); /** @todo Not used yet. */
+ MsgHdr.pvChecksum.SetPtr(NULL, 0); /** @todo Not used yet. */
+ MsgHdr.cbChecksum.SetUInt32(0); /** @todo Not used yet. */
+
+ int rc = VbglR3HGCMCall(&MsgHdr.hdr, sizeof(MsgHdr));
+
+ LogFlowFunc(("cbTotal=%RU64, cbMeta=%RU32, cObjects=%RU64, rc=%Rrc\n",
+ pDataHdr->cbTotal, pDataHdr->cbMeta, pDataHdr->cObjects, rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ HGCMMsgGHSendData MsgData;
+ VBGL_HGCM_HDR_INIT(&MsgData.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_DATA, 5);
+ MsgData.u.v3.uContext.SetUInt32(0); /** @todo Not used yet. */
+ MsgData.u.v3.pvChecksum.SetPtr(NULL, 0); /** @todo Not used yet. */
+ MsgData.u.v3.cbChecksum.SetUInt32(0); /** @todo Not used yet. */
+
+ uint32_t cbCurChunk;
+ const uint32_t cbMaxChunk = pCtx->cbMaxChunkSize;
+ uint32_t cbSent = 0;
+
+ while (cbSent < cbData)
+ {
+ cbCurChunk = RT_MIN(cbData - cbSent, cbMaxChunk);
+ MsgData.u.v3.pvData.SetPtr(static_cast<uint8_t *>(pvData) + cbSent, cbCurChunk);
+ MsgData.u.v3.cbData.SetUInt32(cbCurChunk);
+
+ rc = VbglR3HGCMCall(&MsgData.hdr, sizeof(MsgData));
+ if (RT_FAILURE(rc))
+ break;
+
+ cbSent += cbCurChunk;
+ }
+
+ LogFlowFunc(("cbMaxChunk=%RU32, cbData=%RU64, cbSent=%RU32, rc=%Rrc\n",
+ cbMaxChunk, cbData, cbSent, rc));
+
+ if (RT_SUCCESS(rc))
+ Assert(cbSent == cbData);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Guest -> Host
+ * Utility function to send a guest directory to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pObj transfer object containing the directory to send.
+ */
+static int vbglR3DnDGHSendDir(PVBGLR3GUESTDNDCMDCTX pCtx, DNDTRANSFEROBJECT *pObj)
+{
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(DnDTransferObjectGetType(pObj) == DNDTRANSFEROBJTYPE_DIRECTORY, VERR_INVALID_PARAMETER);
+
+ const char *pcszPath = DnDTransferObjectGetDestPath(pObj);
+ const size_t cbPath = RTStrNLen(pcszPath, RTPATH_MAX) + 1 /* Include termination. */;
+ const RTFMODE fMode = DnDTransferObjectGetMode(pObj);
+
+ LogFlowFunc(("strDir=%s (%zu bytes), fMode=0x%x\n", pcszPath, cbPath, fMode));
+
+ if (cbPath > RTPATH_MAX + 1) /* Can't happen, but check anyway. */
+ return VERR_INVALID_PARAMETER;
+
+ HGCMMsgGHSendDir Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_DIR, 4);
+ /** @todo Context ID not used yet. */
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.pvName.SetPtr((void *)pcszPath, (uint32_t)cbPath);
+ Msg.u.v3.cbName.SetUInt32((uint32_t)cbPath);
+ Msg.u.v3.fMode.SetUInt32(fMode);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Guest -> Host
+ * Utility function to send a file from the guest to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pObj Transfer object containing the file to send.
+ */
+static int vbglR3DnDGHSendFile(PVBGLR3GUESTDNDCMDCTX pCtx, PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+ AssertReturn(DnDTransferObjectIsOpen(pObj) == false, VERR_INVALID_STATE);
+ AssertReturn(DnDTransferObjectGetType(pObj) == DNDTRANSFEROBJTYPE_FILE, VERR_INVALID_PARAMETER);
+
+ uint64_t fOpen = RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE;
+
+ int rc = DnDTransferObjectOpen(pObj, fOpen, 0 /* fMode */, DNDTRANSFEROBJECT_FLAGS_NONE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint32_t cbBuf = pCtx->cbMaxChunkSize;
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+ void *pvBuf = RTMemAlloc(cbBuf); /** @todo Make this buffer part of PVBGLR3GUESTDNDCMDCTX? */
+ if (!pvBuf)
+ {
+ int rc2 = DnDTransferObjectClose(pObj);
+ AssertRC(rc2);
+ return VERR_NO_MEMORY;
+ }
+
+ const char *pcszPath = DnDTransferObjectGetDestPath(pObj);
+ const size_t cchPath = RTStrNLen(pcszPath, RTPATH_MAX);
+ const uint64_t cbSize = DnDTransferObjectGetSize(pObj);
+ const RTFMODE fMode = DnDTransferObjectGetMode(pObj);
+
+ LogFlowFunc(("strFile=%s (%zu), cbSize=%RU64, fMode=0x%x\n", pcszPath, cchPath, cbSize, fMode));
+
+ HGCMMsgGHSendFileHdr MsgHdr;
+ VBGL_HGCM_HDR_INIT(&MsgHdr.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_FILE_HDR, 6);
+ MsgHdr.uContext.SetUInt32(0); /* Context ID; unused at the moment. */
+ MsgHdr.pvName.SetPtr((void *)pcszPath, (uint32_t)(cchPath + 1)); /* Include termination. */
+ MsgHdr.cbName.SetUInt32((uint32_t)(cchPath + 1)); /* Ditto. */
+ MsgHdr.uFlags.SetUInt32(0); /* Flags; unused at the moment. */
+ MsgHdr.fMode.SetUInt32(fMode); /* File mode */
+ MsgHdr.cbTotal.SetUInt64(cbSize); /* File size (in bytes). */
+
+ rc = VbglR3HGCMCall(&MsgHdr.hdr, sizeof(MsgHdr));
+
+ LogFlowFunc(("Sending file header resulted in %Rrc\n", rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Send the actual file data, chunk by chunk.
+ */
+ HGCMMsgGHSendFileData Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_DND_FN_GH_SND_FILE_DATA, 5);
+ Msg.u.v3.uContext.SetUInt32(0);
+ Msg.u.v3.pvChecksum.SetPtr(NULL, 0);
+ Msg.u.v3.cbChecksum.SetUInt32(0);
+
+ uint64_t cbToReadTotal = cbSize;
+ uint64_t cbWrittenTotal = 0;
+ while (cbToReadTotal)
+ {
+ uint32_t cbToRead = RT_MIN(cbToReadTotal, cbBuf);
+ uint32_t cbRead = 0;
+ if (cbToRead)
+ rc = DnDTransferObjectRead(pObj, pvBuf, cbToRead, &cbRead);
+
+ LogFlowFunc(("cbToReadTotal=%RU64, cbToRead=%RU32, cbRead=%RU32, rc=%Rrc\n",
+ cbToReadTotal, cbToRead, cbRead, rc));
+
+ if ( RT_SUCCESS(rc)
+ && cbRead)
+ {
+ Msg.u.v3.pvData.SetPtr(pvBuf, cbRead);
+ Msg.u.v3.cbData.SetUInt32(cbRead);
+ /** @todo Calculate + set checksums. */
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("Reading failed with rc=%Rrc\n", rc));
+ break;
+ }
+
+ Assert(cbRead <= cbToReadTotal);
+ cbToReadTotal -= cbRead;
+ cbWrittenTotal += cbRead;
+
+ LogFlowFunc(("%RU64/%RU64 -- %RU8%%\n", cbWrittenTotal, cbSize, cbWrittenTotal * 100 / cbSize));
+ };
+ }
+
+ RTMemFree(pvBuf);
+ int rc2 = DnDTransferObjectClose(pObj);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Guest -> Host
+ * Utility function to send a transfer object from guest to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pObj Transfer object to send from guest to the host.
+ */
+static int vbglR3DnDGHSendURIObject(PVBGLR3GUESTDNDCMDCTX pCtx, PDNDTRANSFEROBJECT pObj)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pObj, VERR_INVALID_POINTER);
+
+ int rc;
+
+ const DNDTRANSFEROBJTYPE enmType = DnDTransferObjectGetType(pObj);
+
+ switch (enmType)
+ {
+ case DNDTRANSFEROBJTYPE_DIRECTORY:
+ rc = vbglR3DnDGHSendDir(pCtx, pObj);
+ break;
+
+ case DNDTRANSFEROBJTYPE_FILE:
+ rc = vbglR3DnDGHSendFile(pCtx, pObj);
+ break;
+
+ default:
+ AssertMsgFailed(("Object type %ld not implemented\n", enmType));
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Guest -> Host
+ * Utility function to send raw data from guest to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pvData Block to raw data to send.
+ * @param cbData Size (in bytes) of raw data to send.
+ */
+static int vbglR3DnDGHSendRawData(PVBGLR3GUESTDNDCMDCTX pCtx, void *pvData, size_t cbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ /* cbData can be 0. */
+
+ VBOXDNDDATAHDR dataHdr;
+ RT_ZERO(dataHdr);
+
+ dataHdr.cbMeta = (uint32_t)cbData;
+ dataHdr.cbTotal = cbData;
+
+ return vbglR3DnDGHSendDataInternal(pCtx, pvData, cbData, &dataHdr);
+}
+
+/**
+ * Guest -> Host
+ * Utility function to send transfer data from guest to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pTransferList Dnd transfer list to send.
+ */
+static int vbglR3DnDGHSendTransferData(PVBGLR3GUESTDNDCMDCTX pCtx, PDNDTRANSFERLIST pTransferList)
+{
+ AssertPtrReturn(pCtx,VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransferList, VERR_INVALID_POINTER);
+
+ /*
+ * Send the (meta) data; in case of URIs it's the root entries of a
+ * transfer list the host needs to know upfront to set up the drag'n drop operation.
+ */
+ char *pszList = NULL;
+ size_t cbList;
+ int rc = DnDTransferListGetRoots(pTransferList, DNDTRANSFERLISTFMT_URI, &pszList, &cbList);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ void *pvURIList = (void *)pszList;
+ uint32_t cbURLIist = (uint32_t)cbList;
+
+ /* The total size also contains the size of the meta data. */
+ uint64_t cbTotal = cbURLIist;
+ cbTotal += DnDTransferListObjTotalBytes(pTransferList);
+
+ /* We're going to send a transfer list in text format. */
+ const char szMetaFmt[] = "text/uri-list";
+ const uint32_t cbMetaFmt = (uint32_t)strlen(szMetaFmt) + 1; /* Include termination. */
+
+ VBOXDNDDATAHDR dataHdr;
+ dataHdr.uFlags = 0; /* Flags not used yet. */
+ dataHdr.cbTotal = cbTotal;
+ dataHdr.cbMeta = cbURLIist;
+ dataHdr.pvMetaFmt = (void *)szMetaFmt;
+ dataHdr.cbMetaFmt = cbMetaFmt;
+ dataHdr.cObjects = DnDTransferListObjCount(pTransferList);
+
+ rc = vbglR3DnDGHSendDataInternal(pCtx, pvURIList, cbURLIist, &dataHdr);
+
+ if (RT_SUCCESS(rc))
+ {
+ while (DnDTransferListObjCount(pTransferList))
+ {
+ PDNDTRANSFEROBJECT pObj = DnDTransferListObjGetFirst(pTransferList);
+
+ rc = vbglR3DnDGHSendURIObject(pCtx, pObj);
+ if (RT_FAILURE(rc))
+ break;
+
+ DnDTransferListObjRemoveFirst(pTransferList);
+ }
+
+ Assert(DnDTransferListObjCount(pTransferList) == 0);
+ }
+
+ return rc;
+}
+
+/**
+ * Guest -> Host
+ * Sends data, which either can be raw or URI data, from guest to the host. This function
+ * initiates the actual data transfer from guest to the host.
+ *
+ * @returns IPRT status code.
+ * @param pCtx DnD context to use.
+ * @param pszFormat In which format the data will be sent.
+ * @param pvData Data block to send.
+ * For URI data this must contain the absolute local URI paths, separated by DND_PATH_SEPARATOR_STR.
+ * @param cbData Size (in bytes) of data block to send.
+ */
+VBGLR3DECL(int) VbglR3DnDGHSendData(PVBGLR3GUESTDNDCMDCTX pCtx, const char *pszFormat, void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFormat, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pszFormat=%s, pvData=%p, cbData=%RU32\n", pszFormat, pvData, cbData));
+
+ LogRel2(("DnD: Sending %RU32 bytes meta data in format '%s'\n", cbData, pszFormat));
+
+ int rc;
+ if (DnDMIMEHasFileURLs(pszFormat, strlen(pszFormat)))
+ {
+ DNDTRANSFERLIST lstTransfer;
+ RT_ZERO(lstTransfer);
+
+ rc = DnDTransferListInit(&lstTransfer);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Add symlink support (DNDTRANSFERLIST_FLAGS_RESOLVE_SYMLINKS) here. */
+ /** @todo Add lazy loading (DNDTRANSFERLIST_FLAGS_LAZY) here. */
+ const DNDTRANSFERLISTFLAGS fFlags = DNDTRANSFERLIST_FLAGS_RECURSIVE;
+
+ rc = DnDTransferListAppendPathsFromBuffer(&lstTransfer, DNDTRANSFERLISTFMT_URI, (const char *)pvData, cbData,
+ DND_PATH_SEPARATOR_STR, fFlags);
+ if (RT_SUCCESS(rc))
+ rc = vbglR3DnDGHSendTransferData(pCtx, &lstTransfer);
+ DnDTransferListDestroy(&lstTransfer);
+ }
+ }
+ else
+ rc = vbglR3DnDGHSendRawData(pCtx, pvData, cbData);
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DnD: Sending data failed with rc=%Rrc\n", rc));
+
+ if (rc != VERR_CANCELLED)
+ {
+ int rc2 = VbglR3DnDSendError(pCtx, rc);
+ if (RT_FAILURE(rc2))
+ LogFlowFunc(("Unable to send error (%Rrc) to host, rc=%Rrc\n", rc, rc2));
+ }
+ }
+
+ return rc;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDrmClient.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDrmClient.cpp
new file mode 100644
index 00000000..66a8bca7
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibDrmClient.cpp
@@ -0,0 +1,199 @@
+/* $Id: VBoxGuestR3LibDrmClient.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, DRM client handling.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+
+#include <iprt/env.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+
+#if defined(RT_OS_LINUX)
+# include <VBox/HostServices/GuestPropertySvc.h>
+
+/** Defines the DRM client executable (image). */
+# define VBOX_DRMCLIENT_EXECUTABLE "/usr/bin/VBoxDRMClient"
+# define VBOX_DRMCLIENT_LEGACY_EXECUTABLE "/usr/bin/VBoxClient"
+/** Defines the guest property that defines if the DRM resizing client needs to be active or not. */
+# define VBOX_DRMCLIENT_GUEST_PROP_RESIZE "/VirtualBox/GuestAdd/DRMResize"
+
+/**
+ * Check if specified guest property exist.
+ *
+ * @returns \c true if the property exists and its flags do match, \c false otherwise.
+ * @param pszPropName Guest property name.
+ * @param fPropFlags Guest property flags mask to verify if property exist.
+ * If \p fPropFlags is 0, flags verification is omitted.
+ */
+static bool vbglR3DrmClientCheckProp(const char *pszPropName, uint32_t fPropFlags)
+{
+ bool fExist = false;
+# if defined(VBOX_WITH_GUEST_PROPS)
+ uint32_t idClient;
+
+ int rc = VbglR3GuestPropConnect(&idClient);
+ if (RT_SUCCESS(rc))
+ {
+ char *pcszFlags = NULL;
+
+ rc = VbglR3GuestPropReadEx(idClient, pszPropName, NULL /* ppszValue */, &pcszFlags, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check property flags match. */
+ if (fPropFlags)
+ {
+ uint32_t fFlags = 0;
+
+ rc = GuestPropValidateFlags(pcszFlags, &fFlags);
+ fExist = RT_SUCCESS(rc) && (fFlags == fPropFlags);
+ }
+ else
+ fExist = true;
+
+ RTStrFree(pcszFlags);
+ }
+
+ VbglR3GuestPropDisconnect(idClient);
+ }
+# endif /* VBOX_WITH_GUEST_PROPS */
+ return fExist;
+}
+#endif /* RT_OS_LINUX */
+
+/**
+ * Returns true if the DRM resizing client is needed.
+ * This is achieved by querying existence of a guest property.
+ *
+ * @returns \c true if the DRM resizing client is needed, \c false if not.
+ */
+VBGLR3DECL(bool) VbglR3DrmClientIsNeeded(void)
+{
+#if defined(RT_OS_LINUX)
+ return vbglR3DrmClientCheckProp(VBOX_DRMCLIENT_GUEST_PROP_RESIZE, 0);
+#else
+ return false;
+#endif
+}
+
+/**
+ * Returns true if the DRM IPC server socket access should be restricted.
+ *
+ * Restricted access means that only users from a certain group should
+ * be granted with read and write access permission to IPC socket. Check
+ * is done by examining \c VBGLR3DRMIPCPROPRESTRICT guest property. Property
+ * is only considered valid if is read-only for guest. I.e., the following
+ * property should be set on the host side:
+ *
+ * VBoxManage guestproperty set <VM> /VirtualBox/GuestAdd/DRMIpcRestricted 1 --flags RDONLYGUEST
+ *
+ * @returns \c true if restricted socket access is required, \c false otherwise.
+ */
+VBGLR3DECL(bool) VbglR3DrmRestrictedIpcAccessIsNeeded(void)
+{
+#if defined(RT_OS_LINUX)
+ return vbglR3DrmClientCheckProp(VBGLR3DRMIPCPROPRESTRICT, GUEST_PROP_F_RDONLYGUEST);
+#else
+ return false;
+#endif
+}
+
+/**
+ * Returns true if the DRM resizing client already is running.
+ * This is achieved by querying existence of a guest property.
+ *
+ * @returns \c true if the DRM resizing client is running, \c false if not.
+ */
+VBGLR3DECL(bool) VbglR3DrmClientIsRunning(void)
+{
+ return VbglR3DrmClientIsNeeded();
+}
+
+#if defined(RT_OS_LINUX)
+static int VbglR3DrmStart(const char *pszCmd, const char **apszArgs)
+{
+ return RTProcCreate(pszCmd, apszArgs, RTENV_DEFAULT,
+ RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL);
+}
+#endif
+
+/**
+ * Starts (executes) the DRM resizing client process ("VBoxDRMClient").
+ *
+ * @returns VBox status code.
+ */
+VBGLR3DECL(int) VbglR3DrmClientStart(void)
+{
+#if defined(RT_OS_LINUX)
+ const char *apszArgs[2] = { VBOX_DRMCLIENT_EXECUTABLE, NULL }; /** @todo r=andy Pass path + process name as argv0? */
+ return VbglR3DrmStart(VBOX_DRMCLIENT_EXECUTABLE, apszArgs);
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Starts (executes) the legacy DRM resizing client process ("VBoxClient --vmsvga").
+ *
+ * @returns VBox status code.
+ */
+VBGLR3DECL(int) VbglR3DrmLegacyClientStart(void)
+{
+#if defined(RT_OS_LINUX)
+ const char *apszArgs[3] = { VBOX_DRMCLIENT_LEGACY_EXECUTABLE, "--vmsvga", NULL };
+ return VbglR3DrmStart(VBOX_DRMCLIENT_LEGACY_EXECUTABLE, apszArgs);
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+/**
+ * Starts (executes) the legacy X11 resizing agent process ("VBoxClient --display").
+ *
+ * @returns VBox status code.
+ */
+VBGLR3DECL(int) VbglR3DrmLegacyX11AgentStart(void)
+{
+#if defined(RT_OS_LINUX)
+ const char *apszArgs[3] = { VBOX_DRMCLIENT_LEGACY_EXECUTABLE, "--display", NULL };
+ return VbglR3DrmStart(VBOX_DRMCLIENT_LEGACY_EXECUTABLE, apszArgs);
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp
new file mode 100644
index 00000000..f459f607
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibEvent.cpp
@@ -0,0 +1,103 @@
+/* $Id: VBoxGuestR3LibEvent.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Events.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Wait for the host to signal one or more events and return which.
+ *
+ * The events will only be delivered by the host if they have been enabled
+ * previously using @a VbglR3CtlFilterMask. If one or several of the events
+ * have already been signalled but not yet waited for, this function will return
+ * immediately and return those events.
+ *
+ * @returns IPRT status code.
+ *
+ * @param fMask The events we want to wait for, or-ed together.
+ * @param cMillies How long to wait before giving up and returning
+ * (VERR_TIMEOUT). Use RT_INDEFINITE_WAIT to wait until we
+ * are interrupted or one of the events is signalled.
+ * @param pfEvents Where to store the events signalled. Optional.
+ */
+VBGLR3DECL(int) VbglR3WaitEvent(uint32_t fMask, uint32_t cMillies, uint32_t *pfEvents)
+{
+ LogFlow(("VbglR3WaitEvent: fMask=%#x, cMillies=%u, pfEvents=%p\n", fMask, cMillies, pfEvents));
+ AssertReturn((fMask & ~VMMDEV_EVENT_VALID_EVENT_MASK) == 0, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pfEvents, VERR_INVALID_POINTER);
+
+ VBGLIOCWAITFOREVENTS WaitEvents;
+ VBGLREQHDR_INIT(&WaitEvents.Hdr, WAIT_FOR_EVENTS);
+ WaitEvents.u.In.fEvents = fMask;
+ WaitEvents.u.In.cMsTimeOut = cMillies;
+ int rc = vbglR3DoIOCtl(VBGL_IOCTL_WAIT_FOR_EVENTS, &WaitEvents.Hdr, sizeof(WaitEvents));
+ if (pfEvents)
+ {
+ if (RT_SUCCESS(rc))
+ *pfEvents = WaitEvents.u.Out.fEvents;
+ else
+ *pfEvents = 0;
+ }
+
+ LogFlow(("VbglR3WaitEvent: rc=%Rrc fEvents=%#x\n", rc, WaitEvents.u.Out.fEvents));
+ return rc;
+}
+
+
+/**
+ * Causes any pending VbglR3WaitEvent calls (VBGL_IOCTL_WAIT_FOR_EVENTS) to
+ * return with a VERR_INTERRUPTED status.
+ *
+ * Can be used in combination with a termination flag variable for interrupting
+ * event loops. After calling this, VBGL_IOCTL_WAIT_FOR_EVENTS should no longer
+ * be called in the same session. At the time of writing this is not enforced;
+ * at the time of reading it may be.
+ *
+ * @returns IPRT status code.
+ */
+VBGLR3DECL(int) VbglR3InterruptEventWaits(void)
+{
+ VBGLREQHDR Req;
+ VBGLREQHDR_INIT(&Req, INTERRUPT_ALL_WAIT_FOR_EVENTS);
+ return vbglR3DoIOCtl(VBGL_IOCTL_INTERRUPT_ALL_WAIT_FOR_EVENTS, &Req, sizeof(Req));
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp
new file mode 100644
index 00000000..e80f1f55
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGR.cpp
@@ -0,0 +1,90 @@
+/* $Id: VBoxGuestR3LibGR.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, GR.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+#include "VBoxGuestR3LibInternal.h"
+
+
+int vbglR3GRAlloc(VMMDevRequestHeader **ppReq, size_t cb, VMMDevRequestType enmReqType)
+{
+ VMMDevRequestHeader *pReq;
+
+ AssertPtrReturn(ppReq, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cb >= sizeof(VMMDevRequestHeader) && cb < _1G, ("%#zx vs %#zx\n", cb, sizeof(VMMDevRequestHeader)),
+ VERR_INVALID_PARAMETER);
+
+ pReq = (VMMDevRequestHeader *)RTMemTmpAlloc(cb);
+ if (RT_LIKELY(pReq))
+ {
+ pReq->size = (uint32_t)cb;
+ pReq->version = VMMDEV_REQUEST_HEADER_VERSION;
+ pReq->requestType = enmReqType;
+ pReq->rc = VERR_GENERAL_FAILURE;
+ pReq->reserved1 = 0;
+ pReq->fRequestor = 0;
+
+ *ppReq = pReq;
+
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+int vbglR3GRPerform(VMMDevRequestHeader *pReq)
+{
+ PVBGLREQHDR pReqHdr = (PVBGLREQHDR)pReq;
+ uint32_t const cbReq = pReqHdr->cbIn;
+ Assert(pReqHdr->cbOut == 0 || pReqHdr->cbOut == cbReq);
+ pReqHdr->cbOut = cbReq;
+ if (pReq->size < _1K)
+ return vbglR3DoIOCtl(VBGL_IOCTL_VMMDEV_REQUEST(cbReq), pReqHdr, cbReq);
+ return vbglR3DoIOCtl(VBGL_IOCTL_VMMDEV_REQUEST_BIG, pReqHdr, cbReq);
+}
+
+
+void vbglR3GRFree(VMMDevRequestHeader *pReq)
+{
+ RTMemTmpFree(pReq);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp
new file mode 100644
index 00000000..fc29936c
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestCtrl.cpp
@@ -0,0 +1,2214 @@
+/* $Id: VBoxGuestR3LibGuestCtrl.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, guest control.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/cpp/autores.h>
+#include <iprt/stdarg.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/GuestHost/GuestControl.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+
+#ifndef RT_OS_WINDOWS
+# include <signal.h>
+# ifdef RT_OS_DARWIN
+# include <pthread.h>
+# define sigprocmask pthread_sigmask /* On xnu sigprocmask works on the process, not the calling thread as elsewhere. */
+# endif
+#endif
+
+#include "VBoxGuestR3LibInternal.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Set if GUEST_MSG_PEEK_WAIT and friends are supported. */
+static int g_fVbglR3GuestCtrlHavePeekGetCancel = -1;
+
+
+/**
+ * Connects to the guest control service.
+ *
+ * @returns VBox status code
+ * @param pidClient Where to put The client ID on success. The client ID
+ * must be passed to all the other calls to the service.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlConnect(uint32_t *pidClient)
+{
+ return VbglR3HGCMConnect("VBoxGuestControlSvc", pidClient);
+}
+
+
+/**
+ * Disconnect from the guest control service.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlDisconnect(uint32_t idClient)
+{
+ return VbglR3HGCMDisconnect(idClient);
+}
+
+
+/**
+ * Waits until a new host message arrives.
+ * This will block until a message becomes available.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ * @param pidMsg Where to store the message id.
+ * @param pcParameters Where to store the number of parameters which will
+ * be received in a second call to the host.
+ */
+static int vbglR3GuestCtrlMsgWaitFor(uint32_t idClient, uint32_t *pidMsg, uint32_t *pcParameters)
+{
+ AssertPtrReturn(pidMsg, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcParameters, VERR_INVALID_POINTER);
+
+ HGCMMsgWaitFor Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient,
+ GUEST_MSG_WAIT, /* Tell the host we want our next message. */
+ 2); /* Just peek for the next message! */
+ VbglHGCMParmUInt32Set(&Msg.msg, 0);
+ VbglHGCMParmUInt32Set(&Msg.num_parms, 0);
+
+ /*
+ * We should always get a VERR_TOO_MUCH_DATA response here, see
+ * guestControl::HostMessage::Peek() and its caller ClientState::SendReply().
+ * We accept success too here, in case someone decide to make the protocol
+ * slightly more sane.
+ *
+ * Note! A really sane protocol design would have a separate call for getting
+ * info about a pending message (returning VINF_SUCCESS), and a separate
+ * one for retriving the actual message parameters. Not this weird
+ * stuff, to put it rather bluntly.
+ *
+ * Note! As a result of this weird design, we are not able to correctly
+ * retrieve message if we're interrupted by a signal, like SIGCHLD.
+ * Because IPRT wants to use waitpid(), we're forced to have a handler
+ * installed for SIGCHLD, so when working with child processes there
+ * will be signals in the air and we will get VERR_INTERRUPTED returns.
+ * The way HGCM handles interrupted calls is to silently (?) drop them
+ * as they complete (see VMMDev), so the server knows little about it
+ * and just goes on to the next message inline.
+ *
+ * So, as a "temporary" mesasure, we block SIGCHLD here while waiting,
+ * because it will otherwise be impossible do simple stuff like 'mkdir'
+ * on a mac os x guest, and probably most other unix guests.
+ */
+#ifdef RT_OS_WINDOWS
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+#else
+ sigset_t SigSet;
+ sigemptyset(&SigSet);
+ sigaddset(&SigSet, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &SigSet, NULL);
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ sigprocmask(SIG_UNBLOCK, &SigSet, NULL);
+#endif
+ if ( rc == VERR_TOO_MUCH_DATA
+ || RT_SUCCESS(rc))
+ {
+ int rc2 = VbglHGCMParmUInt32Get(&Msg.msg, pidMsg);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = VbglHGCMParmUInt32Get(&Msg.num_parms, pcParameters);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Ok, so now we know what message type and how much parameters there are. */
+ return rc;
+ }
+ }
+ rc = rc2;
+ }
+ *pidMsg = UINT32_MAX - 1;
+ *pcParameters = UINT32_MAX - 2;
+ return rc;
+}
+
+
+/**
+ * Determins the value of g_fVbglR3GuestCtrlHavePeekGetCancel.
+ *
+ * @returns true if supported, false if not.
+ * @param idClient The client ID to use for the testing.
+ */
+DECL_NO_INLINE(static, bool) vbglR3GuestCtrlDetectPeekGetCancelSupport(uint32_t idClient)
+{
+ /*
+ * Seems we get VINF_SUCCESS back from the host if we try unsupported
+ * guest control messages, so we need to supply some random message
+ * parameters and check that they change.
+ */
+ uint32_t const idDummyMsg = UINT32_C(0x8350bdca);
+ uint32_t const cDummyParmeters = UINT32_C(0x7439604f);
+ uint32_t const cbDummyMask = UINT32_C(0xc0ffe000);
+ Assert(cDummyParmeters > VMMDEV_MAX_HGCM_PARMS);
+
+ int rc;
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter idMsg;
+ HGCMFunctionParameter cParams;
+ HGCMFunctionParameter acbParams[14];
+ } PeekCall;
+ Assert(RT_ELEMENTS(PeekCall.acbParams) + 2 < VMMDEV_MAX_HGCM_PARMS);
+
+ do
+ {
+ memset(&PeekCall, 0xf6, sizeof(PeekCall));
+ VBGL_HGCM_HDR_INIT(&PeekCall.Hdr, idClient, GUEST_MSG_PEEK_NOWAIT, 16);
+ VbglHGCMParmUInt32Set(&PeekCall.idMsg, idDummyMsg);
+ VbglHGCMParmUInt32Set(&PeekCall.cParams, cDummyParmeters);
+ for (uint32_t i = 0; i < RT_ELEMENTS(PeekCall.acbParams); i++)
+ VbglHGCMParmUInt32Set(&PeekCall.acbParams[i], i | cbDummyMask);
+
+ rc = VbglR3HGCMCall(&PeekCall.Hdr, sizeof(PeekCall));
+ } while (rc == VERR_INTERRUPTED);
+
+ LogRel2(("vbglR3GuestCtrlDetectPeekGetCancelSupport: rc=%Rrc %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x\n",
+ rc, PeekCall.idMsg.u.value32, PeekCall.cParams.u.value32,
+ PeekCall.acbParams[ 0].u.value32, PeekCall.acbParams[ 1].u.value32,
+ PeekCall.acbParams[ 2].u.value32, PeekCall.acbParams[ 3].u.value32,
+ PeekCall.acbParams[ 4].u.value32, PeekCall.acbParams[ 5].u.value32,
+ PeekCall.acbParams[ 6].u.value32, PeekCall.acbParams[ 7].u.value32,
+ PeekCall.acbParams[ 8].u.value32, PeekCall.acbParams[ 9].u.value32,
+ PeekCall.acbParams[10].u.value32, PeekCall.acbParams[11].u.value32,
+ PeekCall.acbParams[12].u.value32, PeekCall.acbParams[13].u.value32));
+
+ /*
+ * VERR_TRY_AGAIN is likely and easy.
+ */
+ if ( rc == VERR_TRY_AGAIN
+ && PeekCall.idMsg.u.value32 == 0
+ && PeekCall.cParams.u.value32 == 0
+ && PeekCall.acbParams[0].u.value32 == 0
+ && PeekCall.acbParams[1].u.value32 == 0
+ && PeekCall.acbParams[2].u.value32 == 0
+ && PeekCall.acbParams[3].u.value32 == 0)
+ {
+ g_fVbglR3GuestCtrlHavePeekGetCancel = 1;
+ LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Supported (#1)\n"));
+ return true;
+ }
+
+ /*
+ * VINF_SUCCESS is annoying but with 16 parameters we've got plenty to check.
+ */
+ if ( rc == VINF_SUCCESS
+ && PeekCall.idMsg.u.value32 != idDummyMsg
+ && PeekCall.idMsg.u.value32 != 0
+ && PeekCall.cParams.u.value32 <= VMMDEV_MAX_HGCM_PARMS)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(PeekCall.acbParams); i++)
+ if (PeekCall.acbParams[i].u.value32 != (i | cbDummyMask))
+ {
+ g_fVbglR3GuestCtrlHavePeekGetCancel = 0;
+ LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Not supported (#1)\n"));
+ return false;
+ }
+ g_fVbglR3GuestCtrlHavePeekGetCancel = 1;
+ LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Supported (#2)\n"));
+ return true;
+ }
+
+ /*
+ * Okay, pretty sure it's not supported then.
+ */
+ LogRel(("vbglR3GuestCtrlDetectPeekGetCancelSupport: Not supported (#3)\n"));
+ g_fVbglR3GuestCtrlHavePeekGetCancel = 0;
+ return false;
+}
+
+
+/**
+ * Reads g_fVbglR3GuestCtrlHavePeekGetCancel and resolved -1.
+ *
+ * @returns true if supported, false if not.
+ * @param idClient The client ID to use for the testing.
+ */
+DECLINLINE(bool) vbglR3GuestCtrlSupportsPeekGetCancel(uint32_t idClient)
+{
+ int fState = g_fVbglR3GuestCtrlHavePeekGetCancel;
+ if (RT_LIKELY(fState != -1))
+ return fState != 0;
+ return vbglR3GuestCtrlDetectPeekGetCancelSupport(idClient);
+}
+
+
+/**
+ * Figures which getter function to use to retrieve the message.
+ */
+DECLINLINE(uint32_t) vbglR3GuestCtrlGetMsgFunctionNo(uint32_t idClient)
+{
+ return vbglR3GuestCtrlSupportsPeekGetCancel(idClient) ? GUEST_MSG_GET : GUEST_MSG_WAIT;
+}
+
+
+/**
+ * Checks if the host supports the optimizes message and session functions.
+ *
+ * @returns true / false.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ * We may need to use this for checking.
+ * @since 6.0
+ */
+VBGLR3DECL(bool) VbglR3GuestCtrlSupportsOptimizations(uint32_t idClient)
+{
+ return vbglR3GuestCtrlSupportsPeekGetCancel(idClient);
+}
+
+
+/**
+ * Make us the guest control master client.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlMakeMeMaster(uint32_t idClient)
+{
+ int rc;
+ do
+ {
+ VBGLIOCHGCMCALL Hdr;
+ VBGL_HGCM_HDR_INIT(&Hdr, idClient, GUEST_MSG_MAKE_ME_MASTER, 0);
+ rc = VbglR3HGCMCall(&Hdr, sizeof(Hdr));
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+}
+
+
+/**
+ * Reports features to the host and retrieve host feature set.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ * @param fGuestFeatures Features to report, VBOX_GUESTCTRL_GF_XXX.
+ * @param pfHostFeatures Where to store the features VBOX_GUESTCTRL_HF_XXX.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlReportFeatures(uint32_t idClient, uint64_t fGuestFeatures, uint64_t *pfHostFeatures)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter f64Features0;
+ HGCMFunctionParameter f64Features1;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_REPORT_FEATURES, 2);
+ VbglHGCMParmUInt64Set(&Msg.f64Features0, fGuestFeatures);
+ VbglHGCMParmUInt64Set(&Msg.f64Features1, VBOX_GUESTCTRL_GF_1_MUST_BE_ONE);
+
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit);
+ Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit);
+ if (Msg.f64Features1.u.value64 & VBOX_GUESTCTRL_GF_1_MUST_BE_ONE)
+ rc = VERR_NOT_SUPPORTED;
+ else if (pfHostFeatures)
+ *pfHostFeatures = Msg.f64Features0.u.value64;
+ break;
+ }
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+
+}
+
+
+/**
+ * Query the host features.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ * @param pfHostFeatures Where to store the host feature, VBOX_GUESTCTRL_HF_XXX.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlQueryFeatures(uint32_t idClient, uint64_t *pfHostFeatures)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter f64Features0;
+ HGCMFunctionParameter f64Features1;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_QUERY_FEATURES, 2);
+ VbglHGCMParmUInt64Set(&Msg.f64Features0, 0);
+ VbglHGCMParmUInt64Set(&Msg.f64Features1, RT_BIT_64(63));
+
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Assert(Msg.f64Features0.type == VMMDevHGCMParmType_64bit);
+ Assert(Msg.f64Features1.type == VMMDevHGCMParmType_64bit);
+ if (Msg.f64Features1.u.value64 & RT_BIT_64(63))
+ rc = VERR_NOT_SUPPORTED;
+ else if (pfHostFeatures)
+ *pfHostFeatures = Msg.f64Features0.u.value64;
+ break;
+ }
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+
+}
+
+
+/**
+ * Peeks at the next host message, waiting for one to turn up.
+ *
+ * @returns VBox status code.
+ * @retval VERR_INTERRUPTED if interrupted. Does the necessary cleanup, so
+ * caller just have to repeat this call.
+ * @retval VERR_VM_RESTORED if the VM has been restored (idRestoreCheck).
+ *
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ * @param pidMsg Where to store the message id.
+ * @param pcParameters Where to store the number of parameters which will
+ * be received in a second call to the host.
+ * @param pidRestoreCheck Pointer to the VbglR3GetSessionId() variable to use
+ * for the VM restore check. Optional.
+ *
+ * @note Restore check is only performed optimally with a 6.0 host.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlMsgPeekWait(uint32_t idClient, uint32_t *pidMsg, uint32_t *pcParameters, uint64_t *pidRestoreCheck)
+{
+ AssertPtrReturn(pidMsg, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcParameters, VERR_INVALID_POINTER);
+
+ int rc;
+ if (vbglR3GuestCtrlSupportsPeekGetCancel(idClient))
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter idMsg; /* Doubles as restore check on input. */
+ HGCMFunctionParameter cParameters;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_PEEK_WAIT, 2);
+ VbglHGCMParmUInt64Set(&Msg.idMsg, pidRestoreCheck ? *pidRestoreCheck : 0);
+ VbglHGCMParmUInt32Set(&Msg.cParameters, 0);
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ LogRel2(("VbglR3GuestCtrlMsgPeekWait -> %Rrc\n", rc));
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsgReturn( Msg.idMsg.type == VMMDevHGCMParmType_64bit
+ && Msg.cParameters.type == VMMDevHGCMParmType_32bit,
+ ("msg.type=%d num_parms.type=%d\n", Msg.idMsg.type, Msg.cParameters.type),
+ VERR_INTERNAL_ERROR_3);
+
+ *pidMsg = (uint32_t)Msg.idMsg.u.value64;
+ *pcParameters = Msg.cParameters.u.value32;
+ return rc;
+ }
+
+ /*
+ * If interrupted we must cancel the call so it doesn't prevent us from making another one.
+ */
+ if (rc == VERR_INTERRUPTED)
+ {
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_CANCEL, 0);
+ int rc2 = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg.Hdr));
+ AssertRC(rc2);
+ }
+
+ /*
+ * If restored, update pidRestoreCheck.
+ */
+ if (rc == VERR_VM_RESTORED && pidRestoreCheck)
+ *pidRestoreCheck = Msg.idMsg.u.value64;
+
+ *pidMsg = UINT32_MAX - 1;
+ *pcParameters = UINT32_MAX - 2;
+ return rc;
+ }
+
+ /*
+ * Fallback if host < v6.0.
+ *
+ * Note! The restore check isn't perfect. Would require checking afterwards
+ * and stash the result if we were restored during the call. Too much
+ * hazzle for a downgrade scenario.
+ */
+ if (pidRestoreCheck)
+ {
+ uint64_t idRestoreCur = *pidRestoreCheck;
+ rc = VbglR3GetSessionId(&idRestoreCur);
+ if (RT_SUCCESS(rc) && idRestoreCur != *pidRestoreCheck)
+ {
+ *pidRestoreCheck = idRestoreCur;
+ return VERR_VM_RESTORED;
+ }
+ }
+
+ rc = vbglR3GuestCtrlMsgWaitFor(idClient, pidMsg, pcParameters);
+ if (rc == VERR_TOO_MUCH_DATA)
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Asks the host guest control service to set a message filter to this
+ * client so that it only will receive certain messages in the future.
+ * The filter(s) are a bitmask for the context IDs, served from the host.
+ *
+ * @return IPRT status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ * @param uValue The value to filter messages for.
+ * @param uMaskAdd Filter mask to add.
+ * @param uMaskRemove Filter mask to remove.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlMsgFilterSet(uint32_t idClient, uint32_t uValue, uint32_t uMaskAdd, uint32_t uMaskRemove)
+{
+ HGCMMsgFilterSet Msg;
+
+ /* Tell the host we want to set a filter. */
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_MSG_FILTER_SET, 4);
+ VbglHGCMParmUInt32Set(&Msg.value, uValue);
+ VbglHGCMParmUInt32Set(&Msg.mask_add, uMaskAdd);
+ VbglHGCMParmUInt32Set(&Msg.mask_remove, uMaskRemove);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0 /* Flags, unused */);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+
+/**
+ * Replies to a message from the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param rc Guest rc to reply.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlMsgReply(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ int rc)
+{
+ return VbglR3GuestCtrlMsgReplyEx(pCtx, rc, 0 /* uType */,
+ NULL /* pvPayload */, 0 /* cbPayload */);
+}
+
+
+/**
+ * Replies to a message from the host, extended version.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param rc Guest rc to reply.
+ * @param uType Reply type; not used yet and must be 0.
+ * @param pvPayload Pointer to data payload to reply. Optional.
+ * @param cbPayload Size of data payload (in bytes) to reply.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlMsgReplyEx(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ int rc, uint32_t uType,
+ void *pvPayload, uint32_t cbPayload)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ /* Everything else is optional. */
+
+ HGCMMsgReply Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_REPLY, 4);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, uType);
+ VbglHGCMParmUInt32Set(&Msg.rc, (uint32_t)rc); /* int vs. uint32_t */
+ VbglHGCMParmPtrSet(&Msg.payload, pvPayload, cbPayload);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Tell the host to skip the current message replying VERR_NOT_SUPPORTED
+ *
+ * @return IPRT status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ * @param rcSkip The status code to pass back to Main when skipping.
+ * @param idMsg The message ID to skip, pass UINT32_MAX to pass any.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlMsgSkip(uint32_t idClient, int rcSkip, uint32_t idMsg)
+{
+ if (vbglR3GuestCtrlSupportsPeekGetCancel(idClient))
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter rcSkip;
+ HGCMFunctionParameter idMsg;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SKIP, 2);
+ VbglHGCMParmUInt32Set(&Msg.rcSkip, (uint32_t)rcSkip);
+ VbglHGCMParmUInt32Set(&Msg.idMsg, idMsg);
+ return VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ }
+
+ /* This is generally better than nothing... */
+ return VbglR3GuestCtrlMsgSkipOld(idClient);
+}
+
+
+/**
+ * Tells the host service to skip the current message returned by
+ * VbglR3GuestCtrlMsgWaitFor().
+ *
+ * @return IPRT status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlMsgSkipOld(uint32_t idClient)
+{
+ HGCMMsgSkip Msg;
+
+ /* Tell the host we want to skip the current assigned message. */
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_MSG_SKIP_OLD, 1);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0 /* Flags, unused */);
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+
+/**
+ * Asks the host to cancel (release) all pending waits which were deferred.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID returned by VbglR3GuestCtrlConnect().
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlCancelPendingWaits(uint32_t idClient)
+{
+ HGCMMsgCancelPendingWaits Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_MSG_CANCEL, 0);
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+
+/**
+ * Prepares a session.
+ * @since 6.0
+ * @sa GUEST_SESSION_PREPARE
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionPrepare(uint32_t idClient, uint32_t idSession, void const *pvKey, uint32_t cbKey)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter idSession;
+ HGCMFunctionParameter pKey;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SESSION_PREPARE, 2);
+ VbglHGCMParmUInt32Set(&Msg.idSession, idSession);
+ VbglHGCMParmPtrSet(&Msg.pKey, (void *)pvKey, cbKey);
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+}
+
+
+/**
+ * Accepts a session.
+ * @since 6.0
+ * @sa GUEST_SESSION_ACCEPT
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionAccept(uint32_t idClient, uint32_t idSession, void const *pvKey, uint32_t cbKey)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter idSession;
+ HGCMFunctionParameter pKey;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SESSION_ACCEPT, 2);
+ VbglHGCMParmUInt32Set(&Msg.idSession, idSession);
+ VbglHGCMParmPtrSet(&Msg.pKey, (void *)pvKey, cbKey);
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+}
+
+
+/**
+ * Cancels a prepared session.
+ * @since 6.0
+ * @sa GUEST_SESSION_CANCEL_PREPARED
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionCancelPrepared(uint32_t idClient, uint32_t idSession)
+{
+ int rc;
+ do
+ {
+ struct
+ {
+ VBGLIOCHGCMCALL Hdr;
+ HGCMFunctionParameter idSession;
+ } Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, idClient, GUEST_MSG_SESSION_CANCEL_PREPARED, 1);
+ VbglHGCMParmUInt32Set(&Msg.idSession, idSession);
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ } while (rc == VERR_INTERRUPTED);
+ return rc;
+}
+
+
+/**
+ * Invalidates the internal state because the (VM) session has been changed (i.e. restored).
+ *
+ * @returns VBox status code.
+ * @param idClient Client ID to use for invalidating state.
+ * @param idNewControlSession New control session ID. Currently unused.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionHasChanged(uint32_t idClient, uint64_t idNewControlSession)
+{
+ RT_NOREF(idNewControlSession);
+
+ vbglR3GuestCtrlDetectPeekGetCancelSupport(idClient);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Asks a specific guest session to close.
+ *
+ * @return IPRT status code.
+ * @param pCtx Guest control command context to use.
+ * @param fFlags Some kind of flag. Figure it out yourself.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionClose(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t fFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER);
+
+ HGCMMsgSessionClose Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_SESSION_CLOSE, pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.flags, fFlags);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+
+/**
+ * Notifies a guest session.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uType Notification type of type GUEST_SESSION_NOTIFYTYPE_XXX.
+ * @param iResult Result code (rc) to notify.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionNotify(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uType, int32_t iResult)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgSessionNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_SESSION_NOTIFY, 3);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, uType);
+ VbglHGCMParmUInt32Set(&Msg.result, (uint32_t)iResult);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+/**
+ * Initializes a session startup info, extended version.
+ *
+ * @returns VBox status code.
+ * @param pStartupInfo Session startup info to initializes.
+ * @param cbUser Size (in bytes) to use for the user name buffer.
+ * @param cbPassword Size (in bytes) to use for the password buffer.
+ * @param cbDomain Size (in bytes) to use for the domain name buffer.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionStartupInfoInitEx(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo,
+ size_t cbUser, size_t cbPassword, size_t cbDomain)
+{
+ AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
+
+ RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO));
+
+#define ALLOC_STR(a_Str, a_cb) \
+ if ((a_cb) > 0) \
+ { \
+ pStartupInfo->psz##a_Str = RTStrAlloc(a_cb); \
+ AssertPtrBreak(pStartupInfo->psz##a_Str); \
+ pStartupInfo->cb##a_Str = (uint32_t)a_cb; \
+ }
+
+ do
+ {
+ ALLOC_STR(User, cbUser);
+ ALLOC_STR(Password, cbPassword);
+ ALLOC_STR(Domain, cbDomain);
+
+ return VINF_SUCCESS;
+
+ } while (0);
+
+#undef ALLOC_STR
+
+ VbglR3GuestCtrlSessionStartupInfoDestroy(pStartupInfo);
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Initializes a session startup info.
+ *
+ * @returns VBox status code.
+ * @param pStartupInfo Session startup info to initializes.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionStartupInfoInit(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo)
+{
+ return VbglR3GuestCtrlSessionStartupInfoInitEx(pStartupInfo,
+ GUEST_PROC_DEF_USER_LEN, GUEST_PROC_DEF_PASSWORD_LEN,
+ GUEST_PROC_DEF_DOMAIN_LEN);
+}
+
+/**
+ * Destroys a session startup info.
+ *
+ * @param pStartupInfo Session startup info to destroy.
+ */
+VBGLR3DECL(void) VbglR3GuestCtrlSessionStartupInfoDestroy(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo)
+{
+ if (!pStartupInfo)
+ return;
+
+ RTStrFree(pStartupInfo->pszUser);
+ RTStrFree(pStartupInfo->pszPassword);
+ RTStrFree(pStartupInfo->pszDomain);
+
+ RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO));
+}
+
+/**
+ * Free's a session startup info.
+ *
+ * @param pStartupInfo Session startup info to free.
+ * The pointer will not be valid anymore after return.
+ */
+VBGLR3DECL(void) VbglR3GuestCtrlSessionStartupInfoFree(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo)
+{
+ if (!pStartupInfo)
+ return;
+
+ VbglR3GuestCtrlSessionStartupInfoDestroy(pStartupInfo);
+
+ RTMemFree(pStartupInfo);
+ pStartupInfo = NULL;
+}
+
+/**
+ * Duplicates a session startup info.
+ *
+ * @returns Duplicated session startup info on success, or NULL on error.
+ * @param pStartupInfo Session startup info to duplicate.
+ */
+VBGLR3DECL(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO) VbglR3GuestCtrlSessionStartupInfoDup(PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo)
+{
+ AssertPtrReturn(pStartupInfo, NULL);
+
+ PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfoDup = (PVBGLR3GUESTCTRLSESSIONSTARTUPINFO)
+ RTMemDup(pStartupInfo, sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO));
+ if (pStartupInfoDup)
+ {
+ do
+ {
+ pStartupInfoDup->pszUser = NULL;
+ pStartupInfoDup->pszPassword = NULL;
+ pStartupInfoDup->pszDomain = NULL;
+
+#define DUP_STR(a_Str) \
+ if (pStartupInfo->cb##a_Str) \
+ { \
+ pStartupInfoDup->psz##a_Str = (char *)RTStrDup(pStartupInfo->psz##a_Str); \
+ AssertPtrBreak(pStartupInfoDup->psz##a_Str); \
+ pStartupInfoDup->cb##a_Str = (uint32_t)strlen(pStartupInfoDup->psz##a_Str) + 1 /* Include terminator */; \
+ }
+ DUP_STR(User);
+ DUP_STR(Password);
+ DUP_STR(Domain);
+
+#undef DUP_STR
+
+ return pStartupInfoDup;
+
+ } while (0); /* To use break macros above. */
+
+ VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfoDup);
+ }
+
+ return NULL;
+}
+
+/**
+ * Retrieves a HOST_SESSION_CREATE message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param ppStartupInfo Where to store the allocated session startup info.
+ * Needs to be free'd by VbglR3GuestCtrlSessionStartupInfoFree().
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionGetOpen(PVBGLR3GUESTCTRLCMDCTX pCtx, PVBGLR3GUESTCTRLSESSIONSTARTUPINFO *ppStartupInfo)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 6, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppStartupInfo, VERR_INVALID_POINTER);
+
+ PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo
+ = (PVBGLR3GUESTCTRLSESSIONSTARTUPINFO)RTMemAlloc(sizeof(VBGLR3GUESTCTRLSESSIONSTARTUPINFO));
+ if (!pStartupInfo)
+ return VERR_NO_MEMORY;
+
+ int rc = VbglR3GuestCtrlSessionStartupInfoInit(pStartupInfo);
+ if (RT_FAILURE(rc))
+ {
+ VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo);
+ return rc;
+ }
+
+ do
+ {
+ HGCMMsgSessionOpen Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_SESSION_CREATE);
+ VbglHGCMParmUInt32Set(&Msg.protocol, 0);
+ VbglHGCMParmPtrSet(&Msg.username, pStartupInfo->pszUser, pStartupInfo->cbUser);
+ VbglHGCMParmPtrSet(&Msg.password, pStartupInfo->pszPassword, pStartupInfo->cbPassword);
+ VbglHGCMParmPtrSet(&Msg.domain, pStartupInfo->pszDomain, pStartupInfo->cbDomain);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.protocol.GetUInt32(&pStartupInfo->uProtocol);
+ Msg.flags.GetUInt32(&pStartupInfo->fFlags);
+
+ pStartupInfo->uSessionID = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pCtx->uContextID);
+ }
+
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppStartupInfo = pStartupInfo;
+ }
+ else
+ VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_SESSION_CLOSE message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlSessionGetClose(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *pfFlags, uint32_t *pidSession)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pfFlags, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgSessionClose Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_SESSION_CLOSE);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.flags.GetUInt32(pfFlags);
+
+ if (pidSession)
+ *pidSession = VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(pCtx->uContextID);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_PATH_RENAME message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlPathGetRename(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ char *pszSource, uint32_t cbSource,
+ char *pszDest, uint32_t cbDest,
+ uint32_t *pfFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pszSource, VERR_INVALID_POINTER);
+ AssertReturn(cbSource, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszDest, VERR_INVALID_POINTER);
+ AssertReturn(cbDest, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pfFlags, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgPathRename Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_PATH_RENAME);
+ VbglHGCMParmPtrSet(&Msg.source, pszSource, cbSource);
+ VbglHGCMParmPtrSet(&Msg.dest, pszDest, cbDest);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.flags.GetUInt32(pfFlags);
+ }
+
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_PATH_USER_DOCUMENTS message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlPathGetUserDocuments(PVBGLR3GUESTCTRLCMDCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 1, VERR_INVALID_PARAMETER);
+
+ int rc;
+ do
+ {
+ HGCMMsgPathUserDocuments Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_PATH_USER_DOCUMENTS);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_PATH_USER_HOME message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlPathGetUserHome(PVBGLR3GUESTCTRLCMDCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 1, VERR_INVALID_PARAMETER);
+
+ int rc;
+ do
+ {
+ HGCMMsgPathUserHome Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_PATH_USER_HOME);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+/**
+ * Retrieves a HOST_MSG_SHUTDOWN message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param pfAction Where to store the action flags on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlGetShutdown(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *pfAction)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pfAction, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgShutdown Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_SHUTDOWN);
+ VbglHGCMParmUInt32Set(&Msg.action, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.action.GetUInt32(pfAction);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+/**
+ * Initializes a process startup info, extended version.
+ *
+ * @returns VBox status code.
+ * @param pStartupInfo Process startup info to initializes.
+ * @param cbCmd Size (in bytes) to use for the command buffer.
+ * @param cbUser Size (in bytes) to use for the user name buffer.
+ * @param cbPassword Size (in bytes) to use for the password buffer.
+ * @param cbDomain Size (in bytes) to use for the domain buffer.
+ * @param cbArgs Size (in bytes) to use for the arguments buffer.
+ * @param cbEnv Size (in bytes) to use for the environment buffer.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcStartupInfoInitEx(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo,
+ size_t cbCmd,
+ size_t cbUser, size_t cbPassword, size_t cbDomain,
+ size_t cbArgs, size_t cbEnv)
+{
+ AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
+ AssertReturn(cbCmd, VERR_INVALID_PARAMETER);
+ AssertReturn(cbUser, VERR_INVALID_PARAMETER);
+ AssertReturn(cbPassword, VERR_INVALID_PARAMETER);
+ AssertReturn(cbDomain, VERR_INVALID_PARAMETER);
+ AssertReturn(cbArgs, VERR_INVALID_PARAMETER);
+ AssertReturn(cbEnv, VERR_INVALID_PARAMETER);
+
+ RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO));
+
+#define ALLOC_STR(a_Str, a_cb) \
+ if ((a_cb) > 0) \
+ { \
+ pStartupInfo->psz##a_Str = RTStrAlloc(a_cb); \
+ AssertPtrBreak(pStartupInfo->psz##a_Str); \
+ pStartupInfo->cb##a_Str = (uint32_t)a_cb; \
+ }
+
+ do
+ {
+ ALLOC_STR(Cmd, cbCmd);
+ ALLOC_STR(Args, cbArgs);
+ ALLOC_STR(Env, cbEnv);
+ ALLOC_STR(User, cbUser);
+ ALLOC_STR(Password, cbPassword);
+ ALLOC_STR(Domain, cbDomain);
+
+ return VINF_SUCCESS;
+
+ } while (0);
+
+#undef ALLOC_STR
+
+ VbglR3GuestCtrlProcStartupInfoDestroy(pStartupInfo);
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Initializes a process startup info with default values.
+ *
+ * @param pStartupInfo Process startup info to initializes.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcStartupInfoInit(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo)
+{
+ return VbglR3GuestCtrlProcStartupInfoInitEx(pStartupInfo,
+ GUEST_PROC_DEF_CMD_LEN,
+ GUEST_PROC_DEF_USER_LEN /* Deprecated, now handled via session creation. */,
+ GUEST_PROC_DEF_PASSWORD_LEN /* Ditto. */,
+ GUEST_PROC_DEF_DOMAIN_LEN /* Ditto. */,
+ GUEST_PROC_DEF_ARGS_LEN, GUEST_PROC_DEF_ENV_LEN);
+}
+
+/**
+ * Destroys a process startup info.
+ *
+ * @param pStartupInfo Process startup info to destroy.
+ */
+VBGLR3DECL(void) VbglR3GuestCtrlProcStartupInfoDestroy(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo)
+{
+ if (!pStartupInfo)
+ return;
+
+ RTStrFree(pStartupInfo->pszCmd);
+ RTStrFree(pStartupInfo->pszArgs);
+ RTStrFree(pStartupInfo->pszEnv);
+ RTStrFree(pStartupInfo->pszUser);
+ RTStrFree(pStartupInfo->pszPassword);
+ RTStrFree(pStartupInfo->pszDomain);
+
+ RT_BZERO(pStartupInfo, sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO));
+}
+
+/**
+ * Free's a process startup info.
+ *
+ * @param pStartupInfo Process startup info to free.
+ * The pointer will not be valid anymore after return.
+ */
+VBGLR3DECL(void) VbglR3GuestCtrlProcStartupInfoFree(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo)
+{
+ if (!pStartupInfo)
+ return;
+
+ VbglR3GuestCtrlProcStartupInfoDestroy(pStartupInfo);
+
+ RTMemFree(pStartupInfo);
+ pStartupInfo = NULL;
+}
+
+/**
+ * Duplicates a process startup info.
+ *
+ * @returns Duplicated process startup info on success, or NULL on error.
+ * @param pStartupInfo Process startup info to duplicate.
+ */
+VBGLR3DECL(PVBGLR3GUESTCTRLPROCSTARTUPINFO) VbglR3GuestCtrlProcStartupInfoDup(PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo)
+{
+ AssertPtrReturn(pStartupInfo, NULL);
+
+ PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfoDup = (PVBGLR3GUESTCTRLPROCSTARTUPINFO)
+ RTMemDup(pStartupInfo, sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO));
+ if (pStartupInfoDup)
+ {
+ do
+ {
+ pStartupInfoDup->pszCmd = NULL;
+ pStartupInfoDup->pszArgs = NULL;
+ pStartupInfoDup->pszEnv = NULL;
+ pStartupInfoDup->pszUser = NULL;
+ pStartupInfoDup->pszPassword = NULL;
+ pStartupInfoDup->pszDomain = NULL;
+
+#define DUP_STR(a_Str) \
+ if (pStartupInfo->cb##a_Str) \
+ { \
+ pStartupInfoDup->psz##a_Str = (char *)RTStrDup(pStartupInfo->psz##a_Str); \
+ AssertPtrBreak(pStartupInfoDup->psz##a_Str); \
+ pStartupInfoDup->cb##a_Str = (uint32_t)strlen(pStartupInfoDup->psz##a_Str) + 1 /* Include terminator */; \
+ }
+
+#define DUP_MEM(a_Str) \
+ if (pStartupInfo->cb##a_Str) \
+ { \
+ pStartupInfoDup->psz##a_Str = (char *)RTMemDup(pStartupInfo->psz##a_Str, pStartupInfo->cb##a_Str); \
+ AssertPtrBreak(pStartupInfoDup->psz##a_Str); \
+ pStartupInfoDup->cb##a_Str = (uint32_t)pStartupInfo->cb##a_Str; \
+ }
+
+ DUP_STR(Cmd);
+ DUP_MEM(Args);
+ DUP_MEM(Env);
+ DUP_STR(User);
+ DUP_STR(Password);
+ DUP_STR(Domain);
+
+#undef DUP_STR
+#undef DUP_MEM
+
+ return pStartupInfoDup;
+
+ } while (0); /* To use break macros above. */
+
+ VbglR3GuestCtrlProcStartupInfoFree(pStartupInfoDup);
+ }
+
+ return NULL;
+}
+
+/**
+ * Retrieves a HOST_EXEC_CMD message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param ppStartupInfo Where to store the allocated session startup info.
+ * Needs to be free'd by VbglR3GuestCtrlProcStartupInfoFree().
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcGetStart(PVBGLR3GUESTCTRLCMDCTX pCtx, PVBGLR3GUESTCTRLPROCSTARTUPINFO *ppStartupInfo)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppStartupInfo, VERR_INVALID_POINTER);
+
+ PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo
+ = (PVBGLR3GUESTCTRLPROCSTARTUPINFO)RTMemAlloc(sizeof(VBGLR3GUESTCTRLPROCSTARTUPINFO));
+ if (!pStartupInfo)
+ return VERR_NO_MEMORY;
+
+ int rc = VbglR3GuestCtrlProcStartupInfoInit(pStartupInfo);
+ if (RT_FAILURE(rc))
+ {
+ VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo);
+ return rc;
+ }
+
+ unsigned cRetries = 0;
+ const unsigned cMaxRetries = 32; /* Should be enough for now. */
+ const unsigned cGrowthFactor = 2; /* By how much the buffers will grow if they're too small yet. */
+
+ do
+ {
+ LogRel(("VbglR3GuestCtrlProcGetStart: Retrieving\n"));
+
+ HGCMMsgProcExec Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_CMD);
+ VbglHGCMParmPtrSet(&Msg.cmd, pStartupInfo->pszCmd, pStartupInfo->cbCmd);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+ VbglHGCMParmUInt32Set(&Msg.num_args, 0);
+ VbglHGCMParmPtrSet(&Msg.args, pStartupInfo->pszArgs, pStartupInfo->cbArgs);
+ VbglHGCMParmUInt32Set(&Msg.num_env, 0);
+ VbglHGCMParmUInt32Set(&Msg.cb_env, 0);
+ VbglHGCMParmPtrSet(&Msg.env, pStartupInfo->pszEnv, pStartupInfo->cbEnv);
+ if (pCtx->uProtocol < 2)
+ {
+ VbglHGCMParmPtrSet(&Msg.u.v1.username, pStartupInfo->pszUser, pStartupInfo->cbUser);
+ VbglHGCMParmPtrSet(&Msg.u.v1.password, pStartupInfo->pszPassword, pStartupInfo->cbPassword);
+ VbglHGCMParmUInt32Set(&Msg.u.v1.timeout, 0);
+ }
+ else
+ {
+ VbglHGCMParmUInt32Set(&Msg.u.v2.timeout, 0);
+ VbglHGCMParmUInt32Set(&Msg.u.v2.priority, 0);
+ VbglHGCMParmUInt32Set(&Msg.u.v2.num_affinity, 0);
+ VbglHGCMParmPtrSet(&Msg.u.v2.affinity, pStartupInfo->uAffinity, sizeof(pStartupInfo->uAffinity));
+ }
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("VbglR3GuestCtrlProcGetStart: 1 - %Rrc (retry %u, cbCmd=%RU32, cbArgs=%RU32, cbEnv=%RU32)\n",
+ rc, cRetries, pStartupInfo->cbCmd, pStartupInfo->cbArgs, pStartupInfo->cbEnv));
+
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && cRetries++ < cMaxRetries)
+ {
+#define GROW_STR(a_Str, a_cbMax) \
+ pStartupInfo->psz##a_Str = (char *)RTMemRealloc(pStartupInfo->psz##a_Str, \
+ RT_MIN(pStartupInfo->cb##a_Str * cGrowthFactor, a_cbMax)); \
+ AssertPtrBreakStmt(pStartupInfo->psz##a_Str, VERR_NO_MEMORY); \
+ pStartupInfo->cb##a_Str = RT_MIN(pStartupInfo->cb##a_Str * cGrowthFactor, a_cbMax);
+
+ /* We can't tell which parameter doesn't fit, so we have to resize all. */
+ GROW_STR(Cmd , GUEST_PROC_MAX_CMD_LEN);
+ GROW_STR(Args, GUEST_PROC_MAX_ARGS_LEN);
+ GROW_STR(Env, GUEST_PROC_MAX_ENV_LEN);
+
+#undef GROW_STR
+ LogRel(("VbglR3GuestCtrlProcGetStart: 2 - %Rrc (retry %u, cbCmd=%RU32, cbArgs=%RU32, cbEnv=%RU32)\n",
+ rc, cRetries, pStartupInfo->cbCmd, pStartupInfo->cbArgs, pStartupInfo->cbEnv));
+ LogRel(("g_fVbglR3GuestCtrlHavePeekGetCancel=%RTbool\n", RT_BOOL(g_fVbglR3GuestCtrlHavePeekGetCancel)));
+ }
+ else
+ break;
+ }
+ else
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.flags.GetUInt32(&pStartupInfo->fFlags);
+ Msg.num_args.GetUInt32(&pStartupInfo->cArgs);
+ Msg.num_env.GetUInt32(&pStartupInfo->cEnvVars);
+ Msg.cb_env.GetUInt32(&pStartupInfo->cbEnv);
+ if (pCtx->uProtocol < 2)
+ Msg.u.v1.timeout.GetUInt32(&pStartupInfo->uTimeLimitMS);
+ else
+ {
+ Msg.u.v2.timeout.GetUInt32(&pStartupInfo->uTimeLimitMS);
+ Msg.u.v2.priority.GetUInt32(&pStartupInfo->uPriority);
+ Msg.u.v2.num_affinity.GetUInt32(&pStartupInfo->cAffinity);
+ }
+ }
+ } while (( rc == VERR_INTERRUPTED
+ || rc == VERR_BUFFER_OVERFLOW) && g_fVbglR3GuestCtrlHavePeekGetCancel);
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppStartupInfo = pStartupInfo;
+ }
+ else
+ VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo);
+
+ LogRel(("VbglR3GuestCtrlProcGetStart: Returning %Rrc (retry %u, cbCmd=%RU32, cbArgs=%RU32, cbEnv=%RU32)\n",
+ rc, cRetries, pStartupInfo->cbCmd, pStartupInfo->cbArgs, pStartupInfo->cbEnv));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Allocates and gets host data, based on the message ID.
+ *
+ * This will block until data becomes available.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param puPID Where to return the guest PID to retrieve output from on success.
+ * @param puHandle Where to return the guest process handle to retrieve output from on success.
+ * @param pfFlags Where to return the output flags on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcGetOutput(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t *puPID, uint32_t *puHandle, uint32_t *pfFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(puPID, VERR_INVALID_POINTER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFlags, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgProcOutput Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_GET_OUTPUT);
+ VbglHGCMParmUInt32Set(&Msg.pid, 0);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, RT_UOFFSETOF(HGCMMsgProcOutput, data));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.pid.GetUInt32(puPID);
+ Msg.handle.GetUInt32(puHandle);
+ Msg.flags.GetUInt32(pfFlags);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves the input data from host which then gets sent to the started
+ * process (HOST_EXEC_SET_INPUT).
+ *
+ * This will block until data becomes available.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcGetInput(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t *puPID, uint32_t *pfFlags,
+ void *pvData, uint32_t cbData,
+ uint32_t *pcbSize)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 5, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(puPID, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFlags, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgProcInput Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_SET_INPUT);
+ VbglHGCMParmUInt32Set(&Msg.pid, 0);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+ VbglHGCMParmPtrSet(&Msg.data, pvData, cbData);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.pid.GetUInt32(puPID);
+ Msg.flags.GetUInt32(pfFlags);
+ Msg.size.GetUInt32(pcbSize);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+
+ if ( rc != VERR_TOO_MUCH_DATA
+ || g_fVbglR3GuestCtrlHavePeekGetCancel)
+ return rc;
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+/**
+ * Retrieves a HOST_DIR_REMOVE message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlDirGetRemove(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ char *pszPath, uint32_t cbPath,
+ uint32_t *pfFlags)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 3, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(cbPath, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pfFlags, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgDirRemove Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_DIR_REMOVE);
+ VbglHGCMParmPtrSet(&Msg.path, pszPath, cbPath);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.flags.GetUInt32(pfFlags);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_OPEN message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetOpen(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ char *pszFileName, uint32_t cbFileName,
+ char *pszAccess, uint32_t cbAccess,
+ char *pszDisposition, uint32_t cbDisposition,
+ char *pszSharing, uint32_t cbSharing,
+ uint32_t *puCreationMode,
+ uint64_t *poffAt)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertReturn(pCtx->uNumParms == 7, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pszFileName, VERR_INVALID_POINTER);
+ AssertReturn(cbFileName, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszAccess, VERR_INVALID_POINTER);
+ AssertReturn(cbAccess, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszDisposition, VERR_INVALID_POINTER);
+ AssertReturn(cbDisposition, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszSharing, VERR_INVALID_POINTER);
+ AssertReturn(cbSharing, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puCreationMode, VERR_INVALID_POINTER);
+ AssertPtrReturn(poffAt, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileOpen Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_OPEN);
+ VbglHGCMParmPtrSet(&Msg.filename, pszFileName, cbFileName);
+ VbglHGCMParmPtrSet(&Msg.openmode, pszAccess, cbAccess);
+ VbglHGCMParmPtrSet(&Msg.disposition, pszDisposition, cbDisposition);
+ VbglHGCMParmPtrSet(&Msg.sharing, pszSharing, cbSharing);
+ VbglHGCMParmUInt32Set(&Msg.creationmode, 0);
+ VbglHGCMParmUInt64Set(&Msg.offset, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.creationmode.GetUInt32(puCreationMode);
+ Msg.offset.GetUInt64(poffAt);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_CLOSE message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetClose(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileClose Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_CLOSE);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.handle.GetUInt32(puHandle);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_READ message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetRead(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle, uint32_t *puToRead)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 3, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(puToRead, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileRead Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_READ);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.handle.GetUInt32(puHandle);
+ Msg.size.GetUInt32(puToRead);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_READ_AT message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetReadAt(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t *puHandle, uint32_t *puToRead, uint64_t *poffAt)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(puToRead, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileReadAt Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_READ_AT);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+ VbglHGCMParmUInt64Set(&Msg.offset, 0);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.handle.GetUInt32(puHandle);
+ Msg.offset.GetUInt64(poffAt);
+ Msg.size.GetUInt32(puToRead);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_WRITE message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetWrite(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle,
+ void *pvData, uint32_t cbData, uint32_t *pcbSize)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileWrite Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_WRITE);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+ VbglHGCMParmPtrSet(&Msg.data, pvData, cbData);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.handle.GetUInt32(puHandle);
+ Msg.size.GetUInt32(pcbSize);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+
+ if ( rc != VERR_TOO_MUCH_DATA
+ || g_fVbglR3GuestCtrlHavePeekGetCancel)
+ return rc;
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_WRITE_AT message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetWriteAt(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle,
+ void *pvData, uint32_t cbData, uint32_t *pcbSize, uint64_t *poffAt)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 5, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileWriteAt Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_WRITE_AT);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+ VbglHGCMParmPtrSet(&Msg.data, pvData, cbData);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+ VbglHGCMParmUInt64Set(&Msg.offset, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.handle.GetUInt32(puHandle);
+ Msg.size.GetUInt32(pcbSize);
+ Msg.offset.GetUInt64(poffAt);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+
+ if ( rc != VERR_TOO_MUCH_DATA
+ || g_fVbglR3GuestCtrlHavePeekGetCancel)
+ return rc;
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_SEEK message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetSeek(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t *puHandle, uint32_t *puSeekMethod, uint64_t *poffAt)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 4, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(puSeekMethod, VERR_INVALID_POINTER);
+ AssertPtrReturn(poffAt, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileSeek Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_SEEK);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+ VbglHGCMParmUInt32Set(&Msg.method, 0);
+ VbglHGCMParmUInt64Set(&Msg.offset, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.handle.GetUInt32(puHandle);
+ Msg.method.GetUInt32(puSeekMethod);
+ Msg.offset.GetUInt64(poffAt);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_TELL message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetTell(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileTell Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_FILE_TELL);
+ VbglHGCMParmUInt32Set(&Msg.handle, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.handle.GetUInt32(puHandle);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_FILE_SET_SIZE message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileGetSetSize(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puHandle, uint64_t *pcbNew)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 3, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puHandle, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbNew, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgFileSetSize Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.Hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.id32Context, HOST_MSG_FILE_SET_SIZE);
+ VbglHGCMParmUInt32Set(&Msg.id32Handle, 0);
+ VbglHGCMParmUInt64Set(&Msg.cb64NewSize, 0);
+
+ rc = VbglR3HGCMCall(&Msg.Hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.id32Context.GetUInt32(&pCtx->uContextID);
+ Msg.id32Handle.GetUInt32(puHandle);
+ Msg.cb64NewSize.GetUInt64(pcbNew);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_EXEC_TERMINATE message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcGetTerminate(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t *puPID)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 2, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puPID, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgProcTerminate Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_TERMINATE);
+ VbglHGCMParmUInt32Set(&Msg.pid, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.pid.GetUInt32(puPID);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Retrieves a HOST_EXEC_WAIT_FOR message.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcGetWaitFor(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t *puPID, uint32_t *puWaitFlags, uint32_t *puTimeoutMS)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ AssertReturn(pCtx->uNumParms == 5, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puPID, VERR_INVALID_POINTER);
+
+ int rc;
+ do
+ {
+ HGCMMsgProcWaitFor Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, vbglR3GuestCtrlGetMsgFunctionNo(pCtx->uClientID), pCtx->uNumParms);
+ VbglHGCMParmUInt32Set(&Msg.context, HOST_MSG_EXEC_WAIT_FOR);
+ VbglHGCMParmUInt32Set(&Msg.pid, 0);
+ VbglHGCMParmUInt32Set(&Msg.flags, 0);
+ VbglHGCMParmUInt32Set(&Msg.timeout, 0);
+
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ Msg.context.GetUInt32(&pCtx->uContextID);
+ Msg.pid.GetUInt32(puPID);
+ Msg.flags.GetUInt32(puWaitFlags);
+ Msg.timeout.GetUInt32(puTimeoutMS);
+ }
+ } while (rc == VERR_INTERRUPTED && g_fVbglR3GuestCtrlHavePeekGetCancel);
+ return rc;
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_OPEN message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param uFileHandle File handle of opened file on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbOpen(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t uRc, uint32_t uFileHandle)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_OPEN);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmUInt32Set(&Msg.u.open.handle, uFileHandle);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.open));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_CLOSE message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbClose(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t uRc)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 3);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_CLOSE);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSETOF(HGCMReplyFileNotify, u));
+}
+
+
+/**
+ * Sends an unexpected file handling error to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbError(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 3);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_ERROR);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSETOF(HGCMReplyFileNotify, u));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_READ message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param pvData Pointer to read file data from guest on success.
+ * @param cbData Size (in bytes) of read file data from guest on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbRead(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t uRc,
+ void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_READ);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmPtrSet(&Msg.u.read.data, pvData, cbData);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.read));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_READ_AT message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param pvData Pointer to read file data from guest on success.
+ * @param cbData Size (in bytes) of read file data from guest on success.
+ * @param offNew New offset (in bytes) the guest file pointer points at on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbReadOffset(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc,
+ void *pvData, uint32_t cbData, int64_t offNew)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 5);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_READ_OFFSET);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmPtrSet(&Msg.u.ReadOffset.pvData, pvData, cbData);
+ VbglHGCMParmUInt64Set(&Msg.u.ReadOffset.off64New, (uint64_t)offNew);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.ReadOffset));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_WRITE message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param cbWritten Size (in bytes) of file data successfully written to guest file. Can be partial.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbWrite(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint32_t cbWritten)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_WRITE);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmUInt32Set(&Msg.u.write.written, cbWritten);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.write));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_WRITE_AT message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param cbWritten Size (in bytes) of file data successfully written to guest file. Can be partial.
+ * @param offNew New offset (in bytes) the guest file pointer points at on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbWriteOffset(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint32_t cbWritten, int64_t offNew)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 5);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_WRITE_OFFSET);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmUInt32Set(&Msg.u.WriteOffset.cb32Written, cbWritten);
+ VbglHGCMParmUInt64Set(&Msg.u.WriteOffset.off64New, (uint64_t)offNew);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.WriteOffset));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_SEEK message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param offCurrent New offset (in bytes) the guest file pointer points at on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbSeek(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint64_t offCurrent)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_SEEK);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmUInt64Set(&Msg.u.seek.offset, offCurrent);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.seek));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_TELL message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param offCurrent Current offset (in bytes) the guest file pointer points at on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbTell(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint64_t offCurrent)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_TELL);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmUInt64Set(&Msg.u.tell.offset, offCurrent);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.tell));
+}
+
+
+/**
+ * Replies to a HOST_MSG_FILE_SET_SIZE message.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uRc Guest rc of operation (note: IPRT-style signed int).
+ * @param cbNew New file size (in bytes) of the guest file on success.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlFileCbSetSize(PVBGLR3GUESTCTRLCMDCTX pCtx, uint32_t uRc, uint64_t cbNew)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMReplyFileNotify Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_FILE_NOTIFY, 4);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.type, GUEST_FILE_NOTIFYTYPE_SET_SIZE);
+ VbglHGCMParmUInt32Set(&Msg.rc, uRc);
+ VbglHGCMParmUInt64Set(&Msg.u.SetSize.cb64Size, cbNew);
+
+ return VbglR3HGCMCall(&Msg.hdr, RT_UOFFSET_AFTER(HGCMReplyFileNotify, u.SetSize));
+}
+
+
+/**
+ * Callback for reporting a guest process status (along with some other stuff) to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uPID Guest process PID to report status for.
+ * @param uStatus Status to report. Of type PROC_STS_XXX.
+ * @param fFlags Additional status flags, depending on the reported status. See RTPROCSTATUS.
+ * @param pvData Pointer to additional status data. Optional.
+ * @param cbData Size (in bytes) of additional status data.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcCbStatus(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t uPID, uint32_t uStatus, uint32_t fFlags,
+ void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgProcStatus Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_EXEC_STATUS, 5);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.pid, uPID);
+ VbglHGCMParmUInt32Set(&Msg.status, uStatus);
+ VbglHGCMParmUInt32Set(&Msg.flags, fFlags);
+ VbglHGCMParmPtrSet(&Msg.data, pvData, cbData);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+
+/**
+ * Sends output (from stdout/stderr) from a running process.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uPID Guest process PID to report status for.
+ * @param uHandle Guest process handle the output belong to.
+ * @param fFlags Additional output flags.
+ * @param pvData Pointer to actual output data.
+ * @param cbData Size (in bytes) of output data.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcCbOutput(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t uPID,uint32_t uHandle, uint32_t fFlags,
+ void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgProcOutput Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_EXEC_OUTPUT, 5);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.pid, uPID);
+ VbglHGCMParmUInt32Set(&Msg.handle, uHandle);
+ VbglHGCMParmUInt32Set(&Msg.flags, fFlags);
+ VbglHGCMParmPtrSet(&Msg.data, pvData, cbData);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+
+/**
+ * Callback for reporting back the input status of a guest process to the host.
+ *
+ * @returns VBox status code.
+ * @param pCtx Guest control command context to use.
+ * @param uPID Guest process PID to report status for.
+ * @param uStatus Status to report. Of type INPUT_STS_XXX.
+ * @param fFlags Additional input flags.
+ * @param cbWritten Size (in bytes) of input data handled.
+ */
+VBGLR3DECL(int) VbglR3GuestCtrlProcCbStatusInput(PVBGLR3GUESTCTRLCMDCTX pCtx,
+ uint32_t uPID, uint32_t uStatus,
+ uint32_t fFlags, uint32_t cbWritten)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ HGCMMsgProcStatusInput Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, pCtx->uClientID, GUEST_MSG_EXEC_INPUT_STATUS, 5);
+ VbglHGCMParmUInt32Set(&Msg.context, pCtx->uContextID);
+ VbglHGCMParmUInt32Set(&Msg.pid, uPID);
+ VbglHGCMParmUInt32Set(&Msg.status, uStatus);
+ VbglHGCMParmUInt32Set(&Msg.flags, fFlags);
+ VbglHGCMParmUInt32Set(&Msg.written, cbWritten);
+
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp
new file mode 100644
index 00000000..106be26e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestProp.cpp
@@ -0,0 +1,1032 @@
+/* $Id: VBoxGuestR3LibGuestProp.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, guest properties.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#if defined(VBOX_VBGLR3_XFREE86) || defined(VBOX_VBGLR3_XORG)
+# define VBOX_VBGLR3_XSERVER
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#ifndef VBOX_VBGLR3_XSERVER
+# include <iprt/mem.h>
+#endif
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/stdarg.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/HostServices/GuestPropertySvc.h>
+
+#include "VBoxGuestR3LibInternal.h"
+
+#ifdef VBOX_VBGLR3_XFREE86
+/* Rather than try to resolve all the header file conflicts, I will just
+ prototype what we need here. */
+extern "C" char* xf86strcpy(char*,const char*);
+# undef strcpy
+# define strcpy xf86strcpy
+extern "C" void* xf86memchr(const void*,int,xf86size_t);
+# undef memchr
+# define memchr xf86memchr
+extern "C" void* xf86memset(const void*,int,xf86size_t);
+# undef memset
+# define memset xf86memset
+
+#endif /* VBOX_VBGLR3_XFREE86 */
+
+#ifdef VBOX_VBGLR3_XSERVER
+
+# undef RTStrEnd
+# define RTStrEnd xf86RTStrEnd
+
+DECLINLINE(char const *) RTStrEnd(char const *pszString, size_t cchMax)
+{
+ /* Avoid potential issues with memchr seen in glibc.
+ * See sysdeps/x86_64/memchr.S in glibc versions older than 2.11 */
+ while (cchMax > RTSTR_MEMCHR_MAX)
+ {
+ char const *pszRet = (char const *)memchr(pszString, '\0', RTSTR_MEMCHR_MAX);
+ if (RT_LIKELY(pszRet))
+ return pszRet;
+ pszString += RTSTR_MEMCHR_MAX;
+ cchMax -= RTSTR_MEMCHR_MAX;
+ }
+ return (char const *)memchr(pszString, '\0', cchMax);
+}
+
+DECLINLINE(char *) RTStrEnd(char *pszString, size_t cchMax)
+{
+ /* Avoid potential issues with memchr seen in glibc.
+ * See sysdeps/x86_64/memchr.S in glibc versions older than 2.11 */
+ while (cchMax > RTSTR_MEMCHR_MAX)
+ {
+ char *pszRet = (char *)memchr(pszString, '\0', RTSTR_MEMCHR_MAX);
+ if (RT_LIKELY(pszRet))
+ return pszRet;
+ pszString += RTSTR_MEMCHR_MAX;
+ cchMax -= RTSTR_MEMCHR_MAX;
+ }
+ return (char *)memchr(pszString, '\0', cchMax);
+}
+
+#endif /* VBOX_VBGLR3_XSERVER */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Structure containing information needed to enumerate through guest
+ * properties.
+ *
+ * @remarks typedef in VBoxGuestLib.h.
+ */
+struct VBGLR3GUESTPROPENUM
+{
+ /** @todo add a magic and validate the handle. */
+ /** The buffer containing the raw enumeration data */
+ char *pchBuf;
+ /** The end of the buffer */
+ char *pchBufEnd;
+ /** Pointer to the next entry to enumerate inside the buffer */
+ char *pchNext;
+};
+
+
+
+/**
+ * Connects to the guest property service.
+ *
+ * @returns VBox status code
+ * @returns VERR_NOT_SUPPORTED if guest properties are not available on the host.
+ * @param pidClient Where to put the client ID on success. The client ID
+ * must be passed to all the other calls to the service.
+ */
+VBGLR3DECL(int) VbglR3GuestPropConnect(HGCMCLIENTID *pidClient)
+{
+ int rc = VbglR3HGCMConnect("VBoxGuestPropSvc", pidClient);
+ if (rc == VERR_NOT_IMPLEMENTED || rc == VERR_HGCM_SERVICE_NOT_FOUND)
+ rc = VERR_NOT_SUPPORTED;
+ return rc;
+}
+
+
+/**
+ * Disconnect from the guest property service.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3InfoSvcConnect().
+ */
+VBGLR3DECL(int) VbglR3GuestPropDisconnect(HGCMCLIENTID idClient)
+{
+ return VbglR3HGCMDisconnect(idClient);
+}
+
+
+/**
+ * Checks if @a pszPropName exists.
+ *
+ * @returns \c true if the guest property exists, \c false if not.
+ * @param idClient The HGCM client ID for the guest property session.
+ * @param pszPropName The property name.
+ */
+VBGLR3DECL(bool) VbglR3GuestPropExist(uint32_t idClient, const char *pszPropName)
+{
+ return RT_SUCCESS(VbglR3GuestPropReadEx(idClient, pszPropName, NULL /*ppszValue*/, NULL /* ppszFlags */, NULL /* puTimestamp */));
+}
+
+
+/**
+ * Write a property value.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3InvsSvcConnect().
+ * @param pszName The property to save to. Utf8
+ * @param pszValue The value to store. Utf8. If this is NULL then
+ * the property will be removed.
+ * @param pszFlags The flags for the property
+ */
+VBGLR3DECL(int) VbglR3GuestPropWrite(HGCMCLIENTID idClient, const char *pszName, const char *pszValue, const char *pszFlags)
+{
+ int rc;
+
+ if (pszValue != NULL)
+ {
+ GuestPropMsgSetProperty Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_SET_PROP_VALUE, 3);
+ VbglHGCMParmPtrSetString(&Msg.name, pszName);
+ VbglHGCMParmPtrSetString(&Msg.value, pszValue);
+ VbglHGCMParmPtrSetString(&Msg.flags, pszFlags);
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ }
+ else
+ {
+ GuestPropMsgDelProperty Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_DEL_PROP, 1);
+ VbglHGCMParmPtrSetString(&Msg.name, pszName);
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ }
+ return rc;
+}
+
+
+/**
+ * Write a property value.
+ *
+ * @returns VBox status code.
+ *
+ * @param idClient The client id returned by VbglR3InvsSvcConnect().
+ * @param pszName The property to save to. Must be valid UTF-8.
+ * @param pszValue The value to store. Must be valid UTF-8.
+ * If this is NULL then the property will be removed.
+ *
+ * @note if the property already exists and pszValue is not NULL then the
+ * property's flags field will be left unchanged
+ */
+VBGLR3DECL(int) VbglR3GuestPropWriteValue(HGCMCLIENTID idClient, const char *pszName, const char *pszValue)
+{
+ int rc;
+
+ if (pszValue != NULL)
+ {
+ GuestPropMsgSetPropertyValue Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_SET_PROP_VALUE, 2);
+ VbglHGCMParmPtrSetString(&Msg.name, pszName);
+ VbglHGCMParmPtrSetString(&Msg.value, pszValue);
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ }
+ else
+ {
+ GuestPropMsgDelProperty Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_DEL_PROP, 1);
+ VbglHGCMParmPtrSetString(&Msg.name, pszName);
+ rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ }
+ return rc;
+}
+
+#ifndef VBOX_VBGLR3_XSERVER
+/**
+ * Write a property value where the value is formatted in RTStrPrintfV fashion.
+ *
+ * @returns The same as VbglR3GuestPropWriteValue with the addition of VERR_NO_STR_MEMORY.
+ *
+ * @param idClient The client ID returned by VbglR3InvsSvcConnect().
+ * @param pszName The property to save to. Must be valid UTF-8.
+ * @param pszValueFormat The value format. This must be valid UTF-8 when fully formatted.
+ * @param va The format arguments.
+ */
+VBGLR3DECL(int) VbglR3GuestPropWriteValueV(HGCMCLIENTID idClient, const char *pszName, const char *pszValueFormat, va_list va)
+{
+ /*
+ * Format the value and pass it on to the setter.
+ */
+ int rc = VERR_NO_STR_MEMORY;
+ char *pszValue;
+ if (RTStrAPrintfV(&pszValue, pszValueFormat, va) >= 0)
+ {
+ rc = VbglR3GuestPropWriteValue(idClient, pszName, pszValue);
+ RTStrFree(pszValue);
+ }
+ return rc;
+}
+
+
+/**
+ * Write a property value where the value is formatted in RTStrPrintf fashion.
+ *
+ * @returns The same as VbglR3GuestPropWriteValue with the addition of VERR_NO_STR_MEMORY.
+ *
+ * @param idClient The client ID returned by VbglR3InvsSvcConnect().
+ * @param pszName The property to save to. Must be valid UTF-8.
+ * @param pszValueFormat The value format. This must be valid UTF-8 when fully formatted.
+ * @param ... The format arguments.
+ */
+VBGLR3DECL(int) VbglR3GuestPropWriteValueF(HGCMCLIENTID idClient, const char *pszName, const char *pszValueFormat, ...)
+{
+ va_list va;
+ va_start(va, pszValueFormat);
+ int rc = VbglR3GuestPropWriteValueV(idClient, pszName, pszValueFormat, va);
+ va_end(va);
+ return rc;
+}
+#endif /* VBOX_VBGLR3_XSERVER */
+
+/**
+ * Retrieve a property.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success, pszValue, pu64Timestamp and pszFlags
+ * containing valid data.
+ * @retval VERR_BUFFER_OVERFLOW if the scratch buffer @a pcBuf is not large
+ * enough. In this case the size needed will be placed in
+ * @a pcbBufActual if it is not NULL.
+ * @retval VERR_NOT_FOUND if the key wasn't found.
+ *
+ * @param idClient The client id returned by VbglR3GuestPropConnect().
+ * @param pszName The value to read. Utf8
+ * @param pvBuf A scratch buffer to store the data retrieved into.
+ * The returned data is only valid for it's lifetime.
+ * @a ppszValue will point to the start of this buffer.
+ * @param cbBuf The size of @a pcBuf
+ * @param ppszValue Where to store the pointer to the value retrieved.
+ * Optional.
+ * @param pu64Timestamp Where to store the timestamp. Optional.
+ * @param ppszFlags Where to store the pointer to the flags. Optional.
+ * @param pcbBufActual If @a pcBuf is not large enough, the size needed.
+ * Optional.
+ */
+VBGLR3DECL(int) VbglR3GuestPropRead(HGCMCLIENTID idClient, const char *pszName,
+ void *pvBuf, uint32_t cbBuf,
+ char **ppszValue, uint64_t *pu64Timestamp,
+ char **ppszFlags,
+ uint32_t *pcbBufActual)
+{
+ /*
+ * Create the GET_PROP message and call the host.
+ */
+ GuestPropMsgGetProperty Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_GET_PROP, 4);
+ VbglHGCMParmPtrSetString(&Msg.name, pszName);
+ VbglHGCMParmPtrSet(&Msg.buffer, pvBuf, cbBuf);
+ VbglHGCMParmUInt64Set(&Msg.timestamp, 0);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ /*
+ * The cbBufActual parameter is also returned on overflow so the call can
+ * adjust his/her buffer.
+ */
+ if ( rc == VERR_BUFFER_OVERFLOW
+ || pcbBufActual != NULL)
+ {
+ int rc2 = VbglHGCMParmUInt32Get(&Msg.size, pcbBufActual);
+ AssertRCReturn(rc2, RT_FAILURE(rc) ? rc : rc2);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Buffer layout: Value\0Flags\0.
+ *
+ * If the caller cares about any of these strings, make sure things are
+ * properly terminated (paranoia).
+ */
+ if ( RT_SUCCESS(rc)
+ && (ppszValue != NULL || ppszFlags != NULL))
+ {
+ /* Validate / skip 'Name'. */
+ char *pszFlags = RTStrEnd((char *)pvBuf, cbBuf) + 1;
+ AssertPtrReturn(pszFlags, VERR_TOO_MUCH_DATA);
+ if (ppszValue)
+ *ppszValue = (char *)pvBuf;
+
+ if (ppszFlags)
+ {
+ /* Validate 'Flags'. */
+ char *pszEos = RTStrEnd(pszFlags, cbBuf - (pszFlags - (char *)pvBuf));
+ AssertPtrReturn(pszEos, VERR_TOO_MUCH_DATA);
+ *ppszFlags = pszFlags;
+ }
+ }
+
+ /* And the timestamp, if requested. */
+ if (pu64Timestamp != NULL)
+ {
+ rc = VbglHGCMParmUInt64Get(&Msg.timestamp, pu64Timestamp);
+ AssertRCReturn(rc, rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads a guest property by returning allocated values.
+ *
+ * @returns VBox status code, fully bitched.
+ *
+ * @param idClient The HGCM client ID for the guest property session.
+ * @param pszPropName The property name.
+ * @param ppszValue Where to return the value. This is always set
+ * to NULL. Needs to be free'd using RTStrFree(). Optional.
+ * @param ppszFlags Where to return the value flags.
+ * Needs to be free'd using RTStrFree(). Optional.
+ * @param puTimestamp Where to return the timestamp. This is only set
+ * on success. Optional.
+ */
+VBGLR3DECL(int) VbglR3GuestPropReadEx(uint32_t idClient,
+ const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp)
+{
+ AssertPtrReturn(pszPropName, VERR_INVALID_POINTER);
+
+ uint32_t cbBuf = _1K;
+ void *pvBuf = NULL;
+ int rc = VINF_SUCCESS; /* MSC can't figure out the loop */
+
+ if (ppszValue)
+ *ppszValue = NULL;
+
+ for (unsigned cTries = 0; cTries < 10; cTries++)
+ {
+ /*
+ * (Re-)Allocate the buffer and try read the property.
+ */
+ RTMemFree(pvBuf);
+ pvBuf = RTMemAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ char *pszValue;
+ char *pszFlags;
+ uint64_t uTimestamp;
+ rc = VbglR3GuestPropRead(idClient, pszPropName, pvBuf, cbBuf, &pszValue, &uTimestamp, &pszFlags, NULL);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* try again with a bigger buffer. */
+ cbBuf *= 2;
+ continue;
+ }
+ break;
+ }
+
+ if (ppszValue)
+ {
+ *ppszValue = RTStrDup(pszValue);
+ if (!*ppszValue)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+
+ if (puTimestamp)
+ *puTimestamp = uTimestamp;
+ if (ppszFlags)
+ *ppszFlags = RTStrDup(pszFlags);
+ break; /* done */
+ }
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+ return rc;
+}
+
+#ifndef VBOX_VBGLR3_XSERVER
+/**
+ * Retrieve a property value, allocating space for it.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success, *ppszValue containing valid data.
+ * @retval VERR_NOT_FOUND if the key wasn't found.
+ * @retval VERR_TOO_MUCH_DATA if we were unable to determine the right size
+ * to allocate for the buffer. This can happen as the result of a
+ * race between our allocating space and the host changing the
+ * property value.
+ *
+ * @param idClient The client id returned by VbglR3GuestPropConnect().
+ * @param pszName The value to read. Must be valid UTF-8.
+ * @param ppszValue Where to store the pointer to the value returned.
+ * This is always set to NULL or to the result, even
+ * on failure.
+ */
+VBGLR3DECL(int) VbglR3GuestPropReadValueAlloc(HGCMCLIENTID idClient, const char *pszName, char **ppszValue)
+{
+ /*
+ * Quick input validation.
+ */
+ AssertPtr(ppszValue);
+ *ppszValue = NULL;
+ AssertPtrReturn(pszName, VERR_INVALID_PARAMETER);
+
+ /*
+ * There is a race here between our reading the property size and the
+ * host changing the value before we read it. Try up to ten times and
+ * report the problem if that fails.
+ */
+ char *pszValue = NULL;
+ void *pvBuf = NULL;
+ uint32_t cbBuf = GUEST_PROP_MAX_VALUE_LEN;
+ int rc = VERR_BUFFER_OVERFLOW;
+ for (unsigned i = 0; i < 10 && rc == VERR_BUFFER_OVERFLOW; ++i)
+ {
+ /* We leave a bit of space here in case the maximum value is raised. */
+ cbBuf += 1024;
+ void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvTmpBuf)
+ {
+ pvBuf = pvTmpBuf;
+ rc = VbglR3GuestPropRead(idClient, pszName, pvBuf, cbBuf, &pszValue, NULL, NULL, &cbBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pszValue == (char *)pvBuf);
+ *ppszValue = pszValue;
+ }
+ else
+ {
+ RTMemFree(pvBuf);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ /* VERR_BUFFER_OVERFLOW has a different meaning here as a
+ * return code, but we need to report the race. */
+ rc = VERR_TOO_MUCH_DATA;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Free the memory used by VbglR3GuestPropReadValueAlloc for returning a
+ * value.
+ *
+ * @param pszValue the memory to be freed. NULL pointers will be ignored.
+ */
+VBGLR3DECL(void) VbglR3GuestPropReadValueFree(char *pszValue)
+{
+ RTMemFree(pszValue);
+}
+#endif /* VBOX_VBGLR3_XSERVER */
+
+/**
+ * Retrieve a property value, using a user-provided buffer to store it.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success, pszValue containing valid data.
+ * @retval VERR_BUFFER_OVERFLOW and the size needed in pcchValueActual if the
+ * buffer provided was too small
+ * @retval VERR_NOT_FOUND if the key wasn't found.
+ *
+ * @note There is a race here between obtaining the size of the buffer
+ * needed to hold the value and the value being updated.
+ *
+ * @param idClient The client id returned by VbglR3GuestPropConnect().
+ * @param pszName The value to read. Utf8
+ * @param pszValue Where to store the value retrieved.
+ * @param cchValue The size of the buffer pointed to by @a pszValue
+ * @param pcchValueActual Where to store the size of the buffer needed if
+ * the buffer supplied is too small. Optional.
+ */
+VBGLR3DECL(int) VbglR3GuestPropReadValue(HGCMCLIENTID idClient, const char *pszName,
+ char *pszValue, uint32_t cchValue,
+ uint32_t *pcchValueActual)
+{
+ void *pvBuf = pszValue;
+ uint32_t cchValueActual;
+ int rc = VbglR3GuestPropRead(idClient, pszName, pvBuf, cchValue, &pszValue, NULL, NULL, &cchValueActual);
+ if (pcchValueActual != NULL)
+ *pcchValueActual = cchValueActual;
+ return rc;
+}
+
+
+#ifndef VBOX_VBGLR3_XSERVER
+/**
+ * Raw API for enumerating guest properties which match a given pattern.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success and pcBuf points to a packed array
+ * of the form \<name\>, \<value\>, \<timestamp string\>, \<flags\>,
+ * terminated by four empty strings. pcbBufActual will contain the
+ * total size of the array.
+ * @retval VERR_BUFFER_OVERFLOW if the buffer provided was too small. In
+ * this case pcbBufActual will contain the size of the buffer needed.
+ * @returns IPRT error code in other cases, and pchBufActual is undefined.
+ *
+ * @param idClient The client ID returned by VbglR3GuestPropConnect
+ * @param pszzPatterns A packed array of zero terminated strings, terminated
+ * by an empty string.
+ * @param pcBuf The buffer to store the results to.
+ * @param cbBuf The size of the buffer
+ * @param pcbBufActual Where to store the size of the returned data on
+ * success or the buffer size needed if @a pcBuf is too
+ * small.
+ */
+VBGLR3DECL(int) VbglR3GuestPropEnumRaw(HGCMCLIENTID idClient,
+ const char *pszzPatterns,
+ char *pcBuf,
+ uint32_t cbBuf,
+ uint32_t *pcbBufActual)
+{
+ GuestPropMsgEnumProperties Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_ENUM_PROPS, 3);
+
+ /* Get the length of the patterns array... */
+ size_t cchPatterns = 0;
+ for (size_t cchCurrent = strlen(pszzPatterns); cchCurrent != 0;
+ cchCurrent = strlen(pszzPatterns + cchPatterns))
+ cchPatterns += cchCurrent + 1;
+ /* ...including the terminator. */
+ ++cchPatterns;
+ VbglHGCMParmPtrSet(&Msg.patterns, (char *)pszzPatterns, (uint32_t)cchPatterns);
+ VbglHGCMParmPtrSet(&Msg.strings, pcBuf, cbBuf);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+ if ( pcbBufActual
+ && ( RT_SUCCESS(rc)
+ || rc == VERR_BUFFER_OVERFLOW))
+ {
+ int rc2 = VbglHGCMParmUInt32Get(&Msg.size, pcbBufActual);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Start enumerating guest properties which match a given pattern.
+ *
+ * This function creates a handle which can be used to continue enumerating.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success, *ppHandle points to a handle for continuing
+ * the enumeration and *ppszName, *ppszValue, *pu64Timestamp and
+ * *ppszFlags are set.
+ * @retval VERR_TOO_MUCH_DATA if it was not possible to determine the amount
+ * of local space needed to store all the enumeration data. This is
+ * due to a race between allocating space and the host adding new
+ * data, so retrying may help here. Other parameters are left
+ * uninitialised
+ *
+ * @param idClient The client id returned by VbglR3InfoSvcConnect().
+ * @param papszPatterns The patterns against which the properties are
+ * matched. Pass NULL if everything should be matched.
+ * @param cPatterns The number of patterns in @a papszPatterns. 0 means
+ * match everything.
+ * @param ppHandle where the handle for continued enumeration is stored
+ * on success. This must be freed with
+ * VbglR3GuestPropEnumFree when it is no longer needed.
+ * @param ppszName Where to store the next property name. This will be
+ * set to NULL if there are no more properties to
+ * enumerate. This pointer should not be freed. Optional.
+ * @param ppszValue Where to store the next property value. This will be
+ * set to NULL if there are no more properties to
+ * enumerate. This pointer should not be freed. Optional.
+ * @param pu64Timestamp Where to store the next property timestamp. This
+ * will be set to zero if there are no more properties
+ * to enumerate. Optional.
+ * @param ppszFlags Where to store the next property flags. This will be
+ * set to NULL if there are no more properties to
+ * enumerate. This pointer should not be freed. Optional.
+ *
+ * @remarks While all output parameters are optional, you need at least one to
+ * figure out when to stop.
+ */
+VBGLR3DECL(int) VbglR3GuestPropEnum(HGCMCLIENTID idClient,
+ char const * const *papszPatterns,
+ uint32_t cPatterns,
+ PVBGLR3GUESTPROPENUM *ppHandle,
+ char const **ppszName,
+ char const **ppszValue,
+ uint64_t *pu64Timestamp,
+ char const **ppszFlags)
+{
+ /* Create the handle. */
+ PVBGLR3GUESTPROPENUM pHandle = (PVBGLR3GUESTPROPENUM)RTMemAllocZ(sizeof(VBGLR3GUESTPROPENUM));
+ if (RT_LIKELY(pHandle))
+ {/* likely */}
+ else
+ return VERR_NO_MEMORY;
+
+ /* Get the length of the pattern string, including the final terminator. */
+ size_t cbPatterns = 1;
+ for (uint32_t i = 0; i < cPatterns; ++i)
+ cbPatterns += strlen(papszPatterns[i]) + 1;
+
+ /* Pack the pattern array. */
+ char *pszzPatterns = (char *)RTMemAlloc(cbPatterns);
+ size_t off = 0;
+ for (uint32_t i = 0; i < cPatterns; ++i)
+ {
+ size_t cb = strlen(papszPatterns[i]) + 1;
+ memcpy(&pszzPatterns[off], papszPatterns[i], cb);
+ off += cb;
+ }
+ pszzPatterns[off] = '\0';
+
+ /* In reading the guest property data we are racing against the host
+ * adding more of it, so loop a few times and retry on overflow. */
+ uint32_t cbBuf = 4096; /* picked out of thin air */
+ char *pchBuf = NULL;
+ int rc = VINF_SUCCESS;
+ for (int i = 0; i < 10; ++i)
+ {
+ void *pvNew = RTMemRealloc(pchBuf, cbBuf);
+ if (pvNew)
+ pchBuf = (char *)pvNew;
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ rc = VbglR3GuestPropEnumRaw(idClient, pszzPatterns, pchBuf, cbBuf, &cbBuf);
+ if (rc != VERR_BUFFER_OVERFLOW)
+ break;
+ cbBuf += 4096; /* Just to increase our chances */
+ }
+ RTMemFree(pszzPatterns);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Complete the handle and call VbglR3GuestPropEnumNext to retrieve the first entry.
+ */
+ pHandle->pchNext = pchBuf;
+ pHandle->pchBuf = pchBuf;
+ pHandle->pchBufEnd = pchBuf + cbBuf;
+
+ const char *pszNameTmp;
+ if (!ppszName)
+ ppszName = &pszNameTmp;
+ rc = VbglR3GuestPropEnumNext(pHandle, ppszName, ppszValue, pu64Timestamp, ppszFlags);
+ if (RT_SUCCESS(rc))
+ {
+ *ppHandle = pHandle;
+ return rc;
+ }
+ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ rc = VERR_TOO_MUCH_DATA;
+ RTMemFree(pchBuf);
+ RTMemFree(pHandle);
+ return rc;
+}
+
+
+/**
+ * Get the next guest property.
+ *
+ * See @a VbglR3GuestPropEnum.
+ *
+ * @returns VBox status code.
+ *
+ * @param pHandle Handle obtained from @a VbglR3GuestPropEnum.
+ * @param ppszName Where to store the next property name. This will be
+ * set to NULL if there are no more properties to
+ * enumerate. This pointer should not be freed. Optional.
+ * @param ppszValue Where to store the next property value. This will be
+ * set to NULL if there are no more properties to
+ * enumerate. This pointer should not be freed. Optional.
+ * @param pu64Timestamp Where to store the next property timestamp. This
+ * will be set to zero if there are no more properties
+ * to enumerate. Optional.
+ * @param ppszFlags Where to store the next property flags. This will be
+ * set to NULL if there are no more properties to
+ * enumerate. This pointer should not be freed. Optional.
+ *
+ * @remarks While all output parameters are optional, you need at least one to
+ * figure out when to stop.
+ */
+VBGLR3DECL(int) VbglR3GuestPropEnumNext(PVBGLR3GUESTPROPENUM pHandle,
+ char const **ppszName,
+ char const **ppszValue,
+ uint64_t *pu64Timestamp,
+ char const **ppszFlags)
+{
+ /*
+ * The VBGLR3GUESTPROPENUM structure contains a buffer containing the raw
+ * properties data and a pointer into the buffer which tracks how far we
+ * have parsed so far. The buffer contains packed strings in groups of
+ * four - name, value, timestamp (as a decimal string) and flags. It is
+ * terminated by four empty strings. We can rely on this layout unless
+ * the caller has been poking about in the structure internals, in which
+ * case they must take responsibility for the results.
+ *
+ * Layout:
+ * Name\0Value\0Timestamp\0Flags\0
+ */
+ char *pchNext = pHandle->pchNext; /* The cursor. */
+ char *pchEnd = pHandle->pchBufEnd; /* End of buffer, for size calculations. */
+
+ char *pszName = pchNext;
+ char *pszValue = pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1;
+ AssertPtrReturn(pchNext, VERR_PARSE_ERROR); /* 0x1 is also an invalid pointer :) */
+
+ char *pszTimestamp = pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1;
+ AssertPtrReturn(pchNext, VERR_PARSE_ERROR);
+
+ char *pszFlags = pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1;
+ AssertPtrReturn(pchNext, VERR_PARSE_ERROR);
+
+ /*
+ * Don't move the index pointer if we found the terminating "\0\0\0\0" entry.
+ * Don't try convert the timestamp either.
+ */
+ uint64_t u64Timestamp;
+ if (*pszName != '\0')
+ {
+ pchNext = RTStrEnd(pchNext, pchEnd - pchNext) + 1;
+ AssertPtrReturn(pchNext, VERR_PARSE_ERROR);
+
+ /* Convert the timestamp string into a number. */
+ int rc = RTStrToUInt64Full(pszTimestamp, 0, &u64Timestamp);
+ AssertRCSuccessReturn(rc, VERR_PARSE_ERROR);
+
+ pHandle->pchNext = pchNext;
+ AssertPtr(pchNext);
+ }
+ else
+ {
+ u64Timestamp = 0;
+ AssertMsgReturn(!*pszValue && !*pszTimestamp && !*pszFlags,
+ ("'%s' '%s' '%s'\n", pszValue, pszTimestamp, pszFlags),
+ VERR_PARSE_ERROR);
+ }
+
+ /*
+ * Everything is fine, set the return values.
+ */
+ if (ppszName)
+ *ppszName = *pszName != '\0' ? pszName : NULL;
+ if (ppszValue)
+ *ppszValue = *pszValue != '\0' ? pszValue : NULL;
+ if (pu64Timestamp)
+ *pu64Timestamp = u64Timestamp;
+ if (ppszFlags)
+ *ppszFlags = *pszFlags != '\0' ? pszFlags : NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Free an enumeration handle returned by @a VbglR3GuestPropEnum.
+ * @param pHandle the handle to free
+ */
+VBGLR3DECL(void) VbglR3GuestPropEnumFree(PVBGLR3GUESTPROPENUM pHandle)
+{
+ if (!pHandle)
+ return;
+ RTMemFree(pHandle->pchBuf);
+ RTMemFree(pHandle);
+}
+
+
+/**
+ * Deletes a guest property.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3InvsSvcConnect().
+ * @param pszName The property to delete. Utf8
+ */
+VBGLR3DECL(int) VbglR3GuestPropDelete(HGCMCLIENTID idClient, const char *pszName)
+{
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ GuestPropMsgDelProperty Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.hdr, idClient, GUEST_PROP_FN_DEL_PROP, 1);
+ VbglHGCMParmPtrSetString(&Msg.name, pszName);
+ return VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+}
+
+
+/**
+ * Deletes a set of keys.
+ *
+ * The set is specified in the same way as for VbglR3GuestPropEnum.
+ *
+ * @returns VBox status code. Stops on first failure.
+ * See also VbglR3GuestPropEnum.
+ *
+ * @param idClient The client id returned by VbglR3InfoSvcConnect().
+ * @param papszPatterns The patterns against which the properties are
+ * matched. Pass NULL if everything should be matched.
+ * @param cPatterns The number of patterns in @a papszPatterns. 0 means
+ * match everything.
+ */
+VBGLR3DECL(int) VbglR3GuestPropDelSet(HGCMCLIENTID idClient,
+ const char * const *papszPatterns,
+ uint32_t cPatterns)
+{
+ PVBGLR3GUESTPROPENUM pHandle;
+ char const *pszName, *pszValue, *pszFlags;
+ uint64_t pu64Timestamp;
+ int rc = VbglR3GuestPropEnum(idClient,
+ (char **)papszPatterns, /** @todo fix this cast. */
+ cPatterns,
+ &pHandle,
+ &pszName,
+ &pszValue,
+ &pu64Timestamp,
+ &pszFlags);
+
+ while (RT_SUCCESS(rc) && pszName)
+ {
+ rc = VbglR3GuestPropWriteValue(idClient, pszName, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = VbglR3GuestPropEnumNext(pHandle,
+ &pszName,
+ &pszValue,
+ &pu64Timestamp,
+ &pszFlags);
+ }
+
+ VbglR3GuestPropEnumFree(pHandle);
+ return rc;
+}
+
+
+/**
+ * Wait for notification of changes to a guest property. If this is called in
+ * a loop, the timestamp of the last notification seen can be passed as a
+ * parameter to be sure that no notifications are missed.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on success, @a ppszName, @a ppszValue,
+ * @a pu64Timestamp and @a ppszFlags containing valid data.
+ * @retval VINF_NOT_FOUND if no previous notification could be found with the
+ * timestamp supplied. This will normally mean that a large number
+ * of notifications occurred in between.
+ * @retval VERR_BUFFER_OVERFLOW if the scratch buffer @a pvBuf is not large
+ * enough. In this case the size needed will be placed in
+ * @a pcbBufActual if it is not NULL.
+ * @retval VERR_TIMEOUT if a timeout occurred before a notification was seen.
+ *
+ * @param idClient The client id returned by VbglR3GuestPropConnect().
+ * @param pszPatterns The patterns that the property names must matchfor
+ * the change to be reported.
+ * @param pvBuf A scratch buffer to store the data retrieved into.
+ * The returned data is only valid for it's lifetime.
+ * @a ppszValue will point to the start of this buffer.
+ * @param cbBuf The size of @a pvBuf
+ * @param u64Timestamp The timestamp of the last event seen. Pass zero
+ * to wait for the next event.
+ * @param cMillies Timeout in milliseconds. Use RT_INDEFINITE_WAIT
+ * to wait indefinitely.
+ * @param ppszName Where to store the pointer to the name retrieved.
+ * Optional.
+ * @param ppszValue Where to store the pointer to the value retrieved.
+ * Optional.
+ * @param pu64Timestamp Where to store the timestamp. Optional.
+ * @param ppszFlags Where to store the pointer to the flags. Optional.
+ * @param pcbBufActual If @a pcBuf is not large enough, the size needed.
+ * Optional.
+ * @param pfWasDeleted A flag which indicates that property was deleted.
+ * Optional.
+ */
+VBGLR3DECL(int) VbglR3GuestPropWait(HGCMCLIENTID idClient,
+ const char *pszPatterns,
+ void *pvBuf, uint32_t cbBuf,
+ uint64_t u64Timestamp, uint32_t cMillies,
+ char ** ppszName, char **ppszValue,
+ uint64_t *pu64Timestamp, char **ppszFlags,
+ uint32_t *pcbBufActual, bool *pfWasDeleted)
+{
+ /*
+ * Create the GET_NOTIFICATION message and call the host.
+ */
+ GuestPropMsgGetNotification Msg;
+ VBGL_HGCM_HDR_INIT_TIMED(&Msg.hdr, idClient, GUEST_PROP_FN_GET_NOTIFICATION, 4, cMillies);
+
+ VbglHGCMParmPtrSetString(&Msg.patterns, pszPatterns);
+ RT_BZERO(pvBuf, cbBuf);
+ VbglHGCMParmPtrSet(&Msg.buffer, pvBuf, cbBuf);
+ VbglHGCMParmUInt64Set(&Msg.timestamp, u64Timestamp);
+ VbglHGCMParmUInt32Set(&Msg.size, 0);
+
+ int rc = VbglR3HGCMCall(&Msg.hdr, sizeof(Msg));
+
+ /*
+ * The cbBufActual parameter is also returned on overflow so the caller can
+ * adjust their buffer.
+ */
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && pcbBufActual != NULL)
+ {
+ int rc2 = Msg.size.GetUInt32(pcbBufActual);
+ AssertRCReturn(rc2, RT_FAILURE(rc) ? rc : rc2);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Buffer layout: Name\0Value\0Flags\0fWasDeleted\0.
+ *
+ * If the caller cares about any of these strings, make sure things are
+ * properly terminated (paranoia).
+ */
+ if ( RT_SUCCESS(rc)
+ && (ppszName != NULL || ppszValue != NULL || ppszFlags != NULL || pfWasDeleted != NULL))
+ {
+ /* Validate / skip 'Name'. */
+ char *pszValue = RTStrEnd((char *)pvBuf, cbBuf) + 1;
+ AssertPtrReturn(pszValue, VERR_TOO_MUCH_DATA);
+ if (ppszName)
+ *ppszName = (char *)pvBuf;
+
+ /* Validate / skip 'Value'. */
+ char *pszFlags = RTStrEnd(pszValue, cbBuf - (pszValue - (char *)pvBuf)) + 1;
+ AssertPtrReturn(pszFlags, VERR_TOO_MUCH_DATA);
+ if (ppszValue)
+ *ppszValue = pszValue;
+
+ if (ppszFlags)
+ *ppszFlags = pszFlags;
+
+ /* Skip 'Flags' and deal with 'fWasDeleted' if it's present. */
+ char *pszWasDeleted = RTStrEnd(pszFlags, cbBuf - (pszFlags - (char *)pvBuf)) + 1;
+ AssertPtrReturn(pszWasDeleted, VERR_TOO_MUCH_DATA);
+ char chWasDeleted = 0;
+ if ( (size_t)pszWasDeleted - (size_t)pvBuf < cbBuf
+ && (chWasDeleted = *pszWasDeleted) != '\0')
+ AssertMsgReturn((chWasDeleted == '0' || chWasDeleted == '1') && pszWasDeleted[1] == '\0',
+ ("'%s'\n", pszWasDeleted), VERR_PARSE_ERROR);
+ if (pfWasDeleted)
+ *pfWasDeleted = chWasDeleted == '1';
+ }
+
+ /* And the timestamp, if requested. */
+ if (pu64Timestamp != NULL)
+ {
+ rc = Msg.timestamp.GetUInt64(pu64Timestamp);
+ AssertRCReturn(rc, rc);
+ }
+
+ return VINF_SUCCESS;
+}
+#endif /* VBOX_VBGLR3_XSERVER */
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp
new file mode 100644
index 00000000..afa280fa
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibGuestUser.cpp
@@ -0,0 +1,119 @@
+/* $Id: VBoxGuestR3LibGuestUser.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions,
+ * guest user reporting / utility functions.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Reports a state change of a specific guest user.
+ *
+ * @returns IPRT status value
+ * @param pszUser Guest user name to report state for.
+ * @param pszDomain Domain the guest user's account is bound to.
+ * @param enmState Guest user state to report.
+ * @param puDetails Pointer to state details. Optional.
+ * @param cbDetails Size (in bytes) of state details. Pass 0
+ * if puDetails is NULL.
+ */
+VBGLR3DECL(int) VbglR3GuestUserReportState(const char *pszUser, const char *pszDomain, VBoxGuestUserState enmState,
+ uint8_t *puDetails, uint32_t cbDetails)
+{
+ AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
+ /* pszDomain is optional. */
+ /* puDetails is optional. */
+ AssertReturn(cbDetails == 0 || puDetails != NULL, VERR_INVALID_PARAMETER);
+ AssertReturn(cbDetails < 16U*_1M, VERR_OUT_OF_RANGE);
+
+ uint32_t cbBase = sizeof(VMMDevReportGuestUserState);
+ uint32_t cbUser = (uint32_t)strlen(pszUser) + 1; /* Include terminating zero */
+ uint32_t cbDomain = pszDomain ? (uint32_t)strlen(pszDomain) + 1 /* Ditto */ : 0;
+
+ /* Allocate enough space for all fields. */
+ uint32_t cbSize = cbBase
+ + cbUser
+ + cbDomain
+ + cbDetails;
+ VMMDevReportGuestUserState *pReport = (VMMDevReportGuestUserState *)RTMemAllocZ(cbSize);
+ if (!pReport)
+ return VERR_NO_MEMORY;
+
+ int rc = vmmdevInitRequest(&pReport->header, VMMDevReq_ReportGuestUserState);
+ if (RT_SUCCESS(rc))
+ {
+ pReport->header.size = cbSize;
+
+ pReport->status.state = enmState;
+ pReport->status.cbUser = cbUser;
+ pReport->status.cbDomain = cbDomain;
+ pReport->status.cbDetails = cbDetails;
+
+ /*
+ * Note: cbOffDynamic contains the first dynamic array entry within
+ * VBoxGuestUserStatus.
+ * Therefore it's vital to *not* change the order of the struct members
+ * without altering this code. Don't try this at home.
+ */
+ uint32_t cbOffDynamic = RT_UOFFSETOF(VBoxGuestUserStatus, szUser);
+
+ /* pDynamic marks the beginning for the dynamically allocated areas. */
+ uint8_t *pDynamic = (uint8_t *)&pReport->status;
+ pDynamic += cbOffDynamic;
+ AssertPtr(pDynamic);
+
+ memcpy(pDynamic, pszUser, cbUser);
+ if (cbDomain)
+ memcpy(pDynamic + cbUser, pszDomain, cbDomain);
+ if (cbDetails)
+ memcpy(pDynamic + cbUser + cbDomain, puDetails, cbDetails);
+
+ rc = vbglR3GRPerform(&pReport->header);
+ }
+
+ RTMemFree(pReport);
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp
new file mode 100644
index 00000000..8053f0be
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHGCM.cpp
@@ -0,0 +1,108 @@
+/* $Id: VBoxGuestR3LibHGCM.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions,
+ * generic HGCM.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+#include <VBox/VBoxGuestLib.h>
+#include <iprt/string.h>
+
+
+/**
+ * Connects to an HGCM service.
+ *
+ * @returns VBox status code
+ * @param pszServiceName Name of the host service.
+ * @param pidClient Where to put the client ID on success. The client ID
+ * must be passed to all the other calls to the service.
+ */
+VBGLR3DECL(int) VbglR3HGCMConnect(const char *pszServiceName, HGCMCLIENTID *pidClient)
+{
+ AssertPtrReturn(pszServiceName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pidClient, VERR_INVALID_POINTER);
+
+ VBGLIOCHGCMCONNECT Info;
+ RT_ZERO(Info);
+ VBGLREQHDR_INIT(&Info.Hdr, HGCM_CONNECT);
+ Info.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing;
+ int rc = RTStrCopy(Info.u.In.Loc.u.host.achName, sizeof(Info.u.In.Loc.u.host.achName), pszServiceName);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = vbglR3DoIOCtl(VBGL_IOCTL_HGCM_CONNECT, &Info.Hdr, sizeof(Info));
+ if (RT_SUCCESS(rc))
+ *pidClient = Info.u.Out.idClient;
+ return rc;
+}
+
+
+/**
+ * Disconnect from an HGCM service.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3HGCMConnect().
+ */
+VBGLR3DECL(int) VbglR3HGCMDisconnect(HGCMCLIENTID idClient)
+{
+ VBGLIOCHGCMDISCONNECT Info;
+ VBGLREQHDR_INIT(&Info.Hdr, HGCM_DISCONNECT);
+ Info.u.In.idClient = idClient;
+
+ return vbglR3DoIOCtl(VBGL_IOCTL_HGCM_DISCONNECT, &Info.Hdr, sizeof(Info));
+}
+
+
+/**
+ * Makes a fully prepared HGCM call.
+ *
+ * @returns VBox status code.
+ * @param pInfo Fully prepared HGCM call info.
+ * @param cbInfo Size of the info. This may sometimes be larger than
+ * what the parameter count indicates because of
+ * parameter changes between versions and such.
+ */
+VBGLR3DECL(int) VbglR3HGCMCall(PVBGLIOCHGCMCALL pInfo, size_t cbInfo)
+{
+ /* Expect caller to have filled in pInfo. */
+ AssertMsg(pInfo->Hdr.cbIn == cbInfo, ("cbIn=%#x cbInfo=%#zx\n", pInfo->Hdr.cbIn, cbInfo));
+ AssertMsg(pInfo->Hdr.cbOut == cbInfo, ("cbOut=%#x cbInfo=%#zx\n", pInfo->Hdr.cbOut, cbInfo));
+ Assert(sizeof(*pInfo) + pInfo->cParms * sizeof(HGCMFunctionParameter) <= cbInfo);
+ Assert(pInfo->u32ClientID != 0);
+
+ return vbglR3DoIOCtl(VBGL_IOCTL_HGCM_CALL(cbInfo), &pInfo->Hdr, cbInfo);
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp
new file mode 100644
index 00000000..9c9e377e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostChannel.cpp
@@ -0,0 +1,235 @@
+/* $Id: VBoxGuestR3LibHostChannel.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Host Channel.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+#include <iprt/mem.h>
+
+#include <VBox/HostServices/VBoxHostChannel.h>
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+VBGLR3DECL(int) VbglR3HostChannelInit(uint32_t *pidClient)
+{
+ return VbglR3HGCMConnect("VBoxHostChannel", pidClient);
+}
+
+VBGLR3DECL(void) VbglR3HostChannelTerm(uint32_t idClient)
+{
+ VbglR3HGCMDisconnect(idClient);
+}
+
+VBGLR3DECL(int) VbglR3HostChannelAttach(uint32_t *pu32ChannelHandle,
+ uint32_t u32HGCMClientId,
+ const char *pszName,
+ uint32_t u32Flags)
+{
+ /* Make a heap copy of the name, because HGCM can not use some of other memory types. */
+ size_t cbName = strlen(pszName) + 1;
+ char *pszCopy = (char *)RTMemAlloc(cbName);
+ if (pszCopy == NULL)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ memcpy(pszCopy, pszName, cbName);
+
+ VBoxHostChannelAttach parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_ATTACH, 3);
+ VbglHGCMParmPtrSet(&parms.name, pszCopy, (uint32_t)cbName);
+ VbglHGCMParmUInt32Set(&parms.flags, u32Flags);
+ VbglHGCMParmUInt32Set(&parms.handle, 0);
+
+ int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+
+ if (RT_SUCCESS(rc))
+ *pu32ChannelHandle = parms.handle.u.value32;
+
+ RTMemFree(pszCopy);
+
+ return rc;
+}
+
+VBGLR3DECL(void) VbglR3HostChannelDetach(uint32_t u32ChannelHandle,
+ uint32_t u32HGCMClientId)
+{
+ VBoxHostChannelDetach parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_DETACH, 1);
+ VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle);
+
+ VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+}
+
+VBGLR3DECL(int) VbglR3HostChannelSend(uint32_t u32ChannelHandle,
+ uint32_t u32HGCMClientId,
+ void *pvData,
+ uint32_t cbData)
+{
+ VBoxHostChannelSend parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_SEND, 2);
+ VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle);
+ VbglHGCMParmPtrSet(&parms.data, pvData, cbData);
+
+ return VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+}
+
+VBGLR3DECL(int) VbglR3HostChannelRecv(uint32_t u32ChannelHandle,
+ uint32_t u32HGCMClientId,
+ void *pvData,
+ uint32_t cbData,
+ uint32_t *pu32SizeReceived,
+ uint32_t *pu32SizeRemaining)
+{
+ VBoxHostChannelRecv parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_RECV, 4);
+ VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle);
+ VbglHGCMParmPtrSet(&parms.data, pvData, cbData);
+ VbglHGCMParmUInt32Set(&parms.sizeReceived, 0);
+ VbglHGCMParmUInt32Set(&parms.sizeRemaining, 0);
+
+ int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+
+ if (RT_SUCCESS(rc))
+ {
+ *pu32SizeReceived = parms.sizeReceived.u.value32;
+ *pu32SizeRemaining = parms.sizeRemaining.u.value32;
+ }
+
+ return rc;
+}
+
+VBGLR3DECL(int) VbglR3HostChannelControl(uint32_t u32ChannelHandle,
+ uint32_t u32HGCMClientId,
+ uint32_t u32Code,
+ void *pvParm,
+ uint32_t cbParm,
+ void *pvData,
+ uint32_t cbData,
+ uint32_t *pu32SizeDataReturned)
+{
+ VBoxHostChannelControl parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_CONTROL, 5);
+ VbglHGCMParmUInt32Set(&parms.handle, u32ChannelHandle);
+ VbglHGCMParmUInt32Set(&parms.code, u32Code);
+ VbglHGCMParmPtrSet(&parms.parm, pvParm, cbParm);
+ VbglHGCMParmPtrSet(&parms.data, pvData, cbData);
+ VbglHGCMParmUInt32Set(&parms.sizeDataReturned, 0);
+
+ int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+
+ if (RT_SUCCESS(rc))
+ {
+ *pu32SizeDataReturned = parms.sizeDataReturned.u.value32;
+ }
+
+ return rc;
+}
+
+VBGLR3DECL(int) VbglR3HostChannelEventWait(uint32_t *pu32ChannelHandle,
+ uint32_t u32HGCMClientId,
+ uint32_t *pu32EventId,
+ void *pvParm,
+ uint32_t cbParm,
+ uint32_t *pu32SizeReturned)
+{
+ VBoxHostChannelEventWait parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_EVENT_WAIT, 4);
+ VbglHGCMParmUInt32Set(&parms.handle, 0);
+ VbglHGCMParmUInt32Set(&parms.id, 0);
+ VbglHGCMParmPtrSet(&parms.parm, pvParm, cbParm);
+ VbglHGCMParmUInt32Set(&parms.sizeReturned, 0);
+
+ int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+
+ if (RT_SUCCESS(rc))
+ {
+ *pu32ChannelHandle = parms.handle.u.value32;
+ *pu32EventId = parms.id.u.value32;
+ *pu32SizeReturned = parms.sizeReturned.u.value32;
+ }
+
+ return rc;
+}
+
+VBGLR3DECL(int) VbglR3HostChannelEventCancel(uint32_t u32ChannelHandle,
+ uint32_t u32HGCMClientId)
+{
+ RT_NOREF1(u32ChannelHandle);
+
+ VBoxHostChannelEventCancel parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_EVENT_CANCEL, 0);
+
+ return VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+}
+
+VBGLR3DECL(int) VbglR3HostChannelQuery(const char *pszName,
+ uint32_t u32HGCMClientId,
+ uint32_t u32Code,
+ void *pvParm,
+ uint32_t cbParm,
+ void *pvData,
+ uint32_t cbData,
+ uint32_t *pu32SizeDataReturned)
+{
+ /* Make a heap copy of the name, because HGCM can not use some of other memory types. */
+ size_t cbName = strlen(pszName) + 1;
+ char *pszCopy = (char *)RTMemAlloc(cbName);
+ if (pszCopy == NULL)
+ {
+ return VERR_NO_MEMORY;
+ }
+
+ memcpy(pszCopy, pszName, cbName);
+
+ VBoxHostChannelQuery parms;
+ VBGL_HGCM_HDR_INIT(&parms.hdr, u32HGCMClientId, VBOX_HOST_CHANNEL_FN_QUERY, 5);
+ VbglHGCMParmPtrSet(&parms.name, pszCopy, (uint32_t)cbName);
+ VbglHGCMParmUInt32Set(&parms.code, u32Code);
+ VbglHGCMParmPtrSet(&parms.parm, pvParm, cbParm);
+ VbglHGCMParmPtrSet(&parms.data, pvData, cbData);
+ VbglHGCMParmUInt32Set(&parms.sizeDataReturned, 0);
+
+ int rc = VbglR3HGCMCall(&parms.hdr, sizeof(parms));
+
+ if (RT_SUCCESS(rc))
+ {
+ *pu32SizeDataReturned = parms.sizeDataReturned.u.value32;
+ }
+
+ RTMemFree(pszCopy);
+
+ return rc;
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp
new file mode 100644
index 00000000..f3d7740f
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibHostVersion.cpp
@@ -0,0 +1,226 @@
+/* $Id: */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, host version check.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include <VBox/log.h>
+
+#ifdef RT_OS_WINDOWS
+ #define WIN32_LEAN_AND_MEAN
+ #include <iprt/win/windows.h>
+#endif
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Checks for a Guest Additions update by comparing the installed version on the
+ * guest and the reported host version.
+ *
+ * @returns VBox status code
+ *
+ * @param idClient The client id returned by
+ * VbglR3InfoSvcConnect().
+ * @param pfUpdate Receives pointer to boolean flag indicating
+ * whether an update was found or not.
+ * @param ppszHostVersion Receives pointer of allocated version string.
+ * The returned pointer must be freed using
+ * VbglR3GuestPropReadValueFree(). Always set to
+ * NULL.
+ * @param ppszGuestVersion Receives pointer of allocated revision string.
+ * The returned pointer must be freed using
+ * VbglR3GuestPropReadValueFree(). Always set to
+ * NULL.
+ */
+VBGLR3DECL(int) VbglR3HostVersionCheckForUpdate(HGCMCLIENTID idClient, bool *pfUpdate, char **ppszHostVersion, char **ppszGuestVersion)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+ Assert(idClient > 0);
+ AssertPtr(pfUpdate);
+ AssertPtr(ppszHostVersion);
+ AssertPtr(ppszGuestVersion);
+
+ *ppszHostVersion = NULL;
+ *ppszGuestVersion = NULL;
+
+ /* We assume we have an update initially.
+ Every block down below is allowed to veto */
+ *pfUpdate = true;
+
+ /* Do we need to do all this stuff? */
+ char *pszCheckHostVersion;
+ int rc = VbglR3GuestPropReadValueAlloc(idClient, "/VirtualBox/GuestAdd/CheckHostVersion", &pszCheckHostVersion);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS; /* If we don't find the value above we do the check by default */
+ else
+ LogFlow(("Could not read check host version flag! rc = %Rrc\n", rc));
+ }
+ else
+ {
+ /* Only don't do the check if we have a valid "0" in it */
+ if (!strcmp(pszCheckHostVersion, "0"))
+ {
+ LogRel(("No host version update check performed (disabled).\n"));
+ *pfUpdate = false;
+ }
+ VbglR3GuestPropReadValueFree(pszCheckHostVersion);
+ }
+
+ /* Collect all needed information */
+ /* Make sure we only notify the user once by comparing the host version with
+ * the last checked host version (if any) */
+ if (RT_SUCCESS(rc) && *pfUpdate)
+ {
+ /* Look up host version */
+ rc = VbglR3GuestPropReadValueAlloc(idClient, "/VirtualBox/HostInfo/VBoxVer", ppszHostVersion);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("Could not read VBox host version! rc = %Rrc\n", rc));
+ }
+ else
+ {
+ LogFlow(("Host version: %s\n", *ppszHostVersion));
+
+ /* Get last checked host version */
+ char *pszLastCheckedHostVersion;
+ rc = VbglR3HostVersionLastCheckedLoad(idClient, &pszLastCheckedHostVersion);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("Last checked host version: %s\n", pszLastCheckedHostVersion));
+ if (strcmp(*ppszHostVersion, pszLastCheckedHostVersion) == 0)
+ *pfUpdate = false; /* We already notified this version, skip */
+ VbglR3GuestPropReadValueFree(pszLastCheckedHostVersion);
+ }
+ else if (rc == VERR_NOT_FOUND) /* Never wrote a last checked host version before */
+ {
+ LogFlow(("Never checked a host version before.\n"));
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ /* Look up guest version */
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3GetAdditionsVersion(ppszGuestVersion, NULL /* Extended version not needed here */,
+ NULL /* Revision not needed here */);
+ if (RT_FAILURE(rc))
+ LogFlow(("Could not read VBox guest version! rc = %Rrc\n", rc));
+ }
+ }
+
+ /* Do the actual version comparison (if needed, see block(s) above) */
+ if (RT_SUCCESS(rc) && *pfUpdate)
+ {
+ if (RTStrVersionCompare(*ppszHostVersion, *ppszGuestVersion) > 0) /* Is host version greater than guest add version? */
+ {
+ /* Yay, we have an update! */
+ LogRel(("Guest Additions update found! Please upgrade this machine to the latest Guest Additions.\n"));
+ }
+ else
+ {
+ /* How sad ... */
+ *pfUpdate = false;
+ }
+ }
+
+ /* Cleanup on failure */
+ if (RT_FAILURE(rc))
+ {
+ if (*ppszHostVersion)
+ {
+ VbglR3GuestPropReadValueFree(*ppszHostVersion);
+ *ppszHostVersion = NULL;
+ }
+ if (*ppszGuestVersion)
+ {
+ VbglR3GuestPropReadValueFree(*ppszGuestVersion);
+ *ppszGuestVersion = NULL;
+ }
+ }
+ return rc;
+#else /* !VBOX_WITH_GUEST_PROPS */
+ RT_NOREF(idClient, pfUpdate, ppszHostVersion, ppszGuestVersion);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+/** Retrieves the last checked host version.
+ *
+ * @returns VBox status code.
+ *
+ * @param idClient The client id returned by VbglR3InfoSvcConnect().
+ * @param ppszVer Receives pointer of allocated version string.
+ * The returned pointer must be freed using RTStrFree() on VINF_SUCCESS.
+ */
+VBGLR3DECL(int) VbglR3HostVersionLastCheckedLoad(HGCMCLIENTID idClient, char **ppszVer)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+ Assert(idClient > 0);
+ AssertPtr(ppszVer);
+ return VbglR3GuestPropReadValueAlloc(idClient, "/VirtualBox/GuestAdd/HostVerLastChecked", ppszVer);
+#else /* !VBOX_WITH_GUEST_PROPS */
+ RT_NOREF(idClient, ppszVer);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+/** Stores the last checked host version for later lookup.
+ * Requires strings in form of "majorVer.minorVer.build".
+ *
+ * @returns VBox status code.
+ *
+ * @param idClient The client id returned by VbglR3InfoSvcConnect().
+ * @param pszVer Pointer to version string to store.
+ */
+VBGLR3DECL(int) VbglR3HostVersionLastCheckedStore(HGCMCLIENTID idClient, const char *pszVer)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+ Assert(idClient > 0);
+ AssertPtr(pszVer);
+ return VbglR3GuestPropWriteValue(idClient, "/VirtualBox/GuestAdd/HostVerLastChecked", pszVer);
+#else /* !VBOX_WITH_GUEST_PROPS */
+ RT_NOREF(idClient, pszVer);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h
new file mode 100644
index 00000000..fb97ace3
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibInternal.h
@@ -0,0 +1,131 @@
+/* $Id: VBoxGuestR3LibInternal.h $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 support library for the guest additions, Internal header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR3LibInternal_h
+#define GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR3LibInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/VMMDev.h>
+#include <VBox/VBoxGuest.h>
+#include <VBox/VBoxGuestLib.h>
+
+#ifdef VBOX_VBGLR3_XFREE86
+/* Rather than try to resolve all the header file conflicts, I will just
+ prototype what we need here. */
+typedef unsigned long xf86size_t;
+extern "C" xf86size_t xf86strlen(const char*);
+# undef strlen
+# define strlen xf86strlen
+#endif /* VBOX_VBGLR3_XFREE86 */
+
+RT_C_DECLS_BEGIN
+
+int vbglR3DoIOCtl(uintptr_t uFunction, PVBGLREQHDR pReq, size_t cbReq);
+int vbglR3DoIOCtlRaw(uintptr_t uFunction, PVBGLREQHDR pReq, size_t cbReq);
+int vbglR3GRAlloc(VMMDevRequestHeader **ppReq, size_t cb, VMMDevRequestType enmReqType);
+int vbglR3GRPerform(VMMDevRequestHeader *pReq);
+void vbglR3GRFree(VMMDevRequestHeader *pReq);
+
+
+
+DECLINLINE(void) VbglHGCMParmUInt32Set(HGCMFunctionParameter *pParm, uint32_t u32)
+{
+ pParm->type = VMMDevHGCMParmType_32bit;
+ pParm->u.value64 = 0; /* init unused bits to 0 */
+ pParm->u.value32 = u32;
+}
+
+
+DECLINLINE(int) VbglHGCMParmUInt32Get(HGCMFunctionParameter *pParm, uint32_t *pu32)
+{
+ if (pParm->type == VMMDevHGCMParmType_32bit)
+ {
+ *pu32 = pParm->u.value32;
+ return VINF_SUCCESS;
+ }
+ *pu32 = UINT32_MAX; /* shut up gcc */
+ return VERR_WRONG_PARAMETER_TYPE;
+}
+
+
+DECLINLINE(void) VbglHGCMParmUInt64Set(HGCMFunctionParameter *pParm, uint64_t u64)
+{
+ pParm->type = VMMDevHGCMParmType_64bit;
+ pParm->u.value64 = u64;
+}
+
+
+DECLINLINE(int) VbglHGCMParmUInt64Get(HGCMFunctionParameter *pParm, uint64_t *pu64)
+{
+ if (pParm->type == VMMDevHGCMParmType_64bit)
+ {
+ *pu64 = pParm->u.value64;
+ return VINF_SUCCESS;
+ }
+ *pu64 = UINT64_MAX; /* shut up gcc */
+ return VERR_WRONG_PARAMETER_TYPE;
+}
+
+
+DECLINLINE(void) VbglHGCMParmPtrSet(HGCMFunctionParameter *pParm, void *pv, uint32_t cb)
+{
+ pParm->type = VMMDevHGCMParmType_LinAddr;
+ pParm->u.Pointer.size = cb;
+ pParm->u.Pointer.u.linearAddr = (uintptr_t)pv;
+}
+
+
+#ifdef IPRT_INCLUDED_string_h
+
+DECLINLINE(void) VbglHGCMParmPtrSetString(HGCMFunctionParameter *pParm, const char *psz)
+{
+ pParm->type = VMMDevHGCMParmType_LinAddr_In;
+ pParm->u.Pointer.size = (uint32_t)strlen(psz) + 1;
+ pParm->u.Pointer.u.linearAddr = (uintptr_t)psz;
+}
+
+#endif /* IPRT_INCLUDED_string_h */
+
+#ifdef VBOX_VBGLR3_XFREE86
+# undef strlen
+#endif /* VBOX_VBGLR3_XFREE86 */
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxGuest_lib_VBoxGuestR3LibInternal_h */
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp
new file mode 100644
index 00000000..04a167e0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibLog.cpp
@@ -0,0 +1,94 @@
+/* $Id: VBoxGuestR3LibLog.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Logging.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Write to the backdoor logger from ring 3 guest code.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pch The string to log. Does not need to be terminated.
+ * @param cch The number of chars (bytes) to log.
+ *
+ * @remarks This currently does not accept more than 255 bytes of data at
+ * one time. It should probably be rewritten to use pass a pointer
+ * in the IOCtl.
+ */
+VBGLR3DECL(int) VbglR3WriteLog(const char *pch, size_t cch)
+{
+ /*
+ * Quietly skip empty strings.
+ * (Happens in the RTLogBackdoorPrintf case.)
+ */
+ int rc;
+ if (cch > 0)
+ {
+ if (RT_VALID_PTR(pch))
+ {
+ /*
+ * We need to repackage the string for ring-0.
+ */
+ size_t cbMsg = VBGL_IOCTL_LOG_SIZE(cch);
+ PVBGLIOCLOG pMsg = (PVBGLIOCLOG)RTMemTmpAlloc(cbMsg);
+ if (pMsg)
+ {
+ VBGLREQHDR_INIT_EX(&pMsg->Hdr, VBGL_IOCTL_LOG_SIZE_IN(cch), VBGL_IOCTL_LOG_SIZE_OUT);
+ memcpy(pMsg->u.In.szMsg, pch, cch);
+ pMsg->u.In.szMsg[cch] = '\0';
+ rc = vbglR3DoIOCtl(VBGL_IOCTL_LOG(cch), &pMsg->Hdr, cbMsg);
+
+ RTMemTmpFree(pMsg);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+ }
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp
new file mode 100644
index 00000000..81e7b272
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMisc.cpp
@@ -0,0 +1,135 @@
+/* $Id: VBoxGuestR3LibMisc.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Misc.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/log.h>
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Change the IRQ filter mask.
+ *
+ * @returns IPRT status code.
+ * @param fOr The OR mask.
+ * @param fNot The NOT mask.
+ */
+VBGLR3DECL(int) VbglR3CtlFilterMask(uint32_t fOr, uint32_t fNot)
+{
+ VBGLIOCCHANGEFILTERMASK Info;
+ VBGLREQHDR_INIT(&Info.Hdr, CHANGE_FILTER_MASK);
+ Info.u.In.fOrMask = fOr;
+ Info.u.In.fNotMask = fNot;
+ return vbglR3DoIOCtl(VBGL_IOCTL_CHANGE_FILTER_MASK, &Info.Hdr, sizeof(Info));
+}
+
+
+/**
+ * Report a change in the capabilities that we support to the host.
+ *
+ * @returns IPRT status code.
+ * @param fOr Capabilities which have been added.
+ * @param fNot Capabilities which have been removed.
+ *
+ * @todo Move to a different file.
+ */
+VBGLR3DECL(int) VbglR3SetGuestCaps(uint32_t fOr, uint32_t fNot)
+{
+ VBGLIOCSETGUESTCAPS Info;
+ VBGLREQHDR_INIT(&Info.Hdr, CHANGE_GUEST_CAPABILITIES);
+ Info.u.In.fOrMask = fOr;
+ Info.u.In.fNotMask = fNot;
+ return vbglR3DoIOCtl(VBGL_IOCTL_CHANGE_GUEST_CAPABILITIES, &Info.Hdr, sizeof(Info));
+}
+
+
+/**
+ * Acquire capabilities to report to the host.
+ *
+ * The capabilities which can be acquired are the same as those reported by
+ * VbglR3SetGuestCaps, and once a capability has been acquired once is is
+ * switched to "acquire mode" and can no longer be set using VbglR3SetGuestCaps.
+ * Capabilities can also be switched to acquire mode without actually being
+ * acquired. A client can not acquire a capability which has been acquired and
+ * not released by another client. Capabilities acquired are automatically
+ * released on session termination.
+ *
+ * @returns IPRT status code
+ * @returns VERR_RESOURCE_BUSY and acquires nothing if another client has
+ * acquired and not released at least one of the @a fOr capabilities
+ * @param fOr Capabilities to acquire or to switch to acquire mode
+ * @param fNot Capabilities to release
+ * @param fConfig if set, capabilities in @a fOr are switched to acquire mode
+ * but not acquired, and @a fNot is ignored. See
+ * VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE for details.
+ */
+VBGLR3DECL(int) VbglR3AcquireGuestCaps(uint32_t fOr, uint32_t fNot, bool fConfig)
+{
+ VBGLIOCACQUIREGUESTCAPS Info;
+ VBGLREQHDR_INIT(&Info.Hdr, ACQUIRE_GUEST_CAPABILITIES);
+ Info.u.In.fFlags = fConfig ? VBGL_IOC_AGC_FLAGS_CONFIG_ACQUIRE_MODE : VBGL_IOC_AGC_FLAGS_DEFAULT;
+ Info.u.In.fOrMask = fOr;
+ Info.u.In.fNotMask = fNot;
+ return vbglR3DoIOCtl(VBGL_IOCTL_ACQUIRE_GUEST_CAPABILITIES, &Info.Hdr, sizeof(Info));
+}
+
+
+/**
+ * Query the session ID of this VM.
+ *
+ * The session id is an unique identifier that gets changed for each VM start,
+ * reset or restore. Useful for detection a VM restore.
+ *
+ * @returns IPRT status code.
+ * @param pu64IdSession Session id (out). This is NOT changed on
+ * failure, so the caller can depend on this to
+ * deal with backward compatibility (see
+ * VBoxServiceVMInfoWorker() for an example.)
+ */
+VBGLR3DECL(int) VbglR3GetSessionId(uint64_t *pu64IdSession)
+{
+ VMMDevReqSessionId Req;
+
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetSessionId);
+ Req.idSession = 0;
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ *pu64IdSession = Req.idSession;
+
+ return rc;
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp
new file mode 100644
index 00000000..371b42d7
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibModule.cpp
@@ -0,0 +1,180 @@
+/* $Id: VBoxGuestR3LibModule.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared modules.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+/**
+ * Registers a new shared module for the VM
+ *
+ * @returns IPRT status code.
+ * @param pszModuleName Module name
+ * @param pszVersion Module version
+ * @param GCBaseAddr Module base address
+ * @param cbModule Module size
+ * @param cRegions Number of shared region descriptors
+ * @param pRegions Shared region(s)
+ */
+VBGLR3DECL(int) VbglR3RegisterSharedModule(char *pszModuleName, char *pszVersion,
+ RTGCPTR64 GCBaseAddr, uint32_t cbModule,
+ unsigned cRegions, VMMDEVSHAREDREGIONDESC *pRegions)
+{
+ VMMDevSharedModuleRegistrationRequest *pReq;
+ int rc;
+
+ /* Sanity check. */
+ AssertReturn(cRegions < VMMDEVSHAREDREGIONDESC_MAX, VERR_INVALID_PARAMETER);
+
+ pReq = (VMMDevSharedModuleRegistrationRequest *)RTMemAllocZ(RT_UOFFSETOF_DYN(VMMDevSharedModuleRegistrationRequest,
+ aRegions[cRegions]));
+ AssertReturn(pReq, VERR_NO_MEMORY);
+
+ vmmdevInitRequest(&pReq->header, VMMDevReq_RegisterSharedModule);
+ pReq->header.size = RT_UOFFSETOF_DYN(VMMDevSharedModuleRegistrationRequest, aRegions[cRegions]);
+ pReq->GCBaseAddr = GCBaseAddr;
+ pReq->cbModule = cbModule;
+ pReq->cRegions = cRegions;
+#ifdef RT_OS_WINDOWS
+# if ARCH_BITS == 32
+ pReq->enmGuestOS = VBOXOSFAMILY_Windows32;
+# else
+ pReq->enmGuestOS = VBOXOSFAMILY_Windows64;
+# endif
+#else
+ /** @todo */
+ pReq->enmGuestOS = VBOXOSFAMILY_Unknown;
+#endif
+ for (unsigned i = 0; i < cRegions; i++)
+ pReq->aRegions[i] = pRegions[i];
+
+ if ( RTStrCopy(pReq->szName, sizeof(pReq->szName), pszModuleName) != VINF_SUCCESS
+ || RTStrCopy(pReq->szVersion, sizeof(pReq->szVersion), pszVersion) != VINF_SUCCESS)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ goto end;
+ }
+
+ rc = vbglR3GRPerform(&pReq->header);
+
+end:
+ RTMemFree(pReq);
+ return rc;
+
+}
+
+/**
+ * Unregisters a shared module for the VM
+ *
+ * @returns IPRT status code.
+ * @param pszModuleName Module name
+ * @param pszVersion Module version
+ * @param GCBaseAddr Module base address
+ * @param cbModule Module size
+ */
+VBGLR3DECL(int) VbglR3UnregisterSharedModule(char *pszModuleName, char *pszVersion, RTGCPTR64 GCBaseAddr, uint32_t cbModule)
+{
+ VMMDevSharedModuleUnregistrationRequest Req;
+
+ vmmdevInitRequest(&Req.header, VMMDevReq_UnregisterSharedModule);
+ Req.GCBaseAddr = GCBaseAddr;
+ Req.cbModule = cbModule;
+
+ if ( RTStrCopy(Req.szName, sizeof(Req.szName), pszModuleName) != VINF_SUCCESS
+ || RTStrCopy(Req.szVersion, sizeof(Req.szVersion), pszVersion) != VINF_SUCCESS)
+ {
+ return VERR_BUFFER_OVERFLOW;
+ }
+ return vbglR3GRPerform(&Req.header);
+}
+
+/**
+ * Checks registered modules for shared pages
+ *
+ * @returns IPRT status code.
+ */
+VBGLR3DECL(int) VbglR3CheckSharedModules()
+{
+ VMMDevSharedModuleCheckRequest Req;
+
+ vmmdevInitRequest(&Req.header, VMMDevReq_CheckSharedModules);
+ return vbglR3GRPerform(&Req.header);
+}
+
+/**
+ * Checks if page sharing is enabled.
+ *
+ * @returns true/false enabled/disabled
+ */
+VBGLR3DECL(bool) VbglR3PageSharingIsEnabled()
+{
+ VMMDevPageSharingStatusRequest Req;
+
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetPageSharingStatus);
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ return Req.fEnabled;
+ return false;
+}
+
+/**
+ * Checks if page sharing is enabled.
+ *
+ * @returns true/false enabled/disabled
+ */
+VBGLR3DECL(int) VbglR3PageIsShared(RTGCPTR pPage, bool *pfShared, uint64_t *puPageFlags)
+{
+#ifdef DEBUG
+ VMMDevPageIsSharedRequest Req;
+
+ vmmdevInitRequest(&Req.header, VMMDevReq_DebugIsPageShared);
+ Req.GCPtrPage = pPage;
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ {
+ *pfShared = Req.fShared;
+ *puPageFlags = Req.uPageFlags;
+ }
+ return rc;
+#else
+ RT_NOREF3(pPage, pfShared, puPageFlags);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp
new file mode 100644
index 00000000..b93872d4
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibMouse.cpp
@@ -0,0 +1,90 @@
+/* $Id: VBoxGuestR3LibMouse.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Mouse.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Retrieve mouse coordinates and features from the host.
+ *
+ * @returns VBox status code.
+ *
+ * @param pfFeatures Where to store the mouse features.
+ * @param px Where to store the X co-ordinate.
+ * @param py Where to store the Y co-ordinate.
+ */
+VBGLR3DECL(int) VbglR3GetMouseStatus(uint32_t *pfFeatures, uint32_t *px, uint32_t *py)
+{
+ VMMDevReqMouseStatus Req;
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetMouseStatus);
+ Req.mouseFeatures = 0;
+ Req.pointerXPos = 0;
+ Req.pointerYPos = 0;
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ {
+ if (pfFeatures)
+ *pfFeatures = Req.mouseFeatures;
+ if (px)
+ *px = Req.pointerXPos;
+ if (py)
+ *py = Req.pointerYPos;
+ }
+ return rc;
+}
+
+
+/**
+ * Send mouse features to the host.
+ *
+ * @returns VBox status code.
+ *
+ * @param fFeatures Supported mouse pointer features. The main guest driver
+ * will mediate different callers and show the host any
+ * feature enabled by any guest caller.
+ */
+VBGLR3DECL(int) VbglR3SetMouseStatus(uint32_t fFeatures)
+{
+ VBGLIOCSETMOUSESTATUS Req;
+ VBGLREQHDR_INIT(&Req.Hdr, SET_MOUSE_STATUS);
+ Req.u.In.fStatus = fFeatures;
+ return vbglR3DoIOCtl(VBGL_IOCTL_SET_MOUSE_STATUS, &Req.Hdr, sizeof(Req));
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp
new file mode 100644
index 00000000..ff7c312d
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibPidFile.cpp
@@ -0,0 +1,151 @@
+/** $Id: VBoxGuestR3LibPidFile.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions,
+ * Create a PID file.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/process.h>
+#include <iprt/thread.h>
+#include <iprt/err.h>
+#include "VBoxGuestR3LibInternal.h"
+
+/* A time to wait before starting the next attempt to check a pidfile. */
+#define VBGL_PIDFILE_WAIT_RELAX_TIME_MS (250)
+
+/**
+ * Creates a PID File and returns the open file descriptor.
+ *
+ * On DOS based system, file sharing (deny write) is used for locking the PID
+ * file.
+ *
+ * On Unix-y systems, an exclusive advisory lock is used for locking the PID
+ * file since the file sharing support is usually missing there.
+ *
+ * This API will overwrite any existing PID Files without a lock on them, on the
+ * assumption that they are stale files which an old process did not properly
+ * clean up.
+ *
+ * @returns IPRT status code.
+ * @param pszPath The path and filename to create the PID File under
+ * @param phFile Where to store the file descriptor of the open (and locked
+ * on Unix-y systems) PID File. On failure, or if another
+ * process owns the PID File, this will be set to NIL_RTFILE.
+ */
+VBGLR3DECL(int) VbglR3PidFile(const char *pszPath, PRTFILE phFile)
+{
+ AssertPtrReturn(pszPath, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phFile, VERR_INVALID_PARAMETER);
+ *phFile = NIL_RTFILE;
+
+ RTFILE hPidFile;
+ int rc = RTFileOpen(&hPidFile, pszPath,
+ RTFILE_O_READWRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_WRITE
+ | (0644 << RTFILE_O_CREATE_MODE_SHIFT));
+ if (RT_SUCCESS(rc))
+ {
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ /** @todo using size 0 for locking means lock all on Posix.
+ * We should adopt this as our convention too, or something
+ * similar. */
+ rc = RTFileLock(hPidFile, RTFILE_LOCK_WRITE, 0, 0);
+ if (RT_FAILURE(rc))
+ RTFileClose(hPidFile);
+ else
+#endif
+ {
+ char szBuf[256];
+ size_t cbPid = RTStrPrintf(szBuf, sizeof(szBuf), "%d\n",
+ RTProcSelf());
+ RTFileWrite(hPidFile, szBuf, cbPid, NULL);
+ *phFile = hPidFile;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Close and remove an open PID File.
+ *
+ * @param pszPath The path to the PID File,
+ * @param hFile The handle for the file. NIL_RTFILE is ignored as usual.
+ */
+VBGLR3DECL(void) VbglR3ClosePidFile(const char *pszPath, RTFILE hFile)
+{
+ AssertPtrReturnVoid(pszPath);
+ if (hFile != NIL_RTFILE)
+ {
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RTFileWriteAt(hFile, 0, "-1", 2, NULL);
+#else
+ RTFileDelete(pszPath);
+#endif
+ RTFileClose(hFile);
+ }
+}
+
+
+/**
+ * Wait for other process to release pidfile.
+ *
+ * This function is a wrapper to VbglR3PidFile().
+ *
+ * @returns IPRT status code.
+ * @param szPidfile Path to pidfile.
+ * @param phPidfile Handle to pidfile.
+ * @param u64TimeoutMs A timeout value in milliseconds to wait for
+ * other process to release pidfile.
+ */
+VBGLR3DECL(int) VbglR3PidfileWait(const char *szPidfile, RTFILE *phPidfile, uint64_t u64TimeoutMs)
+{
+ int rc = VERR_FILE_LOCK_VIOLATION;
+ uint64_t u64Start = RTTimeSystemMilliTS();
+
+ AssertPtrReturn(szPidfile, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phPidfile, VERR_INVALID_PARAMETER);
+
+ while ( !RT_SUCCESS((rc = VbglR3PidFile(szPidfile, phPidfile)))
+ && (RTTimeSystemMilliTS() - u64Start < u64TimeoutMs))
+ {
+ RTThreadSleep(VBGL_PIDFILE_WAIT_RELAX_TIME_MS);
+ }
+
+ return rc;
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp
new file mode 100644
index 00000000..8a9d15c0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibRuntimeXF86.cpp
@@ -0,0 +1,108 @@
+/* $Id: VBoxGuestR3LibRuntimeXF86.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions,
+ * implements the minimum of runtime functions needed for
+ * XFree86 driver code.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#if defined(VBOX_VBGLR3_XFREE86)
+extern "C" {
+# define XFree86LOADER
+# include <xf86_ansic.h>
+# undef size_t
+}
+#else
+# include <stdarg.h>
+# include <stdlib.h>
+# define xalloc malloc
+# define xfree free
+extern "C" void ErrorF(const char *f, ...);
+#endif
+
+RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ ErrorF("Assertion failed! Expression: %s at %s in\n", pszExpr,
+ pszFunction);
+ ErrorF("%s:%u\n", pszFile, uLine);
+}
+
+RTDECL(void) RTAssertMsg2Weak(const char *pszFormat, ...)
+{
+ NOREF(pszFormat);
+}
+
+RTDECL(bool) RTAssertShouldPanic(void)
+{
+ return false;
+}
+
+RTDECL(PRTLOGGER) RTLogDefaultInstanceEx(uint32_t fFlagsAndGroup)
+{
+ NOREF(fFlagsAndGroup);
+ return NULL;
+}
+
+RTDECL(PRTLOGGER) RTLogRelGetDefaultInstance(void)
+{
+ return NULL;
+}
+
+RTDECL(PRTLOGGER) RTLogRelGetDefaultInstanceEx(uint32_t fFlagsAndGroup)
+{
+ NOREF(fFlagsAndGroup);
+ return NULL;
+}
+
+RTDECL(void) RTLogLoggerEx(PRTLOGGER, unsigned, unsigned, const char *pszFormat, ...)
+{
+ NOREF(pszFormat);
+}
+
+RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag)
+{
+ NOREF(pszTag);
+ return xalloc(cb);
+}
+
+RTDECL(void) RTMemTmpFree(void *pv)
+{
+ xfree(pv);
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp
new file mode 100644
index 00000000..a13a77b9
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSeamless.cpp
@@ -0,0 +1,209 @@
+/* $Id: VBoxGuestR3LibSeamless.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Seamless mode.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include <VBox/log.h>
+
+#include "VBoxGuestR3LibInternal.h"
+
+#ifdef VBOX_VBGLR3_XFREE86
+/* Rather than try to resolve all the header file conflicts, I will just
+ prototype what we need here. */
+extern "C" void* xf86memcpy(void*,const void*,xf86size_t);
+# undef memcpy
+# define memcpy xf86memcpy
+#endif /* VBOX_VBGLR3_XFREE86 */
+
+/**
+ * Tell the host that we support (or no longer support) seamless mode.
+ *
+ * @returns IPRT status value
+ * @param fState whether or not we support seamless mode
+ */
+VBGLR3DECL(int) VbglR3SeamlessSetCap(bool fState)
+{
+ if (fState)
+ return VbglR3SetGuestCaps(VMMDEV_GUEST_SUPPORTS_SEAMLESS, 0);
+ return VbglR3SetGuestCaps(0, VMMDEV_GUEST_SUPPORTS_SEAMLESS);
+}
+
+/**
+ * Wait for a seamless mode change event.
+ *
+ * @returns IPRT status value.
+ * @param[out] pMode On success, the seamless mode to switch into (i.e.
+ * disabled, visible region or host window).
+ */
+VBGLR3DECL(int) VbglR3SeamlessWaitEvent(VMMDevSeamlessMode *pMode)
+{
+ AssertPtrReturn(pMode, VERR_INVALID_POINTER);
+
+ /** @todo r=andy The (similar / duplicate) Windows code does similar waiting. Merge / fix this. */
+ uint32_t fEvent = 0;
+ int rc = VbglR3WaitEvent(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, 1000 /* ms */, &fEvent);
+ if (RT_SUCCESS(rc))
+ {
+ /* did we get the right event? */
+ if (fEvent & VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST)
+ {
+ VMMDevSeamlessChangeRequest seamlessChangeRequest;
+
+ /* get the seamless change request */
+ vmmdevInitRequest(&seamlessChangeRequest.header, VMMDevReq_GetSeamlessChangeRequest);
+ seamlessChangeRequest.mode = (VMMDevSeamlessMode)-1;
+ seamlessChangeRequest.eventAck = VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST;
+ rc = vbglR3GRPerform(&seamlessChangeRequest.header);
+ if (RT_SUCCESS(rc))
+ {
+ *pMode = seamlessChangeRequest.mode;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_TRY_AGAIN;
+ }
+ else if ( rc == VERR_INTERRUPTED
+ || rc == VERR_TIMEOUT /* just in case */)
+ rc = VERR_TRY_AGAIN;
+ return rc;
+}
+
+/**
+ * Request the last seamless mode switch from the host again.
+ *
+ * @returns IPRT status value.
+ * @param[out] pMode On success, the seamless mode that was switched
+ * into (i.e. disabled, visible region or host window).
+ */
+VBGLR3DECL(int) VbglR3SeamlessGetLastEvent(VMMDevSeamlessMode *pMode)
+{
+ VMMDevSeamlessChangeRequest seamlessChangeRequest;
+ int rc;
+
+ AssertPtrReturn(pMode, VERR_INVALID_PARAMETER);
+
+ /* get the seamless change request */
+ vmmdevInitRequest(&seamlessChangeRequest.header, VMMDevReq_GetSeamlessChangeRequest);
+ seamlessChangeRequest.mode = (VMMDevSeamlessMode)-1;
+ seamlessChangeRequest.eventAck = VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST;
+ rc = vbglR3GRPerform(&seamlessChangeRequest.header);
+ if (RT_SUCCESS(rc))
+ {
+ *pMode = seamlessChangeRequest.mode;
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+/**
+ * Inform the host about the visible region
+ *
+ * @returns IPRT status code
+ * @param cRects number of rectangles in the list of visible rectangles
+ * @param pRects list of visible rectangles on the guest display
+ *
+ * @todo A scatter-gather version of vbglR3GRPerform would be nice, so that we don't have
+ * to copy our rectangle and header data into a single structure and perform an
+ * additional allocation.
+ * @todo Would that really gain us much, given that the rectangles may not
+ * be grouped at all, or in the format we need? Keeping the memory
+ * for our "single structure" around (re-alloc-ing it if necessary)
+ * sounds like a simpler optimisation if we need it.
+ */
+VBGLR3DECL(int) VbglR3SeamlessSendRects(uint32_t cRects, PRTRECT pRects)
+{
+ VMMDevVideoSetVisibleRegion *pReq;
+ int rc;
+
+ AssertReturn(pRects || cRects == 0, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cRects <= _1M, ("%u\n", cRects), VERR_OUT_OF_RANGE);
+
+ rc = vbglR3GRAlloc((VMMDevRequestHeader **)&pReq,
+ sizeof(VMMDevVideoSetVisibleRegion)
+ + cRects * sizeof(RTRECT)
+ - sizeof(RTRECT),
+ VMMDevReq_VideoSetVisibleRegion);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->cRect = cRects;
+ if (cRects)
+ memcpy(&pReq->Rect, pRects, cRects * sizeof(RTRECT));
+ /* This will fail harmlessly for cRect == 0 and older host code */
+ rc = vbglR3GRPerform(&pReq->header);
+ LogFunc(("Visible region request returned %Rrc, internal %Rrc.\n",
+ rc, pReq->header.rc));
+ if (RT_SUCCESS(rc))
+ rc = pReq->header.rc;
+ vbglR3GRFree(&pReq->header);
+ }
+ LogFunc(("Sending %u rectangles to the host: %Rrc\n", cRects, rc));
+ return rc;
+}
+
+VBGLR3DECL(int) VbglR3SeamlessSendMonitorPositions(uint32_t cPositions, PRTPOINT pPositions)
+{
+ if (!pPositions || cPositions <= 0)
+ return VERR_INVALID_PARAMETER;
+
+ VMMDevVideoUpdateMonitorPositions *pReq;
+ int rc;
+
+ rc = vbglR3GRAlloc((VMMDevRequestHeader **)&pReq,
+ sizeof(VMMDevVideoUpdateMonitorPositions)
+ + (cPositions - 1) * sizeof(RTPOINT),
+ VMMDevReq_VideoUpdateMonitorPositions);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->cPositions = cPositions;
+ if (cPositions)
+ memcpy(&pReq->aPositions, pPositions, cPositions * sizeof(RTPOINT));
+ rc = vbglR3GRPerform(&pReq->header);
+ LogFunc(("Monitor position update request returned %Rrc, internal %Rrc.\n",
+ rc, pReq->header.rc));
+ if (RT_SUCCESS(rc))
+ rc = pReq->header.rc;
+ vbglR3GRFree(&pReq->header);
+ }
+ LogFunc(("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc));
+ return rc;
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp
new file mode 100644
index 00000000..47137fd2
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibSharedFolders.cpp
@@ -0,0 +1,432 @@
+/* $Id: VBoxGuestR3LibSharedFolders.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, shared folders.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/cpp/autores.h>
+#include <iprt/stdarg.h>
+#include <VBox/log.h>
+#include <VBox/shflsvc.h> /** @todo File should be moved to VBox/HostServices/SharedFolderSvc.h */
+
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Connects to the shared folder service.
+ *
+ * @returns VBox status code
+ * @param pidClient Where to put the client id on success. The client id
+ * must be passed to all the other calls to the service.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderConnect(HGCMCLIENTID *pidClient)
+{
+ return VbglR3HGCMConnect("VBoxSharedFolders", pidClient);
+}
+
+
+/**
+ * Disconnect from the shared folder service.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3InfoSvcConnect().
+ */
+VBGLR3DECL(int) VbglR3SharedFolderDisconnect(HGCMCLIENTID idClient)
+{
+ return VbglR3HGCMDisconnect(idClient);
+}
+
+
+/**
+ * Checks whether a shared folder share exists or not.
+ *
+ * @returns True if shared folder exists, false if not.
+ * @param idClient The client id returned by VbglR3InfoSvcConnect().
+ * @param pszShareName Shared folder name to check.
+ */
+VBGLR3DECL(bool) VbglR3SharedFolderExists(HGCMCLIENTID idClient, const char *pszShareName)
+{
+ AssertPtr(pszShareName);
+
+ uint32_t cMappings;
+ VBGLR3SHAREDFOLDERMAPPING *paMappings;
+
+ /** @todo Use some caching here? */
+ bool fFound = false;
+ int rc = VbglR3SharedFolderGetMappings(idClient, true /* Only process auto-mounted folders */, &paMappings, &cMappings);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t i = 0; i < cMappings && !fFound; i++)
+ {
+ char *pszName = NULL;
+ rc = VbglR3SharedFolderGetName(idClient, paMappings[i].u32Root, &pszName);
+ if ( RT_SUCCESS(rc)
+ && *pszName)
+ {
+ if (RTStrICmp(pszName, pszShareName) == 0)
+ fFound = true;
+ RTStrFree(pszName);
+ }
+ }
+ VbglR3SharedFolderFreeMappings(paMappings);
+ }
+ return fFound;
+}
+
+
+/**
+ * Get the list of available shared folders.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3SharedFolderConnect().
+ * @param fAutoMountOnly Flag whether only auto-mounted shared folders
+ * should be reported.
+ * @param ppaMappings Allocated array which will retrieve the mapping info. Needs
+ * to be freed with VbglR3SharedFolderFreeMappings() later.
+ * @param pcMappings The number of mappings returned in @a ppaMappings.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderGetMappings(HGCMCLIENTID idClient, bool fAutoMountOnly,
+ PVBGLR3SHAREDFOLDERMAPPING *ppaMappings, uint32_t *pcMappings)
+{
+ AssertPtrReturn(pcMappings, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppaMappings, VERR_INVALID_PARAMETER);
+
+ *pcMappings = 0;
+ *ppaMappings = NULL;
+
+ VBoxSFQueryMappings Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_QUERY_MAPPINGS, 3);
+
+ /* Set the mapping flags. */
+ uint32_t u32Flags = 0; /** @todo SHFL_MF_UTF8 is not implemented yet. */
+ if (fAutoMountOnly) /* We only want the mappings which get auto-mounted. */
+ u32Flags |= SHFL_MF_AUTOMOUNT;
+ VbglHGCMParmUInt32Set(&Msg.flags, u32Flags);
+
+ /*
+ * Prepare and get the actual mappings from the host service.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cMappings = 8; /* Should be a good default value. */
+ uint32_t cbSize = cMappings * sizeof(VBGLR3SHAREDFOLDERMAPPING);
+ VBGLR3SHAREDFOLDERMAPPING *ppaMappingsTemp = (PVBGLR3SHAREDFOLDERMAPPING)RTMemAllocZ(cbSize);
+ if (!ppaMappingsTemp)
+ return VERR_NO_MEMORY;
+
+ do
+ {
+ VbglHGCMParmUInt32Set(&Msg.numberOfMappings, cMappings);
+ VbglHGCMParmPtrSet(&Msg.mappings, ppaMappingsTemp, cbSize);
+
+ rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ VbglHGCMParmUInt32Get(&Msg.numberOfMappings, pcMappings);
+
+ /* Do we have more mappings than we have allocated space for? */
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ cMappings = *pcMappings;
+ cbSize = cMappings * sizeof(VBGLR3SHAREDFOLDERMAPPING);
+ void *pvNew = RTMemRealloc(ppaMappingsTemp, cbSize);
+ AssertPtrBreakStmt(pvNew, rc = VERR_NO_MEMORY);
+ ppaMappingsTemp = (PVBGLR3SHAREDFOLDERMAPPING)pvNew;
+ }
+ }
+ } while (rc == VINF_BUFFER_OVERFLOW); /** @todo r=bird: This won't happen because the weird host code never returns it. */
+
+ if ( RT_FAILURE(rc)
+ || !*pcMappings)
+ {
+ RTMemFree(ppaMappingsTemp);
+ ppaMappingsTemp = NULL;
+ }
+
+ /* In this case, just return success with 0 mappings */
+ if ( rc == VERR_INVALID_PARAMETER
+ && fAutoMountOnly)
+ rc = VINF_SUCCESS;
+
+ *ppaMappings = ppaMappingsTemp;
+
+ return rc;
+}
+
+
+/**
+ * Frees the shared folder mappings allocated by
+ * VbglR3SharedFolderGetMappings() before.
+ *
+ * @param paMappings What
+ */
+VBGLR3DECL(void) VbglR3SharedFolderFreeMappings(PVBGLR3SHAREDFOLDERMAPPING paMappings)
+{
+ if (paMappings)
+ RTMemFree(paMappings);
+}
+
+
+/**
+ * Get the real name of a shared folder.
+ *
+ * @returns VBox status code.
+ * @param idClient The client id returned by VbglR3InvsSvcConnect().
+ * @param u32Root Root ID of shared folder to get the name for.
+ * @param ppszName Where to return the name string. This shall be
+ * freed by calling RTStrFree.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderGetName(HGCMCLIENTID idClient, uint32_t u32Root, char **ppszName)
+{
+ AssertPtr(ppszName);
+
+ VBoxSFQueryMapName Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_QUERY_MAP_NAME, 2);
+
+ int rc;
+ uint32_t cbString = SHFLSTRING_HEADER_SIZE + SHFL_MAX_LEN * sizeof(RTUTF16);
+ PSHFLSTRING pString = (PSHFLSTRING)RTMemAlloc(cbString);
+ if (pString)
+ {
+ if (!ShflStringInitBuffer(pString, cbString))
+ {
+ RTMemFree(pString);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ VbglHGCMParmUInt32Set(&Msg.root, u32Root);
+ VbglHGCMParmPtrSet(&Msg.name, pString, cbString);
+
+ rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ *ppszName = NULL;
+ rc = RTUtf16ToUtf8(&pString->String.ucs2[0], ppszName);
+ }
+ RTMemFree(pString);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Queries information about a shared folder.
+ *
+ * @returns VBox status code.
+ *
+ * @param idClient The client ID.
+ * @param idRoot The root ID of the folder to query information for.
+ * @param fQueryFlags SHFL_MIQF_XXX.
+ * @param ppszName Where to return the pointer to the name.
+ * Free using RTStrFree. Optional.
+ * @param ppszMountPoint Where to return the pointer to the auto mount point.
+ * Free using RTStrFree. Optional.
+ * @param pfFlags Where to return the flags (SHFL_MIF_XXX). Optional.
+ * @param puRootIdVersion where to return the root ID version. Optional.
+ * This helps detecting root-id reuse.
+ *
+ * @remarks ASSUMES UTF-16 connection to host.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderQueryFolderInfo(HGCMCLIENTID idClient, uint32_t idRoot, uint64_t fQueryFlags,
+ char **ppszName, char **ppszMountPoint,
+ uint64_t *pfFlags, uint32_t *puRootIdVersion)
+{
+ AssertReturn(!(fQueryFlags & ~(SHFL_MIQF_DRIVE_LETTER | SHFL_MIQF_PATH)), VERR_INVALID_FLAGS);
+
+ /*
+ * Allocate string buffers first.
+ */
+ int rc;
+ PSHFLSTRING pNameBuf = (PSHFLSTRING)RTMemAlloc(SHFLSTRING_HEADER_SIZE + (SHFL_MAX_LEN + 1) * sizeof(RTUTF16));
+ PSHFLSTRING pMountPoint = (PSHFLSTRING)RTMemAlloc(SHFLSTRING_HEADER_SIZE + (260 + 1) * sizeof(RTUTF16));
+ if (pNameBuf && pMountPoint)
+ {
+ ShflStringInitBuffer(pNameBuf, SHFLSTRING_HEADER_SIZE + (SHFL_MAX_LEN + 1) * sizeof(RTUTF16));
+ ShflStringInitBuffer(pMountPoint, SHFLSTRING_HEADER_SIZE + (260 + 1) * sizeof(RTUTF16));
+
+ /*
+ * Make the call.
+ */
+ VBoxSFQueryMapInfo Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_QUERY_MAP_INFO, 5);
+ VbglHGCMParmUInt32Set(&Msg.root, idRoot);
+ VbglHGCMParmPtrSet(&Msg.name, pNameBuf, SHFLSTRING_HEADER_SIZE + pNameBuf->u16Size);
+ VbglHGCMParmPtrSet(&Msg.mountPoint, pMountPoint, SHFLSTRING_HEADER_SIZE + pMountPoint->u16Size);
+ VbglHGCMParmUInt64Set(&Msg.flags, fQueryFlags);
+ VbglHGCMParmUInt32Set(&Msg.rootIdVersion, 0);
+
+ rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Copy out the results.
+ */
+ if (puRootIdVersion)
+ *puRootIdVersion = Msg.rootIdVersion.u.value64;
+
+ if (pfFlags)
+ *pfFlags = Msg.flags.u.value64;
+
+ if (ppszName)
+ {
+ *ppszName = NULL;
+ rc = RTUtf16ToUtf8Ex(pNameBuf->String.utf16, pNameBuf->u16Length / sizeof(RTUTF16), ppszName, 0, NULL);
+ }
+
+ if (ppszMountPoint && RT_SUCCESS(rc))
+ {
+ *ppszMountPoint = NULL;
+ rc = RTUtf16ToUtf8Ex(pMountPoint->String.utf16, pMountPoint->u16Length / sizeof(RTUTF16), ppszMountPoint, 0, NULL);
+ if (RT_FAILURE(rc) && ppszName)
+ {
+ RTStrFree(*ppszName);
+ *ppszName = NULL;
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTMemFree(pMountPoint);
+ RTMemFree(pNameBuf);
+ return rc;
+}
+
+
+/**
+ * Waits for changes to the mappings (add, remove, restore).
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS on change
+ * @retval VINF_TRY_AGAIN on restore.
+ * @retval VERR_OUT_OF_RESOURCES if there are too many guys waiting.
+ *
+ * @param idClient The client ID.
+ * @param uPrevVersion The mappings config version number returned the last
+ * time around. Use UINT32_MAX for the first call.
+ * @param puCurVersion Where to return the current mappings config version.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderWaitForMappingsChanges(HGCMCLIENTID idClient, uint32_t uPrevVersion, uint32_t *puCurVersion)
+{
+ VBoxSFWaitForMappingsChanges Msg;
+ VBGL_HGCM_HDR_INIT(&Msg.callInfo, idClient, SHFL_FN_WAIT_FOR_MAPPINGS_CHANGES, 1);
+ VbglHGCMParmUInt32Set(&Msg.version, uPrevVersion);
+
+ int rc = VbglR3HGCMCall(&Msg.callInfo, sizeof(Msg));
+
+ *puCurVersion = Msg.version.u.value32;
+ return rc;
+}
+
+
+/**
+ * Cancels all threads currently waiting for changes for this client.
+ *
+ * @returns VBox status code.
+ * @param idClient The client ID.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderCancelMappingsChangesWaits(HGCMCLIENTID idClient)
+{
+ VBGLIOCHGCMCALL CallInfo;
+ VBGL_HGCM_HDR_INIT(&CallInfo, idClient, SHFL_FN_CANCEL_MAPPINGS_CHANGES_WAITS, 0);
+
+ return VbglR3HGCMCall(&CallInfo, sizeof(CallInfo));
+}
+
+
+/**
+ * Retrieves the prefix for a shared folder mount point. If no prefix
+ * is set in the guest properties "sf_" is returned.
+ *
+ * @returns VBox status code.
+ * @param ppszPrefix Where to return the prefix string. This shall be
+ * freed by calling RTStrFree.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderGetMountPrefix(char **ppszPrefix)
+{
+ AssertPtrReturn(ppszPrefix, VERR_INVALID_POINTER);
+ int rc;
+#ifdef VBOX_WITH_GUEST_PROPS
+ HGCMCLIENTID idClientGuestProp;
+ rc = VbglR3GuestPropConnect(&idClientGuestProp);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3GuestPropReadValueAlloc(idClientGuestProp, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", ppszPrefix);
+ if (rc == VERR_NOT_FOUND) /* No prefix set? Then set the default. */
+ {
+#endif
+/** @todo r=bird: Inconsistent! VbglR3SharedFolderGetMountDir does not return a default. */
+ rc = RTStrDupEx(ppszPrefix, "sf_");
+#ifdef VBOX_WITH_GUEST_PROPS
+ }
+ VbglR3GuestPropDisconnect(idClientGuestProp);
+ }
+#endif
+ return rc;
+}
+
+
+/**
+ * Retrieves the mount root directory for auto-mounted shared
+ * folders. mount point. If no string is set (VERR_NOT_FOUND)
+ * it's up on the caller (guest) to decide where to mount.
+ *
+ * @returns VBox status code.
+ * @param ppszDir Where to return the directory
+ * string. This shall be freed by
+ * calling RTStrFree.
+ */
+VBGLR3DECL(int) VbglR3SharedFolderGetMountDir(char **ppszDir)
+{
+ AssertPtrReturn(ppszDir, VERR_INVALID_POINTER);
+ int rc = VERR_NOT_FOUND;
+#ifdef VBOX_WITH_GUEST_PROPS
+ HGCMCLIENTID idClientGuestProp;
+ rc = VbglR3GuestPropConnect(&idClientGuestProp);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3GuestPropReadValueAlloc(idClientGuestProp, "/VirtualBox/GuestAdd/SharedFolders/MountDir", ppszDir);
+ VbglR3GuestPropDisconnect(idClientGuestProp);
+ }
+#endif
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp
new file mode 100644
index 00000000..30bc4631
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibStat.cpp
@@ -0,0 +1,79 @@
+/* $Id: VBoxGuestR3LibStat.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Statistics.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+
+
+/**
+ * Query the current statistics update interval.
+ *
+ * @returns IPRT status code.
+ * @param pcMsInterval Update interval in ms (out).
+ */
+VBGLR3DECL(int) VbglR3StatQueryInterval(PRTMSINTERVAL pcMsInterval)
+{
+ VMMDevGetStatisticsChangeRequest Req;
+
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetStatisticsChangeRequest);
+ Req.eventAck = VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST;
+ Req.u32StatInterval = 1;
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ {
+ *pcMsInterval = Req.u32StatInterval * 1000;
+ if (*pcMsInterval / 1000 != Req.u32StatInterval)
+ *pcMsInterval = ~(RTMSINTERVAL)0;
+ }
+ return rc;
+}
+
+
+/**
+ * Report guest statistics.
+ *
+ * @returns IPRT status code.
+ * @param pReq Request packet with statistics.
+ */
+VBGLR3DECL(int) VbglR3StatReport(VMMDevReportGuestStats *pReq)
+{
+ vmmdevInitRequest(&pReq->header, VMMDevReq_ReportGuestStats);
+ return vbglR3GRPerform(&pReq->header);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp
new file mode 100644
index 00000000..603e83f7
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibTime.cpp
@@ -0,0 +1,55 @@
+/* $Id: VBoxGuestR3LibTime.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Time.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/time.h>
+#include "VBoxGuestR3LibInternal.h"
+
+
+VBGLR3DECL(int) VbglR3GetHostTime(PRTTIMESPEC pTime)
+{
+ VMMDevReqHostTime Req;
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetHostTime);
+ Req.time = UINT64_MAX;
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ RTTimeSpecSetMilli(pTime, (int64_t)Req.time);
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp
new file mode 100644
index 00000000..38a79c58
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVideo.cpp
@@ -0,0 +1,618 @@
+/* $Id: VBoxGuestR3LibVideo.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Video.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR3LibInternal.h"
+
+#include <VBox/log.h>
+#include <VBox/HostServices/GuestPropertySvc.h> /* For Save and RetrieveVideoMode */
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#ifdef VBOX_VBGLR3_XFREE86
+/* Rather than try to resolve all the header file conflicts, I will just
+ prototype what we need here. */
+extern "C" void* xf86memcpy(void*,const void*,xf86size_t);
+# undef memcpy
+# define memcpy xf86memcpy
+extern "C" void* xf86memset(const void*,int,xf86size_t);
+# undef memset
+# define memset xf86memset
+#endif /* VBOX_VBGLR3_XFREE86 */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VIDEO_PROP_PREFIX "/VirtualBox/GuestAdd/Vbgl/Video/"
+
+
+/**
+ * Enable or disable video acceleration.
+ *
+ * @returns VBox status code.
+ *
+ * @param fEnable Pass zero to disable, any other value to enable.
+ */
+VBGLR3DECL(int) VbglR3VideoAccelEnable(bool fEnable)
+{
+ VMMDevVideoAccelEnable Req;
+ vmmdevInitRequest(&Req.header, VMMDevReq_VideoAccelEnable);
+ Req.u32Enable = fEnable;
+ Req.cbRingBuffer = VMMDEV_VBVA_RING_BUFFER_SIZE;
+ Req.fu32Status = 0;
+ return vbglR3GRPerform(&Req.header);
+}
+
+
+/**
+ * Flush the video buffer.
+ *
+ * @returns VBox status code.
+ */
+VBGLR3DECL(int) VbglR3VideoAccelFlush(void)
+{
+ VMMDevVideoAccelFlush Req;
+ vmmdevInitRequest(&Req.header, VMMDevReq_VideoAccelFlush);
+ return vbglR3GRPerform(&Req.header);
+}
+
+
+/**
+ * Send mouse pointer shape information to the host.
+ *
+ * @returns VBox status code.
+ *
+ * @param fFlags Mouse pointer flags.
+ * @param xHot X coordinate of hot spot.
+ * @param yHot Y coordinate of hot spot.
+ * @param cx Pointer width.
+ * @param cy Pointer height.
+ * @param pvImg Pointer to the image data (can be NULL).
+ * @param cbImg Size of the image data pointed to by pvImg.
+ */
+VBGLR3DECL(int) VbglR3SetPointerShape(uint32_t fFlags, uint32_t xHot, uint32_t yHot, uint32_t cx, uint32_t cy,
+ const void *pvImg, size_t cbImg)
+{
+ VMMDevReqMousePointer *pReq;
+ size_t cbReq = vmmdevGetMousePointerReqSize(cx, cy);
+ AssertReturn( !pvImg
+ || cbReq == RT_UOFFSETOF(VMMDevReqMousePointer, pointerData) + cbImg,
+ VERR_INVALID_PARAMETER);
+ int rc = vbglR3GRAlloc((VMMDevRequestHeader **)&pReq, cbReq, VMMDevReq_SetPointerShape);
+ if (RT_SUCCESS(rc))
+ {
+ pReq->fFlags = fFlags;
+ pReq->xHot = xHot;
+ pReq->yHot = yHot;
+ pReq->width = cx;
+ pReq->height = cy;
+ if (pvImg)
+ memcpy(pReq->pointerData, pvImg, cbImg);
+
+ rc = vbglR3GRPerform(&pReq->header);
+ if (RT_SUCCESS(rc))
+ rc = pReq->header.rc;
+ vbglR3GRFree(&pReq->header);
+ }
+ return rc;
+}
+
+
+/**
+ * Send mouse pointer shape information to the host.
+ * This version of the function accepts a request for clients that
+ * already allocate and manipulate the request structure directly.
+ *
+ * @returns VBox status code.
+ *
+ * @param pReq Pointer to the VMMDevReqMousePointer structure.
+ */
+VBGLR3DECL(int) VbglR3SetPointerShapeReq(VMMDevReqMousePointer *pReq)
+{
+ int rc = vbglR3GRPerform(&pReq->header);
+ if (RT_SUCCESS(rc))
+ rc = pReq->header.rc;
+ return rc;
+}
+
+
+/**
+ * Query the last display change request sent from the host to the guest.
+ *
+ * @returns iprt status value
+ * @param pcx Where to store the horizontal pixel resolution
+ * @param pcy Where to store the vertical pixel resolution
+ * requested (a value of zero means do not change).
+ * @param pcBits Where to store the bits per pixel requested (a value
+ * of zero means do not change).
+ * @param piDisplay Where to store the display number the request was for
+ * - 0 for the primary display, 1 for the first
+ * secondary display, etc.
+ * @param fAck whether or not to acknowledge the newest request sent by
+ * the host. If this is set, the function will return the
+ * most recent host request, otherwise it will return the
+ * last request to be acknowledged.
+ *
+ */
+static int getDisplayChangeRequest2(uint32_t *pcx, uint32_t *pcy,
+ uint32_t *pcBits, uint32_t *piDisplay,
+ bool fAck)
+{
+ VMMDevDisplayChangeRequest2 Req;
+
+ AssertPtrReturn(pcx, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcy, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcBits, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(piDisplay, VERR_INVALID_PARAMETER);
+ RT_ZERO(Req);
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetDisplayChangeRequest2);
+ if (fAck)
+ Req.eventAck = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ rc = Req.header.rc;
+ if (RT_SUCCESS(rc))
+ {
+ *pcx = Req.xres;
+ *pcy = Req.yres;
+ *pcBits = Req.bpp;
+ *piDisplay = Req.display;
+ }
+ return rc;
+}
+
+
+/**
+ * Query the last display change request sent from the host to the guest.
+ *
+ * @returns iprt status value
+ * @param pcx Where to store the horizontal pixel resolution
+ * requested (a value of zero means do not change).
+ * @param pcy Where to store the vertical pixel resolution
+ * requested (a value of zero means do not change).
+ * @param pcBits Where to store the bits per pixel requested (a value
+ * of zero means do not change).
+ * @param piDisplay Where to store the display number the request was for
+ * - 0 for the primary display, 1 for the first
+ * secondary display, etc.
+ * @param fAck whether or not to acknowledge the newest request sent by
+ * the host. If this is set, the function will return the
+ * most recent host request, otherwise it will return the
+ * last request to be acknowledged.
+ *
+ * @param pdx New horizontal position of the secondary monitor.
+ * Optional.
+ * @param pdy New vertical position of the secondary monitor.
+ * Optional.
+ * @param pfEnabled Secondary monitor is enabled or not. Optional.
+ * @param pfChangeOrigin Whether the mode hint retrieved included
+ * information about origin/display offset inside the
+ * frame-buffer. Optional.
+ *
+ */
+VBGLR3DECL(int) VbglR3GetDisplayChangeRequest(uint32_t *pcx, uint32_t *pcy,
+ uint32_t *pcBits,
+ uint32_t *piDisplay,
+ uint32_t *pdx, uint32_t *pdy,
+ bool *pfEnabled,
+ bool *pfChangeOrigin,
+ bool fAck)
+{
+ VMMDevDisplayChangeRequestEx Req;
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(pcx, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcy, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcBits, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pdx, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pdy, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(piDisplay, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pfEnabled, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pfChangeOrigin, VERR_INVALID_PARAMETER);
+
+ RT_ZERO(Req);
+ rc = vmmdevInitRequest(&Req.header, VMMDevReq_GetDisplayChangeRequestEx);
+ AssertRCReturn(rc, rc);
+ if (fAck)
+ Req.eventAck = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
+ rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ rc = Req.header.rc;
+ if (RT_SUCCESS(rc))
+ {
+ *pcx = Req.xres;
+ *pcy = Req.yres;
+ *pcBits = Req.bpp;
+ *piDisplay = Req.display;
+ if (pdx)
+ *pdx = Req.cxOrigin;
+ if (pdy)
+ *pdy = Req.cyOrigin;
+ if (pfEnabled)
+ *pfEnabled = Req.fEnabled;
+ if (pfChangeOrigin)
+ *pfChangeOrigin = Req.fChangeOrigin;
+ return VINF_SUCCESS;
+ }
+
+ /* NEEDS TESTING: test below with current Additions on VBox 4.1 or older. */
+ /** @todo Can we find some standard grep-able string for "NEEDS TESTING"? */
+ if (rc == VERR_NOT_IMPLEMENTED) /* Fall back to the old API. */
+ {
+ if (pfEnabled)
+ *pfEnabled = true;
+ if (pfChangeOrigin)
+ *pfChangeOrigin = false;
+ return getDisplayChangeRequest2(pcx, pcy, pcBits, piDisplay, fAck);
+ }
+ return rc;
+}
+
+
+/**
+ * Query the last display change request sent from the host to the guest.
+ *
+ * @returns iprt status value
+ * @param cDisplaysIn How many elements in the paDisplays array.
+ * @param pcDisplaysOut How many elements were returned.
+ * @param paDisplays Display information.
+ * @param fAck Whether or not to acknowledge the newest request sent by
+ * the host. If this is set, the function will return the
+ * most recent host request, otherwise it will return the
+ * last request to be acknowledged.
+ */
+VBGLR3DECL(int) VbglR3GetDisplayChangeRequestMulti(uint32_t cDisplaysIn,
+ uint32_t *pcDisplaysOut,
+ VMMDevDisplayDef *paDisplays,
+ bool fAck)
+{
+ VMMDevDisplayChangeRequestMulti *pReq;
+ size_t cbDisplays;
+ size_t cbAlloc;
+ int rc = VINF_SUCCESS;
+
+ AssertReturn(cDisplaysIn > 0 && cDisplaysIn <= 64 /* VBOX_VIDEO_MAX_SCREENS */, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcDisplaysOut, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(paDisplays, VERR_INVALID_PARAMETER);
+
+ cbDisplays = cDisplaysIn * sizeof(VMMDevDisplayDef);
+ cbAlloc = RT_UOFFSETOF(VMMDevDisplayChangeRequestMulti, aDisplays) + cbDisplays;
+ pReq = (VMMDevDisplayChangeRequestMulti *)RTMemTmpAlloc(cbAlloc);
+ AssertPtrReturn(pReq, VERR_NO_MEMORY);
+
+ memset(pReq, 0, cbAlloc);
+ rc = vmmdevInitRequest(&pReq->header, VMMDevReq_GetDisplayChangeRequestMulti);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pReq), rc);
+
+ pReq->header.size += (uint32_t)cbDisplays;
+ pReq->cDisplays = cDisplaysIn;
+ if (fAck)
+ pReq->eventAck = VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST;
+
+ rc = vbglR3GRPerform(&pReq->header);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pReq), rc);
+
+ rc = pReq->header.rc;
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(paDisplays, pReq->aDisplays, pReq->cDisplays * sizeof(VMMDevDisplayDef));
+ *pcDisplaysOut = pReq->cDisplays;
+ }
+
+ RTMemTmpFree(pReq);
+ return rc;
+}
+
+
+/**
+ * Query the host as to whether it likes a specific video mode.
+ *
+ * @returns the result of the query
+ * @param cx the width of the mode being queried
+ * @param cy the height of the mode being queried
+ * @param cBits the bpp of the mode being queried
+ */
+VBGLR3DECL(bool) VbglR3HostLikesVideoMode(uint32_t cx, uint32_t cy, uint32_t cBits)
+{
+ bool fRc = true; /* If for some reason we can't contact the host then
+ * we like everything. */
+ int rc;
+ VMMDevVideoModeSupportedRequest req;
+
+ vmmdevInitRequest(&req.header, VMMDevReq_VideoModeSupported);
+ req.width = cx;
+ req.height = cy;
+ req.bpp = cBits;
+ req.fSupported = true;
+ rc = vbglR3GRPerform(&req.header);
+ if (RT_SUCCESS(rc) && RT_SUCCESS(req.header.rc))
+ fRc = req.fSupported;
+ return fRc;
+}
+
+/**
+ * Get the highest screen number for which there is a saved video mode or "0"
+ * if there are no saved modes.
+ *
+ * @returns iprt status value
+ * @returns VERR_NOT_SUPPORTED if the guest property service is not available.
+ * @param pcScreen where to store the virtual screen number
+ */
+VBGLR3DECL(int) VbglR3VideoModeGetHighestSavedScreen(unsigned *pcScreen)
+{
+#if defined(VBOX_WITH_GUEST_PROPS)
+ int rc;
+ HGCMCLIENTID idClient = 0;
+ PVBGLR3GUESTPROPENUM pHandle = NULL;
+ const char *pszName = NULL;
+ unsigned cHighestScreen = 0;
+
+ /* Validate input. */
+ AssertPtrReturn(pcScreen, VERR_INVALID_POINTER);
+
+ /* Query the data. */
+ rc = VbglR3GuestPropConnect(&idClient);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszPattern = VIDEO_PROP_PREFIX"*";
+ rc = VbglR3GuestPropEnum(idClient, &pszPattern, 1, &pHandle, &pszName, NULL, NULL, NULL);
+ int rc2 = VbglR3GuestPropDisconnect(idClient);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /* Process the data. */
+ while (RT_SUCCESS(rc) && pszName != NULL)
+ {
+ uint32_t cScreen;
+
+ rc = RTStrToUInt32Full(pszName + sizeof(VIDEO_PROP_PREFIX) - 1, 10, &cScreen);
+ if (RT_SUCCESS(rc)) /* There may be similar properties with text. */
+ cHighestScreen = RT_MAX(cHighestScreen, cScreen);
+ rc = VbglR3GuestPropEnumNext(pHandle, &pszName, NULL, NULL, NULL);
+ }
+
+ VbglR3GuestPropEnumFree(pHandle);
+
+ /* Return result. */
+ if (RT_SUCCESS(rc))
+ *pcScreen = cHighestScreen;
+ return rc;
+#else /* !VBOX_WITH_GUEST_PROPS */
+ RT_NOREF(pcScreen);
+ return VERR_NOT_SUPPORTED;
+#endif /* !VBOX_WITH_GUEST_PROPS */
+}
+
+/**
+ * Save video mode parameters to the guest property store.
+ *
+ * @returns iprt status value
+ * @param idScreen The virtual screen number.
+ * @param cx mode width
+ * @param cy mode height
+ * @param cBits bits per pixel for the mode
+ * @param x virtual screen X offset
+ * @param y virtual screen Y offset
+ * @param fEnabled is this virtual screen enabled?
+ */
+VBGLR3DECL(int) VbglR3SaveVideoMode(unsigned idScreen, unsigned cx, unsigned cy, unsigned cBits,
+ unsigned x, unsigned y, bool fEnabled)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+ unsigned cHighestScreen = 0;
+ int rc = VbglR3VideoModeGetHighestSavedScreen(&cHighestScreen);
+ if (RT_SUCCESS(rc))
+ {
+ HGCMCLIENTID idClient = 0;
+ rc = VbglR3GuestPropConnect(&idClient);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2;
+ char szModeName[GUEST_PROP_MAX_NAME_LEN];
+ char szModeParms[GUEST_PROP_MAX_VALUE_LEN];
+ RTStrPrintf(szModeName, sizeof(szModeName), VIDEO_PROP_PREFIX "%u", idScreen);
+ RTStrPrintf(szModeParms, sizeof(szModeParms), "%ux%ux%u,%ux%u,%u", cx, cy, cBits, x, y, (unsigned) fEnabled);
+
+ rc = VbglR3GuestPropWriteValue(idClient, szModeName, szModeParms);
+ /* Write out the mode using the legacy name too, in case the user
+ * re-installs older Additions. */
+ if (idScreen == 0)
+ {
+ RTStrPrintf(szModeParms, sizeof(szModeParms), "%ux%ux%u", cx, cy, cBits);
+ VbglR3GuestPropWriteValue(idClient, VIDEO_PROP_PREFIX "SavedMode", szModeParms);
+ }
+
+ rc2 = VbglR3GuestPropDisconnect(idClient);
+ if (rc != VINF_PERMISSION_DENIED)
+ {
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ if (RT_SUCCESS(rc))
+ {
+ /* Sanity check 1. We do not try to make allowance for someone else
+ * changing saved settings at the same time as us. */
+ bool fEnabled2 = false;
+ unsigned cx2 = 0;
+ unsigned cy2 = 0;
+ unsigned cBits2 = 0;
+ unsigned x2 = 0;
+ unsigned y2 = 0;
+ rc = VbglR3RetrieveVideoMode(idScreen, &cx2, &cy2, &cBits2, &x2, &y2, &fEnabled2);
+ if ( RT_SUCCESS(rc)
+ && (cx != cx2 || cy != cy2 || cBits != cBits2 || x != x2 || y != y2 || fEnabled != fEnabled2))
+ rc = VERR_WRITE_ERROR;
+ /* Sanity check 2. Same comment. */
+ else if (RT_SUCCESS(rc))
+ {
+ unsigned cHighestScreen2 = 0;
+ rc = VbglR3VideoModeGetHighestSavedScreen(&cHighestScreen2);
+ if (RT_SUCCESS(rc))
+ if (cHighestScreen2 != RT_MAX(cHighestScreen, idScreen))
+ rc = VERR_INTERNAL_ERROR;
+ }
+ }
+ }
+ }
+ }
+ return rc;
+#else /* !VBOX_WITH_GUEST_PROPS */
+ RT_NOREF(idScreen, cx, cy, cBits, x, y, fEnabled);
+ return VERR_NOT_SUPPORTED;
+#endif /* !VBOX_WITH_GUEST_PROPS */
+}
+
+
+/**
+ * Retrieve video mode parameters from the guest property store.
+ *
+ * @returns iprt status value
+ * @param idScreen The virtual screen number.
+ * @param pcx where to store the mode width
+ * @param pcy where to store the mode height
+ * @param pcBits where to store the bits per pixel for the mode
+ * @param px where to store the virtual screen X offset
+ * @param py where to store the virtual screen Y offset
+ * @param pfEnabled where to store whether this virtual screen is enabled
+ */
+VBGLR3DECL(int) VbglR3RetrieveVideoMode(unsigned idScreen,
+ unsigned *pcx, unsigned *pcy,
+ unsigned *pcBits,
+ unsigned *px, unsigned *py,
+ bool *pfEnabled)
+{
+#ifdef VBOX_WITH_GUEST_PROPS
+ /*
+ * First we retrieve the video mode which is saved as a string in the
+ * guest property store.
+ */
+ HGCMCLIENTID idClient = 0;
+ int rc = VbglR3GuestPropConnect(&idClient);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2;
+ /* The buffer for VbglR3GuestPropReadValue. If this is too small then
+ * something is wrong with the data stored in the property. */
+ char szModeParms[1024];
+ char szModeName[GUEST_PROP_MAX_NAME_LEN]; /** @todo add a VbglR3GuestPropReadValueF/FV that does the RTStrPrintf for you. */
+ RTStrPrintf(szModeName, sizeof(szModeName), VIDEO_PROP_PREFIX "%u", idScreen);
+ rc = VbglR3GuestPropReadValue(idClient, szModeName, szModeParms, sizeof(szModeParms), NULL);
+ /* Try legacy single screen name. */
+ if (rc == VERR_NOT_FOUND && idScreen == 0)
+ rc = VbglR3GuestPropReadValue(idClient,
+ VIDEO_PROP_PREFIX"SavedMode",
+ szModeParms, sizeof(szModeParms),
+ NULL);
+ rc2 = VbglR3GuestPropDisconnect(idClient);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ /*
+ * Now we convert the string returned to numeric values.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ /* Mandatory chunk: 640x480x32 */
+ char *pszNext;
+ uint32_t cx = 0;
+ rc = VERR_PARSE_ERROR;
+ rc2 = RTStrToUInt32Ex(szModeParms, &pszNext, 10, &cx);
+ if (rc2 == VWRN_TRAILING_CHARS && *pszNext == 'x')
+ {
+ uint32_t cy = 0;
+ rc2 = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &cy);
+ if (rc2 == VWRN_TRAILING_CHARS && *pszNext == 'x')
+ {
+ uint8_t cBits = 0;
+ rc2 = RTStrToUInt8Ex(pszNext + 1, &pszNext, 10, &cBits);
+ if (rc2 == VINF_SUCCESS || rc2 == VWRN_TRAILING_CHARS)
+ {
+ /* Optional chunk: ,32x64,1 (we fail if this is partially there) */
+ uint32_t x = 0;
+ uint32_t y = 0;
+ uint8_t fEnabled = 1;
+ if (rc2 == VINF_SUCCESS)
+ rc = VINF_SUCCESS;
+ else if (*pszNext == ',')
+ {
+ rc2 = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &x);
+ if (rc2 == VWRN_TRAILING_CHARS && *pszNext == 'x')
+ {
+ rc2 = RTStrToUInt32Ex(pszNext + 1, &pszNext, 10, &y);
+ if (rc2 == VWRN_TRAILING_CHARS && *pszNext == ',')
+ {
+ rc2 = RTStrToUInt8Ex(pszNext + 1, &pszNext, 10, &fEnabled);
+ if (rc2 == VINF_SUCCESS)
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+
+ /*
+ * Set result if successful.
+ */
+ if (rc == VINF_SUCCESS)
+ {
+ if (pcx)
+ *pcx = cx;
+ if (pcy)
+ *pcy = cy;
+ if (pcBits)
+ *pcBits = cBits;
+ if (px)
+ *px = x;
+ if (py)
+ *py = y;
+ if (pfEnabled)
+ *pfEnabled = RT_BOOL(fEnabled);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return rc;
+#else /* !VBOX_WITH_GUEST_PROPS */
+ RT_NOREF(idScreen, pcx, pcy, pcBits, px, py, pfEnabled);
+ return VERR_NOT_SUPPORTED;
+#endif /* !VBOX_WITH_GUEST_PROPS */
+}
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp
new file mode 100644
index 00000000..89a19d09
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibVrdp.cpp
@@ -0,0 +1,64 @@
+/* $Id: VBoxGuestR3LibVrdp.cpp $ */
+/** @file
+ * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, VRDP.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/time.h>
+#include <iprt/string.h>
+#include "VBoxGuestR3LibInternal.h"
+
+
+VBGLR3DECL(int) VbglR3VrdpGetChangeRequest(bool *pfActive, uint32_t *puExperienceLevel)
+{
+ VMMDevVRDPChangeRequest Req;
+ RT_ZERO(Req); /* implicit padding */
+ vmmdevInitRequest(&Req.header, VMMDevReq_GetVRDPChangeRequest); //VMMDEV_REQ_HDR_INIT(&Req.header, sizeof(Req), VMMDevReq_GetVRDPChangeRequest);
+ int rc = vbglR3GRPerform(&Req.header);
+ if (RT_SUCCESS(rc))
+ {
+ *pfActive = Req.u8VRDPActive != 0;
+ *puExperienceLevel = Req.u32VRDPExperienceLevel;
+ }
+ else
+ {
+ *pfActive = false;
+ *puExperienceLevel = 0;
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VbglR0CanUsePhysPageList.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VbglR0CanUsePhysPageList.cpp
new file mode 100644
index 00000000..38646784
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/VbglR0CanUsePhysPageList.cpp
@@ -0,0 +1,52 @@
+/* $Id: VbglR0CanUsePhysPageList.cpp $ */
+/** @file
+ * VBoxGuestLibR0 - Physical memory heap.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxGuestR0LibInternal.h"
+
+
+/**
+ * Checks whether the host supports physical page lists or not.
+ *
+ * @returns true if it does, false if it doesn't.
+ */
+DECLR0VBGL(bool) VbglR0CanUsePhysPageList(void)
+{
+ /* a_fLocked is false, because the actual capability of the host is requested.
+ * See VBGLR0_CAN_USE_PHYS_PAGE_LIST definition.
+ */
+ int rc = vbglR0Enter();
+ return RT_SUCCESS(rc)
+ && VBGLR0_CAN_USE_PHYS_PAGE_LIST(/*a_fLocked =*/ false);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/lib/testcase/Makefile.kmk
new file mode 100644
index 00000000..6d99a40f
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/testcase/Makefile.kmk
@@ -0,0 +1,52 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the common guest addition code library testcases.
+#
+
+#
+# Copyright (C) 2022-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_BUILD)
+
+ #
+ # Testcase for the physical heap.
+ #
+ PROGRAMS += tstVbglR0PhysHeap-1
+ tstVbglR0PhysHeap-1_TEMPLATE = VBoxR3TstExe
+ tstVbglR0PhysHeap-1_SOURCES = \
+ tstVbglR0PhysHeap-1.cpp
+
+
+endif # defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_BUILD)
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/VBoxGuest/lib/testcase/tstVbglR0PhysHeap-1.cpp b/src/VBox/Additions/common/VBoxGuest/lib/testcase/tstVbglR0PhysHeap-1.cpp
new file mode 100644
index 00000000..3646fd95
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/lib/testcase/tstVbglR0PhysHeap-1.cpp
@@ -0,0 +1,415 @@
+/* $Id: tstVbglR0PhysHeap-1.cpp $ */
+/** @file
+ * IPRT Testcase - Offset Based Heap.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/param.h>
+#include <iprt/test.h>
+#include <iprt/time.h>
+
+#define IN_TESTCASE
+#define IN_RING0 /* pretend we're in ring-0 so we get access to the functions */
+#include "../VBoxGuestR0LibInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct
+{
+ uint32_t cb;
+ void *pv;
+} TSTHISTORYENTRY;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+VBGLDATA g_vbgldata;
+
+int g_cChunks = 0;
+size_t g_cbChunks = 0;
+
+/** Drop-in replacement for RTMemContAlloc */
+static void *tstMemContAlloc(PRTCCPHYS pPhys, size_t cb)
+{
+ RTTESTI_CHECK(cb > 0);
+
+#define TST_MAX_CHUNKS 24
+ if (g_cChunks < TST_MAX_CHUNKS)
+ {
+ void *pvRet = RTMemAlloc(cb);
+ if (pvRet)
+ {
+ g_cChunks++;
+ g_cbChunks += cb;
+ *pPhys = (uint32_t)(uintptr_t)pvRet ^ (UINT32_C(0xf0f0f0f0) & ~(uint32_t)PAGE_OFFSET_MASK);
+
+ /* Avoid problematic values that won't happen in real life: */
+ if (!*pPhys)
+ *pPhys = 4U << PAGE_SHIFT;
+ if (UINT32_MAX - *pPhys < cb)
+ *pPhys -= RT_ALIGN_32(cb, PAGE_SIZE);
+
+ return pvRet;
+ }
+ }
+
+ *pPhys = NIL_RTCCPHYS;
+ return NULL;
+}
+
+
+/** Drop-in replacement for RTMemContFree */
+static void tstMemContFree(void *pv, size_t cb)
+{
+ RTTESTI_CHECK(RT_VALID_PTR(pv));
+ RTTESTI_CHECK(cb > 0);
+ RTTESTI_CHECK(g_cChunks > 0);
+ RTMemFree(pv);
+ g_cChunks--;
+ g_cbChunks -= cb;
+}
+
+
+#define RTMemContAlloc tstMemContAlloc
+#define RTMemContFree tstMemContFree
+#include "../VBoxGuestR0LibPhysHeap.cpp"
+
+
+static void PrintStats(TSTHISTORYENTRY const *paHistory, size_t cHistory, const char *pszDesc)
+{
+ size_t cbAllocated = 0;
+ unsigned cLargeBlocks = 0;
+ unsigned cAllocated = 0;
+ for (size_t i = 0; i < cHistory; i++)
+ if (paHistory[i].pv)
+ {
+ cAllocated += 1;
+ cbAllocated += paHistory[i].cb;
+ cLargeBlocks += paHistory[i].cb > _1K;
+ }
+
+ size_t const cbOverhead = g_cChunks * sizeof(VBGLPHYSHEAPCHUNK) + cAllocated * sizeof(VBGLPHYSHEAPBLOCK);
+ size_t const cbFragmentation = g_cbChunks - cbOverhead - cbAllocated;
+ RTTestIPrintf(RTTESTLVL_ALWAYS,
+ "%s: %'9zu bytes in %2d chunks; %'9zu bytes in %4u blocks (%2u large)\n"
+ " => int-frag %'9zu (%2zu.%1zu%%) overhead %'9zu (%1zu.%02zu%%)\n",
+ pszDesc,
+ g_cbChunks, g_cChunks,
+ cbAllocated, cAllocated, cLargeBlocks,
+ cbFragmentation, cbFragmentation * 100 / g_cbChunks, (cbFragmentation * 1000 / g_cbChunks) % 10,
+ cbOverhead, cbOverhead * 100 / g_cbChunks, (cbOverhead * 10000 / g_cbChunks) % 100);
+}
+
+
+int main(int argc, char **argv)
+{
+ RT_NOREF_PV(argc); RT_NOREF_PV(argv);
+
+ /*
+ * Init runtime.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstVbglR0PhysHeap-1", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ /*
+ * Arguments are taken to be random seeding.
+ */
+ uint64_t uRandSeed = RTTimeNanoTS();
+ for (int i = 1; i < argc; i++)
+ {
+ rc = RTStrToUInt64Full(argv[i], 0, &uRandSeed);
+ if (rc != VINF_SUCCESS)
+ {
+ RTTestIFailed("Invalid parameter: %Rrc: %s\n", rc, argv[i]);
+ return RTTestSummaryAndDestroy(hTest);
+ }
+ }
+
+ /*
+ * Create a heap.
+ */
+ RTTestSub(hTest, "Basics");
+ RTTESTI_CHECK_RC(rc = VbglR0PhysHeapInit(), VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ return RTTestSummaryAndDestroy(hTest);
+ RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL));
+
+#define CHECK_PHYS_ADDR(a_pv) do { \
+ uint32_t const uPhys = VbglR0PhysHeapGetPhysAddr(a_pv); \
+ if (uPhys == 0 || uPhys == UINT32_MAX || (uPhys & PAGE_OFFSET_MASK) != ((uintptr_t)(a_pv) & PAGE_OFFSET_MASK)) \
+ RTTestIFailed("line %u: %s=%p: uPhys=%#x\n", __LINE__, #a_pv, (a_pv), uPhys); \
+ } while (0)
+
+ /*
+ * Try allocate.
+ */
+ static struct TstPhysHeapOps
+ {
+ uint32_t cb;
+ unsigned iFreeOrder;
+ void *pvAlloc;
+ } s_aOps[] =
+ {
+ { 16, 0, NULL }, // 0
+ { 16, 1, NULL },
+ { 16, 2, NULL },
+ { 16, 5, NULL },
+ { 16, 4, NULL },
+ { 32, 3, NULL }, // 5
+ { 31, 6, NULL },
+ { 1024, 8, NULL },
+ { 1024, 10, NULL },
+ { 1024, 12, NULL },
+ { PAGE_SIZE, 13, NULL }, // 10
+ { 1024, 9, NULL },
+ { PAGE_SIZE, 11, NULL },
+ { PAGE_SIZE, 14, NULL },
+ { 16, 15, NULL },
+ { 9, 7, NULL }, // 15
+ { 16, 7, NULL },
+ { 36, 7, NULL },
+ { 16, 7, NULL },
+ { 12344, 7, NULL },
+ { 50, 7, NULL }, // 20
+ { 16, 7, NULL },
+ };
+ uint32_t i;
+ //RTHeapOffsetDump(Heap, (PFNRTHEAPOFFSETPRINTF)(uintptr_t)RTPrintf); /** @todo Add some detail info output with a signature identical to RTPrintf. */
+ //size_t cbBefore = VbglR0PhysHeapGetFreeSize();
+ static char const s_szFill[] = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ /* allocate */
+ for (i = 0; i < RT_ELEMENTS(s_aOps); i++)
+ {
+ s_aOps[i].pvAlloc = VbglR0PhysHeapAlloc(s_aOps[i].cb);
+ RTTESTI_CHECK_MSG(s_aOps[i].pvAlloc, ("VbglR0PhysHeapAlloc(%#x) -> NULL i=%d\n", s_aOps[i].cb, i));
+ if (!s_aOps[i].pvAlloc)
+ return RTTestSummaryAndDestroy(hTest);
+
+ memset(s_aOps[i].pvAlloc, s_szFill[i], s_aOps[i].cb);
+ RTTESTI_CHECK_MSG(RT_ALIGN_P(s_aOps[i].pvAlloc, sizeof(void *)) == s_aOps[i].pvAlloc,
+ ("VbglR0PhysHeapAlloc(%#x) -> %p\n", s_aOps[i].cb, i));
+
+ CHECK_PHYS_ADDR(s_aOps[i].pvAlloc);
+
+ /* Check heap integrity: */
+ RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL));
+ }
+
+ /* free and allocate the same node again. */
+ for (i = 0; i < RT_ELEMENTS(s_aOps); i++)
+ {
+ if (!s_aOps[i].pvAlloc)
+ continue;
+ //RTPrintf("debug: i=%d pv=%#x cb=%#zx align=%#zx cbReal=%#zx\n", i, s_aOps[i].pvAlloc,
+ // s_aOps[i].cb, s_aOps[i].uAlignment, RTHeapOffsetSize(Heap, s_aOps[i].pvAlloc));
+ size_t cbBeforeSub = VbglR0PhysHeapGetFreeSize();
+ VbglR0PhysHeapFree(s_aOps[i].pvAlloc);
+ size_t cbAfterSubFree = VbglR0PhysHeapGetFreeSize();
+ RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL));
+
+ void *pv;
+ pv = VbglR0PhysHeapAlloc(s_aOps[i].cb);
+ RTTESTI_CHECK_MSG(pv, ("VbglR0PhysHeapAlloc(%#x) -> NULL i=%d\n", s_aOps[i].cb, i));
+ if (!pv)
+ return RTTestSummaryAndDestroy(hTest);
+ CHECK_PHYS_ADDR(pv);
+ RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL));
+
+ //RTPrintf("debug: i=%d pv=%p cbReal=%#zx cbBeforeSub=%#zx cbAfterSubFree=%#zx cbAfterSubAlloc=%#zx \n", i, pv, RTHeapOffsetSize(Heap, pv),
+ // cbBeforeSub, cbAfterSubFree, VbglR0PhysHeapGetFreeSize());
+
+ if (pv != s_aOps[i].pvAlloc)
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Warning: Free+Alloc returned different address. new=%p old=%p i=%d\n", pv, s_aOps[i].pvAlloc, i);
+ s_aOps[i].pvAlloc = pv;
+ size_t cbAfterSubAlloc = VbglR0PhysHeapGetFreeSize();
+ if (cbBeforeSub != cbAfterSubAlloc)
+ {
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Warning: cbBeforeSub=%#zx cbAfterSubFree=%#zx cbAfterSubAlloc=%#zx. i=%d\n",
+ cbBeforeSub, cbAfterSubFree, cbAfterSubAlloc, i);
+ //return 1; - won't work correctly until we start creating free block instead of donating memory on alignment.
+ }
+ }
+
+ VbglR0PhysHeapTerminate();
+ RTTESTI_CHECK_MSG(g_cChunks == 0, ("g_cChunks=%d\n", g_cChunks));
+
+
+ /*
+ * Use random allocation pattern
+ */
+ RTTestSub(hTest, "Random Test");
+ RTTESTI_CHECK_RC(rc = VbglR0PhysHeapInit(), VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ return RTTestSummaryAndDestroy(hTest);
+
+ RTRAND hRand;
+ RTTESTI_CHECK_RC(rc = RTRandAdvCreateParkMiller(&hRand), VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ return RTTestSummaryAndDestroy(hTest);
+ RTRandAdvSeed(hRand, uRandSeed);
+ RTTestValue(hTest, "RandSeed", uRandSeed, RTTESTUNIT_NONE);
+
+ static TSTHISTORYENTRY s_aHistory[3072];
+ RT_ZERO(s_aHistory);
+
+ for (unsigned iTest = 0; iTest < 131072; iTest++)
+ {
+ i = RTRandAdvU32Ex(hRand, 0, RT_ELEMENTS(s_aHistory) - 1);
+ if (!s_aHistory[i].pv)
+ {
+ s_aHistory[i].cb = RTRandAdvU32Ex(hRand, 8, 1024);
+ s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb);
+ if (!s_aHistory[i].pv)
+ {
+ s_aHistory[i].cb = 9;
+ s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb);
+ }
+ if (s_aHistory[i].pv)
+ {
+ memset(s_aHistory[i].pv, 0xbb, s_aHistory[i].cb);
+ CHECK_PHYS_ADDR(s_aHistory[i].pv);
+ }
+ }
+ else
+ {
+ VbglR0PhysHeapFree(s_aHistory[i].pv);
+ s_aHistory[i].pv = NULL;
+ }
+
+#if 1
+ /* Check heap integrity: */
+ RTTESTI_CHECK_RC_OK(VbglR0PhysHeapCheck(NULL));
+ int cChunks = 0;
+ for (VBGLPHYSHEAPCHUNK *pCurChunk = g_vbgldata.pChunkHead; pCurChunk; pCurChunk = pCurChunk->pNext)
+ cChunks++;
+ RTTESTI_CHECK_MSG(cChunks == g_cChunks, ("g_cChunks=%u, but only %u chunks in the list!\n", g_cChunks, cChunks));
+#endif
+
+ if ((iTest % 7777) == 7776)
+ {
+ /* exhaust the heap */
+ PrintStats(s_aHistory, RT_ELEMENTS(s_aHistory), "Exhaust-pre ");
+
+ for (i = 0; i < RT_ELEMENTS(s_aHistory) && (VbglR0PhysHeapGetFreeSize() >= 256 || g_cChunks < TST_MAX_CHUNKS); i++)
+ if (!s_aHistory[i].pv)
+ {
+ s_aHistory[i].cb = RTRandAdvU32Ex(hRand, VBGL_PH_CHUNKSIZE / 8, VBGL_PH_CHUNKSIZE / 2 + VBGL_PH_CHUNKSIZE / 4);
+ s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb);
+ if (s_aHistory[i].pv)
+ {
+ memset(s_aHistory[i].pv, 0x55, s_aHistory[i].cb);
+ CHECK_PHYS_ADDR(s_aHistory[i].pv);
+ }
+ }
+
+ size_t cbFree = VbglR0PhysHeapGetFreeSize();
+ if (cbFree)
+ for (i = 0; i < RT_ELEMENTS(s_aHistory); i++)
+ if (!s_aHistory[i].pv)
+ {
+ s_aHistory[i].cb = RTRandAdvU32Ex(hRand, 1, (uint32_t)cbFree);
+ s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb);
+ while (s_aHistory[i].pv == NULL && s_aHistory[i].cb > 2)
+ {
+ s_aHistory[i].cb >>= 1;
+ s_aHistory[i].pv = VbglR0PhysHeapAlloc(s_aHistory[i].cb);
+ }
+ if (s_aHistory[i].pv)
+ {
+ memset(s_aHistory[i].pv, 0x55, s_aHistory[i].cb);
+ CHECK_PHYS_ADDR(s_aHistory[i].pv);
+ }
+
+ cbFree = VbglR0PhysHeapGetFreeSize();
+ if (!cbFree)
+ break;
+ }
+
+ RTTESTI_CHECK_MSG(VbglR0PhysHeapGetFreeSize() == 0, ("%zu\n", VbglR0PhysHeapGetFreeSize()));
+ PrintStats(s_aHistory, RT_ELEMENTS(s_aHistory), "Exhaust-post");
+ }
+ else if ((iTest % 7777) == 1111)
+ {
+ /* free all */
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Free-all-pre: cFreeBlocks=%u cAllocedBlocks=%u in %u chunk(s)\n",
+ g_vbgldata.cFreeBlocks, g_vbgldata.cBlocks - g_vbgldata.cFreeBlocks, g_cChunks);
+ for (i = 0; i < RT_ELEMENTS(s_aHistory); i++)
+ {
+ VbglR0PhysHeapFree(s_aHistory[i].pv);
+ s_aHistory[i].pv = NULL;
+ }
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "Free-all-post: cFreeBlocks=%u in %u chunk(s)\n", g_vbgldata.cFreeBlocks, g_cChunks);
+ RTTESTI_CHECK_MSG(g_cChunks == 1, ("g_cChunks=%d\n", g_cChunks));
+ RTTESTI_CHECK_MSG(g_vbgldata.cFreeBlocks == g_vbgldata.cBlocks,
+ ("g_vbgldata.cFreeBlocks=%d cBlocks=%d\n", g_vbgldata.cFreeBlocks, g_vbgldata.cBlocks));
+
+ //size_t cbAfterRand = VbglR0PhysHeapGetFreeSize();
+ //RTTESTI_CHECK_MSG(cbAfterRand == cbAfter, ("cbAfterRand=%zu cbAfter=%zu\n", cbAfterRand, cbAfter));
+ }
+ }
+
+ /* free the rest. */
+ for (i = 0; i < RT_ELEMENTS(s_aHistory); i++)
+ {
+ VbglR0PhysHeapFree(s_aHistory[i].pv);
+ s_aHistory[i].pv = NULL;
+ }
+
+ RTTESTI_CHECK_MSG(g_cChunks == 1, ("g_cChunks=%d\n", g_cChunks));
+
+ VbglR0PhysHeapTerminate();
+ RTTESTI_CHECK_MSG(g_cChunks == 0, ("g_cChunks=%d\n", g_cChunks));
+
+ RTTESTI_CHECK_RC(rc = RTRandAdvDestroy(hRand), VINF_SUCCESS);
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/Additions/common/VBoxGuest/linux/Makefile b/src/VBox/Additions/common/VBoxGuest/linux/Makefile
new file mode 100644
index 00000000..65384f7f
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/linux/Makefile
@@ -0,0 +1,213 @@
+# $Id: Makefile $
+## @file
+# VirtualBox Guest Additions Module Makefile.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# Linux kbuild sets this to our source directory if we are called from there
+obj ?= $(CURDIR)
+include $(obj)/Makefile-header.gmk
+VBOXGUEST_DIR = $(VBOX_MODULE_SRC_DIR)
+
+#VBOX_WITHOUT_COMBINED_SOURCES=1
+
+VBOXMOD_NAME = vboxguest
+VBOXMOD_OBJS = \
+ VBoxGuest-linux.o \
+ VBoxGuest-common.o
+ifndef VBOX_WITHOUT_COMBINED_SOURCES
+VBOXMOD_OBJS += \
+ common/string/strformatrt.o \
+ combined-agnostic.o \
+ combined-os-specific.o
+else # VBOX_WITHOUT_COMBINED_SOURCES
+VBOXMOD_OBJS += \
+ VBoxGuestR0LibGenericRequest.o \
+ VBoxGuestR0LibHGCMInternal.o \
+ VBoxGuestR0LibInit.o \
+ VBoxGuestR0LibPhysHeap.o \
+ VBoxGuestR0LibVMMDev.o \
+ r0drv/alloc-r0drv.o \
+ r0drv/initterm-r0drv.o \
+ r0drv/memobj-r0drv.o \
+ r0drv/mpnotification-r0drv.o \
+ r0drv/powernotification-r0drv.o \
+ r0drv/linux/alloc-r0drv-linux.o \
+ r0drv/linux/assert-r0drv-linux.o \
+ r0drv/linux/initterm-r0drv-linux.o \
+ r0drv/linux/memobj-r0drv-linux.o \
+ r0drv/linux/memuserkernel-r0drv-linux.o \
+ r0drv/linux/mp-r0drv-linux.o \
+ r0drv/linux/mpnotification-r0drv-linux.o \
+ r0drv/linux/process-r0drv-linux.o \
+ r0drv/linux/semevent-r0drv-linux.o \
+ r0drv/linux/semeventmulti-r0drv-linux.o \
+ r0drv/linux/semfastmutex-r0drv-linux.o \
+ r0drv/linux/semmutex-r0drv-linux.o \
+ r0drv/linux/spinlock-r0drv-linux.o \
+ r0drv/linux/thread-r0drv-linux.o \
+ r0drv/linux/thread2-r0drv-linux.o \
+ r0drv/linux/time-r0drv-linux.o \
+ r0drv/linux/timer-r0drv-linux.o \
+ r0drv/linux/RTLogWriteDebugger-r0drv-linux.o \
+ r0drv/generic/semspinmutex-r0drv-generic.o \
+ common/alloc/alloc.o \
+ common/checksum/crc32.o \
+ common/err/RTErrConvertFromErrno.o \
+ common/err/RTErrConvertToErrno.o \
+ common/err/errinfo.o \
+ common/log/log.o \
+ common/log/logellipsis.o \
+ common/log/logrel.o \
+ common/log/logrelellipsis.o \
+ common/log/logcom.o \
+ common/log/logformat.o \
+ common/log/RTLogCreateEx.o \
+ common/misc/RTAssertMsg1Weak.o \
+ common/misc/RTAssertMsg2.o \
+ common/misc/RTAssertMsg2Add.o \
+ common/misc/RTAssertMsg2AddWeak.o \
+ common/misc/RTAssertMsg2AddWeakV.o \
+ common/misc/RTAssertMsg2Weak.o \
+ common/misc/RTAssertMsg2WeakV.o \
+ common/misc/assert.o \
+ common/misc/thread.o \
+ common/string/RTStrCat.o \
+ common/string/RTStrCmp.o \
+ common/string/RTStrCopy.o \
+ common/string/RTStrCopyEx.o \
+ common/string/RTStrCopyP.o \
+ common/string/RTStrEnd.o \
+ common/string/RTStrICmpAscii.o \
+ common/string/RTStrNICmpAscii.o \
+ common/string/RTStrNCmp.o \
+ common/string/RTStrNLen.o \
+ common/string/stringalloc.o \
+ common/string/strformat.o \
+ common/string/RTStrFormat.o \
+ common/string/strformatnum.o \
+ common/string/strformatrt.o \
+ common/string/strformattype.o \
+ common/string/strprintf.o \
+ common/string/strprintf-ellipsis.o \
+ common/string/strprintf2.o \
+ common/string/strprintf2-ellipsis.o \
+ common/string/strtonum.o \
+ common/string/utf-8.o \
+ common/table/avlpv.o \
+ common/time/time.o \
+ generic/RTAssertShouldPanic-generic.o \
+ generic/RTLogWriteStdErr-stub-generic.o \
+ generic/RTLogWriteStdOut-stub-generic.o \
+ generic/RTMpGetCoreCount-generic.o \
+ generic/RTSemEventWait-2-ex-generic.o \
+ generic/RTSemEventWaitNoResume-2-ex-generic.o \
+ generic/RTSemEventMultiWait-2-ex-generic.o \
+ generic/RTSemEventMultiWaitNoResume-2-ex-generic.o \
+ generic/rtStrFormatKernelAddress-generic.o \
+ generic/errvars-generic.o \
+ generic/mppresent-generic.o \
+ VBox/log-vbox.o \
+ VBox/logbackdoor.o \
+ VBox/RTLogWriteVmm-amd64-x86.o
+ ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64)
+VBOXMOD_OBJS += common/alloc/heapsimple.o
+ endif
+endif # VBOX_WITHOUT_COMBINED_SOURCES
+ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86)
+VBOXMOD_OBJS += \
+ common/math/gcc/divdi3.o \
+ common/math/gcc/divmoddi4.o \
+ common/math/gcc/moddi3.o \
+ common/math/gcc/udivdi3.o \
+ common/math/gcc/udivmoddi4.o \
+ common/math/gcc/umoddi3.o \
+ common/math/gcc/qdivrem.o
+endif
+
+VBOXMOD_DEFS = \
+ VBOX \
+ RT_OS_LINUX \
+ IN_RING0 \
+ IN_RT_R0 \
+ IN_GUEST \
+ IN_GUEST_R0 \
+ IN_MODULE \
+ RT_WITH_VBOX \
+ VBGL_VBOXGUEST \
+ VBOX_WITH_HGCM
+ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64)
+VBOXMOD_DEFS += VBOX_WITH_64_BITS_GUESTS
+endif
+ifeq ($(KERN_VERSION),24)
+VBOXMOD_DEFS += EXPORT_SYMTAB
+endif
+
+VBOXMOD_INCL = \
+ $(VBOXGUEST_DIR) \
+ $(VBOXGUEST_DIR)include \
+ $(VBOXGUEST_DIR)r0drv/linux
+
+VBOXMOD_CFLAGS := $(call VBOX_GCC_CHECK_CC,-Wno-declaration-after-statement,-Wno-declaration-after-statement,,)
+VBOXMOD_CFLAGS += $(call VBOX_GCC_CHECK_CC,-fno-pie,-fno-pie,,)
+ifneq ($(KERN_VERSION),24)
+VBOXMOD_CFLAGS += -include $(VBOXGUEST_DIR)include/VBox/VBoxGuestMangling.h
+endif
+
+VBOXMOD_CLEAN = \
+ . \
+ linux \
+ r0drv \
+ generic \
+ r0drv/linux \
+ r0drv/generic \
+ VBox \
+ common/alloc \
+ common/err \
+ common/log \
+ common/math/gcc \
+ common/misc \
+ common/string \
+ common/table \
+ common/time
+
+include $(obj)/Makefile-footer.gmk
+
+check: $(VBOXMOD_NAME)
+ @if ! readelf -p __ksymtab_strings vboxguest.ko | grep -E "\[.*\] *(RT|g_..*RT.*)"; then \
+ echo "All exported IPRT symbols are properly renamed!"; \
+ else \
+ echo "error: Some exported IPRT symbols was not properly renamed! See above." >&2; \
+ false; \
+ fi
+
diff --git a/src/VBox/Additions/common/VBoxGuest/linux/Makefile.kup b/src/VBox/Additions/common/VBoxGuest/linux/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/linux/Makefile.kup
diff --git a/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c b/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c
new file mode 100644
index 00000000..8509f97f
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c
@@ -0,0 +1,189 @@
+/* $Id: combined-agnostic.c $ */
+/** @file
+ * VBoxGuest - Combine a bunch of OS agnostic sources into one compile unit.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define LOG_GROUP LOG_GROUP_DEFAULT
+#include "internal/iprt.h"
+#include <VBox/log.h>
+
+//#undef LOG_GROUP
+#include "VBoxGuestR0LibGenericRequest.c"
+#undef LOG_GROUP
+#include "VBoxGuestR0LibHGCMInternal.c"
+//#undef LOG_GROUP
+#include "VBoxGuestR0LibInit.c"
+//#undef LOG_GROUP
+#include "VBoxGuestR0LibPhysHeap.c"
+#undef LOG_GROUP
+#include "VBoxGuestR0LibVMMDev.c"
+#undef LOG_GROUP
+#include "r0drv/alloc-r0drv.c"
+#undef LOG_GROUP
+#include "r0drv/initterm-r0drv.c"
+#undef LOG_GROUP
+#include "r0drv/memobj-r0drv.c"
+#undef LOG_GROUP
+#include "r0drv/mpnotification-r0drv.c"
+#undef LOG_GROUP
+#include "r0drv/powernotification-r0drv.c"
+#undef LOG_GROUP
+#include "r0drv/generic/semspinmutex-r0drv-generic.c"
+#undef LOG_GROUP
+#include "common/alloc/alloc.c"
+#undef LOG_GROUP
+#include "common/checksum/crc32.c"
+#undef LOG_GROUP
+#include "common/err/errinfo.c"
+#undef LOG_GROUP
+#include "common/log/log.c"
+#undef LOG_GROUP
+#include "common/log/logellipsis.c"
+#undef LOG_GROUP
+#include "common/log/logrel.c"
+#undef LOG_GROUP
+#include "common/log/logrelellipsis.c"
+#undef LOG_GROUP
+#include "common/log/logcom.c"
+#undef LOG_GROUP
+#include "common/log/logformat.c"
+#undef LOG_GROUP
+#include "common/log/RTLogCreateEx.c"
+#undef LOG_GROUP
+#include "common/misc/RTAssertMsg1Weak.c"
+#undef LOG_GROUP
+#include "common/misc/RTAssertMsg2.c"
+#undef LOG_GROUP
+#include "common/misc/RTAssertMsg2Add.c"
+#undef LOG_GROUP
+#include "common/misc/RTAssertMsg2AddWeak.c"
+#undef LOG_GROUP
+#include "common/misc/RTAssertMsg2AddWeakV.c"
+#undef LOG_GROUP
+#include "common/misc/RTAssertMsg2Weak.c"
+#undef LOG_GROUP
+#include "common/misc/RTAssertMsg2WeakV.c"
+#undef LOG_GROUP
+#include "common/misc/assert.c"
+#undef LOG_GROUP
+#include "common/misc/thread.c"
+#undef LOG_GROUP
+#include "common/string/RTStrCat.c"
+#undef LOG_GROUP
+#include "common/string/RTStrCmp.c"
+#undef LOG_GROUP
+#include "common/string/RTStrCopy.c"
+#undef LOG_GROUP
+#include "common/string/RTStrCopyEx.c"
+#undef LOG_GROUP
+#include "common/string/RTStrCopyP.c"
+#undef LOG_GROUP
+#include "common/string/RTStrEnd.c"
+#undef LOG_GROUP
+#include "common/string/RTStrICmpAscii.c"
+#undef LOG_GROUP
+#include "common/string/RTStrNICmpAscii.c"
+#undef LOG_GROUP
+#include "common/string/RTStrNCmp.c"
+#undef LOG_GROUP
+#include "common/string/RTStrNLen.c"
+#undef LOG_GROUP
+#include "common/string/stringalloc.c"
+#undef LOG_GROUP
+#include "common/string/strformat.c"
+#undef LOG_GROUP
+#include "common/string/RTStrFormat.c"
+#undef LOG_GROUP
+#include "common/string/strformatnum.c"
+#undef LOG_GROUP
+#include "common/string/strformattype.c"
+#undef LOG_GROUP
+#include "common/string/strprintf.c"
+#undef LOG_GROUP
+#include "common/string/strprintf-ellipsis.c"
+#undef LOG_GROUP
+#include "common/string/strprintf2.c"
+#undef LOG_GROUP
+#include "common/string/strprintf2-ellipsis.c"
+#undef LOG_GROUP
+#include "common/string/strtonum.c"
+#undef LOG_GROUP
+#include "common/string/utf-8.c"
+#undef LOG_GROUP
+#include "common/table/avlpv.c"
+#undef LOG_GROUP
+#include "common/time/time.c"
+#undef LOG_GROUP
+#include "generic/RTAssertShouldPanic-generic.c"
+#undef LOG_GROUP
+#include "generic/RTLogWriteStdErr-stub-generic.c"
+#undef LOG_GROUP
+#include "generic/RTLogWriteStdOut-stub-generic.c"
+#undef LOG_GROUP
+#include "generic/RTMpGetCoreCount-generic.c"
+#undef LOG_GROUP
+#include "generic/RTSemEventWait-2-ex-generic.c"
+#undef LOG_GROUP
+#include "generic/RTSemEventWaitNoResume-2-ex-generic.c"
+#undef LOG_GROUP
+#include "generic/RTSemEventMultiWait-2-ex-generic.c"
+#undef LOG_GROUP
+#include "generic/RTSemEventMultiWaitNoResume-2-ex-generic.c"
+#undef LOG_GROUP
+#include "generic/rtStrFormatKernelAddress-generic.c"
+#undef LOG_GROUP
+#include "generic/errvars-generic.c"
+#undef LOG_GROUP
+#include "generic/mppresent-generic.c"
+#undef LOG_GROUP
+#include "VBox/log-vbox.c"
+#undef LOG_GROUP
+#include "VBox/logbackdoor.c"
+#undef LOG_GROUP
+#include "VBox/RTLogWriteVmm-amd64-x86.c"
+
+#ifdef RT_ARCH_AMD64
+# undef LOG_GROUP
+# include "common/alloc/heapsimple.c"
+#endif
+
+#if 0 //def RT_ARCH_X86 - iprt/nocrt/limit.h clashes.
+# include "common/math/gcc/divdi3.c"
+# include "common/math/gcc/moddi3.c"
+# include "common/math/gcc/udivdi3.c"
+# include "common/math/gcc/udivmoddi4.c"
+# include "common/math/gcc/umoddi3.c"
+# include "common/math/gcc/qdivrem.c"
+#endif
+
diff --git a/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c b/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c
new file mode 100644
index 00000000..8981b058
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c
@@ -0,0 +1,61 @@
+/* $Id: combined-os-specific.c $ */
+/** @file
+ * VBoxGuest - Combine a bunch of OS specific sources into one compile unit.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+#include "the-linux-kernel.h"
+
+#include "r0drv/linux/alloc-r0drv-linux.c"
+#include "r0drv/linux/assert-r0drv-linux.c"
+#include "r0drv/linux/initterm-r0drv-linux.c"
+#include "r0drv/linux/memobj-r0drv-linux.c"
+#include "r0drv/linux/memuserkernel-r0drv-linux.c"
+#include "r0drv/linux/mp-r0drv-linux.c"
+#include "r0drv/linux/mpnotification-r0drv-linux.c"
+#include "r0drv/linux/process-r0drv-linux.c"
+#include "r0drv/linux/semevent-r0drv-linux.c"
+#include "r0drv/linux/semeventmulti-r0drv-linux.c"
+#include "r0drv/linux/semfastmutex-r0drv-linux.c"
+#include "r0drv/linux/semmutex-r0drv-linux.c"
+#include "r0drv/linux/spinlock-r0drv-linux.c"
+#include "r0drv/linux/thread-r0drv-linux.c"
+#include "r0drv/linux/thread2-r0drv-linux.c"
+#undef LOG_GROUP
+#include "r0drv/linux/time-r0drv-linux.c"
+#include "r0drv/linux/timer-r0drv-linux.c"
+#include "r0drv/linux/RTLogWriteDebugger-r0drv-linux.c"
+#include "common/err/RTErrConvertFromErrno.c"
+#include "common/err/RTErrConvertToErrno.c"
+
diff --git a/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest b/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest
new file mode 100755
index 00000000..5df520b6
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest
@@ -0,0 +1,237 @@
+#!/bin/sh
+# $Id: files_vboxguest $
+## @file
+# Shared file between Makefile.kmk and export_modules.sh.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+FILES_VBOXGUEST_NOBIN=" \
+ ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \
+ ${PATH_ROOT}/include/iprt/alloca.h=>include/iprt/alloca.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/avl.h=>include/iprt/avl.h \
+ ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \
+ ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \
+ ${PATH_ROOT}/include/iprt/crc.h=>include/iprt/crc.h \
+ ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.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/errno.h=>include/iprt/errno.h \
+ ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \
+ ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \
+ ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \
+ ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \
+ ${PATH_ROOT}/include/iprt/lockvalidator.h=>include/iprt/lockvalidator.h \
+ ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \
+ ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \
+ ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \
+ ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \
+ ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \
+ ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.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/power.h=>include/iprt/power.h \
+ ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \
+ ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \
+ ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.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/thread.h=>include/iprt/thread.h \
+ ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \
+ ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \
+ ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \
+ ${PATH_ROOT}/include/iprt/uint64.h=>include/iprt/uint64.h \
+ ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \
+ ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \
+ ${PATH_ROOT}/include/iprt/x86.h=>include/iprt/x86.h \
+ ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \
+ ${PATH_ROOT}/include/iprt/linux/version.h=>include/iprt/linux/version.h \
+ ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \
+ ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \
+ ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \
+ ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \
+ ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \
+ ${PATH_ROOT}/include/VBox/ostypes.h=>include/VBox/ostypes.h \
+ ${PATH_ROOT}/include/VBox/VMMDev.h=>include/VBox/VMMDev.h \
+ ${PATH_ROOT}/include/VBox/VMMDevCoreTypes.h=>include/VBox/VMMDevCoreTypes.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/VBoxGuestMangling.h=>include/VBox/VBoxGuestMangling.h \
+ ${PATH_ROOT}/include/VBox/version.h=>include/VBox/version.h \
+ ${PATH_ROOT}/include/VBox/HostServices/GuestPropertySvc.h=>include/VBox/HostServices/GuestPropertySvc.h \
+ ${PATH_ROOT}/include/VBox/vmm/cpuidcall.h=>include/VBox/vmm/cpuidcall.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest.cpp=>VBoxGuest-common.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuest-linux.c=>VBoxGuest-linux.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/VBoxGuestInternal.h=>VBoxGuestInternal.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/linux/Makefile=>Makefile \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/linux/combined-agnostic.c=>combined-agnostic.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/linux/combined-os-specific.c=>combined-os-specific.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h=>VBoxGuestR0LibInternal.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp=>VBoxGuestR0LibGenericRequest.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCMInternal.cpp=>VBoxGuestR0LibHGCMInternal.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp=>VBoxGuestR0LibInit.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp=>VBoxGuestR0LibPhysHeap.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibVMMDev.cpp=>VBoxGuestR0LibVMMDev.c \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/assert.h=>include/internal/assert.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/initterm.h=>include/internal/initterm.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/iprt.h=>include/internal/iprt.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/lockvalidator.h=>include/internal/lockvalidator.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/magics.h=>include/internal/magics.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/mem.h=>include/internal/mem.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/memobj.h=>include/internal/memobj.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/string.h=>include/internal/string.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/thread.h=>include/internal/thread.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/time.h=>include/internal/time.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/sched.h=>include/internal/sched.h \
+ ${PATH_ROOT}/src/VBox/Runtime/include/internal/process.h=>include/internal/process.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/alloc/alloc.cpp=>common/alloc/alloc.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/alloc/heapsimple.cpp=>common/alloc/heapsimple.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/checksum/crc32.cpp=>common/checksum/crc32.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp=>common/err/RTErrConvertFromErrno.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp=>common/err/RTErrConvertToErrno.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/err/errinfo.cpp=>common/err/errinfo.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/log.cpp=>common/log/log.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logellipsis.cpp=>common/log/logellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logrel.cpp=>common/log/logrel.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logrelellipsis.cpp=>common/log/logrelellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logformat.cpp=>common/log/logformat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/logcom.cpp=>common/log/logcom.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/log/RTLogCreateEx.cpp=>common/log/RTLogCreateEx.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divdi3.c=>common/math/gcc/divdi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divmoddi4.c=>common/math/gcc/divmoddi4.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/moddi3.c=>common/math/gcc/moddi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/qdivrem.c=>common/math/gcc/qdivrem.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/quad.h=>common/math/gcc/quad.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivdi3.c=>common/math/gcc/udivdi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivmoddi4.c=>common/math/gcc/udivmoddi4.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/umoddi3.c=>common/math/gcc/umoddi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp=>common/misc/RTAssertMsg1Weak.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp=>common/misc/RTAssertMsg2.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp=>common/misc/RTAssertMsg2Add.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp=>common/misc/RTAssertMsg2AddWeak.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp=>common/misc/RTAssertMsg2AddWeakV.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp=>common/misc/RTAssertMsg2Weak.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp=>common/misc/RTAssertMsg2WeakV.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/assert.cpp=>common/misc/assert.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/misc/thread.cpp=>common/misc/thread.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCat.cpp=>common/string/RTStrCat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCmp.cpp=>common/string/RTStrCmp.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopy.cpp=>common/string/RTStrCopy.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyEx.cpp=>common/string/RTStrCopyEx.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyP.cpp=>common/string/RTStrCopyP.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrEnd.cpp=>common/string/RTStrEnd.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrICmpAscii.cpp=>common/string/RTStrICmpAscii.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp=>common/string/RTStrNICmpAscii.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNCmp.cpp=>common/string/RTStrNCmp.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2.cpp=>common/string/strprintf2.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp=>common/string/strprintf2-ellipsis.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/string/utf-8.cpp=>common/string/utf-8.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avlpv.cpp=>common/table/avlpv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Base.cpp.h=>common/table/avl_Base.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Get.cpp.h=>common/table/avl_Get.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h=>common/table/avl_GetBestFit.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h=>common/table/avl_RemoveBestFit.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h=>common/table/avl_DoWithAll.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Destroy.cpp.h=>common/table/avl_Destroy.cpp.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/time/time.cpp=>common/time/time.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTAssertShouldPanic-generic.cpp=>generic/RTAssertShouldPanic-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdErr-stub-generic.cpp=>generic/RTLogWriteStdErr-stub-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdOut-stub-generic.cpp=>generic/RTLogWriteStdOut-stub-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTMpGetCoreCount-generic.cpp=>generic/RTMpGetCoreCount-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWait-2-ex-generic.cpp=>generic/RTSemEventWait-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventWaitNoResume-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWait-2-ex-generic.cpp=>generic/RTSemEventMultiWait-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventMultiWaitNoResume-2-ex-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/rtStrFormatKernelAddress-generic.cpp=>generic/rtStrFormatKernelAddress-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/errvars-generic.cpp=>generic/errvars-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/generic/mppresent-generic.cpp=>generic/mppresent-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.cpp=>r0drv/alloc-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.h=>r0drv/alloc-r0drv.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/initterm-r0drv.cpp=>r0drv/initterm-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/memobj-r0drv.cpp=>r0drv/memobj-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/mp-r0drv.h=>r0drv/mp-r0drv.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/mpnotification-r0drv.c=>r0drv/mpnotification-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/power-r0drv.h=>r0drv/power-r0drv.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/powernotification-r0drv.c=>r0drv/powernotification-r0drv.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/alloc-r0drv-linux.c=>r0drv/linux/alloc-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/assert-r0drv-linux.c=>r0drv/linux/assert-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/initterm-r0drv-linux.c=>r0drv/linux/initterm-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c=>r0drv/linux/memobj-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/memuserkernel-r0drv-linux.c=>r0drv/linux/memuserkernel-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/mp-r0drv-linux.c=>r0drv/linux/mp-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/mpnotification-r0drv-linux.c=>r0drv/linux/mpnotification-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/process-r0drv-linux.c=>r0drv/linux/process-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semevent-r0drv-linux.c=>r0drv/linux/semevent-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semeventmulti-r0drv-linux.c=>r0drv/linux/semeventmulti-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semfastmutex-r0drv-linux.c=>r0drv/linux/semfastmutex-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semmutex-r0drv-linux.c=>r0drv/linux/semmutex-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/spinlock-r0drv-linux.c=>r0drv/linux/spinlock-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/string.h=>r0drv/linux/string.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/thread-r0drv-linux.c=>r0drv/linux/thread-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/thread2-r0drv-linux.c=>r0drv/linux/thread2-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/time-r0drv-linux.c=>r0drv/linux/time-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/timer-r0drv-linux.c=>r0drv/linux/timer-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/waitqueue-r0drv-linux.h=>r0drv/linux/waitqueue-r0drv-linux.h \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/RTLogWriteDebugger-r0drv-linux.c=>r0drv/linux/RTLogWriteDebugger-r0drv-linux.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/semspinmutex-r0drv-generic.c=>r0drv/generic/semspinmutex-r0drv-generic.c \
+ ${PATH_ROOT}/src/VBox/Runtime/VBox/log-vbox.cpp=>VBox/log-vbox.c \
+ ${PATH_ROOT}/src/VBox/Runtime/VBox/logbackdoor.cpp=>VBox/logbackdoor.c \
+ ${PATH_ROOT}/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.cpp=>VBox/RTLogWriteVmm-amd64-x86.c \
+ ${PATH_OUT}/version-generated.h=>version-generated.h \
+ ${PATH_OUT}/product-generated.h=>product-generated.h \
+ ${PATH_OUT}/revision-generated.h=>revision-generated.h \
+"
+
+FILES_VBOXGUEST_BIN=" \
+"
+
diff --git a/src/VBox/Additions/common/VBoxGuest/netbsd/locators.h b/src/VBox/Additions/common/VBoxGuest/netbsd/locators.h
new file mode 100644
index 00000000..91272794
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/netbsd/locators.h
@@ -0,0 +1,8 @@
+/* $Id: locators.h $ */
+/** @file
+ * Placeholder "locators.h" that wsmousevar.h needs (bad hygiene).
+ *
+ * It's normally generated by config(8), but see the explanatory
+ * comment in vboxguest.ioconf
+ */
+#define WSMOUSEDEVCF_MUX 0
diff --git a/src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf b/src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf
new file mode 100644
index 00000000..21beed84
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/netbsd/vboxguest.ioconf
@@ -0,0 +1,66 @@
+# $Id: vboxguest.ioconf $
+## @file
+# NetBSD vboxguest module configuration
+#
+
+#
+# Copyright (C) 2017-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# XXX:
+#
+# VBoxGuest-netbsd.c has manually edited copy of the config glue and
+# we also provide stub "locators.h" in this directory. Both should
+# really be generated by config(8) from this ioconf file but that runs
+# into a couple of problems.
+#
+# We want to attach wsmouse(4) as a child, but we cannot expect the
+# kernel that loads us to have "wsmouse* at wsmousedev?" attachment
+# and in fact until recently x86 kernels didn't have it.
+#
+# But when we specify "wsmouse* at vboxguest?" attachment below
+# config(8) thinks that this module defines wsmouse and generates
+# CFDRIVER_DECL() for it and also includes it into cfdriver and
+# cfattachinit arrays it emits.
+
+ioconf vboxguest
+
+include "conf/files"
+
+include "dev/i2o/files.i2o" # XXX: pci needs device iop
+include "dev/pci/files.pci"
+
+device vboxguest: wsmousedev
+attach vboxguest at pci
+
+pseudo-root pci*
+vboxguest0 at pci? dev ? function ?
+
+wsmouse* at vboxguest?
diff --git a/src/VBox/Additions/common/VBoxGuest/solaris/deps.asm b/src/VBox/Additions/common/VBoxGuest/solaris/deps.asm
new file mode 100644
index 00000000..a6abf2bf
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/solaris/deps.asm
@@ -0,0 +1,48 @@
+; $Id: deps.asm $
+;; @file
+; Solaris kernel module dependency
+;
+
+;
+; Copyright (C) 2012-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/solaris/kmoddeps.mac"
+
+kmoddeps_header ; ELF header, section table and shared string table
+
+kmoddeps_dynstr_start ; ELF .dynstr section
+kmoddeps_dynstr_string str_misc_ctf, "misc/ctf"
+kmoddeps_dynstr_end
+
+kmoddeps_dynamic_start ; ELF .dynamic section
+kmoddeps_dynamic_needed str_misc_ctf
+kmoddeps_dynamic_end
+
diff --git a/src/VBox/Additions/common/VBoxGuest/solaris/load.sh b/src/VBox/Additions/common/VBoxGuest/solaris/load.sh
new file mode 100755
index 00000000..379cba0c
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/solaris/load.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# $Id: load.sh $
+## @file
+# For GA development.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+DRVNAME="vboxguest"
+DRIVERS_USING_IT="vboxfs"
+
+DRVFILE=`dirname "$0"`
+DRVFILE=`cd "$DRVFILE" && pwd`
+DRVFILE="$DRVFILE/$DRVNAME"
+if [ ! -f "$DRVFILE" ]; then
+ echo "load.sh: Cannot find $DRVFILE or it's not a file..."
+ exit 1;
+fi
+
+SUDO=sudo
+#set -x
+
+# Unload driver that may depend on the driver we're going to (re-)load
+# as well as the driver itself.
+for drv in $DRIVERS_USING_IT $DRVNAME;
+do
+ LOADED=`modinfo | grep -w "$drv"`
+ if test -n "$LOADED"; then
+ MODID=`echo "$LOADED" | cut -d ' ' -f 1`
+ $SUDO modunload -i $MODID;
+ LOADED=`modinfo | grep -w "$drv"`;
+ if test -n "$LOADED"; then
+ echo "load.sh: failed to unload $drv";
+ dmesg | tail
+ exit 1;
+ fi
+ fi
+done
+
+#
+# Update the devlink.tab file so we get a /dev/vboxguest node.
+#
+set -e
+sed -e '/name=vboxguest/d' /etc/devlink.tab > /tmp/devlink.vbox
+echo -e "type=ddi_pseudo;name=vboxguest\t\D" >> /tmp/devlink.vbox
+$SUDO cp /tmp/devlink.vbox /etc/devlink.tab
+$SUDO ln -fs ../devices/pci@0,0/pci80ee,cafe@4:vboxguest /dev/vboxguest
+set +e
+
+#
+# The add_drv command will load the driver, so we need to temporarily put it
+# in a place that is searched in order to load it.
+#
+MY_RC=1
+set -e
+$SUDO rm -f \
+ "/usr/kernel/drv/${DRVNAME}" \
+ "/usr/kernel/drv/amd64/${DRVNAME}"
+sync
+$SUDO cp "${DRVFILE}" /platform/i86pc/kernel/drv/amd64/
+set +e
+
+$SUDO rem_drv $DRVNAME
+if $SUDO add_drv -ipci80ee,cafe -m"* 0666 root sys" -v $DRVNAME; then
+ sync
+ $SUDO /usr/sbin/devfsadm -i $DRVNAME
+ MY_RC=0
+else
+ dmesg | tail
+ echo "load.sh: add_drv failed."
+fi
+
+$SUDO rm -f \
+ "/usr/kernel/drv/${DRVNAME}" \
+ "/usr/kernel/drv/amd64/${DRVNAME}"
+sync
+
+exit $MY_RC;
+
diff --git a/src/VBox/Additions/common/VBoxGuest/win/Makefile.kup b/src/VBox/Additions/common/VBoxGuest/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/win/Makefile.kup
diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf
new file mode 100644
index 00000000..ce64cf7b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.inf
@@ -0,0 +1,98 @@
+; $Id: VBoxGuest.inf $
+;; @file
+; INF file for installing the VirtualBox Windows guest driver, XP and later.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+[Version]
+Signature="$WINDOWS NT$"
+Provider=%ORACLE%
+ClassGuid={4D36E97D-E325-11CE-BFC1-08002BE10318}
+Class=System
+DriverPackageType=PlugAndPlay
+;edit-DriverVer=08/26/2008,2.00.0000
+;cat CatalogFile=VBoxGuest.cat
+
+[SourceDisksNames]
+1 = %VBoxGuest.MediaDesc%
+
+[SourceDisksFiles]
+VBoxGuest.sys = 1
+VBoxControl.exe = 1
+VBoxTray.exe = 1
+
+[DestinationDirs]
+DefaultDestDir = 12 ; drivers
+VBoxTray_CopyFiles = 11 ; system32
+
+[Manufacturer]
+%ORACLE%=VBoxGuest@COMMA-NT-ARCH@
+
+[VBoxGuest@DOT-NT-ARCH@]
+%VBoxGuest.DeviceDesc%=VBoxGuest_Install,PCI\VEN_80ee&DEV_cafe
+
+[VBoxGuest_Install]
+CopyFiles = VBoxGuest_CopyFiles, VBoxTray_CopyFiles
+AddReg = VBoxTray_Add_Reg
+
+[VBoxGuest_CopyFiles]
+VBoxGuest.sys
+
+[VBoxTray_CopyFiles]
+VBoxTray.exe
+VBoxControl.exe
+
+[VBoxGuest_Install.Services]
+AddService = VBoxGuest, 0x00000002, VBoxGuest_ServiceInstallSection
+DelService = VBoxTray, 0x00000004
+
+[VBoxGuest_ServiceInstallSection]
+DisplayName = %VBoxGuest_svcdesc%
+ServiceType = 0x00000001 ; kernel driver
+StartType = 0x00000000 ; boot start
+ErrorControl = 0x00000001 ; normal error handling
+LoadOrderGroup = Base
+ServiceBinary = %12%\VBoxGuest.sys
+
+[VBoxTray_Add_Reg]
+HKLM, SOFTWARE\Microsoft\Windows\CurrentVersion\Run, VBoxTray, 0x00020000, %%SystemRoot%%\system32\VBoxTray.exe
+
+[ClassInstall32]
+; This should fix the error 0xe0000101 (The required section was not found in the INF).
+
+[Strings]
+ORACLE = "Oracle Corporation"
+VBoxGuest.DeviceDesc = "VirtualBox Guest Device"
+VBoxGuest_svcdesc = "VirtualBox Guest Driver"
+VBoxTray_svcdesc = "VirtualBox Guest Tray"
+VBoxGuest.MediaDesc = "VirtualBox Guest Driver Installation Disk"
diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc
new file mode 100644
index 00000000..ba7d500e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuest.rc
@@ -0,0 +1,72 @@
+/* $Id: VBoxGuest.rc $ */
+/** @file
+ * VBoxGuest - Resource file containing version info and icon.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <windows.h>
+#include <VBox/version.h>
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VBOX_RC_FILE_VERSION
+ PRODUCTVERSION VBOX_RC_FILE_VERSION
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+ FILEFLAGS VBOX_RC_FILE_FLAGS
+ FILEOS VBOX_RC_FILE_OS
+ FILETYPE VBOX_RC_TYPE_DLL
+ FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "VirtualBox Guest Driver\0"
+ VALUE "InternalName", "VBoxGuest\0"
+ VALUE "OriginalFilename", "VBoxGuest.sys\0"
+ VALUE "CompanyName", VBOX_RC_COMPANY_NAME
+ VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR
+ VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT
+ VALUE "ProductName", VBOX_RC_PRODUCT_NAME_GA_STR
+ VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR
+ VBOX_RC_MORE_STRINGS
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+// #include <VBoxGuestMsg.rc>
diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestEarlyNT.inf b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestEarlyNT.inf
new file mode 100644
index 00000000..c0bfd4e5
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestEarlyNT.inf
@@ -0,0 +1,100 @@
+; $Id: VBoxGuestEarlyNT.inf $
+;; @file
+; INF file for installing the VirtualBox Windows guest driver, pre-XP variant.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+[Version]
+Signature="$WINDOWS NT$"
+Provider=%ORACLE%
+ClassGuid={4D36E97D-E325-11CE-BFC1-08002BE10318}
+Class=System
+DriverPackageType=PlugAndPlay
+;edit-DriverVer=08/26/2008,2.00.0000
+;cat CatalogFile=VBoxGuestEarlyNT.cat
+
+[SourceDisksNames]
+1 = %VBoxGuest.MediaDesc%
+
+[SourceDisksFiles]
+VBoxGuest.sys = 1
+VBoxControl.exe = 1
+VBoxTray.exe = 1
+
+[DestinationDirs]
+DefaultDestDir = 12 ; drivers
+VBoxTray_CopyFiles = 11 ; system32
+
+; Windows 2000 and NT4 treats entries here as pure 'name=section' and will not
+; split the value on commas. Newer InfVerif.exe requires the comma stuff here.
+[Manufacturer]
+%ORACLE%=VBoxGuest
+
+[VBoxGuest]
+%VBoxGuest.DeviceDesc%=VBoxGuest_Install,PCI\VEN_80ee&DEV_cafe
+
+[VBoxGuest_Install]
+CopyFiles = VBoxGuest_CopyFiles, VBoxTray_CopyFiles
+AddReg = VBoxTray_Add_Reg
+
+[VBoxGuest_CopyFiles]
+VBoxGuest.sys
+
+[VBoxTray_CopyFiles]
+VBoxTray.exe
+VBoxControl.exe
+
+[VBoxGuest_Install.Services]
+AddService = VBoxGuest, 0x00000002, VBoxGuest_ServiceInstallSection
+DelService = VBoxTray, 0x00000004
+
+[VBoxGuest_ServiceInstallSection]
+DisplayName = %VBoxGuest_svcdesc%
+ServiceType = 0x00000001 ; kernel driver
+StartType = 0x00000000 ; boot start
+ErrorControl = 0x00000001 ; normal error handling
+LoadOrderGroup = Base
+ServiceBinary = %12%\VBoxGuest.sys
+
+[VBoxTray_Add_Reg]
+HKLM, SOFTWARE\Microsoft\Windows\CurrentVersion\Run, VBoxTray, 0x00020000, %%SystemRoot%%\system32\VBoxTray.exe
+
+[ClassInstall32]
+; This should fix the error 0xe0000101 (The required section was not found in the INF).
+
+[Strings]
+ORACLE = "Oracle Corporation"
+VBoxGuest.DeviceDesc = "VirtualBox Guest Device"
+VBoxGuest_svcdesc = "VirtualBox Guest Driver"
+VBoxTray_svcdesc = "VirtualBox Guest Tray"
+VBoxGuest.MediaDesc = "VirtualBox Guest Driver Installation Disk"
diff --git a/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp
new file mode 100644
index 00000000..2429280c
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxGuest/win/VBoxGuestInst.cpp
@@ -0,0 +1,227 @@
+/* $Id: VBoxGuestInst.cpp $ */
+/** @file
+ * Small tool to (un)install the VBoxGuest device driver (for testing).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/win/windows.h>
+
+#include <VBox/VBoxGuest.h> /* for VBOXGUEST_SERVICE_NAME */
+#include <iprt/errcore.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/utf16.h>
+
+
+
+
+static RTEXITCODE installDriver(bool fStartIt)
+{
+ /*
+ * Assume it didn't exist, so we'll create the service.
+ */
+ SC_HANDLE hSMgrCreate = OpenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG);
+ if (!hSMgrCreate)
+ return RTMsgErrorExitFailure("OpenSCManager(,,create) failed: %u", GetLastError());
+
+ const wchar_t *pwszSlashName = L"\\VBoxGuest.sys";
+ wchar_t wszDriver[MAX_PATH * 2];
+ GetCurrentDirectoryW(MAX_PATH, wszDriver);
+ RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName);
+ if (GetFileAttributesW(wszDriver) == INVALID_FILE_ATTRIBUTES)
+ {
+ GetSystemDirectoryW(wszDriver, MAX_PATH);
+ RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), L"\\drivers");
+ RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName);
+
+ /* Try FAT name abbreviation. */
+ if (GetFileAttributesW(wszDriver) == INVALID_FILE_ATTRIBUTES)
+ {
+ pwszSlashName = L"\\VBoxGst.sys";
+ GetCurrentDirectoryW(MAX_PATH, wszDriver);
+ RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName);
+ if (GetFileAttributesW(wszDriver) == INVALID_FILE_ATTRIBUTES)
+ {
+ GetSystemDirectoryW(wszDriver, MAX_PATH);
+ RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), L"\\drivers");
+ RTUtf16Cat(wszDriver, RT_ELEMENTS(wszDriver), pwszSlashName);
+ }
+ }
+ }
+
+ RTEXITCODE rcExit;
+ SC_HANDLE hService = CreateServiceW(hSMgrCreate,
+ RT_CONCAT(L,VBOXGUEST_SERVICE_NAME),
+ L"VBoxGuest Support Driver",
+ SERVICE_QUERY_STATUS | (fStartIt ? SERVICE_START : 0),
+ SERVICE_KERNEL_DRIVER,
+ SERVICE_BOOT_START,
+ SERVICE_ERROR_NORMAL,
+ wszDriver,
+ L"System",
+ NULL, NULL, NULL, NULL);
+ if (hService)
+ {
+ RTMsgInfo("Successfully created service '%s' for driver '%ls'.\n", VBOXGUEST_SERVICE_NAME, wszDriver);
+ rcExit = RTEXITCODE_SUCCESS;
+ if (fStartIt)
+ {
+ if (StartService(hService, 0, NULL))
+ RTMsgInfo("successfully started driver '%ls'\n", wszDriver);
+ else
+ rcExit = RTMsgErrorExitFailure("StartService failed: %u", GetLastError());
+ }
+ CloseServiceHandle(hService);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("CreateService failed! %u (wszDriver=%ls)\n", GetLastError(), wszDriver);
+ CloseServiceHandle(hSMgrCreate);
+ return rcExit;
+}
+
+
+static RTEXITCODE uninstallDriver(void)
+{
+ SC_HANDLE hSMgr = OpenSCManagerW(NULL, NULL, SERVICE_CHANGE_CONFIG);
+ if (!hSMgr)
+ return RTMsgErrorExitFailure("OpenSCManager(,,change_config) failed: %u", GetLastError());
+
+ RTEXITCODE rcExit;
+ SC_HANDLE hService = OpenServiceW(hSMgr, RT_CONCAT(L, VBOXGUEST_SERVICE_NAME), SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE);
+ if (hService)
+ {
+ /*
+ * Try stop it if it's running.
+ */
+ SERVICE_STATUS Status = { 0, 0, 0, 0, 0, 0, 0 };
+ QueryServiceStatus(hService, &Status);
+ if (Status.dwCurrentState == SERVICE_STOPPED)
+ rcExit = RTEXITCODE_SUCCESS;
+ else if (ControlService(hService, SERVICE_CONTROL_STOP, &Status))
+ {
+ int iWait = 100;
+ while (Status.dwCurrentState == SERVICE_STOP_PENDING && iWait-- > 0)
+ {
+ Sleep(100);
+ QueryServiceStatus(hService, &Status);
+ }
+ if (Status.dwCurrentState == SERVICE_STOPPED)
+ rcExit = RTEXITCODE_SUCCESS;
+ else
+ rcExit = RTMsgErrorExitFailure("Failed to stop service! Service status: %u (%#x)\n",
+ Status.dwCurrentState, Status.dwCurrentState);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("ControlService failed: %u, Service status: %u (%#x)",
+ GetLastError(), Status.dwCurrentState, Status.dwCurrentState);
+
+ /*
+ * Delete the service.
+ */
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (DeleteService(hService))
+ RTMsgInfo("Successfully deleted the %s service\n", VBOXGUEST_SERVICE_NAME);
+ else
+ rcExit = RTMsgErrorExitFailure("DeleteService failed: %u", GetLastError());
+ }
+
+ CloseServiceHandle(hService);
+ }
+ else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
+ {
+ RTMsgInfo("Nothing to do, the service %s does not exist.\n", VBOXGUEST_SERVICE_NAME);
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("OpenService failed: %u", GetLastError());
+
+ CloseServiceHandle(hSMgr);
+ return rcExit;
+}
+
+
+static RTEXITCODE performTest(void)
+{
+ HANDLE hDevice = CreateFileW(RT_CONCAT(L,VBOXGUEST_DEVICE_NAME), // Win2k+: VBOXGUEST_DEVICE_NAME_GLOBAL
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (hDevice != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(hDevice);
+ RTMsgInfo("Test succeeded\n");
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("Test failed! Unable to open driver (CreateFileW -> %u).", GetLastError());
+}
+
+
+static RTEXITCODE usage(const char *pszProgName)
+{
+ RTPrintf("\n"
+ "Usage: %s [install|uninstall|test]\n", pszProgName);
+ return RTEXITCODE_SYNTAX;
+}
+
+
+int main(int argc, char **argv)
+{
+ if (argc != 2)
+ {
+ RTMsgError(argc < 2 ? "Too few arguments! Expected one." : "Too many arguments! Expected only one.");
+ return usage(argv[0]);
+ }
+
+ RTEXITCODE rcExit;
+ if (strcmp(argv[1], "install") == 0)
+ rcExit = installDriver(true);
+ else if (strcmp(argv[1], "uninstall") == 0)
+ rcExit = uninstallDriver();
+ else if (strcmp(argv[1], "test") == 0)
+ rcExit = performTest();
+ else
+ {
+ RTMsgError("Unknown argument: '%s'", argv[1]);
+ rcExit = usage(argv[0]);
+ }
+ return rcExit;
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/Makefile.kmk b/src/VBox/Additions/common/VBoxService/Makefile.kmk
new file mode 100644
index 00000000..27fcd8b0
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/Makefile.kmk
@@ -0,0 +1,220 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Cross Platform Guest Addition Services.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Incldue testcases.
+#
+include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+
+
+#
+# Target lists.
+#
+PROGRAMS += VBoxService
+
+
+#
+# Globals?
+#
+# Enable the timesync service within VBoxService.
+VBOX_WITH_VBOXSERVICE_TIMESYNC := 1
+
+# Busybox-like toolbox, embedded into VBoxService.
+VBOX_WITH_VBOXSERVICE_TOOLBOX := 1
+
+# VM-management functions, like memory ballooning and statistics.
+VBOX_WITH_VBOXSERVICE_MANAGEMENT := 1
+
+if1of ($(KBUILD_TARGET), linux)
+ # CPU hotplugging.
+ VBOX_WITH_VBOXSERVICE_CPUHOTPLUG := 1
+endif
+
+# Page Sharing (Page Fusion).
+if1of ($(KBUILD_TARGET), win)
+ VBOX_WITH_VBOXSERVICE_PAGE_SHARING := 1
+endif
+
+ifdef VBOX_WITH_GUEST_PROPS
+ VBOX_WITH_VBOXSERVICE_VMINFO := 1
+endif
+
+# Guest Control.
+ifdef VBOX_WITH_GUEST_CONTROL
+ VBOX_WITH_VBOXSERVICE_CONTROL := 1
+endif
+
+# Shared Clipboard.
+ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VBOX_WITH_VBOXSERVICE_CLIPBOARD := 1
+endif
+
+# DRM Resize.
+if "$(KBUILD_TARGET)" == "linux" && defined(VBOX_WITH_GUEST_PROPS)
+ # The DRM resizing code needs guest properties.
+ VBOX_WITH_VBOXSERVICE_DRMRESIZE := 1
+endif
+
+
+#
+# VBoxService
+#
+VBoxService_TEMPLATE = VBoxGuestR3Exe
+
+VBoxService_DEFS = \
+ $(if $(VBOX_WITH_VBOXSERVICE_CONTROL),VBOX_WITH_VBOXSERVICE_CONTROL,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_CPUHOTPLUG),VBOX_WITH_VBOXSERVICE_CPUHOTPLUG,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_DRMRESIZE),VBOX_WITH_VBOXSERVICE_DRMRESIZE,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_MANAGEMENT),VBOX_WITH_VBOXSERVICE_MANAGEMENT,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_PAGE_SHARING),VBOX_WITH_VBOXSERVICE_PAGE_SHARING,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_TIMESYNC),VBOX_WITH_VBOXSERVICE_TIMESYNC,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_TOOLBOX),VBOX_WITH_VBOXSERVICE_TOOLBOX,) \
+ $(if $(VBOX_WITH_VBOXSERVICE_VMINFO),VBOX_WITH_VBOXSERVICE_VMINFO,) \
+ $(if $(VBOX_WITH_DBUS),VBOX_WITH_DBUS,) \
+ $(if $(VBOX_WITH_GUEST_CONTROL),VBOX_WITH_GUEST_CONTROL,) \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_WITH_HGCM),VBOX_WITH_HGCM,)
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxService_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ VBoxService_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+VBoxService_DEFS.win += _WIN32_WINNT=0x0501
+VBoxService_DEFS.os2 = VBOX_WITH_HGCM
+
+VBoxService_SOURCES = \
+ VBoxService.cpp \
+ VBoxServiceUtils.cpp \
+ VBoxServiceStats.cpp
+
+ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC
+ VBoxService_SOURCES += \
+ VBoxServiceTimeSync.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD
+ VBoxService_DEFS.os2 += VBOX_WITH_VBOXSERVICE_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD
+ VBoxService_SOURCES.os2 += \
+ VBoxServiceClipboard-os2.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ VBoxService_SOURCES += \
+ VBoxServiceToolBox.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ VBoxService_SOURCES += \
+ VBoxServiceControl.cpp \
+ VBoxServiceControlProcess.cpp \
+ VBoxServiceControlSession.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT
+ ifdef VBOX_WITH_MEMBALLOON
+ VBoxService_SOURCES += \
+ VBoxServiceBalloon.cpp
+ VBoxService_DEFS += VBOX_WITH_MEMBALLOON
+ endif
+endif
+
+if1of ($(KBUILD_TARGET), win)
+ VBoxService_SOURCES += \
+ VBoxServicePageSharing.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_VMINFO
+ VBoxService_SOURCES.win += \
+ VBoxServiceVMInfo-win.cpp
+ VBoxService_SOURCES += \
+ VBoxServiceVMInfo.cpp \
+ VBoxServicePropCache.cpp
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_CPUHOTPLUG
+ VBoxService_SOURCES += \
+ VBoxServiceCpuHotPlug.cpp
+endif
+
+ifdef VBOX_WITH_SHARED_FOLDERS
+ if1of ($(KBUILD_TARGET), linux os2 solaris win)
+ VBoxService_DEFS += VBOX_WITH_SHARED_FOLDERS
+ VBoxService_SOURCES += \
+ VBoxServiceAutoMount.cpp
+ VBoxService_SOURCES.linux += \
+ ../../linux/sharedfolders/vbsfmount.c
+ VBoxService_LIBS.win += \
+ Mpr.Lib
+ endif
+endif
+
+VBoxService_SOURCES.win += \
+ VBoxService-win.cpp
+
+VBoxService_SOURCES.os2 += \
+ VBoxService-os2.def
+
+VBoxService_LDFLAGS.darwin = -framework IOKit
+
+VBoxService_LIBS += \
+ $(VBOX_LIB_IPRT_GUEST_R3) \
+ $(VBOX_LIB_VBGL_R3) \
+ $(VBOX_LIB_IPRT_GUEST_R3) # (The joy of unix linkers.)
+ifdef VBOX_WITH_DBUS
+ if1of ($(KBUILD_TARGET), linux solaris) # FreeBSD?
+ VBoxService_LIBS += \
+ dl
+ endif
+endif
+VBoxService_LIBS.netbsd += crypt
+ifdef VBOX_WITH_GUEST_PROPS
+ VBoxService_LIBS.win += \
+ Secur32.lib \
+ WtsApi32.lib \
+ Psapi.lib
+ VBoxService_LIBS.solaris += \
+ nsl \
+ kstat \
+ contract
+endif
+
+ifdef VBOX_WITH_VBOXSERVICE_VMINFO
+ VBoxServiceVMInfo.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV)
+ VBoxServiceVMInfo.cpp_DEPS = $(VBOX_SVN_REV_KMK)
+endif
+
+VBoxService_USES.win += vboximportchecker
+VBoxService_VBOX_IMPORT_CHECKER.win.x86 = nt31
+VBoxService_VBOX_IMPORT_CHECKER.win.amd64 = xp64
+
+$(call VBOX_SET_VER_INFO_EXE,VBoxService,VirtualBox Guest Additions Service,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description.
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-os2.def b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def
new file mode 100644
index 00000000..b50cc211
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxService-os2.def
@@ -0,0 +1,33 @@
+; $Id: VBoxService-os2.def $
+;; @file
+; VBoxService - OS/2 definition file.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; SPDX-License-Identifier: GPL-3.0-only
+;
+
+
+NAME VBoxSvc
+DESCRIPTION 'VirtualBox Guest Additions Service for OS/2.'
+CODE SHARED
+DATA MULTIPLE NONSHARED
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp
new file mode 100644
index 00000000..fb2769fb
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxService-win.cpp
@@ -0,0 +1,670 @@
+/* $Id: VBoxService-win.cpp $ */
+/** @file
+ * VBoxService - Guest Additions Service Skeleton, Windows Specific Parts.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/ldr.h>
+#include <iprt/system.h> /* For querying OS version. */
+#include <VBox/VBoxGuestLib.h>
+
+#define WIN32_NO_STATUS
+#include <iprt/win/ws2tcpip.h>
+#include <iprt/win/winsock2.h>
+#undef WIN32_NO_STATUS
+#include <iprt/nt/nt-and-windows.h>
+#include <iprt/win/iphlpapi.h>
+#include <aclapi.h>
+#include <tlhelp32.h>
+#define _NTDEF_
+#include <Ntsecapi.h>
+
+#include "VBoxServiceInternal.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static DWORD g_dwWinServiceLastStatus = 0;
+SERVICE_STATUS_HANDLE g_hWinServiceStatus = NULL;
+/** The semaphore for the dummy Windows service. */
+static RTSEMEVENT g_WindowsEvent = NIL_RTSEMEVENT;
+
+static SERVICE_TABLE_ENTRY const g_aServiceTable[] =
+{
+ { VBOXSERVICE_NAME, vgsvcWinMain },
+ { NULL, NULL}
+};
+
+/** @name APIs from ADVAPI32.DLL.
+ * @{ */
+decltype(RegisterServiceCtrlHandlerExA) *g_pfnRegisterServiceCtrlHandlerExA; /**< W2K+ */
+decltype(ChangeServiceConfig2A) *g_pfnChangeServiceConfig2A; /**< W2K+ */
+decltype(GetNamedSecurityInfoA) *g_pfnGetNamedSecurityInfoA; /**< NT4+ */
+decltype(SetEntriesInAclA) *g_pfnSetEntriesInAclA; /**< NT4+ */
+decltype(SetNamedSecurityInfoA) *g_pfnSetNamedSecurityInfoA; /**< NT4+ */
+decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError; /**< NT3.51+ */
+/** @} */
+
+/** @name API from KERNEL32.DLL
+ * @{ */
+decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Process32First) *g_pfnProcess32First; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Process32Next) *g_pfnProcess32Next; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Module32First) *g_pfnModule32First; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(Module32Next) *g_pfnModule32Next; /**< W2K+, but Geoff says NT4. Hmm. */
+decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment; /**< NT 3.50+ */
+decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment; /**< NT 3.50+ */
+/** @} */
+
+/** @name API from NTDLL.DLL
+ * @{ */
+decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation; /**< NT4 (where as NtQuerySystemInformation is W2K). */
+/** @} */
+
+/** @name API from IPHLPAPI.DLL
+ * @{ */
+decltype(GetAdaptersInfo) *g_pfnGetAdaptersInfo;
+/** @} */
+
+/** @name APIs from WS2_32.DLL
+ * @note WSAIoctl is not present in wsock32.dll, so no point in trying the
+ * fallback here.
+ * @{ */
+decltype(WSAStartup) *g_pfnWSAStartup;
+decltype(WSACleanup) *g_pfnWSACleanup;
+decltype(WSASocketA) *g_pfnWSASocketA;
+decltype(WSAIoctl) *g_pfnWSAIoctl;
+decltype(WSAGetLastError) *g_pfnWSAGetLastError;
+decltype(closesocket) *g_pfnclosesocket;
+decltype(inet_ntoa) *g_pfninet_ntoa;
+
+/** @} */
+
+/**
+ * Resolve APIs not present on older windows versions.
+ */
+void VGSvcWinResolveApis(void)
+{
+ RTLDRMOD hLdrMod;
+#define RESOLVE_SYMBOL(a_fn) do { RT_CONCAT(g_pfn, a_fn) = (decltype(a_fn) *)RTLdrGetFunction(hLdrMod, #a_fn); } while (0)
+
+ /* From ADVAPI32.DLL: */
+ int rc = RTLdrLoadSystem("advapi32.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(RegisterServiceCtrlHandlerExA);
+ RESOLVE_SYMBOL(ChangeServiceConfig2A);
+ RESOLVE_SYMBOL(GetNamedSecurityInfoA);
+ RESOLVE_SYMBOL(SetEntriesInAclA);
+ RESOLVE_SYMBOL(SetNamedSecurityInfoA);
+ RESOLVE_SYMBOL(LsaNtStatusToWinError);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From KERNEL32.DLL: */
+ rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(CreateToolhelp32Snapshot);
+ RESOLVE_SYMBOL(Process32First);
+ RESOLVE_SYMBOL(Process32Next);
+ RESOLVE_SYMBOL(Module32First);
+ RESOLVE_SYMBOL(Module32Next);
+ RESOLVE_SYMBOL(GetSystemTimeAdjustment);
+ RESOLVE_SYMBOL(SetSystemTimeAdjustment);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From NTDLL.DLL: */
+ rc = RTLdrLoadSystem("ntdll.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(ZwQuerySystemInformation);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From IPHLPAPI.DLL: */
+ rc = RTLdrLoadSystem("iphlpapi.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(GetAdaptersInfo);
+ RTLdrClose(hLdrMod);
+ }
+
+ /* From WS2_32.DLL: */
+ rc = RTLdrLoadSystem("ws2_32.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ RESOLVE_SYMBOL(WSAStartup);
+ RESOLVE_SYMBOL(WSACleanup);
+ RESOLVE_SYMBOL(WSASocketA);
+ RESOLVE_SYMBOL(WSAIoctl);
+ RESOLVE_SYMBOL(WSAGetLastError);
+ RESOLVE_SYMBOL(closesocket);
+ RESOLVE_SYMBOL(inet_ntoa);
+ RTLdrClose(hLdrMod);
+ }
+}
+
+
+/**
+ * @todo Add full unicode support.
+ * @todo Add event log capabilities / check return values.
+ */
+static int vgsvcWinAddAceToObjectsSecurityDescriptor(LPTSTR pszObjName, SE_OBJECT_TYPE enmObjectType, const char *pszTrustee,
+ TRUSTEE_FORM enmTrusteeForm, DWORD dwAccessRights, ACCESS_MODE fAccessMode,
+ DWORD dwInheritance)
+{
+ int rc;
+ if ( g_pfnGetNamedSecurityInfoA
+ && g_pfnSetEntriesInAclA
+ && g_pfnSetNamedSecurityInfoA)
+ {
+ /* Get a pointer to the existing DACL. */
+ PSECURITY_DESCRIPTOR pSD = NULL;
+ PACL pOldDACL = NULL;
+ DWORD rcWin = g_pfnGetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION,
+ NULL, NULL, &pOldDACL, NULL, &pSD);
+ if (rcWin == ERROR_SUCCESS)
+ {
+ /* Initialize an EXPLICIT_ACCESS structure for the new ACE. */
+ EXPLICIT_ACCESSA ExplicitAccess;
+ RT_ZERO(ExplicitAccess);
+ ExplicitAccess.grfAccessPermissions = dwAccessRights;
+ ExplicitAccess.grfAccessMode = fAccessMode;
+ ExplicitAccess.grfInheritance = dwInheritance;
+ ExplicitAccess.Trustee.TrusteeForm = enmTrusteeForm;
+ ExplicitAccess.Trustee.ptstrName = (char *)pszTrustee;
+
+ /* Create a new ACL that merges the new ACE into the existing DACL. */
+ PACL pNewDACL = NULL;
+ rcWin = g_pfnSetEntriesInAclA(1, &ExplicitAccess, pOldDACL, &pNewDACL);
+ if (rcWin == ERROR_SUCCESS)
+ {
+ /* Attach the new ACL as the object's DACL. */
+ rcWin = g_pfnSetNamedSecurityInfoA(pszObjName, enmObjectType, DACL_SECURITY_INFORMATION,
+ NULL, NULL, pNewDACL, NULL);
+ if (rcWin == ERROR_SUCCESS)
+ rc = VINF_SUCCESS;
+ else
+ {
+ VGSvcError("AddAceToObjectsSecurityDescriptor: SetNamedSecurityInfo: Error %u\n", rcWin);
+ rc = RTErrConvertFromWin32(rcWin);
+ }
+ if (pNewDACL)
+ LocalFree(pNewDACL);
+ }
+ else
+ {
+ VGSvcError("AddAceToObjectsSecurityDescriptor: SetEntriesInAcl: Error %u\n", rcWin);
+ rc = RTErrConvertFromWin32(rcWin);
+ }
+ if (pSD)
+ LocalFree(pSD);
+ }
+ else
+ {
+ if (rcWin == ERROR_FILE_NOT_FOUND)
+ VGSvcError("AddAceToObjectsSecurityDescriptor: Object not found/installed: %s\n", pszObjName);
+ else
+ VGSvcError("AddAceToObjectsSecurityDescriptor: GetNamedSecurityInfo: Error %u\n", rcWin);
+ rc = RTErrConvertFromWin32(rcWin);
+ }
+ }
+ else
+ rc = VINF_SUCCESS; /* fake it */
+ return rc;
+}
+
+
+/** Reports our current status to the SCM. */
+static BOOL vgsvcWinSetStatus(DWORD dwStatus, DWORD dwCheckPoint)
+{
+ if (g_hWinServiceStatus == NULL) /* Program could be in testing mode, so no service environment available. */
+ return FALSE;
+
+ VGSvcVerbose(2, "Setting service status to: %ld\n", dwStatus);
+ g_dwWinServiceLastStatus = dwStatus;
+
+ SERVICE_STATUS ss;
+ RT_ZERO(ss);
+
+ ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ ss.dwCurrentState = dwStatus;
+ /* Don't accept controls when in start pending state. */
+ if (ss.dwCurrentState != SERVICE_START_PENDING)
+ {
+ ss.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+
+ /* Don't use SERVICE_ACCEPT_SESSIONCHANGE on Windows 2000 or earlier. This makes SCM angry. */
+ char szOSVersion[32];
+ int rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szOSVersion, sizeof(szOSVersion));
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrVersionCompare(szOSVersion, "5.1") >= 0)
+ ss.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE;
+ }
+ else
+ VGSvcError("Error determining OS version, rc=%Rrc\n", rc);
+ }
+
+ ss.dwWin32ExitCode = NO_ERROR;
+ ss.dwServiceSpecificExitCode = 0; /* Not used */
+ ss.dwCheckPoint = dwCheckPoint;
+ ss.dwWaitHint = 3000;
+
+ BOOL fStatusSet = SetServiceStatus(g_hWinServiceStatus, &ss);
+ if (!fStatusSet)
+ VGSvcError("Error reporting service status=%ld (controls=%x, checkpoint=%ld) to SCM: %ld\n",
+ dwStatus, ss.dwControlsAccepted, dwCheckPoint, GetLastError());
+ return fStatusSet;
+}
+
+
+/**
+ * Reports SERVICE_STOP_PENDING to SCM.
+ *
+ * @param uCheckPoint Some number.
+ */
+void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint)
+{
+ vgsvcWinSetStatus(SERVICE_STOP_PENDING, uCheckPoint);
+}
+
+
+static RTEXITCODE vgsvcWinSetDesc(SC_HANDLE hService)
+{
+ /* On W2K+ there's ChangeServiceConfig2() which lets us set some fields
+ like a longer service description. */
+ if (g_pfnChangeServiceConfig2A)
+ {
+ /** @todo On Vista+ SERVICE_DESCRIPTION also supports localized strings! */
+ SERVICE_DESCRIPTION desc;
+ desc.lpDescription = VBOXSERVICE_DESCRIPTION;
+ if (!g_pfnChangeServiceConfig2A(hService, SERVICE_CONFIG_DESCRIPTION, &desc))
+ {
+ VGSvcError("Cannot set the service description! Error: %ld\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Installs the service.
+ */
+RTEXITCODE VGSvcWinInstall(void)
+{
+ VGSvcVerbose(1, "Installing service ...\n");
+
+ TCHAR imagePath[MAX_PATH] = { 0 };
+ GetModuleFileName(NULL, imagePath, sizeof(imagePath));
+
+ SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+ if (hSCManager == NULL)
+ {
+ VGSvcError("Could not open SCM! Error: %ld\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE rc = RTEXITCODE_SUCCESS;
+ SC_HANDLE hService = CreateService(hSCManager,
+ VBOXSERVICE_NAME, VBOXSERVICE_FRIENDLY_NAME,
+ SERVICE_ALL_ACCESS,
+ SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
+ SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
+ imagePath, NULL, NULL, NULL, NULL, NULL);
+ if (hService != NULL)
+ VGSvcVerbose(0, "Service successfully installed!\n");
+ else
+ {
+ DWORD dwErr = GetLastError();
+ switch (dwErr)
+ {
+ case ERROR_SERVICE_EXISTS:
+ VGSvcVerbose(1, "Service already exists, just updating the service config.\n");
+ hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS);
+ if (hService)
+ {
+ if (ChangeServiceConfig(hService,
+ SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
+ SERVICE_DEMAND_START,
+ SERVICE_ERROR_NORMAL,
+ imagePath,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ VBOXSERVICE_FRIENDLY_NAME))
+ VGSvcVerbose(1, "The service config has been successfully updated.\n");
+ else
+ rc = VGSvcError("Could not change service config! Error: %ld\n", GetLastError());
+ }
+ else
+ rc = VGSvcError("Could not open service! Error: %ld\n", GetLastError());
+ break;
+
+ default:
+ rc = VGSvcError("Could not create service! Error: %ld\n", dwErr);
+ break;
+ }
+ }
+
+ if (rc == RTEXITCODE_SUCCESS)
+ rc = vgsvcWinSetDesc(hService);
+
+ CloseServiceHandle(hService);
+ CloseServiceHandle(hSCManager);
+ return rc;
+}
+
+/**
+ * Uninstalls the service.
+ */
+RTEXITCODE VGSvcWinUninstall(void)
+{
+ VGSvcVerbose(1, "Uninstalling service ...\n");
+
+ SC_HANDLE hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
+ if (hSCManager == NULL)
+ {
+ VGSvcError("Could not open SCM! Error: %d\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+
+ RTEXITCODE rcExit;
+ SC_HANDLE hService = OpenService(hSCManager, VBOXSERVICE_NAME, SERVICE_ALL_ACCESS );
+ if (hService != NULL)
+ {
+ if (DeleteService(hService))
+ {
+ /*
+ * ???
+ */
+ HKEY hKey = NULL;
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ "SYSTEM\\CurrentControlSet\\Services\\EventLog\\System",
+ 0,
+ KEY_ALL_ACCESS,
+ &hKey)
+ == ERROR_SUCCESS)
+ {
+ RegDeleteKey(hKey, VBOXSERVICE_NAME);
+ RegCloseKey(hKey);
+ }
+
+ VGSvcVerbose(0, "Service successfully uninstalled!\n");
+ rcExit = RTEXITCODE_SUCCESS;
+ }
+ else
+ rcExit = VGSvcError("Could not remove service! Error: %d\n", GetLastError());
+ CloseServiceHandle(hService);
+ }
+ else
+ rcExit = VGSvcError("Could not open service! Error: %d\n", GetLastError());
+ CloseServiceHandle(hSCManager);
+
+ return rcExit;
+}
+
+
+static int vgsvcWinStart(void)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Create a well-known SID for the "Builtin Users" group and modify the ACE
+ * for the shared folders miniport redirector DN (whatever DN means).
+ */
+ PSID pBuiltinUsersSID = NULL;
+ SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_LOCAL_SID_AUTHORITY;
+ if (AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_LOCAL_RID, 0, 0, 0, 0, 0, 0, 0, &pBuiltinUsersSID))
+ {
+ rc = vgsvcWinAddAceToObjectsSecurityDescriptor(TEXT("\\\\.\\VBoxMiniRdrDN"), SE_FILE_OBJECT,
+ (LPTSTR)pBuiltinUsersSID, TRUSTEE_IS_SID,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE, SET_ACCESS, NO_INHERITANCE);
+ /* If we don't find our "VBoxMiniRdrDN" (for Shared Folders) object above,
+ don't report an error; it just might be not installed. Otherwise this
+ would cause the SCM to hang on starting up the service. */
+ if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
+ rc = VINF_SUCCESS;
+
+ FreeSid(pBuiltinUsersSID);
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Start the service.
+ */
+ vgsvcWinSetStatus(SERVICE_START_PENDING, 0);
+
+ rc = VGSvcStartServices();
+ if (RT_SUCCESS(rc))
+ {
+ vgsvcWinSetStatus(SERVICE_RUNNING, 0);
+ VGSvcMainWait();
+ }
+ else
+ {
+ vgsvcWinSetStatus(SERVICE_STOPPED, 0);
+#if 0 /** @todo r=bird: Enable this if SERVICE_CONTROL_STOP isn't triggered automatically */
+ VGSvcStopServices();
+#endif
+ }
+ }
+ else
+ vgsvcWinSetStatus(SERVICE_STOPPED, 0);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Service failed to start with rc=%Rrc!\n", rc);
+
+ return rc;
+}
+
+
+/**
+ * Call StartServiceCtrlDispatcher.
+ *
+ * The main() thread invokes this when not started in foreground mode. It
+ * won't return till the service is being shutdown (unless start up fails).
+ *
+ * @returns RTEXITCODE_SUCCESS on normal return after service shutdown.
+ * Something else on failure, error will have been reported.
+ */
+RTEXITCODE VGSvcWinEnterCtrlDispatcher(void)
+{
+ if (!StartServiceCtrlDispatcher(&g_aServiceTable[0]))
+ return VGSvcError("StartServiceCtrlDispatcher: %u. Please start %s with option -f (foreground)!\n",
+ GetLastError(), g_pszProgName);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Event code to description.
+ *
+ * @returns String.
+ * @param dwEvent The event code.
+ */
+static const char *vgsvcWTSStateToString(DWORD dwEvent)
+{
+ switch (dwEvent)
+ {
+ case WTS_CONSOLE_CONNECT: return "A session was connected to the console terminal";
+ case WTS_CONSOLE_DISCONNECT: return "A session was disconnected from the console terminal";
+ case WTS_REMOTE_CONNECT: return "A session connected to the remote terminal";
+ case WTS_REMOTE_DISCONNECT: return "A session was disconnected from the remote terminal";
+ case WTS_SESSION_LOGON: return "A user has logged on to a session";
+ case WTS_SESSION_LOGOFF: return "A user has logged off the session";
+ case WTS_SESSION_LOCK: return "A session has been locked";
+ case WTS_SESSION_UNLOCK: return "A session has been unlocked";
+ case WTS_SESSION_REMOTE_CONTROL: return "A session has changed its remote controlled status";
+#ifdef WTS_SESSION_CREATE
+ case WTS_SESSION_CREATE: return "A session has been created";
+#endif
+#ifdef WTS_SESSION_TERMINATE
+ case WTS_SESSION_TERMINATE: return "The session has been terminated";
+#endif
+ default: return "Uknonwn state";
+ }
+}
+
+
+/**
+ * Common control handler.
+ *
+ * @returns Return code for NT5+.
+ * @param dwControl The control code.
+ */
+static DWORD vgsvcWinCtrlHandlerCommon(DWORD dwControl)
+{
+ DWORD rcRet = NO_ERROR;
+ switch (dwControl)
+ {
+ case SERVICE_CONTROL_INTERROGATE:
+ vgsvcWinSetStatus(g_dwWinServiceLastStatus, 0);
+ break;
+
+ case SERVICE_CONTROL_STOP:
+ case SERVICE_CONTROL_SHUTDOWN:
+ {
+ vgsvcWinSetStatus(SERVICE_STOP_PENDING, 0);
+
+ int rc2 = VGSvcStopServices();
+ if (RT_FAILURE(rc2))
+ rcRet = ERROR_GEN_FAILURE;
+ else
+ {
+ rc2 = VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated);
+ AssertRC(rc2);
+ }
+
+ vgsvcWinSetStatus(SERVICE_STOPPED, 0);
+ break;
+ }
+
+ default:
+ VGSvcVerbose(1, "Control handler: Function not implemented: %#x\n", dwControl);
+ rcRet = ERROR_CALL_NOT_IMPLEMENTED;
+ break;
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Callback registered by RegisterServiceCtrlHandler on NT4 and earlier.
+ */
+static VOID WINAPI vgsvcWinCtrlHandlerNt4(DWORD dwControl) RT_NOTHROW_DEF
+{
+ VGSvcVerbose(2, "Control handler (NT4): dwControl=%#x\n", dwControl);
+ vgsvcWinCtrlHandlerCommon(dwControl);
+}
+
+
+/**
+ * Callback registered by RegisterServiceCtrlHandler on NT5 and later.
+ */
+static DWORD WINAPI
+vgsvcWinCtrlHandlerNt5Plus(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) RT_NOTHROW_DEF
+{
+ VGSvcVerbose(2, "Control handler: dwControl=%#x, dwEventType=%#x\n", dwControl, dwEventType);
+ RT_NOREF1(lpContext);
+
+ switch (dwControl)
+ {
+ default:
+ return vgsvcWinCtrlHandlerCommon(dwControl);
+
+ case SERVICE_CONTROL_SESSIONCHANGE: /* Only Windows 2000 and up. */
+ {
+ AssertPtr(lpEventData);
+ PWTSSESSION_NOTIFICATION pNotify = (PWTSSESSION_NOTIFICATION)lpEventData;
+ Assert(pNotify->cbSize == sizeof(WTSSESSION_NOTIFICATION));
+
+ VGSvcVerbose(1, "Control handler: %s (Session=%ld, Event=%#x)\n",
+ vgsvcWTSStateToString(dwEventType), pNotify->dwSessionId, dwEventType);
+
+ /* Handle all events, regardless of dwEventType. */
+ int rc2 = VGSvcVMInfoSignal();
+ AssertRC(rc2);
+
+ return NO_ERROR;
+ }
+ }
+}
+
+
+static void WINAPI vgsvcWinMain(DWORD argc, LPTSTR *argv)
+{
+ RT_NOREF2(argc, argv);
+ VGSvcVerbose(2, "Registering service control handler ...\n");
+ if (g_pfnRegisterServiceCtrlHandlerExA)
+ g_hWinServiceStatus = g_pfnRegisterServiceCtrlHandlerExA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt5Plus, NULL);
+ else
+ g_hWinServiceStatus = RegisterServiceCtrlHandlerA(VBOXSERVICE_NAME, vgsvcWinCtrlHandlerNt4);
+ if (g_hWinServiceStatus != NULL)
+ {
+ VGSvcVerbose(2, "Service control handler registered.\n");
+ vgsvcWinStart();
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ switch (dwErr)
+ {
+ case ERROR_INVALID_NAME:
+ VGSvcError("Invalid service name!\n");
+ break;
+ case ERROR_SERVICE_DOES_NOT_EXIST:
+ VGSvcError("Service does not exist!\n");
+ break;
+ default:
+ VGSvcError("Could not register service control handle! Error: %ld\n", dwErr);
+ break;
+ }
+ }
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxService.cpp b/src/VBox/Additions/common/VBoxService/VBoxService.cpp
new file mode 100644
index 00000000..5b5dc5ee
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxService.cpp
@@ -0,0 +1,1311 @@
+/* $Id: VBoxService.cpp $ */
+/** @file
+ * VBoxService - Guest Additions Service Skeleton.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_vgsvc VBoxService
+ *
+ * VBoxService is a root daemon for implementing guest additions features.
+ *
+ * It is structured as one binary that contains many sub-services. The reason
+ * for this is partially historical and partially practical. The practical
+ * reason is that the VBoxService binary is typically statically linked, at
+ * least with IPRT and the guest library, so we save quite a lot of space having
+ * on single binary instead individual binaries for each sub-service and their
+ * helpers (currently up to 9 subservices and 8 helpers). The historical is
+ * simply that it started its life on OS/2 dreaming of conquring Windows next,
+ * so it kind of felt natural to have it all in one binary.
+ *
+ * Even if it's structured as a single binary, it is possible, by using command
+ * line options, to start each subservice as an individual process.
+ *
+ * Subservices:
+ * - @subpage pg_vgsvc_timesync "Time Synchronization"
+ * - @subpage pg_vgsvc_vminfo "VM Information"
+ * - @subpage pg_vgsvc_vmstats "VM Statistics"
+ * - @subpage pg_vgsvc_gstctrl "Guest Control"
+ * - @subpage pg_vgsvc_pagesharing "Page Sharing"
+ * - @subpage pg_vgsvc_memballoon "Memory Balooning"
+ * - @subpage pg_vgsvc_cpuhotplug "CPU Hot-Plugging"
+ * - @subpage pg_vgsvc_automount "Shared Folder Automounting"
+ * - @subpage pg_vgsvc_clipboard "Clipboard (OS/2 only)"
+ *
+ * Now, since the service predates a lot of stuff, including RTGetOpt, we're
+ * currently doing our own version of argument parsing here, which is kind of
+ * stupid. That will hopefully be cleaned up eventually.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/** @todo LOG_GROUP*/
+#ifndef _MSC_VER
+# include <unistd.h>
+#endif
+#ifndef RT_OS_WINDOWS
+# include <errno.h>
+# include <signal.h>
+# ifdef RT_OS_OS2
+# define pthread_sigmask sigprocmask
+# endif
+#endif
+#ifdef RT_OS_FREEBSD
+# include <pthread.h>
+#endif
+
+#include <package-generated.h>
+#include "product-generated.h"
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/initterm.h>
+#include <iprt/file.h>
+#ifdef DEBUG
+# include <iprt/memtracker.h>
+#endif
+#include <iprt/env.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+# include "VBoxServiceControl.h"
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+# include "VBoxServiceToolBox.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The program name (derived from argv[0]). */
+char *g_pszProgName = (char *)"";
+/** The current verbosity level. */
+unsigned g_cVerbosity = 0;
+char g_szLogFile[RTPATH_MAX + 128] = "";
+char g_szPidFile[RTPATH_MAX] = "";
+/** 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. */
+/** Critical section for (debug) logging. */
+#ifdef DEBUG
+ RTCRITSECT g_csLog;
+#endif
+/** The default service interval (the -i | --interval) option). */
+uint32_t g_DefaultInterval = 0;
+#ifdef RT_OS_WINDOWS
+/** Signal shutdown to the Windows service thread. */
+static bool volatile g_fWindowsServiceShutdown;
+/** Event the Windows service thread waits for shutdown. */
+static RTSEMEVENT g_hEvtWindowsService;
+#endif
+
+/**
+ * The details of the services that has been compiled in.
+ */
+static struct
+{
+ /** Pointer to the service descriptor. */
+ PCVBOXSERVICE pDesc;
+ /** The worker thread. NIL_RTTHREAD if it's the main thread. */
+ RTTHREAD Thread;
+ /** Whether Pre-init was called. */
+ bool fPreInited;
+ /** Shutdown indicator. */
+ bool volatile fShutdown;
+ /** Indicator set by the service thread exiting. */
+ bool volatile fStopped;
+ /** Whether the service was started or not. */
+ bool fStarted;
+ /** Whether the service is enabled or not. */
+ bool fEnabled;
+} g_aServices[] =
+{
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ { &g_Control, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_TIMESYNC
+ { &g_TimeSync, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD
+ { &g_Clipboard, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_VMINFO
+ { &g_VMInfo, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_CPUHOTPLUG
+ { &g_CpuHotPlug, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT
+# ifdef VBOX_WITH_MEMBALLOON
+ { &g_MemBalloon, NIL_RTTHREAD, false, false, false, false, true },
+# endif
+ { &g_VMStatistics, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING)
+ { &g_PageSharing, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+ { &g_AutoMount, NIL_RTTHREAD, false, false, false, false, true },
+#endif
+};
+
+
+/*
+ * Default call-backs for services which do not need special behaviour.
+ */
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit, Default Implementation}
+ */
+DECLCALLBACK(int) VGSvcDefaultPreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption, Default Implementation}
+ */
+DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc,
+ char **argv, int *pi)
+{
+ NOREF(ppszShort);
+ NOREF(argc);
+ NOREF(argv);
+ NOREF(pi);
+
+ return -1;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit, Default Implementation}
+ */
+DECLCALLBACK(int) VGSvcDefaultInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm, Default Implementation}
+ */
+DECLCALLBACK(void) VGSvcDefaultTerm(void)
+{
+ return;
+}
+
+
+/**
+ * @callback_method_impl{FNRTLOGPHASE, Release logger callback}
+ */
+static DECLCALLBACK(void) vgsvcLogHeaderFooter(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,
+ "VBoxService %s r%s (verbosity: %u) %s (%s %s) release log\n"
+ "Log opened %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, 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);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(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 */
+ break;
+ }
+}
+
+
+/**
+ * Creates the default release logger outputting to the specified file.
+ *
+ * Pass NULL to disabled logging.
+ *
+ * @return IPRT status code.
+ * @param pszLogFile Filename for log output. NULL disables logging
+ * (r=bird: No, it doesn't!).
+ */
+int VGSvcLogCreate(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;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXSERVICE_RELEASE_LOG", fFlags, "all",
+ RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER,
+ vgsvcLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : "");
+ if (RT_SUCCESS(rc))
+ {
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(g_pLoggerRelease);
+
+ /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */
+ RTLogFlush(g_pLoggerRelease);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Logs a verbose message.
+ *
+ * @param pszFormat The message text.
+ * @param va Format arguments.
+ */
+void VGSvcLogV(const char *pszFormat, va_list va)
+{
+#ifdef DEBUG
+ int rc = RTCritSectEnter(&g_csLog);
+ if (RT_SUCCESS(rc))
+ {
+#endif
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, va);
+
+ AssertPtr(psz);
+ LogRel(("%s", psz));
+
+ RTStrFree(psz);
+#ifdef DEBUG
+ RTCritSectLeave(&g_csLog);
+ }
+#endif
+}
+
+
+/**
+ * Destroys the currently active logging instance.
+ */
+void VGSvcLogDestroy(void)
+{
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+}
+
+
+/**
+ * Displays the program usage message.
+ *
+ * @returns 1.
+ */
+static int vgsvcUsage(void)
+{
+ RTPrintf("Usage: %s [-f|--foreground] [-v|--verbose] [-l|--logfile <file>]\n"
+ " [-p|--pidfile <file>] [-i|--interval <seconds>]\n"
+ " [--disable-<service>] [--enable-<service>]\n"
+ " [--only-<service>] [-h|-?|--help]\n", g_pszProgName);
+#ifdef RT_OS_WINDOWS
+ RTPrintf(" [-r|--register] [-u|--unregister]\n");
+#endif
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (g_aServices[j].pDesc->pszUsage)
+ RTPrintf("%s\n", g_aServices[j].pDesc->pszUsage);
+ RTPrintf("\n"
+ "Options:\n"
+ " -i | --interval The default interval.\n"
+ " -f | --foreground Don't daemonize the program. For debugging.\n"
+ " -l | --logfile <file> Enables logging to a file.\n"
+ " -p | --pidfile <file> Write the process ID to a file.\n"
+ " -v | --verbose Increment the verbosity level. For debugging.\n"
+ " -V | --version Show version information.\n"
+ " -h | -? | --help Show this message and exit with status 1.\n"
+ );
+#ifdef RT_OS_WINDOWS
+ RTPrintf(" -r | --register Installs the service.\n"
+ " -u | --unregister Uninstall service.\n");
+#endif
+
+ RTPrintf("\n"
+ "Service-specific options:\n");
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ RTPrintf(" --enable-%-14s Enables the %s service. (default)\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
+ RTPrintf(" --disable-%-13s Disables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
+ RTPrintf(" --only-%-16s Only enables the %s service.\n", g_aServices[j].pDesc->pszName, g_aServices[j].pDesc->pszName);
+ if (g_aServices[j].pDesc->pszOptions)
+ RTPrintf("%s", g_aServices[j].pDesc->pszOptions);
+ }
+ RTPrintf("\n"
+ " Copyright (C) 2009-" VBOX_C_YEAR " " VBOX_VENDOR "\n");
+
+ return 1;
+}
+
+
+/**
+ * Displays an error message.
+ *
+ * @returns RTEXITCODE_FAILURE.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+RTEXITCODE VGSvcError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ LogRel(("Error: %s", psz));
+
+ RTStrFree(psz);
+
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Displays a verbose message based on the currently
+ * set global verbosity level.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_cVerbosity)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ VGSvcLogV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * Reports the current VBoxService status to the host.
+ *
+ * This makes sure that the Failed state is sticky.
+ *
+ * @return IPRT status code.
+ * @param enmStatus Status to report to the host.
+ */
+int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus)
+{
+ /*
+ * VBoxGuestFacilityStatus_Failed is sticky.
+ */
+ static VBoxGuestFacilityStatus s_enmLastStatus = VBoxGuestFacilityStatus_Inactive;
+ VGSvcVerbose(4, "Setting VBoxService status to %u\n", enmStatus);
+ if (s_enmLastStatus != VBoxGuestFacilityStatus_Failed)
+ {
+ int rc = VbglR3ReportAdditionsStatus(VBoxGuestFacilityType_VBoxService, enmStatus, 0 /* Flags */);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Could not report VBoxService status (%u), rc=%Rrc\n", enmStatus, rc);
+ return rc;
+ }
+ s_enmLastStatus = enmStatus;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets a 32-bit value argument.
+ * @todo Get rid of this and VGSvcArgString() as soon as we have RTOpt handling.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param argc The argument count.
+ * @param argv The argument vector
+ * @param psz Where in *pi to start looking for the value argument.
+ * @param pi Where to find and perhaps update the argument index.
+ * @param pu32 Where to store the 32-bit value.
+ * @param u32Min The minimum value.
+ * @param u32Max The maximum value.
+ */
+int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max)
+{
+ if (*psz == ':' || *psz == '=')
+ psz++;
+ if (!*psz)
+ {
+ if (*pi + 1 >= argc)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing value for the '%s' argument\n", argv[*pi]);
+ psz = argv[++*pi];
+ }
+
+ char *pszNext;
+ int rc = RTStrToUInt32Ex(psz, &pszNext, 0, pu32);
+ if (RT_FAILURE(rc) || *pszNext)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Failed to convert interval '%s' to a number\n", psz);
+ if (*pu32 < u32Min || *pu32 > u32Max)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The timesync interval of %RU32 seconds is out of range [%RU32..%RU32]\n",
+ *pu32, u32Min, u32Max);
+ return 0;
+}
+
+
+/** @todo Get rid of this and VGSvcArgUInt32() as soon as we have RTOpt handling. */
+static int vgsvcArgString(int argc, char **argv, const char *psz, int *pi, char *pszBuf, size_t cbBuf)
+{
+ AssertPtrReturn(pszBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+
+ if (*psz == ':' || *psz == '=')
+ psz++;
+ if (!*psz)
+ {
+ if (*pi + 1 >= argc)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing string for the '%s' argument\n", argv[*pi]);
+ psz = argv[++*pi];
+ }
+
+ if (!RTStrPrintf(pszBuf, cbBuf, "%s", psz))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "String for '%s' argument too big\n", argv[*pi]);
+ return 0;
+}
+
+
+/**
+ * The service thread.
+ *
+ * @returns Whatever the worker function returns.
+ * @param ThreadSelf My thread handle.
+ * @param pvUser The service index.
+ */
+static DECLCALLBACK(int) vgsvcThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ const unsigned i = (uintptr_t)pvUser;
+
+#ifndef RT_OS_WINDOWS
+ /*
+ * Block all signals for this thread. Only the main thread will handle signals.
+ */
+ sigset_t signalMask;
+ sigfillset(&signalMask);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+#endif
+
+ int rc = g_aServices[i].pDesc->pfnWorker(&g_aServices[i].fShutdown);
+ ASMAtomicXchgBool(&g_aServices[i].fShutdown, true);
+ RTThreadUserSignal(ThreadSelf);
+ return rc;
+}
+
+
+/**
+ * Lazily calls the pfnPreInit method on each service.
+ *
+ * @returns VBox status code, error message displayed.
+ */
+static RTEXITCODE vgsvcLazyPreInit(void)
+{
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (!g_aServices[j].fPreInited)
+ {
+ int rc = g_aServices[j].pDesc->pfnPreInit();
+ if (RT_FAILURE(rc))
+ return VGSvcError("Service '%s' failed pre-init: %Rrc\n", g_aServices[j].pDesc->pszName, rc);
+ g_aServices[j].fPreInited = true;
+ }
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Count the number of enabled services.
+ */
+static unsigned vgsvcCountEnabledServices(void)
+{
+ unsigned cEnabled = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aServices); i++)
+ cEnabled += g_aServices[i].fEnabled;
+ return cEnabled;
+}
+
+
+#ifdef RT_OS_WINDOWS
+/**
+ * Console control event callback.
+ *
+ * @returns TRUE if handled, FALSE if not.
+ * @param dwCtrlType The control event type.
+ *
+ * @remarks This is generally called on a new thread, so we're racing every
+ * other thread in the process.
+ */
+static BOOL WINAPI vgsvcWinConsoleControlHandler(DWORD dwCtrlType) RT_NOTHROW_DEF
+{
+ int rc = VINF_SUCCESS;
+ bool fEventHandled = FALSE;
+ switch (dwCtrlType)
+ {
+ /* User pressed CTRL+C or CTRL+BREAK or an external event was sent
+ * via GenerateConsoleCtrlEvent(). */
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_C_EVENT:
+ VGSvcVerbose(2, "ControlHandler: Received break/close event\n");
+ rc = VGSvcStopServices();
+ fEventHandled = TRUE;
+ break;
+ default:
+ break;
+ /** @todo Add other events here. */
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcError("ControlHandler: Event %ld handled with error rc=%Rrc\n",
+ dwCtrlType, rc);
+ return fEventHandled;
+}
+#endif /* RT_OS_WINDOWS */
+
+
+/**
+ * Starts the service.
+ *
+ * @returns VBox status code, errors are fully bitched.
+ *
+ * @remarks Also called from VBoxService-win.cpp, thus not static.
+ */
+int VGSvcStartServices(void)
+{
+ int rc;
+
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Init);
+
+ /*
+ * Initialize the services.
+ */
+ VGSvcVerbose(2, "Initializing services ...\n");
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (g_aServices[j].fEnabled)
+ {
+ rc = g_aServices[j].pDesc->pfnInit();
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_SERVICE_DISABLED)
+ {
+ VGSvcError("Service '%s' failed to initialize: %Rrc\n", g_aServices[j].pDesc->pszName, rc);
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Failed);
+ return rc;
+ }
+
+ g_aServices[j].fEnabled = false;
+ VGSvcVerbose(0, "Service '%s' was disabled because of missing functionality\n", g_aServices[j].pDesc->pszName);
+ }
+ }
+
+ /*
+ * Start the service(s).
+ */
+ VGSvcVerbose(2, "Starting services ...\n");
+ rc = VINF_SUCCESS;
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ if (!g_aServices[j].fEnabled)
+ continue;
+
+ VGSvcVerbose(2, "Starting service '%s' ...\n", g_aServices[j].pDesc->pszName);
+ rc = RTThreadCreate(&g_aServices[j].Thread, vgsvcThread, (void *)(uintptr_t)j, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_aServices[j].pDesc->pszName);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("RTThreadCreate failed, rc=%Rrc\n", rc);
+ break;
+ }
+ g_aServices[j].fStarted = true;
+
+ /* Wait for the thread to initialize. */
+ /** @todo There is a race between waiting and checking
+ * the fShutdown flag of a thread here and processing
+ * the thread's actual worker loop. If the thread decides
+ * to exit the loop before we skipped the fShutdown check
+ * below the service will fail to start! */
+ /** @todo This presumably means either a one-shot service or that
+ * something has gone wrong. In the second case treating it as failure
+ * to start is probably right, so we need a way to signal the first
+ * rather than leaving the idle thread hanging around. A flag in the
+ * service description? */
+ RTThreadUserWait(g_aServices[j].Thread, 60 * 1000);
+ if (g_aServices[j].fShutdown)
+ {
+ VGSvcError("Service '%s' failed to start!\n", g_aServices[j].pDesc->pszName);
+ rc = VERR_GENERAL_FAILURE;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(1, "All services started.\n");
+ else
+ {
+ VGSvcError("An error occcurred while the services!\n");
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Failed);
+ }
+ return rc;
+}
+
+
+/**
+ * Stops and terminates the services.
+ *
+ * This should be called even when VBoxServiceStartServices fails so it can
+ * clean up anything that we succeeded in starting.
+ *
+ * @remarks Also called from VBoxService-win.cpp, thus not static.
+ */
+int VGSvcStopServices(void)
+{
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Terminating);
+
+ /*
+ * Signal all the services.
+ */
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ ASMAtomicWriteBool(&g_aServices[j].fShutdown, true);
+
+ /*
+ * Do the pfnStop callback on all running services.
+ */
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ if (g_aServices[j].fStarted)
+ {
+ VGSvcVerbose(3, "Calling stop function for service '%s' ...\n", g_aServices[j].pDesc->pszName);
+ g_aServices[j].pDesc->pfnStop();
+ }
+
+ VGSvcVerbose(3, "All stop functions for services called\n");
+
+ /*
+ * Wait for all the service threads to complete.
+ */
+ int rc = VINF_SUCCESS;
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ if (!g_aServices[j].fEnabled) /* Only stop services which were started before. */
+ continue;
+ if (g_aServices[j].Thread != NIL_RTTHREAD)
+ {
+ VGSvcVerbose(2, "Waiting for service '%s' to stop ...\n", g_aServices[j].pDesc->pszName);
+ int rc2 = VINF_SUCCESS;
+ for (int i = 0; i < 30; i++) /* Wait 30 seconds in total */
+ {
+ rc2 = RTThreadWait(g_aServices[j].Thread, 1000 /* Wait 1 second */, NULL);
+ if (RT_SUCCESS(rc2))
+ break;
+#ifdef RT_OS_WINDOWS
+ /* Notify SCM that it takes a bit longer ... */
+ VGSvcWinSetStopPendingStatus(i + j*32);
+#endif
+ }
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Service '%s' failed to stop. (%Rrc)\n", g_aServices[j].pDesc->pszName, rc2);
+ rc = rc2;
+ }
+ }
+ VGSvcVerbose(3, "Terminating service '%s' (%d) ...\n", g_aServices[j].pDesc->pszName, j);
+ g_aServices[j].pDesc->pfnTerm();
+ }
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Wake up and tell the main() thread that we're shutting down (it's
+ * sleeping in VBoxServiceMainWait).
+ */
+ ASMAtomicWriteBool(&g_fWindowsServiceShutdown, true);
+ if (g_hEvtWindowsService != NIL_RTSEMEVENT)
+ {
+ VGSvcVerbose(3, "Stopping the main thread...\n");
+ int rc2 = RTSemEventSignal(g_hEvtWindowsService);
+ AssertRC(rc2);
+ }
+#endif
+
+ VGSvcVerbose(2, "Stopping services returning: %Rrc\n", rc);
+ VGSvcReportStatus(RT_SUCCESS(rc) ? VBoxGuestFacilityStatus_Paused : VBoxGuestFacilityStatus_Failed);
+ return rc;
+}
+
+
+/**
+ * Block the main thread until the service shuts down.
+ *
+ * @remarks Also called from VBoxService-win.cpp, thus not static.
+ */
+void VGSvcMainWait(void)
+{
+ int rc;
+
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Active);
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Wait for the semaphore to be signalled.
+ */
+ VGSvcVerbose(1, "Waiting in main thread\n");
+ rc = RTSemEventCreate(&g_hEvtWindowsService);
+ AssertRC(rc);
+ while (!ASMAtomicReadBool(&g_fWindowsServiceShutdown))
+ {
+ rc = RTSemEventWait(g_hEvtWindowsService, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ RTSemEventDestroy(g_hEvtWindowsService);
+ g_hEvtWindowsService = NIL_RTSEMEVENT;
+#else
+ /*
+ * Wait explicitly for a HUP, INT, QUIT, ABRT or TERM signal, blocking
+ * all important signals.
+ *
+ * The annoying EINTR/ERESTART loop is for the benefit of Solaris where
+ * sigwait returns when we receive a SIGCHLD. Kind of makes sense since
+ * the signal has to be delivered... Anyway, darwin (10.9.5) has a much
+ * worse way of dealing with SIGCHLD, apparently it'll just return any
+ * of the signals we're waiting on when SIGCHLD becomes pending on this
+ * thread. So, we wait for SIGCHLD here and ignores it.
+ */
+ sigset_t signalMask;
+ sigemptyset(&signalMask);
+ sigaddset(&signalMask, SIGHUP);
+ sigaddset(&signalMask, SIGINT);
+ sigaddset(&signalMask, SIGQUIT);
+ sigaddset(&signalMask, SIGABRT);
+ sigaddset(&signalMask, SIGTERM);
+ sigaddset(&signalMask, SIGCHLD);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+
+ int iSignal;
+ do
+ {
+ iSignal = -1;
+ rc = sigwait(&signalMask, &iSignal);
+ }
+ while ( rc == EINTR
+# ifdef ERESTART
+ || rc == ERESTART
+# endif
+ || iSignal == SIGCHLD
+ );
+
+ VGSvcVerbose(3, "VGSvcMainWait: Received signal %d (rc=%d)\n", iSignal, rc);
+#endif /* !RT_OS_WINDOWS */
+}
+
+
+/**
+ * Report VbglR3InitUser / VbglR3Init failure.
+ *
+ * @returns RTEXITCODE_FAILURE
+ * @param rcVbgl The failing status code.
+ */
+static RTEXITCODE vbglInitFailure(int rcVbgl)
+{
+ if (rcVbgl == VERR_ACCESS_DENIED)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "Insufficient privileges to start %s! Please start with Administrator/root privileges!\n",
+ g_pszProgName);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "VbglR3Init failed with rc=%Rrc\n", rcVbgl);
+}
+
+
+int main(int argc, char **argv)
+{
+ RTEXITCODE rcExit;
+
+ /*
+ * Init globals and such.
+ *
+ * Note! The --utf8-argv stuff is an internal hack to avoid locale configuration
+ * issues preventing us from passing non-ASCII string to child processes.
+ */
+ uint32_t fIprtFlags = 0;
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+ if (argc > 1 && strcmp(argv[1], VBOXSERVICE_ARG1_UTF8_ARGV) == 0)
+ {
+ argv[1] = argv[0];
+ argv++;
+ argc--;
+ fIprtFlags |= RTR3INIT_FLAGS_UTF8_ARGV;
+ }
+#endif
+ int rc = RTR3InitExe(argc, &argv, fIprtFlags);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ g_pszProgName = RTPathFilename(argv[0]);
+#ifdef RT_OS_WINDOWS
+ VGSvcWinResolveApis();
+#endif
+#ifdef DEBUG
+ rc = RTCritSectInit(&g_csLog);
+ AssertRC(rc);
+#endif
+
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ /*
+ * Run toolbox code before all other stuff since these things are simpler
+ * shell/file/text utility like programs that just happens to be inside
+ * VBoxService and shouldn't be subject to /dev/vboxguest, pid-files and
+ * global mutex restrictions.
+ */
+ if (VGSvcToolboxMain(argc, argv, &rcExit))
+ return rcExit;
+#endif
+
+ bool fUserSession = false;
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ /*
+ * Check if we're the specially spawned VBoxService.exe process that
+ * handles a guest control session.
+ */
+ if ( argc >= 2
+ && !RTStrICmp(argv[1], VBOXSERVICECTRLSESSION_GETOPT_PREFIX))
+ fUserSession = true;
+#endif
+
+ /*
+ * Connect to the kernel part before daemonizing and *before* we do the sub-service
+ * pre-init just in case one of services needs do to some initial stuff with it.
+ *
+ * However, we do not fail till after we've parsed arguments, because that will
+ * prevent useful stuff like --help, --register, --unregister and --version from
+ * working when the driver hasn't been installed/loaded yet.
+ */
+ int const rcVbgl = fUserSession ? VbglR3InitUser() : VbglR3Init();
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Check if we're the specially spawned VBoxService.exe process that
+ * handles page fusion. This saves an extra statically linked executable.
+ */
+ if ( argc == 2
+ && !RTStrICmp(argv[1], "pagefusion"))
+ {
+ if (RT_SUCCESS(rcVbgl))
+ return VGSvcPageSharingWorkerChild();
+ return vbglInitFailure(rcVbgl);
+ }
+#endif
+
+#ifdef VBOX_WITH_VBOXSERVICE_CONTROL
+ /*
+ * Check if we're the specially spawned VBoxService.exe process that
+ * handles a guest control session.
+ */
+ if (fUserSession)
+ {
+ if (RT_SUCCESS(rcVbgl))
+ return VGSvcGstCtrlSessionSpawnInit(argc, argv);
+ return vbglInitFailure(rcVbgl);
+ }
+#endif
+
+ /*
+ * Parse the arguments.
+ *
+ * Note! This code predates RTGetOpt, thus the manual parsing.
+ */
+ bool fDaemonize = true;
+ bool fDaemonized = false;
+ for (int i = 1; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ if (*psz != '-')
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'\n", psz);
+ psz++;
+
+ /* translate long argument to short */
+ if (*psz == '-')
+ {
+ psz++;
+ size_t cch = strlen(psz);
+#define MATCHES(strconst) ( cch == sizeof(strconst) - 1 \
+ && !memcmp(psz, strconst, sizeof(strconst) - 1) )
+ if (MATCHES("foreground"))
+ psz = "f";
+ else if (MATCHES("verbose"))
+ psz = "v";
+ else if (MATCHES("version"))
+ psz = "V";
+ else if (MATCHES("help"))
+ psz = "h";
+ else if (MATCHES("interval"))
+ psz = "i";
+#ifdef RT_OS_WINDOWS
+ else if (MATCHES("register"))
+ psz = "r";
+ else if (MATCHES("unregister"))
+ psz = "u";
+#endif
+ else if (MATCHES("logfile"))
+ psz = "l";
+ else if (MATCHES("pidfile"))
+ psz = "p";
+ else if (MATCHES("daemonized"))
+ {
+ fDaemonized = true;
+ continue;
+ }
+ else
+ {
+ bool fFound = false;
+
+ if (cch > sizeof("enable-") && !memcmp(psz, RT_STR_TUPLE("enable-")))
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
+ if ((fFound = !RTStrICmp(psz + sizeof("enable-") - 1, g_aServices[j].pDesc->pszName)))
+ g_aServices[j].fEnabled = true;
+
+ if (cch > sizeof("disable-") && !memcmp(psz, RT_STR_TUPLE("disable-")))
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
+ if ((fFound = !RTStrICmp(psz + sizeof("disable-") - 1, g_aServices[j].pDesc->pszName)))
+ g_aServices[j].fEnabled = false;
+
+ if (cch > sizeof("only-") && !memcmp(psz, RT_STR_TUPLE("only-")))
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ g_aServices[j].fEnabled = !RTStrICmp(psz + sizeof("only-") - 1, g_aServices[j].pDesc->pszName);
+ if (g_aServices[j].fEnabled)
+ fFound = true;
+ }
+
+ if (!fFound)
+ {
+ rcExit = vgsvcLazyPreInit();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ for (unsigned j = 0; !fFound && j < RT_ELEMENTS(g_aServices); j++)
+ {
+ rc = g_aServices[j].pDesc->pfnOption(NULL, argc, argv, &i);
+ fFound = rc == VINF_SUCCESS;
+ if (fFound)
+ break;
+ if (rc != -1)
+ return rc;
+ }
+ }
+ if (!fFound)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%s'\n", argv[i]);
+ continue;
+ }
+#undef MATCHES
+ }
+
+ /* handle the string of short options. */
+ do
+ {
+ switch (*psz)
+ {
+ case 'i':
+ rc = VGSvcArgUInt32(argc, argv, psz + 1, &i, &g_DefaultInterval, 1, (UINT32_MAX / 1000) - 1);
+ if (rc)
+ return rc;
+ psz = NULL;
+ break;
+
+ case 'f':
+ fDaemonize = false;
+ break;
+
+ case 'v':
+ g_cVerbosity++;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+
+ case 'h':
+ case '?':
+ return vgsvcUsage();
+
+#ifdef RT_OS_WINDOWS
+ case 'r':
+ return VGSvcWinInstall();
+
+ case 'u':
+ return VGSvcWinUninstall();
+#endif
+
+ case 'l':
+ {
+ rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szLogFile, sizeof(g_szLogFile));
+ if (rc)
+ return rc;
+ psz = NULL;
+ break;
+ }
+
+ case 'p':
+ {
+ rc = vgsvcArgString(argc, argv, psz + 1, &i, g_szPidFile, sizeof(g_szPidFile));
+ if (rc)
+ return rc;
+ psz = NULL;
+ break;
+ }
+
+ default:
+ {
+ rcExit = vgsvcLazyPreInit();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ bool fFound = false;
+ for (unsigned j = 0; j < RT_ELEMENTS(g_aServices); j++)
+ {
+ rc = g_aServices[j].pDesc->pfnOption(&psz, argc, argv, &i);
+ fFound = rc == VINF_SUCCESS;
+ if (fFound)
+ break;
+ if (rc != -1)
+ return rc;
+ }
+ if (!fFound)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown option '%c' (%s)\n", *psz, argv[i]);
+ break;
+ }
+ }
+ } while (psz && *++psz);
+ }
+
+ /* Now we can report the VBGL failure. */
+ if (RT_FAILURE(rcVbgl))
+ return vbglInitFailure(rcVbgl);
+
+ /* Check that at least one service is enabled. */
+ if (vgsvcCountEnabledServices() == 0)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "At least one service must be enabled\n");
+
+ rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log '%s', rc=%Rrc\n",
+ g_szLogFile[0] ? g_szLogFile : "<None>", rc);
+
+ /* Call pre-init if we didn't do it already. */
+ rcExit = vgsvcLazyPreInit();
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+#ifdef VBOX_WITH_VBOXSERVICE_DRMRESIZE
+# ifdef RT_OS_LINUX
+ rc = VbglR3DrmClientStart();
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(0, "VMSVGA DRM resizing client not started, rc=%Rrc\n", rc);
+# endif /* RT_OS_LINUX */
+#endif /* VBOX_WITH_VBOXSERVICE_DRMRESIZE */
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Make sure only one instance of VBoxService runs at a time. Create a
+ * global mutex for that.
+ *
+ * Note! The \\Global\ namespace was introduced with Win2K, thus the
+ * version check.
+ * Note! If the mutex exists CreateMutex will open it and set last error to
+ * ERROR_ALREADY_EXISTS.
+ */
+ OSVERSIONINFOEX OSInfoEx;
+ RT_ZERO(OSInfoEx);
+ OSInfoEx.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+
+ SetLastError(NO_ERROR);
+ HANDLE hMutexAppRunning;
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(5,0,0)) /* Windows 2000 */
+ hMutexAppRunning = CreateMutexW(NULL, FALSE, L"Global\\" RT_CONCAT(L,VBOXSERVICE_NAME));
+ else
+ hMutexAppRunning = CreateMutexW(NULL, FALSE, RT_CONCAT(L,VBOXSERVICE_NAME));
+ if (hMutexAppRunning == NULL)
+ {
+ DWORD dwErr = GetLastError();
+ if ( dwErr == ERROR_ALREADY_EXISTS
+ || dwErr == ERROR_ACCESS_DENIED)
+ {
+ VGSvcError("%s is already running! Terminating.\n", g_pszProgName);
+ return RTEXITCODE_FAILURE;
+ }
+
+ VGSvcError("CreateMutex failed with last error %u! Terminating.\n", GetLastError());
+ return RTEXITCODE_FAILURE;
+ }
+
+#else /* !RT_OS_WINDOWS */
+ /* On other OSes we have PID file support provided by the actual service definitions / service wrapper scripts,
+ * like vboxadd-service.sh on Linux or vboxservice.xml on Solaris. */
+#endif /* !RT_OS_WINDOWS */
+
+ VGSvcVerbose(0, "%s r%s started. Verbose level = %d\n", RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity);
+
+ /*
+ * Daemonize if requested.
+ */
+ if (fDaemonize && !fDaemonized)
+ {
+#ifdef RT_OS_WINDOWS
+ VGSvcVerbose(2, "Starting service dispatcher ...\n");
+ rcExit = VGSvcWinEnterCtrlDispatcher();
+#else
+ VGSvcVerbose(1, "Daemonizing...\n");
+ rc = VbglR3Daemonize(false /* fNoChDir */, false /* fNoClose */,
+ false /* fRespawn */, NULL /* pcRespawn */);
+ if (RT_FAILURE(rc))
+ return VGSvcError("Daemon failed: %Rrc\n", rc);
+ /* in-child */
+#endif
+ }
+#ifdef RT_OS_WINDOWS
+ else
+#endif
+ {
+ /*
+ * Windows: We're running the service as a console application now. Start the
+ * services, enter the main thread's run loop and stop them again
+ * when it returns.
+ *
+ * POSIX: This is used for both daemons and console runs. Start all services
+ * and return immediately.
+ */
+#ifdef RT_OS_WINDOWS
+ /* Install console control handler. */
+ if (!SetConsoleCtrlHandler(vgsvcWinConsoleControlHandler, TRUE /* Add handler */))
+ {
+ VGSvcError("Unable to add console control handler, error=%ld\n", GetLastError());
+ /* Just skip this error, not critical. */
+ }
+#endif /* RT_OS_WINDOWS */
+ rc = VGSvcStartServices();
+ RTFILE hPidFile = NIL_RTFILE;
+ if (RT_SUCCESS(rc))
+ if (g_szPidFile[0])
+ rc = VbglR3PidFile(g_szPidFile, &hPidFile);
+ rcExit = RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+ if (RT_SUCCESS(rc))
+ VGSvcMainWait();
+ if (g_szPidFile[0] && hPidFile != NIL_RTFILE)
+ VbglR3ClosePidFile(g_szPidFile, hPidFile);
+#ifdef RT_OS_WINDOWS
+ /* Uninstall console control handler. */
+ if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)NULL, FALSE /* Remove handler */))
+ {
+ VGSvcError("Unable to remove console control handler, error=%ld\n", GetLastError());
+ /* Just skip this error, not critical. */
+ }
+#else /* !RT_OS_WINDOWS */
+ /* On Windows - since we're running as a console application - we already stopped all services
+ * through the console control handler. So only do the stopping of services here on other platforms
+ * where the break/shutdown/whatever signal was just received. */
+ VGSvcStopServices();
+#endif /* RT_OS_WINDOWS */
+ }
+ VGSvcReportStatus(VBoxGuestFacilityStatus_Terminated);
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Cleanup mutex.
+ */
+ CloseHandle(hMutexAppRunning);
+#endif
+
+ VGSvcVerbose(0, "Ended.\n");
+
+#ifdef DEBUG
+ RTCritSectDelete(&g_csLog);
+ //RTMemTrackerDumpAllToStdOut();
+#endif
+
+ VGSvcLogDestroy();
+
+ return rcExit;
+}
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp
new file mode 100644
index 00000000..75bf718a
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceAutoMount.cpp
@@ -0,0 +1,2194 @@
+/* $Id: VBoxServiceAutoMount.cpp $ */
+/** @file
+ * VBoxService - Auto-mounting for Shared Folders, only Linux & Solaris atm.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_vgsvc_automount VBoxService - Shared Folder Automounter
+ *
+ * The Shared Folder Automounter subservice mounts shared folders upon request
+ * from the host.
+ *
+ * This retrieves shared folder automount requests from Main via the VMMDev.
+ * The current implemention only does this once, for some inexplicable reason,
+ * so the run-time addition of automounted shared folders are not heeded.
+ *
+ * This subservice is only used on linux and solaris. On Windows the current
+ * thinking is this is better of done from VBoxTray, some one argue that for
+ * drive letter assigned shared folders it would be better to do some magic here
+ * (obviously not involving NDAddConnection).
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/sort.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/shflsvc.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+#ifdef RT_OS_WINDOWS
+#elif defined(RT_OS_OS2)
+# define INCL_DOSFILEMGR
+# define INCL_ERRORS
+# define OS2EMX_PLAIN_CHAR
+# include <os2emx.h>
+#else
+# include <errno.h>
+# include <grp.h>
+# include <sys/mount.h>
+# ifdef RT_OS_SOLARIS
+# include <sys/mntent.h>
+# include <sys/mnttab.h>
+# include <sys/vfs.h>
+# elif defined(RT_OS_LINUX)
+# include <mntent.h>
+# include <paths.h>
+# include <sys/utsname.h>
+RT_C_DECLS_BEGIN
+# include "../../linux/sharedfolders/vbsfmount.h"
+RT_C_DECLS_END
+# else
+# error "Port me!"
+# endif
+# include <unistd.h>
+#endif
+
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR
+ * Default mount directory (unix only).
+ */
+#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR
+# define VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/media"
+#endif
+
+/** @def VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX
+ * Default mount prefix (unix only).
+ */
+#ifndef VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX
+# define VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX "sf_"
+#endif
+
+#ifndef _PATH_MOUNTED
+# ifdef RT_OS_SOLARIS
+# define _PATH_MOUNTED "/etc/mnttab"
+# else
+# define _PATH_MOUNTED "/etc/mtab"
+# endif
+#endif
+
+/** @def VBOXSERVICE_AUTOMOUNT_MIQF
+ * The drive letter / path mount point flag. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_DRIVE_LETTER
+#else
+# define VBOXSERVICE_AUTOMOUNT_MIQF SHFL_MIQF_PATH
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Automounter mount table entry.
+ *
+ * This holds the information returned by SHFL_FN_QUERY_MAP_INFO and
+ * additional mount state info. We only keep entries for mounted mappings.
+ */
+typedef struct VBSVCAUTOMOUNTERENTRY
+{
+ /** The root ID. */
+ uint32_t idRoot;
+ /** The root ID version. */
+ uint32_t uRootIdVersion;
+ /** Map info flags, SHFL_MIF_XXX. */
+ uint64_t fFlags;
+ /** The shared folder (mapping) name. */
+ char *pszName;
+ /** The configured mount point, NULL if none. */
+ char *pszMountPoint;
+ /** The actual mount point, NULL if not mount. */
+ char *pszActualMountPoint;
+} VBSVCAUTOMOUNTERENTRY;
+/** Pointer to an automounter entry. */
+typedef VBSVCAUTOMOUNTERENTRY *PVBSVCAUTOMOUNTERENTRY;
+
+/** Automounter mount table. */
+typedef struct VBSVCAUTOMOUNTERTABLE
+{
+ /** Current number of entries in the array. */
+ uint32_t cEntries;
+ /** Max number of entries the array can hold w/o growing it. */
+ uint32_t cAllocated;
+ /** Pointer to an array of entry pointers. */
+ PVBSVCAUTOMOUNTERENTRY *papEntries;
+} VBSVCAUTOMOUNTERTABLE;
+/** Pointer to an automounter mount table. */
+typedef VBSVCAUTOMOUNTERTABLE *PVBSVCAUTOMOUNTERTABLE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
+/** The Shared Folders service client ID. */
+static uint32_t g_idClientSharedFolders = 0;
+/** Set if we can wait on changes to the mappings. */
+static bool g_fHostSupportsWaitAndInfoQuery = false;
+
+#ifdef RT_OS_OS2
+/** The attachment tag we use to identify attchments that belongs to us. */
+static char const g_szTag[] = "VBoxAutomounter";
+#elif defined(RT_OS_LINUX)
+/** Tag option value that lets us identify mounts that belongs to us. */
+static char const g_szTag[] = "VBoxAutomounter";
+#elif defined(RT_OS_SOLARIS)
+/** Dummy mount option that lets us identify mounts that belongs to us. */
+static char const g_szTag[] = "VBoxAutomounter";
+#endif
+
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbsvcAutomounterInit(void)
+{
+ VGSvcVerbose(3, "vbsvcAutomounterInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_hAutoMountEvent);
+ AssertRCReturn(rc, rc);
+
+ rc = VbglR3SharedFolderConnect(&g_idClientSharedFolders);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterInit: Service Client ID: %#x\n", g_idClientSharedFolders);
+ g_fHostSupportsWaitAndInfoQuery = RT_SUCCESS(VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders));
+ }
+ else
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterInit: Shared Folders service is not available\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ VGSvcError("Control: Failed to connect to the Shared Folders service! Error: %Rrc\n", rc);
+ RTSemEventMultiDestroy(g_hAutoMountEvent);
+ g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
+ }
+
+ return rc;
+}
+
+
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) /* The old code: */
+
+/**
+ * @todo Integrate into RTFsQueryMountpoint()?
+ */
+static bool vbsvcAutoMountShareIsMountedOld(const char *pszShare, char *pszMountPoint, size_t cbMountPoint)
+{
+ AssertPtrReturn(pszShare, false);
+ AssertPtrReturn(pszMountPoint, false);
+ AssertReturn(cbMountPoint, false);
+
+ bool fMounted = false;
+
+# if defined(RT_OS_SOLARIS)
+ /** @todo What to do if we have a relative path in mtab instead
+ * of an absolute one ("temp" vs. "/media/temp")?
+ * procfs contains the full path but not the actual share name ...
+ * FILE *pFh = setmntent("/proc/mounts", "r+t"); */
+ FILE *pFh = fopen(_PATH_MOUNTED, "r");
+ if (!pFh)
+ VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED);
+ else
+ {
+ mnttab mntTab;
+ while ((getmntent(pFh, &mntTab)))
+ {
+ if (!RTStrICmp(mntTab.mnt_special, pszShare))
+ {
+ fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", mntTab.mnt_mountp)
+ ? true : false;
+ break;
+ }
+ }
+ fclose(pFh);
+ }
+# elif defined(RT_OS_LINUX)
+ FILE *pFh = setmntent(_PATH_MOUNTED, "r+t"); /** @todo r=bird: why open it for writing? (the '+') */
+ if (pFh == NULL)
+ VGSvcError("vbsvcAutoMountShareIsMountedOld: Could not open mount tab '%s'!\n", _PATH_MOUNTED);
+ else
+ {
+ mntent *pMntEnt;
+ while ((pMntEnt = getmntent(pFh)))
+ {
+ if (!RTStrICmp(pMntEnt->mnt_fsname, pszShare))
+ {
+ fMounted = RTStrPrintf(pszMountPoint, cbMountPoint, "%s", pMntEnt->mnt_dir)
+ ? true : false;
+ break;
+ }
+ }
+ endmntent(pFh);
+ }
+# else
+# error "PORTME!"
+# endif
+
+ VGSvcVerbose(4, "vbsvcAutoMountShareIsMountedOld: Share '%s' at mount point '%s' = %s\n",
+ pszShare, fMounted ? pszMountPoint : "<None>", fMounted ? "Yes" : "No");
+ return fMounted;
+}
+
+
+/**
+ * Unmounts a shared folder.
+ *
+ * @returns VBox status code
+ * @param pszMountPoint The shared folder mount point.
+ */
+static int vbsvcAutoMountUnmountOld(const char *pszMountPoint)
+{
+ AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ uint8_t uTries = 0;
+ int r;
+ while (uTries++ < 3)
+ {
+ r = umount(pszMountPoint);
+ if (r == 0)
+ break;
+/** @todo r=bird: Why do sleep 5 seconds after the final retry?
+ * May also be a good idea to check for EINVAL or other signs that someone
+ * else have already unmounted the share. */
+ RTThreadSleep(5000); /* Wait a while ... */
+ }
+ if (r == -1) /** @todo r=bird: RTThreadSleep set errno. */
+ rc = RTErrConvertFromErrno(errno);
+ return rc;
+}
+
+
+/**
+ * Prepares a mount point (create it, set group and mode).
+ *
+ * @returns VBox status code
+ * @param pszMountPoint The mount point.
+ * @param pszShareName Unused.
+ * @param gidGroup The group ID.
+ */
+static int vbsvcAutoMountPrepareMountPointOld(const char *pszMountPoint, const char *pszShareName, RTGID gidGroup)
+{
+ AssertPtrReturn(pszMountPoint, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszShareName, VERR_INVALID_PARAMETER);
+
+ /** @todo r=bird: There is no reason why gidGroup should have write access?
+ * Seriously, what kind of non-sense is this? */
+
+ RTFMODE fMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG; /* Owner (=root) and the group (=vboxsf) have full access. */
+ int rc = RTDirCreateFullPath(pszMountPoint, fMode);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathSetOwnerEx(pszMountPoint, NIL_RTUID /* Owner, unchanged */, gidGroup, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathSetMode(pszMountPoint, fMode);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_WRITE_PROTECT)
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountPrepareMountPointOld: Mount directory '%s' already is used/mounted\n",
+ pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set mode %RTfmode for mount directory '%s', rc = %Rrc\n",
+ fMode, pszMountPoint, rc);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not set permissions for mount directory '%s', rc = %Rrc\n",
+ pszMountPoint, rc);
+ }
+ else
+ VGSvcError("vbsvcAutoMountPrepareMountPointOld: Could not create mount directory '%s' with mode %RTfmode, rc = %Rrc\n",
+ pszMountPoint, fMode, rc);
+ return rc;
+}
+
+
+/**
+ * Mounts a shared folder.
+ *
+ * @returns VBox status code reflecting unmount and mount point preparation
+ * results, but not actual mounting
+ *
+ * @param pszShareName The shared folder name.
+ * @param pszMountPoint The mount point.
+ */
+static int vbsvcAutoMountSharedFolderOld(const char *pszShareName, const char *pszMountPoint)
+{
+ /*
+ * Linux and solaris share the same mount structure.
+ */
+ struct group *grp_vboxsf = getgrnam("vboxsf");
+ if (!grp_vboxsf)
+ {
+ VGSvcError("vbsvcAutoMountWorker: Group 'vboxsf' does not exist\n");
+ return VINF_SUCCESS;
+ }
+
+ int rc = vbsvcAutoMountPrepareMountPointOld(pszMountPoint, pszShareName, grp_vboxsf->gr_gid);
+ if (RT_SUCCESS(rc))
+ {
+# ifdef RT_OS_SOLARIS
+ int const fFlags = MS_OPTIONSTR;
+ char szOptBuf[MAX_MNTOPT_STR] = { '\0', };
+ RTStrPrintf(szOptBuf, sizeof(szOptBuf), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000", grp_vboxsf->gr_gid);
+ int r = mount(pszShareName,
+ pszMountPoint,
+ fFlags,
+ "vboxfs",
+ NULL, /* char *dataptr */
+ 0, /* int datalen */
+ szOptBuf,
+ sizeof(szOptBuf));
+ if (r == 0)
+ VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint);
+ else if (errno != EBUSY) /* Share is already mounted? Then skip error msg. */
+ VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s', error = %s\n",
+ pszShareName, pszMountPoint, strerror(errno));
+
+# else /* RT_OS_LINUX */
+ struct utsname uts;
+ AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0"));
+
+ unsigned long const fFlags = MS_NODEV;
+ char szOpts[MAX_MNTOPT_STR] = { '\0' };
+ ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts), "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000",
+ grp_vboxsf->gr_gid);
+ if (cchOpts > 0 && RTStrVersionCompare(uts.release, "2.6.0") < 0)
+ cchOpts = RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pszShareName);
+ if (cchOpts <= 0)
+ {
+ VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd (share %s)\n", cchOpts, pszShareName);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ int r = mount(pszShareName,
+ pszMountPoint,
+ "vboxsf",
+ fFlags,
+ szOpts);
+ if (r == 0)
+ {
+ VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' was mounted to '%s'\n", pszShareName, pszMountPoint);
+
+ r = vbsfmount_complete(pszShareName, pszMountPoint, fFlags, szOpts);
+ switch (r)
+ {
+ case 0: /* Success. */
+ errno = 0; /* Clear all errors/warnings. */
+ break;
+ case 1:
+ VGSvcError("vbsvcAutoMountWorker: Could not update mount table (malloc failure)\n");
+ break;
+ case 2:
+ VGSvcError("vbsvcAutoMountWorker: Could not open mount table for update: %s\n", strerror(errno));
+ break;
+ case 3:
+ /* VGSvcError("vbsvcAutoMountWorker: Could not add an entry to the mount table: %s\n", strerror(errno)); */
+ errno = 0;
+ break;
+ default:
+ VGSvcError("vbsvcAutoMountWorker: Unknown error while completing mount operation: %d\n", r);
+ break;
+ }
+ }
+ else /* r == -1, we got some error in errno. */
+ {
+ switch (errno)
+ {
+ /* If we get EINVAL here, the system already has mounted the Shared Folder to another
+ * mount point. */
+ case EINVAL:
+ VGSvcVerbose(0, "vbsvcAutoMountWorker: Shared folder '%s' is already mounted!\n", pszShareName);
+ /* Ignore this error! */
+ break;
+ case EBUSY:
+ /* Ignore these errors! */
+ break;
+ default:
+ VGSvcError("vbsvcAutoMountWorker: Could not mount shared folder '%s' to '%s': %s (%d)\n",
+ pszShareName, pszMountPoint, strerror(errno), errno);
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+ }
+# endif
+ }
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Mounting returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Processes shared folder mappings retrieved from the host.
+ *
+ * @returns VBox status code.
+ * @param paMappings The mappings.
+ * @param cMappings The number of mappings.
+ * @param pszMountDir The mount directory.
+ * @param pszSharePrefix The share prefix.
+ * @param uClientID The shared folder service (HGCM) client ID.
+ */
+static int vbsvcAutoMountProcessMappingsOld(PCVBGLR3SHAREDFOLDERMAPPING paMappings, uint32_t cMappings,
+ const char *pszMountDir, const char *pszSharePrefix, uint32_t uClientID)
+{
+ if (cMappings == 0)
+ return VINF_SUCCESS;
+ AssertPtrReturn(paMappings, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszMountDir, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszSharePrefix, VERR_INVALID_PARAMETER);
+ AssertReturn(uClientID > 0, VERR_INVALID_PARAMETER);
+
+ /** @todo r=bird: Why is this loop schitzoid about status codes? It quits if
+ * RTPathJoin fails (i.e. if the user specifies a very long name), but happily
+ * continues if RTStrAPrintf failes (mem alloc).
+ *
+ * It also happily continues if the 'vboxsf' group is missing, which is a waste
+ * of effort... In fact, retrieving the group ID could probably be done up
+ * front, outside the loop. */
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++)
+ {
+ char *pszShareName = NULL;
+ rc = VbglR3SharedFolderGetName(uClientID, paMappings[i].u32Root, &pszShareName);
+ if ( RT_SUCCESS(rc)
+ && *pszShareName)
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Connecting share %u (%s) ...\n", i+1, pszShareName);
+
+ /** @todo r=bird: why do you copy things twice here and waste heap space?
+ * szMountPoint has a fixed size.
+ * @code
+ * char szMountPoint[RTPATH_MAX];
+ * rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, *pszSharePrefix ? pszSharePrefix : pszShareName);
+ * if (RT_SUCCESS(rc) && *pszSharePrefix)
+ * rc = RTStrCat(szMountPoint, sizeof(szMountPoint), pszShareName);
+ * @endcode */
+ char *pszShareNameFull = NULL;
+ if (RTStrAPrintf(&pszShareNameFull, "%s%s", pszSharePrefix, pszShareName) > 0)
+ {
+ char szMountPoint[RTPATH_MAX];
+ rc = RTPathJoin(szMountPoint, sizeof(szMountPoint), pszMountDir, pszShareNameFull);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "vbsvcAutoMountWorker: Processing mount point '%s'\n", szMountPoint);
+
+ /*
+ * Already mounted?
+ */
+ /** @todo r-bird: this does not take into account that a shared folder could
+ * be mounted twice... We're really just interested in whether the
+ * folder is mounted on 'szMountPoint', no where else... */
+ bool fSkip = false;
+ char szAlreadyMountedOn[RTPATH_MAX];
+ if (vbsvcAutoMountShareIsMountedOld(pszShareName, szAlreadyMountedOn, sizeof(szAlreadyMountedOn)))
+ {
+ /* Do if it not mounted to our desired mount point */
+ if (RTStrICmp(szMountPoint, szAlreadyMountedOn))
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', unmounting ...\n",
+ pszShareName, szAlreadyMountedOn);
+ rc = vbsvcAutoMountUnmountOld(szAlreadyMountedOn);
+ if (RT_SUCCESS(rc))
+ fSkip = false;
+ else
+ VGSvcError("vbsvcAutoMountWorker: Failed to unmount '%s', %s (%d)! (rc=%Rrc)\n",
+ szAlreadyMountedOn, strerror(errno), errno, rc); /** @todo errno isn't reliable at this point */
+ }
+ if (fSkip)
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder '%s' already mounted on '%s', skipping\n",
+ pszShareName, szAlreadyMountedOn);
+ }
+ if (!fSkip)
+ {
+ /*
+ * Mount it.
+ */
+ rc = vbsvcAutoMountSharedFolderOld(pszShareName, szMountPoint);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Unable to join mount point/prefix/shrae, rc = %Rrc\n", rc);
+ RTStrFree(pszShareNameFull);
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Unable to allocate full share name\n");
+ RTStrFree(pszShareName);
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder name for root node = %u, rc = %Rrc\n",
+ paMappings[i].u32Root, rc);
+ } /* for cMappings. */
+ return rc;
+}
+
+#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) - the old code*/
+
+
+/**
+ * Service worker function for old host.
+ *
+ * This only mount stuff on startup.
+ *
+ * @returns VBox status code.
+ * @param pfShutdown Shutdown indicator.
+ */
+static int vbsvcAutoMountWorkerOld(bool volatile *pfShutdown)
+{
+#if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
+ /*
+ * We only do a single pass here.
+ */
+ uint32_t cMappings;
+ PVBGLR3SHAREDFOLDERMAPPING paMappings;
+ int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /* Only process auto-mounted folders */,
+ &paMappings, &cMappings);
+ if ( RT_SUCCESS(rc)
+ && cMappings)
+ {
+ char *pszMountDir;
+ rc = VbglR3SharedFolderGetMountDir(&pszMountDir);
+ if (rc == VERR_NOT_FOUND)
+ rc = RTStrDupEx(&pszMountDir, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount dir set to '%s'\n", pszMountDir);
+
+ char *pszSharePrefix;
+ rc = VbglR3SharedFolderGetMountPrefix(&pszSharePrefix);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Shared folder mount prefix set to '%s'\n", pszSharePrefix);
+# ifdef USE_VIRTUAL_SHARES
+ /* Check for a fixed/virtual auto-mount share. */
+ if (VbglR3SharedFolderExists(g_idClientSharedFolders, "vbsfAutoMount"))
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Host supports auto-mount root\n");
+ else
+ {
+# endif
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: Got %u shared folder mappings\n", cMappings);
+ rc = vbsvcAutoMountProcessMappingsOld(paMappings, cMappings, pszMountDir, pszSharePrefix,
+ g_idClientSharedFolders);
+# ifdef USE_VIRTUAL_SHARES
+ }
+# endif
+ RTStrFree(pszSharePrefix);
+ } /* Mount share prefix. */
+ else
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mount prefix, rc = %Rrc\n", rc);
+ RTStrFree(pszMountDir);
+ }
+ else
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder directory, rc = %Rrc\n", rc);
+ VbglR3SharedFolderFreeMappings(paMappings);
+ }
+ else if (RT_FAILURE(rc))
+ VGSvcError("vbsvcAutoMountWorker: Error while getting the shared folder mappings, rc = %Rrc\n", rc);
+ else
+ VGSvcVerbose(3, "vbsvcAutoMountWorker: No shared folder mappings found\n");
+
+#else
+ int rc = VINF_SUCCESS;
+#endif /* defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX) */
+
+
+ /*
+ * Wait on shutdown (this used to be a silly RTThreadSleep(500) loop).
+ */
+ while (!*pfShutdown)
+ {
+ rc = RTSemEventMultiWait(g_hAutoMountEvent, RT_MS_1MIN);
+ if (rc != VERR_TIMEOUT)
+ break;
+ }
+
+ VGSvcVerbose(3, "vbsvcAutoMountWorkerOld: Finished with rc=%Rrc\n", rc);
+ return rc;
+}
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+/**
+ * Assembles the mount directory and prefix into @a pszDst.
+ *
+ * Will fall back on defaults if we have trouble with the configuration from the
+ * host. This ASSUMES that @a cbDst is rather large and won't cause trouble
+ * with the default.
+ *
+ * @returns IPRT status code.
+ * @param pszDst Where to return the prefix.
+ * @param cbDst The size of the prefix buffer.
+ */
+static int vbsvcAutomounterQueryMountDirAndPrefix(char *pszDst, size_t cbDst)
+{
+ /*
+ * Query the config first.
+ */
+ /* Mount directory: */
+ const char *pszDir = VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR;
+ char *pszCfgDir;
+ int rc = VbglR3SharedFolderGetMountDir(&pszCfgDir);
+ if (RT_SUCCESS(rc))
+ {
+ if (*pszCfgDir == '/')
+ pszDir = pszCfgDir;
+ }
+ else
+ pszCfgDir = NULL;
+
+ /* Prefix: */
+ const char *pszPrefix = VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX;
+ char *pszCfgPrefix;
+ rc = VbglR3SharedFolderGetMountPrefix(&pszCfgPrefix);
+ if (RT_SUCCESS(rc))
+ {
+ if ( strchr(pszCfgPrefix, '/') == NULL
+ && strchr(pszCfgPrefix, '\\') == NULL
+ && strcmp(pszCfgPrefix, "..") != 0)
+ pszPrefix = pszCfgPrefix;
+ }
+ else
+ pszCfgPrefix = NULL;
+
+ /*
+ * Try combine the two.
+ */
+ rc = RTPathAbs(pszDir, pszDst, cbDst);
+ if (RT_SUCCESS(rc))
+ {
+ if (*pszPrefix)
+ {
+ rc = RTPathAppend(pszDst, cbDst, pszPrefix);
+ if (RT_FAILURE(rc))
+ VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAppend(%s,,%s) -> %Rrc\n", pszDst, pszPrefix, rc);
+ }
+ else
+ {
+ rc = RTPathEnsureTrailingSeparator(pszDst, cbDst);
+ if (RT_FAILURE(rc))
+ VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathEnsureTrailingSeparator(%s) -> %Rrc\n", pszDst, rc);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterQueryMountDirAndPrefix: RTPathAbs(%s) -> %Rrc\n", pszDir, rc);
+
+
+ /*
+ * Return the default dir + prefix if the above failed.
+ */
+ if (RT_FAILURE(rc))
+ {
+ rc = RTStrCopy(pszDst, cbDst, VBOXSERVICE_AUTOMOUNT_DEFAULT_DIR "/" VBOXSERVICE_AUTOMOUNT_DEFAULT_PREFIX);
+ AssertRC(rc);
+ }
+
+ RTStrFree(pszCfgDir);
+ RTStrFree(pszCfgPrefix);
+ return rc;
+}
+#endif /* !RT_OS_WINDOW && !RT_OS_OS2 */
+
+
+/**
+ * @callback_method_impl{FNRTSORTCMP, For sorting mount table by root ID. }
+ */
+static DECLCALLBACK(int) vbsvcAutomounterCompareEntry(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF_PV(pvUser);
+ PVBSVCAUTOMOUNTERENTRY pEntry1 = (PVBSVCAUTOMOUNTERENTRY)pvElement1;
+ PVBSVCAUTOMOUNTERENTRY pEntry2 = (PVBSVCAUTOMOUNTERENTRY)pvElement2;
+ return pEntry1->idRoot < pEntry2->idRoot ? -1
+ : pEntry1->idRoot > pEntry2->idRoot ? 1 : 0;
+}
+
+
+/**
+ * Worker for vbsvcAutomounterPopulateTable for adding discovered entries.
+ *
+ * This is puts dummies in for missing values, depending on
+ * vbsvcAutomounterPopulateTable to query them later.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_MEMORY;
+ * @param pMountTable The mount table to add an entry to.
+ * @param pszName The shared folder name.
+ * @param pszMountPoint The mount point.
+ */
+static int vbsvcAutomounterAddEntry(PVBSVCAUTOMOUNTERTABLE pMountTable, const char *pszName, const char *pszMountPoint)
+{
+ VGSvcVerbose(2, "vbsvcAutomounterAddEntry: %s -> %s\n", pszMountPoint, pszName);
+ PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry));
+ pEntry->idRoot = UINT32_MAX;
+ pEntry->uRootIdVersion = UINT32_MAX;
+ pEntry->fFlags = UINT64_MAX;
+ pEntry->pszName = RTStrDup(pszName);
+ pEntry->pszMountPoint = NULL;
+ pEntry->pszActualMountPoint = RTStrDup(pszMountPoint);
+ if (pEntry->pszName && pEntry->pszActualMountPoint)
+ {
+ if (pMountTable->cEntries + 1 <= pMountTable->cAllocated)
+ {
+ pMountTable->papEntries[pMountTable->cEntries++] = pEntry;
+ return VINF_SUCCESS;
+ }
+
+ void *pvNew = RTMemRealloc(pMountTable->papEntries, (pMountTable->cAllocated + 8) * sizeof(pMountTable->papEntries[0]));
+ if (pvNew)
+ {
+ pMountTable->cAllocated += 8;
+ pMountTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvNew;
+
+ pMountTable->papEntries[pMountTable->cEntries++] = pEntry;
+ return VINF_SUCCESS;
+ }
+ }
+ RTMemFree(pEntry->pszActualMountPoint);
+ RTMemFree(pEntry->pszName);
+ RTMemFree(pEntry);
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Populates the mount table as best we can with existing automount entries.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_MEMORY;
+ * @param pMountTable The mount table (empty).
+ */
+static int vbsvcAutomounterPopulateTable(PVBSVCAUTOMOUNTERTABLE pMountTable)
+{
+ int rc;
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Loop thru the drive letters and check out each of them using QueryDosDeviceW.
+ */
+ static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;";
+ for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--)
+ {
+ RTUTF16 const wszMountPoint[4] = { (RTUTF16)chDrive, ':', '\0', '\0' };
+ RTUTF16 wszTargetPath[RTPATH_MAX];
+ DWORD const cwcResult = QueryDosDeviceW(wszMountPoint, wszTargetPath, RT_ELEMENTS(wszTargetPath));
+ if ( cwcResult > sizeof(s_szDevicePath)
+ && RTUtf16NICmpAscii(wszTargetPath, RT_STR_TUPLE(s_szDevicePath)) == 0)
+ {
+ PCRTUTF16 pwsz = &wszTargetPath[RT_ELEMENTS(s_szDevicePath) - 1];
+ Assert(pwsz[-1] == ';');
+ if ( (pwsz[0] & ~(RTUTF16)0x20) == chDrive
+ && pwsz[1] == ':'
+ && pwsz[2] == '\\')
+ {
+ /* For now we'll just use the special capitalization of the
+ "server" name to identify it as our work. We could check
+ if the symlink is from \Global?? or \??, but that trick does
+ work for older OS versions (<= XP) or when running the
+ service manually for testing/wathever purposes. */
+ /** @todo Modify the windows shared folder driver to allow tagging drives.*/
+ if (RTUtf16NCmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0)
+ {
+ pwsz += 3 + 8;
+ if (*pwsz != '\\' && *pwsz)
+ {
+ /* The shared folder name should follow immediately after the server prefix. */
+ char *pszMountedName = NULL;
+ rc = RTUtf16ToUtf8(pwsz, &pszMountedName);
+ if (RT_SUCCESS(rc))
+ {
+ char const szMountPoint[4] = { chDrive, ':', '\0', '\0' };
+ rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint);
+ RTStrFree(pszMountedName);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Malformed, not ours: %ls -> %ls\n",
+ wszMountPoint, wszTargetPath);
+ }
+ else
+ VGSvcVerbose(3, "vbsvcAutomounterPopulateTable: Not ours: %ls -> %ls\n", wszMountPoint, wszTargetPath);
+ }
+ }
+ }
+
+#elif defined(RT_OS_OS2)
+ /*
+ * Just loop thru the drive letters and check the attachment of each.
+ */
+ for (char chDrive = 'Z'; chDrive >= 'A'; chDrive--)
+ {
+ char const szMountPoint[4] = { chDrive, ':', '\0', '\0' };
+ union
+ {
+ FSQBUFFER2 FsQueryBuf;
+ char achPadding[1024];
+ } uBuf;
+ RT_ZERO(uBuf);
+ ULONG cbBuf = sizeof(uBuf) - 2;
+ APIRET rcOs2 = DosQueryFSAttach(szMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf);
+ if (rcOs2 == NO_ERROR)
+ {
+ const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1];
+ if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV
+ && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0)
+ {
+ const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1];
+ const char *pszTag = pszMountedName + strlen(pszMountedName) + 1; /* (Safe. Always two trailing zero bytes, see above.) */
+ if (strcmp(pszTag, g_szTag) == 0)
+ {
+ rc = vbsvcAutomounterAddEntry(pMountTable, pszMountedName, szMountPoint);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+ }
+
+#elif defined(RT_OS_LINUX)
+ /*
+ * Scan the mount table file for the mount point and then match file system
+ * and device/share. We identify our mounts by mount path + prefix for now,
+ * but later we may use the same approach as on solaris.
+ */
+ FILE *pFile = setmntent("/proc/mounts", "r");
+ int iErrMounts = errno;
+ if (!pFile)
+ pFile = setmntent("/etc/mtab", "r");
+ if (pFile)
+ {
+ rc = VWRN_NOT_FOUND;
+ struct mntent *pEntry;
+ while ((pEntry = getmntent(pFile)) != NULL)
+ if (strcmp(pEntry->mnt_type, "vboxsf") == 0)
+ if (strstr(pEntry->mnt_opts, g_szTag) != NULL)
+ {
+ rc = vbsvcAutomounterAddEntry(pMountTable, pEntry->mnt_fsname, pEntry->mnt_dir);
+ if (RT_FAILURE(rc))
+ {
+ endmntent(pFile);
+ return rc;
+ }
+ }
+ endmntent(pFile);
+ }
+ else
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d) or '/proc/mounts' (errno=%d)\n",
+ _PATH_MOUNTED, errno, iErrMounts);
+
+#elif defined(RT_OS_SOLARIS)
+ /*
+ * Look thru the system mount table and inspect the vboxsf mounts.
+ */
+ FILE *pFile = fopen(_PATH_MOUNTED, "r");
+ if (pFile)
+ {
+ rc = VINF_SUCCESS;
+ struct mnttab Entry;
+ while (getmntent(pFile, &Entry) == 0)
+ if (strcmp(Entry.mnt_fstype, "vboxfs") == 0)
+ {
+ /* Look for the dummy automounter option. */
+ if ( Entry.mnt_mntopts != NULL
+ && strstr(Entry.mnt_mntopts, g_szTag) != NULL)
+ {
+ rc = vbsvcAutomounterAddEntry(pMountTable, Entry.mnt_special, Entry.mnt_mountp);
+ if (RT_FAILURE(rc))
+ {
+ fclose(pFile);
+ return rc;
+ }
+ }
+ }
+ fclose(pFile);
+ }
+ else
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno);
+
+#else
+# error "PORTME!"
+#endif
+
+ /*
+ * Try reconcile the detected folders with data from the host.
+ */
+ uint32_t cMappings = 0;
+ PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL;
+ rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t i = 0; i < cMappings && RT_SUCCESS(rc); i++)
+ {
+ uint32_t const idRootSrc = paMappings[i].u32Root;
+
+ uint32_t uRootIdVer = UINT32_MAX;
+ uint64_t fFlags = 0;
+ char *pszName = NULL;
+ char *pszMntPt = NULL;
+ int rc2 = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
+ &pszName, &pszMntPt, &fFlags, &uRootIdVer);
+ if (RT_SUCCESS(rc2))
+ {
+ uint32_t iPrevHit = UINT32_MAX;
+ for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++)
+ {
+ PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable];
+ if (RTStrICmp(pEntry->pszName, pszName) == 0)
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterPopulateTable: Identified %s -> %s: idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n",
+ pEntry->pszActualMountPoint, pEntry->pszName, idRootSrc, uRootIdVer, fFlags, pszMntPt);
+ pEntry->fFlags = fFlags;
+ pEntry->idRoot = idRootSrc;
+ pEntry->uRootIdVersion = uRootIdVer;
+ RTStrFree(pEntry->pszMountPoint);
+ pEntry->pszMountPoint = RTStrDup(pszMntPt);
+ if (!pEntry->pszMountPoint)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* If multiple mappings of the same folder, pick the first or the one
+ with matching mount point. */
+ if (iPrevHit == UINT32_MAX)
+ iPrevHit = iTable;
+ else if (RTPathCompare(pszMntPt, pEntry->pszActualMountPoint) == 0)
+ {
+ if (iPrevHit != UINT32_MAX)
+ pMountTable->papEntries[iPrevHit]->uRootIdVersion -= 1;
+ iPrevHit = iTable;
+ }
+ else
+ pEntry->uRootIdVersion -= 1;
+ }
+ }
+
+ RTStrFree(pszName);
+ RTStrFree(pszMntPt);
+ }
+ else
+ VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderQueryFolderInfo(%u) failed: %Rrc\n", idRootSrc, rc2);
+ }
+
+ VbglR3SharedFolderFreeMappings(paMappings);
+
+ /*
+ * Sort the table by root ID.
+ */
+ if (pMountTable->cEntries > 1)
+ RTSortApvShell((void **)pMountTable->papEntries, pMountTable->cEntries, vbsvcAutomounterCompareEntry, NULL);
+
+ for (uint32_t iTable = 0; iTable < pMountTable->cEntries; iTable++)
+ {
+ PVBSVCAUTOMOUNTERENTRY pEntry = pMountTable->papEntries[iTable];
+ if (pMountTable->papEntries[iTable]->idRoot != UINT32_MAX)
+ VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s idRoot=%u ver=%u fFlags=%#x AutoMntPt=%s\n",
+ iTable, pEntry->pszActualMountPoint, pEntry->pszName, pEntry->idRoot, pEntry->uRootIdVersion,
+ pEntry->fFlags, pEntry->pszMountPoint);
+ else
+ VGSvcVerbose(1, "vbsvcAutomounterPopulateTable: #%u: %s -> %s - not identified!\n",
+ iTable, pEntry->pszActualMountPoint, pEntry->pszName);
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterPopulateTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Checks whether the shared folder @a pszName is mounted on @a pszMountPoint.
+ *
+ * @returns Exactly one of the following IPRT status codes;
+ * @retval VINF_SUCCESS if mounted
+ * @retval VWRN_NOT_FOUND if nothing is mounted at @a pszMountPoint.
+ * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there.
+ * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted
+ * there.
+ *
+ * @param pszMountPoint The mount point to check.
+ * @param pszName The name of the shared folder (mapping).
+ */
+static int vbsvcAutomounterQueryMountPoint(const char *pszMountPoint, const char *pszName)
+{
+ VGSvcVerbose(4, "vbsvcAutomounterQueryMountPoint: pszMountPoint=%s pszName=%s\n", pszMountPoint, pszName);
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * We could've used RTFsQueryType here but would then have to
+ * calling RTFsQueryLabel for the share name hint, ending up
+ * doing the same work twice. We could also use QueryDosDeviceW,
+ * but output is less clear...
+ */
+ PRTUTF16 pwszMountPoint = NULL;
+ int rc = RTStrToUtf16(pszMountPoint, &pwszMountPoint);
+ if (RT_SUCCESS(rc))
+ {
+ DWORD uSerial = 0;
+ DWORD cchCompMax = 0;
+ DWORD fFlags = 0;
+ RTUTF16 wszLabel[512];
+ RTUTF16 wszFileSystem[256];
+ RT_ZERO(wszLabel);
+ RT_ZERO(wszFileSystem);
+ if (GetVolumeInformationW(pwszMountPoint, wszLabel, RT_ELEMENTS(wszLabel) - 1, &uSerial, &cchCompMax, &fFlags,
+ wszFileSystem, RT_ELEMENTS(wszFileSystem) - 1))
+ {
+ if (RTUtf16ICmpAscii(wszFileSystem, "VBoxSharedFolderFS") == 0)
+ {
+ char *pszLabel = NULL;
+ rc = RTUtf16ToUtf8(wszLabel, &pszLabel);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszMountedName = pszLabel;
+ if (RTStrStartsWith(pszMountedName, "VBOX_"))
+ pszMountedName += sizeof("VBOX_") - 1;
+ if (RTStrICmp(pszMountedName, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ pszMountedName, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ RTStrFree(pszLabel);
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8(%ls,) failed: %Rrc\n", wszLabel, rc);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%ls' with label '%ls' mount at '%s', not '%s'...\n",
+ wszFileSystem, wszLabel, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ rc = GetLastError();
+ if (rc != ERROR_PATH_NOT_FOUND || g_cVerbosity >= 4)
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: GetVolumeInformationW('%ls',,,,) failed: %u\n", pwszMountPoint, rc);
+ if (rc == ERROR_PATH_NOT_FOUND)
+ rc = VWRN_NOT_FOUND;
+ else if ( RT_C_IS_ALPHA(pszMountPoint[0])
+ && pszMountPoint[1] == ':'
+ && ( pszMountPoint[2] == '\0'
+ || (RTPATH_IS_SLASH(pszMountPoint[2]) && pszMountPoint[3] == '\0')))
+ {
+ /* See whether QueryDosDeviceW thinks its a malfunctioning shared folder or
+ something else (it doesn't access the file system). We've seen
+ VERR_NET_HOST_NOT_FOUND here for shared folders that was removed on the
+ host side.
+
+ Note! This duplicates code from vbsvcAutomounterPopulateTable. */
+ rc = VERR_ACCESS_DENIED;
+ static const char s_szDevicePath[] = "\\Device\\VBoxMiniRdr\\;";
+ wszFileSystem[0] = pwszMountPoint[0];
+ wszFileSystem[1] = pwszMountPoint[1];
+ wszFileSystem[2] = '\0';
+ DWORD const cwcResult = QueryDosDeviceW(wszFileSystem, wszLabel, RT_ELEMENTS(wszLabel));
+ if ( cwcResult > sizeof(s_szDevicePath)
+ && RTUtf16NICmpAscii(wszLabel, RT_STR_TUPLE(s_szDevicePath)) == 0)
+ {
+ PCRTUTF16 pwsz = &wszLabel[RT_ELEMENTS(s_szDevicePath) - 1];
+ Assert(pwsz[-1] == ';');
+ if ( (pwsz[0] & ~(RTUTF16)0x20) == (wszFileSystem[0] & ~(RTUTF16)0x20)
+ && pwsz[1] == ':'
+ && pwsz[2] == '\\')
+ {
+ if (RTUtf16NICmpAscii(&pwsz[3], RT_STR_TUPLE("VBoxSvr\\")) == 0)
+ {
+ pwsz += 3 + 8;
+ char *pszMountedName = NULL;
+ rc = RTUtf16ToUtf8(pwsz, &pszMountedName);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrICmp(pszMountedName, pszName) == 0)
+ {
+ rc = VINF_SUCCESS;
+ VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW).\n",
+ pszName, pszMountPoint);
+ }
+ else
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s' (using QueryDosDeviceW), not '%s'...\n",
+ pszMountedName, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ RTStrFree(pszMountedName);
+ }
+ else
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterQueryMountPoint: RTUtf16ToUtf8 failed: %Rrc\n", rc);
+ AssertRC(rc);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ RTUtf16Free(pwszMountPoint);
+ }
+ else
+ {
+ VGSvcError("vbsvcAutomounterQueryMountPoint: RTStrToUtf16(%s,) -> %Rrc\n", pszMountPoint, rc);
+ rc = VWRN_NOT_FOUND;
+ }
+ return rc;
+
+#elif defined(RT_OS_OS2)
+ /*
+ * Query file system attachment info for the given drive letter.
+ */
+ union
+ {
+ FSQBUFFER2 FsQueryBuf;
+ char achPadding[512];
+ } uBuf;
+ RT_ZERO(uBuf);
+
+ ULONG cbBuf = sizeof(uBuf);
+ APIRET rcOs2 = DosQueryFSAttach(pszMountPoint, 0, FSAIL_QUERYNAME, &uBuf.FsQueryBuf, &cbBuf);
+ int rc;
+ if (rcOs2 == NO_ERROR)
+ {
+ const char *pszFsdName = (const char *)&uBuf.FsQueryBuf.szName[uBuf.FsQueryBuf.cbName + 1];
+ if ( uBuf.FsQueryBuf.iType == FSAT_REMOTEDRV
+ && RTStrICmpAscii(pszFsdName, "VBOXSF") == 0)
+ {
+ const char *pszMountedName = (const char *)&pszFsdName[uBuf.FsQueryBuf.cbFSDName + 1];
+ if (RTStrICmp(pszMountedName, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ pszMountedName, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' type %u mount at '%s', not '%s'...\n",
+ pszFsdName, uBuf.FsQueryBuf.iType, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ rc = VWRN_NOT_FOUND;
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: DosQueryFSAttach(%s) -> %u\n", pszMountPoint, rcOs2);
+ AssertMsgStmt(rcOs2 != ERROR_BUFFER_OVERFLOW && rcOs2 != ERROR_INVALID_PARAMETER,
+ ("%s -> %u\n", pszMountPoint, rcOs2), rc = VERR_ACCESS_DENIED);
+ }
+ return rc;
+
+#elif defined(RT_OS_LINUX)
+ /*
+ * Scan one of the mount table file for the mount point and then
+ * match file system and device/share.
+ */
+ FILE *pFile = setmntent("/proc/mounts", "r");
+ int rc = errno;
+ if (!pFile)
+ pFile = setmntent(_PATH_MOUNTED, "r");
+ if (pFile)
+ {
+ rc = VWRN_NOT_FOUND;
+ struct mntent *pEntry;
+ while ((pEntry = getmntent(pFile)) != NULL)
+ if (RTPathCompare(pEntry->mnt_dir, pszMountPoint) == 0)
+ {
+ if (strcmp(pEntry->mnt_type, "vboxsf") == 0)
+ {
+ if (RTStrICmp(pEntry->mnt_fsname, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ pEntry->mnt_fsname, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n",
+ pEntry->mnt_type, pEntry->mnt_fsname, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ /* We continue searching in case of stacked mounts, we want the last one. */
+ }
+ endmntent(pFile);
+ }
+ else
+ {
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '/proc/mounts' (errno=%d) or '%s' (errno=%d)\n",
+ rc, _PATH_MOUNTED, errno);
+ rc = VERR_ACCESS_DENIED;
+ }
+ return rc;
+
+#elif defined(RT_OS_SOLARIS)
+ /*
+ * Similar to linux.
+ */
+ int rc;
+ FILE *pFile = fopen(_PATH_MOUNTED, "r");
+ if (pFile)
+ {
+ rc = VWRN_NOT_FOUND;
+ struct mnttab Entry;
+ while (getmntent(pFile, &Entry) == 0)
+ if (RTPathCompare(Entry.mnt_mountp, pszMountPoint) == 0)
+ {
+ if (strcmp(Entry.mnt_fstype, "vboxfs") == 0)
+ {
+ if (RTStrICmp(Entry.mnt_special, pszName) == 0)
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s'.\n",
+ pszName, pszMountPoint);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found shared folder '%s' at '%s', not '%s'...\n",
+ Entry.mnt_special, pszMountPoint, pszName);
+ rc = VERR_RESOURCE_BUSY;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterQueryMountPoint: Found a '%s' mount of '%s' at '%s', not '%s'...\n",
+ Entry.mnt_fstype, Entry.mnt_special, pszMountPoint, pszName);
+ rc = VERR_ACCESS_DENIED;
+ }
+ /* We continue searching in case of stacked mounts, we want the last one. */
+ }
+ fclose(pFile);
+ }
+ else
+ {
+ VGSvcError("vbsvcAutomounterQueryMountPoint: Could not open mount tab '%s' (errno=%d)\n", _PATH_MOUNTED, errno);
+ rc = VERR_ACCESS_DENIED;
+ }
+ return rc;
+#else
+# error "PORTME"
+#endif
+}
+
+
+/**
+ * Worker for vbsvcAutomounterMountNewEntry that does the OS mounting.
+ *
+ * @returns IPRT status code.
+ * @param pEntry The entry to try mount.
+ */
+static int vbsvcAutomounterMountIt(PVBSVCAUTOMOUNTERENTRY pEntry)
+{
+ VGSvcVerbose(3, "vbsvcAutomounterMountIt: Trying to mount '%s' (idRoot=%#x) on '%s'...\n",
+ pEntry->pszName, pEntry->idRoot, pEntry->pszActualMountPoint);
+#ifdef RT_OS_WINDOWS
+ /*
+ * Attach the shared folder using WNetAddConnection2W.
+ *
+ * According to google we should get a drive symlink in \\GLOBAL?? when
+ * we are running under the system account. Otherwise it will be a session
+ * local link (\\??).
+ */
+ Assert(RT_C_IS_UPPER(pEntry->pszActualMountPoint[0]) && pEntry->pszActualMountPoint[1] == ':' && pEntry->pszActualMountPoint[2] == '\0');
+ RTUTF16 wszDrive[4] = { (RTUTF16)pEntry->pszActualMountPoint[0], ':', '\0', '\0' };
+
+ RTUTF16 wszPrefixedName[RTPATH_MAX];
+ int rc = RTUtf16CopyAscii(wszPrefixedName, RT_ELEMENTS(wszPrefixedName), "\\\\VBoxSvr\\");
+ AssertRC(rc);
+
+ size_t const offName = RTUtf16Len(wszPrefixedName);
+ PRTUTF16 pwszName = &wszPrefixedName[offName];
+ rc = RTStrToUtf16Ex(pEntry->pszName, RTSTR_MAX, &pwszName, sizeof(wszPrefixedName) - offName, NULL);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterMountIt: RTStrToUtf16Ex failed on '%s': %Rrc\n", pEntry->pszName, rc);
+ return rc;
+ }
+
+ VGSvcVerbose(3, "vbsvcAutomounterMountIt: wszDrive='%ls', wszPrefixedName='%ls'\n",
+ wszDrive, wszPrefixedName);
+
+ NETRESOURCEW NetRsrc;
+ RT_ZERO(NetRsrc);
+ NetRsrc.dwType = RESOURCETYPE_DISK;
+ NetRsrc.lpLocalName = wszDrive;
+ NetRsrc.lpRemoteName = wszPrefixedName;
+ NetRsrc.lpProvider = L"VirtualBox Shared Folders"; /* Only try our provider. */
+ NetRsrc.lpComment = pwszName;
+
+ DWORD dwErr = WNetAddConnection2W(&NetRsrc, NULL /*pwszPassword*/, NULL /*pwszUserName*/, 0 /*dwFlags*/);
+ if (dwErr == NO_ERROR)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+ return VINF_SUCCESS;
+ }
+ VGSvcError("vbsvcAutomounterMountIt: Failed to attach '%s' to '%s': %Rrc (%u)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, RTErrConvertFromWin32(dwErr), dwErr);
+ return VERR_OPEN_FAILED;
+
+#elif defined(RT_OS_OS2)
+ /*
+ * It's a rather simple affair on OS/2.
+ *
+ * In order to be able to detect our mounts we add a 2nd string after
+ * the folder name that tags the attachment. The IFS will remember this
+ * and return it when DosQueryFSAttach is called.
+ *
+ * Note! Kernel currently accepts limited 7-bit ASCII names. We could
+ * change that to UTF-8 if we like as that means no extra string
+ * encoding conversion fun here.
+ */
+ char szzNameAndTag[256];
+ size_t cchName = strlen(pEntry->pszName);
+ if (cchName + 1 + sizeof(g_szTag) <= sizeof(szzNameAndTag))
+ {
+ memcpy(szzNameAndTag, pEntry->pszName, cchName);
+ szzNameAndTag[cchName] = '\0';
+ memcpy(&szzNameAndTag[cchName + 1], g_szTag, sizeof(g_szTag));
+
+ APIRET rc = DosFSAttach(pEntry->pszActualMountPoint, "VBOXSF", szzNameAndTag, cchName + 1 + sizeof(g_szTag), FS_ATTACH);
+ if (rc == NO_ERROR)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+ return VINF_SUCCESS;
+ }
+ VGSvcError("vbsvcAutomounterMountIt: DosFSAttach failed to attach '%s' to '%s': %u\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, rc);
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountIt: Share name for attach to '%s' is too long: %u chars - '%s'\n",
+ pEntry->pszActualMountPoint, cchName, pEntry->pszName);
+ return VERR_OPEN_FAILED;
+
+#else
+ /*
+ * Common work for unix-like systems: Get group, make sure mount directory exist.
+ */
+ int rc = RTDirCreateFullPath(pEntry->pszActualMountPoint,
+ RTFS_UNIX_IRWXU | RTFS_UNIX_IXGRP | RTFS_UNIX_IRGRP | RTFS_UNIX_IXOTH | RTFS_UNIX_IROTH);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterMountIt: Failed to create mount path '%s' for share '%s': %Rrc\n",
+ pEntry->pszActualMountPoint, pEntry->pszName, rc);
+ return rc;
+ }
+
+ gid_t gidMount;
+ struct group *grp_vboxsf = getgrnam("vboxsf");
+ if (grp_vboxsf)
+ gidMount = grp_vboxsf->gr_gid;
+ else
+ {
+ VGSvcError("vbsvcAutomounterMountIt: Group 'vboxsf' does not exist\n");
+ gidMount = 0;
+ }
+
+# if defined(RT_OS_LINUX)
+ /*
+ * Linux a bit more work...
+ */
+ struct utsname uts;
+ AssertStmt(uname(&uts) != -1, strcpy(uts.release, "4.4.0"));
+
+ /* Built mount option string. Need st_name for pre 2.6.0 kernels. */
+ unsigned long const fFlags = MS_NODEV;
+ char szOpts[MAX_MNTOPT_STR] = { '\0' };
+ ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts),
+ "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag);
+ if (RTStrVersionCompare(uts.release, "2.6.0") < 0 && cchOpts > 0)
+ cchOpts += RTStrPrintf2(&szOpts[cchOpts], sizeof(szOpts) - cchOpts, ",sf_name=%s", pEntry->pszName);
+ if (cchOpts <= 0)
+ {
+ VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Do the mounting. The fallback w/o tag is for the Linux vboxsf fork
+ which lagged a lot behind when it first appeared in 5.6. */
+ errno = 0;
+ rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts);
+ if (rc != 0 && errno == EINVAL && RTStrVersionCompare(uts.release, "5.6.0") >= 0)
+ {
+ VGSvcVerbose(2, "vbsvcAutomounterMountIt: mount returned EINVAL, retrying without the tag.\n");
+ *strstr(szOpts, ",tag=") = '\0';
+ errno = 0;
+ rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, "vboxsf", fFlags, szOpts);
+ if (rc == 0)
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Running outdated vboxsf module without support for the 'tag' option?\n");
+ }
+ if (rc == 0)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+
+ errno = 0;
+ rc = vbsfmount_complete(pEntry->pszName, pEntry->pszActualMountPoint, fFlags, szOpts);
+ if (rc != 0) /* Ignorable. /etc/mtab is probably a link to /proc/mounts. */
+ VGSvcVerbose(1, "vbsvcAutomounterMountIt: vbsfmount_complete failed: %s (%d/%d)\n",
+ rc == 1 ? "malloc" : rc == 2 ? "setmntent" : rc == 3 ? "addmntent" : "unknown", rc, errno);
+ return VINF_SUCCESS;
+ }
+
+ if (errno == EINVAL)
+ VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s' because it is probably mounted elsewhere arleady! (%d,%d)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, rc, errno);
+ else
+ VGSvcError("vbsvcAutomounterMountIt: Failed to mount '%s' on '%s': %s (%d,%d)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, strerror(errno), rc, errno);
+ return VERR_WRITE_ERROR;
+
+# elif defined(RT_OS_SOLARIS)
+ /*
+ * Solaris is rather simple compared to linux.
+ *
+ * The ',VBoxService=auto' option (g_szTag) is ignored by the kernel but helps
+ * us identify our own mounts on restart. See vbsvcAutomounterPopulateTable().
+ *
+ * Note! Must pass MAX_MNTOPT_STR rather than cchOpts to mount, as it may fail
+ * with EOVERFLOW in vfs_buildoptionstr() during domount() otherwise.
+ */
+ char szOpts[MAX_MNTOPT_STR] = { '\0', };
+ ssize_t cchOpts = RTStrPrintf2(szOpts, sizeof(szOpts),
+ "uid=0,gid=%d,dmode=0770,fmode=0770,dmask=0000,fmask=0000,tag=%s", gidMount, g_szTag);
+ if (cchOpts <= 0)
+ {
+ VGSvcError("vbsvcAutomounterMountIt: szOpts overflow! %zd\n", cchOpts);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ rc = mount(pEntry->pszName, pEntry->pszActualMountPoint, MS_OPTIONSTR, "vboxfs",
+ NULL /*dataptr*/, 0 /* datalen */, szOpts, MAX_MNTOPT_STR);
+ if (rc == 0)
+ {
+ VGSvcVerbose(0, "vbsvcAutomounterMountIt: Successfully mounted '%s' on '%s'\n",
+ pEntry->pszName, pEntry->pszActualMountPoint);
+ return VINF_SUCCESS;
+ }
+
+ rc = errno;
+ VGSvcError("vbsvcAutomounterMountIt: mount failed for '%s' on '%s' (szOpts=%s): %s (%d)\n",
+ pEntry->pszName, pEntry->pszActualMountPoint, szOpts, strerror(rc), rc);
+ return VERR_OPEN_FAILED;
+
+# else
+# error "PORTME!"
+# endif
+#endif
+}
+
+
+/**
+ * Attempts to mount the given shared folder, adding it to the mount table on
+ * success.
+ *
+ * @returns iTable + 1 on success, iTable on failure.
+ * @param pTable The mount table.
+ * @param iTable The mount table index at which to add the mount.
+ * @param pszName The name of the shared folder mapping.
+ * @param pszMntPt The mount point (hint) specified by the host.
+ * @param fFlags The shared folder flags, SHFL_MIF_XXX.
+ * @param idRoot The root ID.
+ * @param uRootIdVersion The root ID version.
+ * @param fAutoMntPt Whether to try automatically assign a mount point if
+ * pszMntPt doesn't work out. This is set in pass \#3.
+ */
+static uint32_t vbsvcAutomounterMountNewEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable,
+ const char *pszName, const char *pszMntPt, uint64_t fFlags,
+ uint32_t idRoot, uint32_t uRootIdVersion, bool fAutoMntPt)
+{
+ VGSvcVerbose(3, "vbsvcAutomounterMountNewEntry: #%u: '%s' at '%s'%s\n",
+ iTable, pszName, pszMntPt, fAutoMntPt ? " auto-assign" : "");
+
+ /*
+ * First we need to figure out the actual mount point.
+ */
+ char szActualMountPoint[RTPATH_MAX];
+
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ /*
+ * Drive letter based. We only care about the first two characters
+ * and ignore the rest (see further down).
+ */
+ char chNextLetter = 'Z';
+ if (RT_C_IS_ALPHA(pszMntPt[0]) && pszMntPt[1] == ':')
+ szActualMountPoint[0] = RT_C_TO_UPPER(pszMntPt[0]);
+ else if (!fAutoMntPt)
+ return iTable;
+ else
+ szActualMountPoint[0] = chNextLetter--;
+ szActualMountPoint[1] = ':';
+ szActualMountPoint[2] = '\0';
+
+ int rc;
+ for (;;)
+ {
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ if (rc == VWRN_NOT_FOUND)
+ break;
+
+ /* next */
+ if (chNextLetter == 'A' || !fAutoMntPt)
+ return iTable;
+ szActualMountPoint[0] = chNextLetter--;
+ }
+
+#else
+ /*
+ * Path based #1: Host specified mount point.
+ */
+
+ /* Skip DOS drive letter if there is a UNIX mount point path following it: */
+ if ( pszMntPt[0] != '/'
+ && pszMntPt[0] != '\0'
+ && pszMntPt[1] == ':'
+ && pszMntPt[2] == '/')
+ pszMntPt += 2;
+
+ /* Try specified mount point if it starts with a UNIX slash: */
+ int rc = VERR_ACCESS_DENIED;
+ if (*pszMntPt == '/')
+ {
+ rc = RTPathAbs(pszMntPt, szActualMountPoint, sizeof(szActualMountPoint));
+ if (RT_SUCCESS(rc))
+ {
+ static const char * const s_apszBlacklist[] =
+ { "/", "/dev", "/bin", "/sbin", "/lib", "/etc", "/var", "/tmp", "/usr", "/usr/bin", "/usr/sbin", "/usr/lib" };
+ for (size_t i = 0; i < RT_ELEMENTS(s_apszBlacklist); i++)
+ if (strcmp(szActualMountPoint, s_apszBlacklist[i]) == 0)
+ {
+ rc = VERR_ACCESS_DENIED;
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ }
+ }
+ if (rc != VWRN_NOT_FOUND)
+ {
+ if (!fAutoMntPt)
+ return iTable;
+
+ /*
+ * Path based #2: Mount dir + prefix + share.
+ */
+ rc = vbsvcAutomounterQueryMountDirAndPrefix(szActualMountPoint, sizeof(szActualMountPoint));
+ if (RT_SUCCESS(rc))
+ {
+ /* Append a sanitized share name: */
+ size_t const offShare = strlen(szActualMountPoint);
+ size_t offDst = offShare;
+ size_t offSrc = 0;
+ for (;;)
+ {
+ char ch = pszName[offSrc++];
+ if (ch == ' ' || ch == '/' || ch == '\\' || ch == ':' || ch == '$')
+ ch = '_';
+ else if (!ch)
+ break;
+ else if (ch < 0x20 || ch == 0x7f)
+ continue;
+ if (offDst < sizeof(szActualMountPoint) - 1)
+ szActualMountPoint[offDst++] = ch;
+ }
+ szActualMountPoint[offDst] = '\0';
+ if (offDst > offShare)
+ {
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ if (rc != VWRN_NOT_FOUND)
+ {
+ /*
+ * Path based #3: Mount dir + prefix + share + _ + number.
+ */
+ if (offDst + 2 >= sizeof(szActualMountPoint))
+ return iTable;
+
+ szActualMountPoint[offDst++] = '_';
+ for (uint32_t iTry = 1; iTry < 10 && rc != VWRN_NOT_FOUND; iTry++)
+ {
+ szActualMountPoint[offDst] = '0' + iTry;
+ szActualMountPoint[offDst + 1] = '\0';
+ rc = vbsvcAutomounterQueryMountPoint(szActualMountPoint, pszName);
+ }
+ if (rc != VWRN_NOT_FOUND)
+ return iTable;
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Bad share name: %.*Rhxs", strlen(pszName), pszName);
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Failed to construct basic auto mount point for '%s'", pszName);
+ }
+#endif
+
+ /*
+ * Prepare a table entry and ensure space in the table..
+ */
+ if (pTable->cEntries + 1 > pTable->cAllocated)
+ {
+ void *pvEntries = RTMemRealloc(pTable->papEntries, sizeof(pTable->papEntries[0]) * (pTable->cAllocated + 8));
+ if (!pvEntries)
+ {
+ VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for growing table (size %u)\n", pTable->cAllocated);
+ return iTable;
+ }
+ pTable->cAllocated += 8;
+ pTable->papEntries = (PVBSVCAUTOMOUNTERENTRY *)pvEntries;
+ }
+
+ PVBSVCAUTOMOUNTERENTRY pEntry = (PVBSVCAUTOMOUNTERENTRY)RTMemAlloc(sizeof(*pEntry));
+ if (pEntry)
+ {
+ pEntry->idRoot = idRoot;
+ pEntry->uRootIdVersion = uRootIdVersion;
+ pEntry->fFlags = fFlags;
+ pEntry->pszName = RTStrDup(pszName);
+ pEntry->pszMountPoint = RTStrDup(pszMntPt);
+ pEntry->pszActualMountPoint = RTStrDup(szActualMountPoint);
+ if (pEntry->pszName && pEntry->pszMountPoint && pEntry->pszActualMountPoint)
+ {
+ /*
+ * Now try mount it.
+ */
+ rc = vbsvcAutomounterMountIt(pEntry);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cToMove = pTable->cEntries - iTable;
+ if (cToMove > 0)
+ memmove(&pTable->papEntries[iTable + 1], &pTable->papEntries[iTable], cToMove * sizeof(pTable->papEntries[0]));
+ pTable->papEntries[iTable] = pEntry;
+ pTable->cEntries++;
+ return iTable + 1;
+ }
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n");
+ RTMemFree(pEntry->pszActualMountPoint);
+ RTMemFree(pEntry->pszMountPoint);
+ RTMemFree(pEntry->pszName);
+ RTMemFree(pEntry);
+ }
+ else
+ VGSvcError("vbsvcAutomounterMountNewEntry: Out of memory for table entry!\n");
+ return iTable;
+}
+
+
+
+/**
+ * Does the actual unmounting.
+ *
+ * @returns Exactly one of the following IPRT status codes;
+ * @retval VINF_SUCCESS if successfully umounted or nothing was mounted there.
+ * @retval VERR_TRY_AGAIN if the shared folder is busy.
+ * @retval VERR_RESOURCE_BUSY if a different shared folder is mounted there.
+ * @retval VERR_ACCESS_DENIED if a non-shared folder file system is mounted
+ * there.
+ *
+ * @param pszMountPoint The mount point.
+ * @param pszName The shared folder (mapping) name.
+ */
+static int vbsvcAutomounterUnmount(const char *pszMountPoint, const char *pszName)
+{
+ /*
+ * Retry for 5 seconds in a hope that busy mounts will quiet down.
+ */
+ for (unsigned iTry = 0; ; iTry++)
+ {
+ /*
+ * Check what's mounted there before we start umounting stuff.
+ */
+ int rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName);
+ if (rc == VINF_SUCCESS)
+ { /* pszName is mounted there */ }
+ else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */
+ return VINF_SUCCESS;
+ else
+ {
+ Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED);
+ return VERR_RESOURCE_BUSY;
+ }
+
+ /*
+ * Do host specific unmounting.
+ */
+#ifdef RT_OS_WINDOWS
+ Assert(RT_C_IS_UPPER(pszMountPoint[0]) && pszMountPoint[1] == ':' && pszMountPoint[2] == '\0');
+ RTUTF16 const wszDrive[4] = { (RTUTF16)pszMountPoint[0], ':', '\0', '\0' };
+ DWORD dwErr = WNetCancelConnection2W(wszDrive, 0 /*dwFlags*/, FALSE /*fForce*/);
+ if (dwErr == NO_ERROR)
+ return VINF_SUCCESS;
+ VGSvcVerbose(2, "vbsvcAutomounterUnmount: WNetCancelConnection2W returns %u for '%s' ('%s')\n", dwErr, pszMountPoint, pszName);
+ if (dwErr == ERROR_NOT_CONNECTED)
+ return VINF_SUCCESS;
+
+#elif defined(RT_OS_OS2)
+ APIRET rcOs2 = DosFSAttach(pszMountPoint, "VBOXSF", NULL, 0, FS_DETACH);
+ if (rcOs2 == NO_ERROR)
+ return VINF_SUCCESS;
+ VGSvcVerbose(2, "vbsvcAutomounterUnmount: DosFSAttach failed on '%s' ('%s'): %u\n", pszMountPoint, pszName, rcOs2);
+ if (rcOs2 == ERROR_INVALID_FSD_NAME)
+ return VERR_ACCESS_DENIED;
+ if ( rcOs2 == ERROR_INVALID_DRIVE
+ || rcOs2 == ERROR_INVALID_PATH)
+ return VERR_TRY_AGAIN;
+
+#else
+ int rc2 = umount(pszMountPoint);
+ if (rc2 == 0)
+ {
+ /* Remove the mount directory if not directly under the root dir. */
+ RTPATHPARSED Parsed;
+ RT_ZERO(Parsed);
+ RTPathParse(pszMountPoint, &Parsed, sizeof(Parsed), RTPATH_STR_F_STYLE_HOST);
+ if (Parsed.cComps >= 3)
+ RTDirRemove(pszMountPoint);
+
+ return VINF_SUCCESS;
+ }
+ rc2 = errno;
+ VGSvcVerbose(2, "vbsvcAutomounterUnmount: umount failed on '%s' ('%s'): %d\n", pszMountPoint, pszName, rc2);
+ if (rc2 != EBUSY && rc2 != EAGAIN)
+ return VERR_ACCESS_DENIED;
+#endif
+
+ /*
+ * Check what's mounted there before we start delaying.
+ */
+ RTThreadSleep(8); /* fudge */
+ rc = vbsvcAutomounterQueryMountPoint(pszMountPoint, pszName);
+ if (rc == VINF_SUCCESS)
+ { /* pszName is mounted there */ }
+ else if (rc == VWRN_NOT_FOUND) /* nothing mounted there */
+ return VINF_SUCCESS;
+ else
+ {
+ Assert(rc == VERR_RESOURCE_BUSY || rc == VERR_ACCESS_DENIED);
+ return VERR_RESOURCE_BUSY;
+ }
+
+ if (iTry >= 5)
+ return VERR_TRY_AGAIN;
+ RTThreadSleep(1000);
+ }
+}
+
+
+/**
+ * Unmounts a mount table entry and evicts it from the table if successful.
+ *
+ * @returns The next iTable (same value on success, +1 on failure).
+ * @param pTable The mount table.
+ * @param iTable The table entry.
+ * @param pszReason Why we're here.
+ */
+static uint32_t vbsvcAutomounterUnmountEntry(PVBSVCAUTOMOUNTERTABLE pTable, uint32_t iTable, const char *pszReason)
+{
+ Assert(iTable < pTable->cEntries);
+ PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable];
+ VGSvcVerbose(2, "vbsvcAutomounterUnmountEntry: #%u: '%s' at '%s' (reason: %s)\n",
+ iTable, pEntry->pszName, pEntry->pszActualMountPoint, pszReason);
+
+ /*
+ * Do we need to umount the entry? Return if unmount fails and we .
+ */
+ if (pEntry->pszActualMountPoint)
+ {
+ int rc = vbsvcAutomounterUnmount(pEntry->pszActualMountPoint, pEntry->pszName);
+ if (rc == VERR_TRY_AGAIN)
+ {
+ VGSvcVerbose(1, "vbsvcAutomounterUnmountEntry: Keeping '%s' -> '%s' (VERR_TRY_AGAIN)\n",
+ pEntry->pszActualMountPoint, pEntry->pszName);
+ return iTable + 1;
+ }
+ }
+
+ /*
+ * Remove the entry by shifting up the ones after it.
+ */
+ pTable->cEntries -= 1;
+ uint32_t cAfter = pTable->cEntries - iTable;
+ if (cAfter)
+ memmove(&pTable->papEntries[iTable], &pTable->papEntries[iTable + 1], cAfter * sizeof(pTable->papEntries[0]));
+ pTable->papEntries[pTable->cEntries] = NULL;
+
+ RTStrFree(pEntry->pszActualMountPoint);
+ pEntry->pszActualMountPoint = NULL;
+ RTStrFree(pEntry->pszMountPoint);
+ pEntry->pszMountPoint = NULL;
+ RTStrFree(pEntry->pszName);
+ pEntry->pszName = NULL;
+ RTMemFree(pEntry);
+
+ return iTable;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSORTCMP, For sorting the mappings by ID. }
+ */
+static DECLCALLBACK(int) vbsvcSharedFolderMappingCompare(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF_PV(pvUser);
+ PVBGLR3SHAREDFOLDERMAPPING pMapping1 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement1;
+ PVBGLR3SHAREDFOLDERMAPPING pMapping2 = (PVBGLR3SHAREDFOLDERMAPPING)pvElement2;
+ return pMapping1->u32Root < pMapping2->u32Root ? -1 : pMapping1->u32Root != pMapping2->u32Root ? 1 : 0;
+}
+
+
+/**
+ * Refreshes the mount table.
+ *
+ * @returns true if we've processed the current config, false if we failed to
+ * query the mappings.
+ * @param pTable The mount table to refresh.
+ */
+static bool vbsvcAutomounterRefreshTable(PVBSVCAUTOMOUNTERTABLE pTable)
+{
+ /*
+ * Query the root IDs of all auto-mountable shared folder mappings.
+ */
+ uint32_t cMappings = 0;
+ PVBGLR3SHAREDFOLDERMAPPING paMappings = NULL;
+ int rc = VbglR3SharedFolderGetMappings(g_idClientSharedFolders, true /*fAutoMountOnly*/, &paMappings, &cMappings);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterRefreshTable: VbglR3SharedFolderGetMappings failed: %Rrc\n", rc);
+ return false;
+ }
+
+ /*
+ * Walk the table and the mappings in parallel, so we have to make sure
+ * they are both sorted by root ID.
+ */
+ if (cMappings > 1)
+ RTSortShell(paMappings, cMappings, sizeof(paMappings[0]), vbsvcSharedFolderMappingCompare, NULL);
+
+ /*
+ * Pass #1: Do all the umounting.
+ *
+ * By doing the umount pass separately from the mount pass, we can
+ * better handle changing involving the same mount points (switching
+ * mount points between two shares, new share on same mount point but
+ * with lower root ID, ++).
+ */
+ uint32_t iTable = 0;
+ for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++)
+ {
+ /*
+ * Unmount table entries up to idRootSrc.
+ */
+ uint32_t const idRootSrc = paMappings[iSrc].u32Root;
+ while ( iTable < pTable->cEntries
+ && pTable->papEntries[iTable]->idRoot < idRootSrc)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped");
+
+ /*
+ * If the paMappings entry and the mount table entry has the same
+ * root ID, umount if anything has changed or if we cannot query
+ * the mapping data.
+ */
+ if (iTable < pTable->cEntries)
+ {
+ PVBSVCAUTOMOUNTERENTRY pEntry = pTable->papEntries[iTable];
+ if (pEntry->idRoot == idRootSrc)
+ {
+ uint32_t uRootIdVer = UINT32_MAX;
+ uint64_t fFlags = 0;
+ char *pszName = NULL;
+ char *pszMntPt = NULL;
+ rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
+ &pszName, &pszMntPt, &fFlags, &uRootIdVer);
+ if (RT_FAILURE(rc))
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "VbglR3SharedFolderQueryFolderInfo failed");
+ else if (pEntry->uRootIdVersion != uRootIdVer)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "root ID version changed");
+ else if (RTPathCompare(pEntry->pszMountPoint, pszMntPt) != 0)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "mount point changed");
+ else if (RTStrICmp(pEntry->pszName, pszName) != 0)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "name changed");
+ else
+ {
+ VGSvcVerbose(3, "vbsvcAutomounterRefreshTable: Unchanged: %s -> %s\n", pEntry->pszMountPoint, pEntry->pszName);
+ iTable++;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszName);
+ RTStrFree(pszMntPt);
+ }
+ }
+ }
+ }
+
+ while (iTable < pTable->cEntries)
+ iTable = vbsvcAutomounterUnmountEntry(pTable, iTable, "dropped (tail)");
+
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u entries in mount table after pass #1.\n", pTable->cEntries);
+
+ /*
+ * Pass #2: Try mount new folders that has mount points assigned.
+ * Pass #3: Try mount new folders not mounted in pass #2.
+ */
+ for (uint32_t iPass = 2; iPass <= 3; iPass++)
+ {
+ iTable = 0;
+ for (uint32_t iSrc = 0; iSrc < cMappings; iSrc++)
+ {
+ uint32_t const idRootSrc = paMappings[iSrc].u32Root;
+
+ /*
+ * Skip tabel entries we couldn't umount in pass #1.
+ */
+ while ( iTable < pTable->cEntries
+ && pTable->papEntries[iTable]->idRoot < idRootSrc)
+ {
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Skipping idRoot=%u %s\n",
+ iPass, iSrc, iTable, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName);
+ iTable++;
+ }
+
+ /*
+ * New share?
+ */
+ if ( iTable >= pTable->cEntries
+ || pTable->papEntries[iTable]->idRoot != idRootSrc)
+ {
+ uint32_t uRootIdVer = UINT32_MAX;
+ uint64_t fFlags = 0;
+ char *pszName = NULL;
+ char *pszMntPt = NULL;
+ rc = VbglR3SharedFolderQueryFolderInfo(g_idClientSharedFolders, idRootSrc, VBOXSERVICE_AUTOMOUNT_MIQF,
+ &pszName, &pszMntPt, &fFlags, &uRootIdVer);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: Mounting idRoot=%u/%u %s\n", iPass, iSrc, iTable,
+ idRootSrc, iTable >= pTable->cEntries ? UINT32_MAX : pTable->papEntries[iTable]->idRoot, pszName);
+ iTable = vbsvcAutomounterMountNewEntry(pTable, iTable, pszName, pszMntPt, fFlags,
+ idRootSrc, uRootIdVer, iPass == 3);
+
+ RTStrFree(pszName);
+ RTStrFree(pszMntPt);
+ }
+ else
+ VGSvcVerbose(1, "vbsvcAutomounterRefreshTable: VbglR3SharedFolderQueryFolderInfo failed: %Rrc\n", rc);
+ }
+ else
+ VGSvcVerbose(4, "vbsvcAutomounterRefreshTable: %u/#%u/%#u: idRootSrc=%u vs idRoot=%u %s\n", iPass, iSrc,
+ iTable, idRootSrc, pTable->papEntries[iTable]->idRoot, pTable->papEntries[iTable]->pszName);
+ }
+ }
+
+ VbglR3SharedFolderFreeMappings(paMappings);
+ return true;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbsvcAutomounterWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* Divert old hosts to original auto-mount code. */
+ if (!g_fHostSupportsWaitAndInfoQuery)
+ return vbsvcAutoMountWorkerOld(pfShutdown);
+
+ /*
+ * Initialize the state in case we're restarted...
+ */
+ VBSVCAUTOMOUNTERTABLE MountTable = { 0, 0, NULL };
+ int rc = vbsvcAutomounterPopulateTable(&MountTable);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("vbsvcAutomounterWorker: vbsvcAutomounterPopulateTable failed (%Rrc), quitting!\n", rc);
+ return rc;
+ }
+
+ /*
+ * Work loop.
+ */
+ uint32_t uConfigVer = UINT32_MAX;
+ uint32_t uNewVersion = 0;
+ bool fForceRefresh = true;
+ while (!*pfShutdown)
+ {
+ /*
+ * Update the mounts.
+ */
+ if ( uConfigVer != uNewVersion
+ || fForceRefresh)
+ {
+ fForceRefresh = !vbsvcAutomounterRefreshTable(&MountTable);
+ uConfigVer = uNewVersion;
+ }
+
+ /*
+ * Wait for more to do.
+ */
+ if (!*pfShutdown)
+ {
+ uNewVersion = uConfigVer - 1;
+ VGSvcVerbose(2, "vbsvcAutomounterWorker: Waiting with uConfigVer=%u\n", uConfigVer);
+ rc = VbglR3SharedFolderWaitForMappingsChanges(g_idClientSharedFolders, uConfigVer, &uNewVersion);
+ VGSvcVerbose(2, "vbsvcAutomounterWorker: Woke up with uNewVersion=%u and rc=%Rrc\n", uNewVersion, rc);
+
+ /* Delay a little before doing a table refresh so the GUI can finish
+ all its updates. Delay a little longer on non-shutdown failure to
+ avoid eating too many CPU cycles if something goes wrong here... */
+ if (!*pfShutdown)
+ RTSemEventMultiWait(g_hAutoMountEvent, RT_SUCCESS(rc) ? 256 : 1000);
+ }
+ }
+
+ /*
+ * Destroy the mount table.
+ */
+ while (MountTable.cEntries-- > 0)
+ RTMemFree(MountTable.papEntries[MountTable.cEntries]);
+ MountTable.papEntries = NULL;
+
+ VGSvcVerbose(3, "vbsvcAutomounterWorker: Finished\n");
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbsvcAutomounterStop(void)
+{
+ RTSemEventMultiSignal(g_hAutoMountEvent);
+ if (g_fHostSupportsWaitAndInfoQuery)
+ VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vbsvcAutomounterTerm(void)
+{
+ VGSvcVerbose(3, "vbsvcAutoMountTerm\n");
+
+ if (g_fHostSupportsWaitAndInfoQuery)
+ VbglR3SharedFolderCancelMappingsChangesWaits(g_idClientSharedFolders);
+
+ VbglR3SharedFolderDisconnect(g_idClientSharedFolders);
+ g_idClientSharedFolders = 0;
+
+ if (g_hAutoMountEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_hAutoMountEvent);
+ g_hAutoMountEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'automount' service description.
+ */
+VBOXSERVICE g_AutoMount =
+{
+ /* pszName. */
+ "automount",
+ /* pszDescription. */
+ "Automounter for Shared Folders",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vbsvcAutomounterInit,
+ vbsvcAutomounterWorker,
+ vbsvcAutomounterStop,
+ vbsvcAutomounterTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp
new file mode 100644
index 00000000..a1229257
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceBalloon.cpp
@@ -0,0 +1,457 @@
+/* $Id: VBoxServiceBalloon.cpp $ */
+/** @file
+ * VBoxService - Memory Ballooning.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_vgsvc_memballoon VBoxService - Memory Ballooning
+ *
+ * The Memory Ballooning subservice works with VBoxGuest, PGM and GMM to
+ * dynamically reallocate memory between VMs.
+ *
+ * Memory ballooning is typically used to deal with overcomitting memory on the
+ * host. It allowes you to borrow memory from one or more VMs and make it
+ * available to others. In theory it could also be used to make memory
+ * available to the host system, however memory fragmentation typically makes
+ * that difficult.
+ *
+ * The memory ballooning subservices talks to PGM, GMM and Main via the VMMDev.
+ * It polls for change requests at an interval and executes them when they
+ * arrive. There are two ways we implement the actual ballooning, either
+ * VBoxGuest allocates kernel memory and donates it to the host, or this service
+ * allocates process memory which VBoxGuest then locks down and donates to the
+ * host. While we prefer the former method it is not practicable on all OS and
+ * we have to use the latter.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+#ifdef RT_OS_LINUX
+# include <iprt/param.h>
+# include <sys/mman.h>
+# ifndef MADV_DONTFORK
+# define MADV_DONTFORK 10
+# endif
+#endif
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The balloon size. */
+static uint32_t g_cMemBalloonChunks = 0;
+
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_MemBalloonEvent = NIL_RTSEMEVENTMULTI;
+
+/** The array holding the R3 pointers of the balloon. */
+static void **g_pavBalloon = NULL;
+
+#ifdef RT_OS_LINUX
+/** True = madvise(MADV_DONTFORK) works, false otherwise. */
+static bool g_fSysMadviseWorks;
+#endif
+
+
+/**
+ * Check whether madvise() works.
+ */
+static void vgsvcBalloonInitMadvise(void)
+{
+#ifdef RT_OS_LINUX
+ void *pv = (void*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (pv != MAP_FAILED)
+ {
+ g_fSysMadviseWorks = madvise(pv, PAGE_SIZE, MADV_DONTFORK) == 0;
+ munmap(pv, PAGE_SIZE);
+ }
+#endif
+}
+
+
+/**
+ * Allocate a chunk of the balloon. Fulfil the prerequisite that we can lock this memory
+ * and protect it against fork() in R0. See also suplibOsPageAlloc().
+ */
+static void *VGSvcBalloonAllocChunk(void)
+{
+ size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE;
+ char *pu8;
+
+#ifdef RT_OS_LINUX
+ if (!g_fSysMadviseWorks)
+ cb += 2 * PAGE_SIZE;
+
+ pu8 = (char*)mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (pu8 == MAP_FAILED)
+ return NULL;
+
+ if (g_fSysMadviseWorks)
+ {
+ /*
+ * It is not fatal if we fail here but a forked child (e.g. the ALSA sound server)
+ * could crash. Linux < 2.6.16 does not implement madvise(MADV_DONTFORK) but the
+ * kernel seems to split bigger VMAs and that is all that we want -- later we set the
+ * VM_DONTCOPY attribute in supdrvOSLockMemOne().
+ */
+ madvise(pu8, cb, MADV_DONTFORK);
+ }
+ else
+ {
+ /*
+ * madvise(MADV_DONTFORK) is not available (most probably Linux 2.4). Enclose any
+ * mmapped region by two unmapped pages to guarantee that there is exactly one VM
+ * area struct of the very same size as the mmap area.
+ */
+ RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_NONE);
+ RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_NONE);
+ pu8 += PAGE_SIZE;
+ }
+
+#else
+
+ pu8 = (char*)RTMemPageAlloc(cb);
+ if (!pu8)
+ return pu8;
+
+#endif
+
+ memset(pu8, 0, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE);
+ return pu8;
+}
+
+
+/**
+ * Free an allocated chunk undoing VGSvcBalloonAllocChunk().
+ */
+static void vgsvcBalloonFreeChunk(void *pv)
+{
+ char *pu8 = (char*)pv;
+ size_t cb = VMMDEV_MEMORY_BALLOON_CHUNK_SIZE;
+
+#ifdef RT_OS_LINUX
+
+ if (!g_fSysMadviseWorks)
+ {
+ cb += 2 * PAGE_SIZE;
+ pu8 -= PAGE_SIZE;
+ /* This is not really necessary */
+ RTMemProtect(pu8, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ RTMemProtect(pu8 + cb - PAGE_SIZE, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ }
+ munmap(pu8, cb);
+
+#else
+
+ RTMemPageFree(pu8, cb);
+
+#endif
+}
+
+
+/**
+ * Adapt the R0 memory balloon by granting/reclaiming 1MB chunks to/from R0.
+ *
+ * returns IPRT status code.
+ * @param cNewChunks The new number of 1MB chunks in the balloon.
+ */
+static int vgsvcBalloonSetUser(uint32_t cNewChunks)
+{
+ if (cNewChunks == g_cMemBalloonChunks)
+ return VINF_SUCCESS;
+
+ VGSvcVerbose(3, "vgsvcBalloonSetUser: cNewChunks=%u g_cMemBalloonChunks=%u\n", cNewChunks, g_cMemBalloonChunks);
+ int rc = VINF_SUCCESS;
+ if (cNewChunks > g_cMemBalloonChunks)
+ {
+ /* inflate */
+ g_pavBalloon = (void**)RTMemRealloc(g_pavBalloon, cNewChunks * sizeof(void*));
+ uint32_t i;
+ for (i = g_cMemBalloonChunks; i < cNewChunks; i++)
+ {
+ void *pv = VGSvcBalloonAllocChunk();
+ if (!pv)
+ break;
+ rc = VbglR3MemBalloonChange(pv, /* inflate=*/ true);
+ if (RT_SUCCESS(rc))
+ {
+ g_pavBalloon[i] = pv;
+#ifndef RT_OS_SOLARIS
+ /*
+ * Protect against access by dangling pointers (ignore errors as it may fail).
+ * On Solaris it corrupts the address space leaving the process unkillable. This
+ * could perhaps be related to what the underlying segment driver does; currently
+ * just disable it.
+ */
+ RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_NONE);
+#endif
+ g_cMemBalloonChunks++;
+ }
+ else
+ {
+ vgsvcBalloonFreeChunk(pv);
+ break;
+ }
+ }
+ VGSvcVerbose(3, "vgsvcBalloonSetUser: inflation complete. chunks=%u rc=%d\n", i, rc);
+ }
+ else
+ {
+ /* deflate */
+ uint32_t i;
+ for (i = g_cMemBalloonChunks; i-- > cNewChunks;)
+ {
+ void *pv = g_pavBalloon[i];
+ rc = VbglR3MemBalloonChange(pv, /* inflate=*/ false);
+ if (RT_SUCCESS(rc))
+ {
+#ifndef RT_OS_SOLARIS
+ /* unprotect */
+ RTMemProtect(pv, VMMDEV_MEMORY_BALLOON_CHUNK_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+#endif
+ vgsvcBalloonFreeChunk(pv);
+ g_pavBalloon[i] = NULL;
+ g_cMemBalloonChunks--;
+ }
+ else
+ break;
+ VGSvcVerbose(3, "vgsvcBalloonSetUser: deflation complete. chunks=%u rc=%d\n", i, rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcBalloonInit(void)
+{
+ VGSvcVerbose(3, "vgsvcBalloonInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_MemBalloonEvent);
+ AssertRCReturn(rc, rc);
+
+ vgsvcBalloonInitMadvise();
+
+ g_cMemBalloonChunks = 0;
+ uint32_t cNewChunks = 0;
+ bool fHandleInR3;
+
+ /* Check balloon size */
+ rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "MemBalloon: New balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0");
+ if (fHandleInR3)
+ rc = vgsvcBalloonSetUser(cNewChunks);
+ else
+ g_cMemBalloonChunks = cNewChunks;
+ }
+ if (RT_FAILURE(rc))
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if ( rc == VERR_NOT_IMPLEMENTED
+#ifdef RT_OS_WINDOWS /** @todo r=bird: Windows kernel driver should return VERR_NOT_IMPLEMENTED,
+ * VERR_INVALID_PARAMETER has too many other uses. */
+ || rc == VERR_INVALID_PARAMETER
+#endif
+ )
+ {
+ VGSvcVerbose(0, "MemBalloon: Memory ballooning support is not available\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ {
+ VGSvcVerbose(3, "MemBalloon: VbglR3MemBalloonRefresh failed with %Rrc\n", rc);
+ rc = VERR_SERVICE_DISABLED; /** @todo Playing safe for now, figure out the exact status codes here. */
+ }
+ RTSemEventMultiDestroy(g_MemBalloonEvent);
+ g_MemBalloonEvent = NIL_RTSEMEVENTMULTI;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Query the size of the memory balloon, given as a page count.
+ *
+ * @returns Number of pages.
+ * @param cbPage The page size.
+ */
+uint32_t VGSvcBalloonQueryPages(uint32_t cbPage)
+{
+ Assert(cbPage > 0);
+ return g_cMemBalloonChunks * (VMMDEV_MEMORY_BALLOON_CHUNK_SIZE / cbPage);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcBalloonWorker(bool volatile *pfShutdown)
+{
+ /* Start monitoring of the stat event change event. */
+ int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc);
+ return rc;
+ }
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ uint32_t fEvents = 0;
+
+ /* Check if an update interval change is pending. */
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_BALLOON_CHANGE_REQUEST, 0 /* no wait */, &fEvents);
+ if ( RT_SUCCESS(rc)
+ && (fEvents & VMMDEV_EVENT_BALLOON_CHANGE_REQUEST))
+ {
+ uint32_t cNewChunks;
+ bool fHandleInR3;
+ rc = VbglR3MemBalloonRefresh(&cNewChunks, &fHandleInR3);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vgsvcBalloonInit: new balloon size %d MB (%s memory)\n", cNewChunks, fHandleInR3 ? "R3" : "R0");
+ if (fHandleInR3)
+ {
+ rc = vgsvcBalloonSetUser(cNewChunks);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(3, "vgsvcBalloonInit: failed to set balloon size %d MB (%s memory)\n",
+ cNewChunks, fHandleInR3 ? "R3" : "R0");
+ }
+ else
+ VGSvcVerbose(3, "vgsvcBalloonInit: successfully set requested balloon size %d.\n", cNewChunks);
+ }
+ else
+ g_cMemBalloonChunks = cNewChunks;
+ }
+ else
+ VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3MemBalloonRefresh failed with %Rrc\n", rc);
+ }
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc2 = RTSemEventMultiWait(g_MemBalloonEvent, 5000);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("vgsvcBalloonInit: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ }
+
+ /* Cancel monitoring of the memory balloon change event. */
+ rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_BALLOON_CHANGE_REQUEST);
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "vgsvcBalloonInit: VbglR3CtlFilterMask failed with %Rrc\n", rc);
+
+ VGSvcVerbose(3, "vgsvcBalloonInit: finished mem balloon change request thread\n");
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcBalloonStop(void)
+{
+ RTSemEventMultiSignal(g_MemBalloonEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcBalloonTerm(void)
+{
+ if (g_MemBalloonEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_MemBalloonEvent);
+ g_MemBalloonEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'memballoon' service description.
+ */
+VBOXSERVICE g_MemBalloon =
+{
+ /* pszName. */
+ "memballoon",
+ /* pszDescription. */
+ "Memory Ballooning",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vgsvcBalloonInit,
+ vgsvcBalloonWorker,
+ vgsvcBalloonStop,
+ vgsvcBalloonTerm
+};
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp
new file mode 100644
index 00000000..35b123e8
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceClipboard-os2.cpp
@@ -0,0 +1,1140 @@
+/** $Id: VBoxServiceClipboard-os2.cpp $ */
+/** @file
+ * VBoxService - Guest Additions Clipboard Service, OS/2.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_vgsvc_clipboard VBoxService - Clipboard (OS/2)
+ *
+ * The Clipboard subservice provides clipboard sharing for OS/2 guests only.
+ *
+ * This was the second subservice that was added to VBoxService. OS/2 is a
+ * single user system and we don't provide any VBoxTray or VBoxClient like
+ * processes. Because it's kind of simple system, it became natural to put the
+ * clipboard sharing here in VBoxService for OS/2.
+ *
+ * In addition to integrating with the native OS/2 PM clipboard formats, we also
+ * try provide the Odin32, a windows API layer for OS/2 (developed by Sander van
+ * Leeuwen and friends, later mainly InnoTek), with additional formats.
+ *
+ * Bitmaps are currently not supported, but that can easily be added should the
+ * need ever arrise.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define INCL_BASE
+#define INCL_PM
+#define INCL_ERRORS
+#include <os2.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <iprt/utf16.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include "VBoxServiceInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Header for Odin32 specific clipboard entries.
+ * (Used to get the correct size of the data.)
+ */
+typedef struct _Odin32ClipboardHeader
+{
+ /** magic number */
+ char achMagic[8];
+ /** Size of the following data.
+ * (The interpretation depends on the type.) */
+ unsigned cbData;
+ /** Odin32 format number. */
+ unsigned uFormat;
+} CLIPHEADER, *PCLIPHEADER;
+
+#define CLIPHEADER_MAGIC "Odin\1\0\1"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** The control thread (main) handle.
+ * Only used to avoid some queue creation trouble. */
+static RTTHREAD g_ThreadCtrl = NIL_RTTHREAD;
+/** The HAB of the control thread (main). */
+static HAB g_habCtrl = NULLHANDLE;
+/** The HMQ of the control thread (main). */
+static HMQ g_hmqCtrl = NULLHANDLE;
+
+/** The Listener thread handle. */
+static RTTHREAD g_ThreadListener = NIL_RTTHREAD;
+/** The HAB of the listener thread. */
+static HAB g_habListener = NULLHANDLE;
+/** The HMQ of the listener thread. */
+static HMQ g_hmqListener = NULLHANDLE;
+/** Indicator that gets set if the listener thread is successfully initialized. */
+static bool volatile g_fListenerOkay = false;
+
+/** The HAB of the worker thread. */
+static HAB g_habWorker = NULLHANDLE;
+/** The HMQ of the worker thread. */
+static HMQ g_hmqWorker = NULLHANDLE;
+/** The object window handle. */
+static HWND g_hwndWorker = NULLHANDLE;
+/** The timer id returned by WinStartTimer. */
+static ULONG g_idWorkerTimer = ~0UL;
+/** The state of the clipboard.
+ * @remark I'm trying out the 'k' prefix from the mac here, bear with me. */
+static enum
+{
+ /** The clipboard hasn't been initialized yet. */
+ kClipboardState_Uninitialized = 0,
+ /** WinSetClipbrdViewer call in progress, ignore WM_DRAWCLIPBOARD. */
+ kClipboardState_SettingViewer,
+ /** We're monitoring the clipboard as a viewer. */
+ kClipboardState_Viewer,
+ /** We're monitoring the clipboard using polling.
+ * This usually means something is wrong... */
+ kClipboardState_Polling,
+ /** We're destroying the clipboard content, ignore WM_DESTROYCLIPBOARD. */
+ kClipboardState_Destroying,
+ /** We're owning the clipboard (i.e. we have data on it). */
+ kClipboardState_Owner
+} g_enmState = kClipboardState_Uninitialized;
+/** Set if the clipboard was empty the last time we polled it. */
+static bool g_fEmptyClipboard = false;
+
+/** A clipboard format atom for the dummy clipboard data we insert
+ * watching for clipboard changes. If this format is found on the
+ * clipboard, the empty clipboard function has not been called
+ * since we last polled it. */
+static ATOM g_atomNothingChanged = 0;
+
+/** The clipboard connection client ID. */
+static uint32_t g_u32ClientId;
+/** Odin32 CF_UNICODETEXT. See user32.cpp. */
+static ATOM g_atomOdin32UnicodeText = 0;
+/** Odin32 CF_UNICODETEXT. See user32.cpp. */
+#define SZFMT_ODIN32_UNICODETEXT (PCSZ)"Odin32 UnicodeText"
+
+
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2PreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Option(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ NOREF(ppszShort);
+ NOREF(argc);
+ NOREF(argv);
+ NOREF(pi);
+
+ return -1;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Init(void)
+{
+ int rc = VERR_GENERAL_FAILURE;
+ g_ThreadCtrl = RTThreadSelf();
+
+ /*
+ * Make PM happy.
+ */
+ PPIB pPib;
+ PTIB pTib;
+ DosGetInfoBlocks(&pTib, &pPib);
+ pPib->pib_ultype = 3; /* PM session type */
+
+ /*
+ * Since we have to send shutdown messages and such from the
+ * service controller (main) thread, create a HAB and HMQ for it.
+ */
+ g_habCtrl = WinInitialize(0);
+ if (g_habCtrl == NULLHANDLE)
+ {
+ VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));
+ return VERR_GENERAL_FAILURE;
+ }
+ g_hmqCtrl = WinCreateMsgQueue(g_habCtrl, 0);
+ if (g_hmqCtrl != NULLHANDLE)
+ {
+ WinCancelShutdown(g_hmqCtrl, TRUE); /* We don't care about shutdown */
+
+ /*
+ * Create the 'nothing-changed' format.
+ */
+ g_atomNothingChanged = WinAddAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
+ LONG lLastError = WinGetLastError(g_habCtrl);
+ if (g_atomNothingChanged == 0)
+ g_atomNothingChanged = WinFindAtom(WinQuerySystemAtomTable(), (PCSZ)"VirtualBox Clipboard Service");
+ if (g_atomNothingChanged)
+ {
+ /*
+ * Connect to the clipboard service.
+ */
+ VGSvcVerbose(4, "clipboard: connecting\n");
+ rc = VbglR3ClipboardConnect(&g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create any extra clipboard type atoms, like the odin unicode text.
+ */
+ g_atomOdin32UnicodeText = WinAddAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
+ lLastError = WinGetLastError(g_habCtrl);
+ if (g_atomOdin32UnicodeText == 0)
+ g_atomOdin32UnicodeText = WinFindAtom(WinQuerySystemAtomTable(), SZFMT_ODIN32_UNICODETEXT);
+ if (g_atomOdin32UnicodeText == 0)
+ VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
+ lLastError, WinGetLastError(g_habCtrl));
+
+ VGSvcVerbose(2, "g_u32ClientId=%RX32 g_atomNothingChanged=%#x g_atomOdin32UnicodeText=%#x\n",
+ g_u32ClientId, g_atomNothingChanged, g_atomOdin32UnicodeText);
+ return VINF_SUCCESS;
+ }
+
+ VGSvcError("Failed to connect to the clipboard service, rc=%Rrc!\n", rc);
+ }
+ else
+ VGSvcError("WinAddAtom() failed, lasterr=%lx; WinFindAtom() failed, lasterror=%lx\n",
+ lLastError, WinGetLastError(g_habCtrl));
+ }
+ else
+ VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));
+ WinTerminate(g_habCtrl);
+ return rc;
+}
+
+
+/**
+ * Check that we're still the view / try make us the viewer.
+ */
+static void vgsvcClipboardOs2PollViewer(void)
+{
+ const int iOrgState = g_enmState;
+
+ HWND hwndClipboardViewer = WinQueryClipbrdViewer(g_habWorker);
+ if (hwndClipboardViewer == g_hwndWorker)
+ return;
+
+ if (hwndClipboardViewer == NULLHANDLE)
+ {
+ /* The API will send a WM_DRAWCLIPBOARD message before returning. */
+ g_enmState = kClipboardState_SettingViewer;
+ if (WinSetClipbrdViewer(g_habWorker, g_hwndWorker))
+ g_enmState = kClipboardState_Viewer;
+ else
+ g_enmState = kClipboardState_Polling;
+ }
+ else
+ g_enmState = kClipboardState_Polling;
+ if ((int)g_enmState != iOrgState)
+ {
+ if (g_enmState == kClipboardState_Viewer)
+ VGSvcVerbose(3, "clipboard: viewer\n");
+ else
+ VGSvcVerbose(3, "clipboard: poller\n");
+ }
+}
+
+
+/**
+ * Advertise the formats available from the host.
+ *
+ * @param fFormats The formats available on the host.
+ */
+static void vgsvcClipboardOs2AdvertiseHostFormats(uint32_t fFormats)
+{
+ /*
+ * Open the clipboard and switch to 'destruction' mode.
+ * Make sure we stop being viewer. Temporarily also make sure we're
+ * not the owner so that PM won't send us any WM_DESTROYCLIPBOARD message.
+ */
+ if (WinOpenClipbrd(g_habWorker))
+ {
+ if (g_enmState == kClipboardState_Viewer)
+ WinSetClipbrdViewer(g_habWorker, NULLHANDLE);
+ if (g_enmState == kClipboardState_Owner)
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+
+ g_enmState = kClipboardState_Destroying;
+ if (WinEmptyClipbrd(g_habWorker))
+ {
+ /*
+ * Take clipboard ownership.
+ */
+ if (WinSetClipbrdOwner(g_habWorker, g_hwndWorker))
+ {
+ g_enmState = kClipboardState_Owner;
+
+ /*
+ * Do the format advertising.
+ */
+ if (fFormats & (VBOX_SHCL_FMT_UNICODETEXT/* | VBOX_SHCL_FMT_HTML ?? */))
+ {
+ if (!WinSetClipbrdData(g_habWorker, 0, CF_TEXT, CFI_POINTER))
+ VGSvcError("WinSetClipbrdData(,,CF_TEXT,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ if ( g_atomOdin32UnicodeText
+ && !WinSetClipbrdData(g_habWorker, 0, g_atomOdin32UnicodeText, CFI_POINTER))
+ VGSvcError("WinSetClipbrdData(,,g_atomOdin32UnicodeText,) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ }
+ if (fFormats & VBOX_SHCL_FMT_BITMAP)
+ {
+ /** @todo bitmaps */
+ }
+ }
+ else
+ {
+ VGSvcError("WinSetClipbrdOwner failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ g_enmState = kClipboardState_Polling;
+ }
+ }
+ else
+ {
+ VGSvcError("WinEmptyClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ g_enmState = kClipboardState_Polling;
+ }
+
+ if (g_enmState == kClipboardState_Polling)
+ {
+ g_fEmptyClipboard = true;
+ vgsvcClipboardOs2PollViewer();
+ }
+
+ WinCloseClipbrd(g_habWorker);
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2AdvertiseHostFormats: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+}
+
+
+/**
+ * Converts (render) to an Odin32 clipboard format.
+ *
+ * We ASSUME we get windows data from the host and all we've got to do here is
+ * slapping an Odin32 header on it.
+ *
+ * @returns Pointer to the data (DosFreeMem).
+ * @param fFormat The host format.
+ * @param usFmt The PM/Odin32 format.
+ * @param pv The data in host formatting.
+ * @param cb The size of the data.
+ */
+static void *vgsvcClipboardOs2ConvertToOdin32(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
+{
+ PVOID pvPM = NULL;
+ APIRET rc = DosAllocSharedMem(&pvPM, NULL, cb + sizeof(CLIPHEADER), OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
+ if (rc)
+ {
+ PCLIPHEADER pHdr = (PCLIPHEADER)pvPM;
+ memcpy(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic));
+ pHdr->cbData = cb;
+ if (usFmt == g_atomOdin32UnicodeText)
+ pHdr->uFormat = usFmt;
+ else
+ AssertFailed();
+ memcpy(pHdr + 1, pv, cb);
+ }
+ else
+ {
+ VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), rc);
+ pvPM = NULL;
+ }
+ return pvPM;
+}
+
+
+/**
+ * Converts (render) to a PM clipboard format.
+ *
+ * @returns Pointer to the data (DosFreeMem).
+ * @param fFormat The host format.
+ * @param usFmt The PM/Odin32 format.
+ * @param pv The data in host formatting.
+ * @param cb The size of the data.
+ */
+static void *vgsvcClipboardOs2ConvertToPM(uint32_t fFormat, USHORT usFmt, void *pv, uint32_t cb)
+{
+ void *pvPM = NULL;
+
+ /*
+ * The Odin32 stuff is simple, we just assume windows data from the host
+ * and all we need to do is add the header.
+ */
+ if ( usFmt
+ && ( usFmt == g_atomOdin32UnicodeText
+ /* || usFmt == ...*/
+ )
+ )
+ pvPM = vgsvcClipboardOs2ConvertToOdin32(fFormat, usFmt, pv, cb);
+ else if (usFmt == CF_TEXT)
+ {
+ /*
+ * Convert the unicode text to the current ctype locale.
+ *
+ * Note that we probably should be using the current PM or DOS codepage
+ * here instead of the LC_CTYPE one which iconv uses by default.
+ * -lazybird
+ */
+ Assert(fFormat & VBOX_SHCL_FMT_UNICODETEXT);
+ char *pszUtf8;
+ int rc = RTUtf16ToUtf8((PCRTUTF16)pv, &pszUtf8);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszLocale;
+ rc = RTStrUtf8ToCurrentCP(&pszLocale, pszUtf8);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbPM = strlen(pszLocale) + 1;
+ APIRET orc = DosAllocSharedMem(&pvPM, NULL, cbPM, OBJ_GIVEABLE | OBJ_GETTABLE | OBJ_TILE | PAG_READ | PAG_WRITE | PAG_COMMIT);
+ if (orc == NO_ERROR)
+ memcpy(pvPM, pszLocale, cbPM);
+ else
+ {
+ VGSvcError("DosAllocSharedMem(,,%#x,,) -> %ld\n", cb + sizeof(CLIPHEADER), orc);
+ pvPM = NULL;
+ }
+ RTStrFree(pszLocale);
+ }
+ else
+ VGSvcError("RTStrUtf8ToCurrentCP() -> %Rrc\n", rc);
+ RTStrFree(pszUtf8);
+ }
+ else
+ VGSvcError("RTUtf16ToUtf8() -> %Rrc\n", rc);
+ }
+
+ return pvPM;
+}
+
+
+/**
+ * Tries to deliver an advertised host format.
+ *
+ * @param usFmt The PM format name.
+ *
+ * @remark We must not try open the clipboard here because WM_RENDERFMT is a
+ * request send synchronously by someone who has already opened the
+ * clipboard. We would enter a deadlock trying to open it here.
+ */
+static void vgsvcClipboardOs2RenderFormat(USHORT usFmt)
+{
+ bool fSucceeded = false;
+
+ /*
+ * Determine which format.
+ */
+ uint32_t fFormat;
+ if ( usFmt == CF_TEXT
+ || usFmt == g_atomOdin32UnicodeText)
+ fFormat = VBOX_SHCL_FMT_UNICODETEXT;
+ else /** @todo bitmaps */
+ fFormat = 0;
+ if (fFormat)
+ {
+ /*
+ * Query the data from the host.
+ * This might require two iterations because of buffer guessing.
+ */
+ uint32_t cb = _4K;
+ uint32_t cbAllocated = cb;
+ int rc = VERR_NO_MEMORY;
+ void *pv = RTMemPageAllocZ(cbAllocated);
+ if (pv)
+ {
+ VGSvcVerbose(4, "clipboard: reading host data (%#x)\n", fFormat);
+ rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ RTMemPageFree(pv, cbAllocated);
+ cbAllocated = cb = RT_ALIGN_32(cb, PAGE_SIZE);
+ pv = RTMemPageAllocZ(cbAllocated);
+ rc = VbglR3ClipboardReadData(g_u32ClientId, fFormat, pv, cb, &cb);
+ }
+ if (RT_FAILURE(rc))
+ RTMemPageFree(pv, cbAllocated);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "clipboard: read %u bytes\n", cb);
+
+ /*
+ * Convert the host clipboard data to PM clipboard data and set it.
+ */
+ PVOID pvPM = vgsvcClipboardOs2ConvertToPM(fFormat, usFmt, pv, cb);
+ if (pvPM)
+ {
+ if (WinSetClipbrdData(g_habWorker, (ULONG)pvPM, usFmt, CFI_POINTER))
+ fSucceeded = true;
+ else
+ {
+ VGSvcError("vgsvcClipboardOs2RenderFormat: WinSetClipbrdData(,%p,%#x, CF_POINTER) failed, lasterror=%lx\n",
+ pvPM, usFmt, WinGetLastError(g_habWorker));
+ DosFreeMem(pvPM);
+ }
+ }
+ RTMemPageFree(pv, cbAllocated);
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2RenderFormat: Failed to query / allocate data. rc=%Rrc cb=%#RX32\n", rc, cb);
+ }
+
+ /*
+ * Empty the clipboard on failure so we don't end up in any loops.
+ */
+ if (!fSucceeded)
+ {
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+ g_enmState = kClipboardState_Destroying;
+ WinEmptyClipbrd(g_habWorker);
+ g_enmState = kClipboardState_Polling;
+ g_fEmptyClipboard = true;
+ vgsvcClipboardOs2PollViewer();
+ }
+}
+
+
+/**
+ * Sends data to the host.
+ *
+ * @param fFormat The data format the host is requesting.
+ */
+static void vgsvcClipboardOs2SendDataToHost(uint32_t fFormat)
+{
+ if (WinOpenClipbrd(g_habWorker))
+ {
+ PRTUTF16 pwszFree = NULL;
+ void *pv = NULL;
+ uint32_t cb = 0;
+
+ if (fFormat & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ /* Got any odin32 unicode text? */
+ PVOID pvPM;
+ PCLIPHEADER pHdr = (PCLIPHEADER)WinQueryClipbrdData(g_habWorker, g_atomOdin32UnicodeText);
+ if ( pHdr
+ && !memcmp(pHdr->achMagic, CLIPHEADER_MAGIC, sizeof(pHdr->achMagic)))
+ {
+ pv = pHdr + 1;
+ cb = pHdr->cbData;
+ }
+
+ /* Got any CF_TEXT? */
+ if ( !pv
+ && (pvPM = (PVOID)WinQueryClipbrdData(g_habWorker, CF_TEXT)) != NULL)
+ {
+ char *pszUtf8;
+ int rc = RTStrCurrentCPToUtf8(&pszUtf8, (const char *)pvPM);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 pwsz;
+ rc = RTStrToUtf16(pszUtf8, &pwsz);
+ if (RT_SUCCESS(rc))
+ {
+ pv = pwszFree = pwsz;
+ cb = (RTUtf16Len(pwsz) + 1) * sizeof(RTUTF16);
+ }
+ RTStrFree(pszUtf8);
+ }
+ }
+ }
+ if (!pv)
+ VGSvcError("vgsvcClipboardOs2SendDataToHost: couldn't find data for %#x\n", fFormat);
+
+ /*
+ * Now, sent whatever we've got to the host (it's waiting).
+ */
+ VGSvcVerbose(4, "clipboard: writing %pv/%#d (fFormat=%#x)\n", pv, cb, fFormat);
+ VbglR3ClipboardWriteData(g_u32ClientId, fFormat, pv, cb);
+ RTUtf16Free(pwszFree);
+
+ WinCloseClipbrd(g_habWorker);
+ }
+ else
+ {
+ VGSvcError("vgsvcClipboardOs2SendDataToHost: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ VGSvcVerbose(4, "clipboard: writing NULL/0 (fFormat=%x)\n", fFormat);
+ VbglR3ClipboardWriteData(g_u32ClientId, fFormat, NULL, 0);
+ }
+}
+
+
+/**
+ * Figure out what's on the clipboard and report it to the host.
+ */
+static void vgsvcClipboardOs2ReportFormats(void)
+{
+ uint32_t fFormats = 0;
+ ULONG ulFormat = 0;
+ while ((ulFormat = WinEnumClipbrdFmts(g_habWorker, ulFormat)) != 0)
+ {
+ if ( ulFormat == CF_TEXT
+ || ulFormat == g_atomOdin32UnicodeText)
+ fFormats |= VBOX_SHCL_FMT_UNICODETEXT;
+ /** @todo else bitmaps and stuff. */
+ }
+ VGSvcVerbose(4, "clipboard: reporting fFormats=%#x\n", fFormats);
+ VbglR3ClipboardReportFormats(g_u32ClientId, fFormats);
+}
+
+
+/**
+ * Poll the clipboard for changes.
+ *
+ * This is called both when we're the viewer and when we're
+ * falling back to polling. If something has changed it will
+ * notify the host.
+ */
+static void vgsvcClipboardOs2Poll(void)
+{
+ if (WinOpenClipbrd(g_habWorker))
+ {
+ /*
+ * If our dummy is no longer there, something has actually changed,
+ * unless the clipboard is really empty.
+ */
+ ULONG fFmtInfo;
+ if (!WinQueryClipbrdFmtInfo(g_habWorker, g_atomNothingChanged, &fFmtInfo))
+ {
+ if (WinEnumClipbrdFmts(g_habWorker, 0) != 0)
+ {
+ g_fEmptyClipboard = false;
+ vgsvcClipboardOs2ReportFormats();
+
+ /* inject the dummy */
+ PVOID pv;
+ APIRET rc = DosAllocSharedMem(&pv, NULL, 1, OBJ_GIVEABLE | OBJ_GETTABLE | PAG_READ | PAG_WRITE | PAG_COMMIT);
+ if (rc == NO_ERROR)
+ {
+ if (WinSetClipbrdData(g_habWorker, (ULONG)pv, g_atomNothingChanged, CFI_POINTER))
+ VGSvcVerbose(4, "clipboard: Added dummy item.\n");
+ else
+ {
+ VGSvcError("vgsvcClipboardOs2Poll: WinSetClipbrdData failed, lasterr=%#lx\n", WinGetLastError(g_habWorker));
+ DosFreeMem(pv);
+ }
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2Poll: DosAllocSharedMem(,,1,) -> %ld\n", rc);
+ }
+ else if (!g_fEmptyClipboard)
+ {
+ g_fEmptyClipboard = true;
+ VGSvcVerbose(3, "Reporting empty clipboard\n");
+ VbglR3ClipboardReportFormats(g_u32ClientId, 0);
+ }
+ }
+ WinCloseClipbrd(g_habWorker);
+ }
+ else
+ VGSvcError("vgsvcClipboardOs2Poll: WinOpenClipbrd failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+}
+
+
+/**
+ * The clipboard we owned was destroyed by someone else.
+ */
+static void vgsvcClipboardOs2Destroyed(void)
+{
+ /* make sure we're no longer the owner. */
+ if (WinQueryClipbrdOwner(g_habWorker) == g_hwndWorker)
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+
+ /* switch to polling state and notify the host. */
+ g_enmState = kClipboardState_Polling;
+ g_fEmptyClipboard = true;
+ VGSvcVerbose(3, "Reporting empty clipboard\n");
+ VbglR3ClipboardReportFormats(g_u32ClientId, 0);
+
+ vgsvcClipboardOs2PollViewer();
+}
+
+
+/**
+ * The window procedure for the object window.
+ *
+ * @returns Message result.
+ *
+ * @param hwnd The window handle.
+ * @param msg The message.
+ * @param mp1 Message parameter 1.
+ * @param mp2 Message parameter 2.
+ */
+static MRESULT EXPENTRY vgsvcClipboardOs2WinProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
+{
+ if (msg != WM_TIMER)
+ VGSvcVerbose(6, "vgsvcClipboardOs2WinProc: hwnd=%#lx msg=%#lx mp1=%#lx mp2=%#lx\n", hwnd, msg, mp1, mp2);
+
+ switch (msg)
+ {
+ /*
+ * Handle the two system defined messages for object windows.
+ *
+ * We'll just use the CREATE/DESTROY message to create that timer we're
+ * using for the viewer checks and polling fallback.
+ */
+ case WM_CREATE:
+ g_idWorkerTimer = WinStartTimer(g_habWorker, hwnd, 1 /* id */, 1000 /* 1 second */);
+ g_fEmptyClipboard = true;
+ g_enmState = kClipboardState_Polling;
+ return NULL; /* FALSE(/NULL) == Continue*/
+
+ case WM_DESTROY:
+ WinStopTimer(g_habWorker, hwnd, g_idWorkerTimer);
+ g_idWorkerTimer = ~0UL;
+ g_hwndWorker = NULLHANDLE;
+ break;
+
+ /*
+ * Clipboard viewer message - the content has been changed.
+ * This is sent *after* releasing the clipboard sem
+ * and during the WinSetClipbrdViewer call.
+ */
+ case WM_DRAWCLIPBOARD:
+ if (g_enmState == kClipboardState_SettingViewer)
+ break;
+ AssertMsgBreak(g_enmState == kClipboardState_Viewer, ("g_enmState=%d\n", g_enmState));
+ vgsvcClipboardOs2Poll();
+ break;
+
+ /*
+ * Clipboard owner message - the content was replaced.
+ * This is sent by someone with an open clipboard, so don't try open it now.
+ */
+ case WM_DESTROYCLIPBOARD:
+ if (g_enmState == kClipboardState_Destroying)
+ break; /* it's us doing the replacing, ignore. */
+ AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
+ vgsvcClipboardOs2Destroyed();
+ break;
+
+ /*
+ * Clipboard owner message - somebody is requesting us to render a format.
+ * This is called by someone which owns the clipboard, but that's fine.
+ */
+ case WM_RENDERFMT:
+ AssertMsgBreak(g_enmState == kClipboardState_Owner, ("g_enmState=%d\n", g_enmState));
+ vgsvcClipboardOs2RenderFormat(SHORT1FROMMP(mp1));
+ break;
+
+ /*
+ * Clipboard owner message - we're about to quit and should render all formats.
+ *
+ * However, because we're lazy, we'll just ASSUME that since we're quitting
+ * we're probably about to shutdown or something and there is no point in
+ * doing anything here except for emptying the clipboard and removing
+ * ourselves as owner. Any failures at this point are silently ignored.
+ */
+ case WM_RENDERALLFMTS:
+ WinOpenClipbrd(g_habWorker);
+ WinSetClipbrdOwner(g_habWorker, NULLHANDLE);
+ g_enmState = kClipboardState_Destroying;
+ WinEmptyClipbrd(g_habWorker);
+ g_enmState = kClipboardState_Polling;
+ g_fEmptyClipboard = true;
+ WinCloseClipbrd(g_habWorker);
+ break;
+
+ /*
+ * Listener message - the host has new formats to offer.
+ */
+ case WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ vgsvcClipboardOs2AdvertiseHostFormats(LONGFROMMP(mp1));
+ break;
+
+ /*
+ * Listener message - the host wish to read our clipboard data.
+ */
+ case WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA:
+ vgsvcClipboardOs2SendDataToHost(LONGFROMMP(mp1));
+ break;
+
+ /*
+ * This is just a fallback polling strategy in case some other
+ * app is trying to view the clipboard too. We also use this
+ * to try recover from errors.
+ *
+ * Because the way the clipboard service works, we have to monitor
+ * it all the time and cannot get away with simpler solutions like
+ * synergy is employing (basically checking upon entering and leaving
+ * a desktop).
+ */
+ case WM_TIMER:
+ if ( g_enmState != kClipboardState_Viewer
+ && g_enmState != kClipboardState_Polling)
+ break;
+
+ /* Lost the position as clipboard viewer?*/
+ if (g_enmState == kClipboardState_Viewer)
+ {
+ if (WinQueryClipbrdViewer(g_habWorker) == hwnd)
+ break;
+ g_enmState = kClipboardState_Polling;
+ }
+
+ /* poll for changes */
+ vgsvcClipboardOs2Poll();
+ vgsvcClipboardOs2PollViewer();
+ break;
+
+
+ /*
+ * Clipboard owner messages dealing with owner drawn content.
+ * We shouldn't be seeing any of these.
+ */
+ case WM_PAINTCLIPBOARD:
+ case WM_SIZECLIPBOARD:
+ case WM_HSCROLLCLIPBOARD:
+ case WM_VSCROLLCLIPBOARD:
+ AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
+ break;
+
+ /*
+ * We shouldn't be seeing any other messages according to the docs.
+ * But for whatever reason, PM sends us a WM_ADJUSTWINDOWPOS message
+ * during WinCreateWindow. So, ignore that and assert on anything else.
+ */
+ default:
+ AssertMsgFailed(("msg=%lx (%ld)\n", msg, msg));
+ case WM_ADJUSTWINDOWPOS:
+ break;
+ }
+ return NULL;
+}
+
+
+/**
+ * The listener thread.
+ *
+ * This thread is dedicated to listening for host messages and forwarding
+ * these to the worker thread (using PM).
+ *
+ * The thread will set g_fListenerOkay and signal its user event when it has
+ * completed initialization. In the case of init failure g_fListenerOkay will
+ * not be set.
+ *
+ * @returns Init error code or VINF_SUCCESS.
+ * @param ThreadSelf Our thread handle.
+ * @param pvUser Pointer to the clipboard service shutdown indicator.
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Listener(RTTHREAD ThreadSelf, void *pvUser)
+{
+ bool volatile *pfShutdown = (bool volatile *)pvUser;
+ int rc = VERR_GENERAL_FAILURE;
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: ThreadSelf=%RTthrd\n", ThreadSelf);
+
+ g_habListener = WinInitialize(0);
+ if (g_habListener != NULLHANDLE)
+ {
+ g_hmqListener = WinCreateMsgQueue(g_habListener, 0);
+ if (g_hmqListener != NULLHANDLE)
+ {
+ WinCancelShutdown(g_hmqListener, TRUE); /* We don't care about shutdown */
+
+ /*
+ * Tell the worker thread that we're good.
+ */
+ rc = VINF_SUCCESS;
+ ASMAtomicXchgBool(&g_fListenerOkay, true);
+ RTThreadUserSignal(ThreadSelf);
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Started successfully\n");
+
+ /*
+ * Loop until termination is requested.
+ */
+ bool fQuit = false;
+ while (!*pfShutdown && !fQuit)
+ {
+ uint32_t Msg;
+ uint32_t fFormats;
+ rc = VbglR3ClipboardGetHostMsgOld(g_u32ClientId, &Msg, &fFormats);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: Msg=%#x fFormats=%#x\n", Msg, fFormats);
+ switch (Msg)
+ {
+ /*
+ * The host has announced available clipboard formats.
+ * Forward the information to the window, so it can later
+ * respond do WM_RENDERFORMAT message.
+ */
+ case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_FORMATS_REPORT,
+ MPFROMLONG(fFormats), 0))
+ VGSvcError("WinPostMsg(%lx, FORMATS,,) failed, lasterr=%#lx\n",
+ g_hwndWorker, WinGetLastError(g_habListener));
+ break;
+
+ /*
+ * The host needs data in the specified format.
+ */
+ case VBOX_SHCL_HOST_MSG_READ_DATA:
+ if (!WinPostMsg(g_hwndWorker, WM_USER + VBOX_SHCL_HOST_MSG_READ_DATA,
+ MPFROMLONG(fFormats), 0))
+ VGSvcError("WinPostMsg(%lx, READ_DATA,,) failed, lasterr=%#lx\n",
+ g_hwndWorker, WinGetLastError(g_habListener));
+ break;
+
+ /*
+ * The host is terminating.
+ */
+ case VBOX_SHCL_HOST_MSG_QUIT:
+ fQuit = true;
+ break;
+
+ default:
+ VGSvcVerbose(1, "vgsvcClipboardOs2Listener: Unknown message %RU32\n", Msg);
+ break;
+ }
+ }
+ else
+ {
+ if (*pfShutdown)
+ break;
+ VGSvcError("VbglR3ClipboardGetHostMsg failed, rc=%Rrc\n", rc);
+ RTThreadSleep(1000);
+ }
+ } /* the loop */
+
+ WinDestroyMsgQueue(g_hmqListener);
+ }
+ WinTerminate(g_habListener);
+ g_habListener = NULLHANDLE;
+ }
+
+ /* Signal our semaphore to make the worker catch on. */
+ RTThreadUserSignal(ThreadSelf);
+ VGSvcVerbose(3, "vgsvcClipboardOs2Listener: terminating, rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcClipboardOs2Worker(bool volatile *pfShutdown)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ /*
+ * Standard PM init.
+ */
+ g_habWorker = RTThreadSelf() != g_ThreadCtrl ? WinInitialize(0) : g_habCtrl;
+ if (g_habWorker != NULLHANDLE)
+ {
+ g_hmqWorker = RTThreadSelf() != g_ThreadCtrl ? WinCreateMsgQueue(g_habWorker, 0) : g_hmqCtrl;
+ if (g_hmqWorker != NULLHANDLE)
+ {
+ if (g_hmqWorker != g_hmqCtrl)
+ WinCancelShutdown(g_hmqWorker, TRUE); /* We don't care about shutdown */
+
+ /*
+ * Create the object window.
+ */
+ if (WinRegisterClass(g_habWorker, (PCSZ)"VBoxServiceClipboardClass", vgsvcClipboardOs2WinProc, 0, 0))
+ {
+ g_hwndWorker = WinCreateWindow(HWND_OBJECT, /* hwndParent */
+ (PCSZ)"VBoxServiceClipboardClass", /* pszClass */
+ (PCSZ)"VirtualBox Clipboard Service", /* pszName */
+ 0, /* flStyle */
+ 0, 0, 0, 0, /* x, y, cx, cy */
+ NULLHANDLE, /* hwndOwner */
+ HWND_BOTTOM, /* hwndInsertBehind */
+ 42, /* id */
+ NULL, /* pCtlData */
+ NULL); /* pPresParams */
+ if (g_hwndWorker != NULLHANDLE)
+ {
+ VGSvcVerbose(3, "g_hwndWorker=%#lx g_habWorker=%#lx g_hmqWorker=%#lx\n", g_hwndWorker, g_habWorker, g_hmqWorker);
+
+ /*
+ * Create the listener thread.
+ */
+ g_fListenerOkay = false;
+ rc = RTThreadCreate(&g_ThreadListener, vgsvcClipboardOs2Listener, (void *)pfShutdown, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "CLIPLISTEN");
+ if (RT_SUCCESS(rc))
+ {
+ RTThreadUserWait(g_ThreadListener, 30*1000);
+ RTThreadUserReset(g_ThreadListener);
+ if (!g_fListenerOkay)
+ RTThreadWait(g_ThreadListener, 60*1000, NULL);
+ if (g_fListenerOkay)
+ {
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * The PM event pump.
+ */
+ VGSvcVerbose(2, "clipboard: Entering PM message loop.\n");
+ rc = VINF_SUCCESS;
+ QMSG qmsg;
+ while (WinGetMsg(g_habWorker, &qmsg, NULLHANDLE, NULLHANDLE, 0))
+ {
+ if (qmsg.msg != WM_TIMER)
+ VGSvcVerbose(6, "WinGetMsg -> hwnd=%p msg=%#x mp1=%p mp2=%p time=%#x ptl=%d,%d rsrv=%#x\n",
+ qmsg.hwnd, qmsg.msg, qmsg.mp1, qmsg.mp2, qmsg.time, qmsg.ptl.x, qmsg.ptl.y, qmsg.reserved);
+ WinDispatchMsg(g_habWorker, &qmsg);
+ }
+ VGSvcVerbose(2, "clipboard: Exited PM message loop. *pfShutdown=%RTbool\n", *pfShutdown);
+
+ RTThreadWait(g_ThreadListener, 60*1000, NULL);
+ }
+ g_ThreadListener = NIL_RTTHREAD;
+ }
+
+ /*
+ * Got a WM_QUIT, clean up.
+ */
+ if (g_hwndWorker != NULLHANDLE)
+ {
+ WinDestroyWindow(g_hwndWorker);
+ g_hwndWorker = NULLHANDLE;
+ }
+ }
+ else
+ VGSvcError("WinCreateWindow() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+ /* no class deregistration in PM. */
+ }
+ else
+ VGSvcError("WinRegisterClass() failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+
+ if (g_hmqCtrl != g_hmqWorker)
+ WinDestroyMsgQueue(g_hmqWorker);
+ g_hmqWorker = NULLHANDLE;
+ }
+ else
+ VGSvcError("WinCreateMsgQueue(,0) failed, lasterr=%lx\n", WinGetLastError(g_habWorker));
+
+ if (g_habCtrl != g_habWorker)
+ WinTerminate(g_habWorker);
+ g_habWorker = NULLHANDLE;
+ }
+ else
+ VGSvcError("WinInitialize(0) failed, lasterr=%lx\n", WinGetLastError(NULLHANDLE));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcClipboardOs2Stop(void)
+{
+ if ( g_hmqWorker != NULLHANDLE
+ && !WinPostQueueMsg(g_hmqWorker, WM_QUIT, NULL, NULL))
+ VGSvcError("WinPostQueueMsg(g_hmqWorker, WM_QUIT, 0,0) failed, lasterr=%lx\n", WinGetLastError(g_habCtrl));
+
+ /* Must disconnect the clipboard here otherwise the listner won't quit and
+ the service shutdown will not stop. */
+ if (g_u32ClientId != 0)
+ {
+ if (g_hmqWorker != NULLHANDLE)
+ RTThreadSleep(32); /* fudge */
+
+ VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
+ int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ g_u32ClientId = 0;
+ else
+ VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
+ }
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcClipboardOs2Term(void)
+{
+ if (g_u32ClientId != 0)
+ {
+ VGSvcVerbose(4, "clipboard: disconnecting %#x\n", g_u32ClientId);
+ int rc = VbglR3ClipboardDisconnect(g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ g_u32ClientId = 0;
+ else
+ VGSvcError("clipboard: VbglR3ClipboardDisconnect(%#x) -> %Rrc\n", g_u32ClientId, rc);
+ }
+ WinDestroyMsgQueue(g_hmqCtrl);
+ g_hmqCtrl = NULLHANDLE;
+ WinTerminate(g_habCtrl);
+ g_habCtrl = NULLHANDLE;
+}
+
+
+/**
+ * The OS/2 'clipboard' service description.
+ */
+VBOXSERVICE g_Clipboard =
+{
+ /* pszName. */
+ "clipboard",
+ /* pszDescription. */
+ "Shared Clipboard",
+ /* pszUsage. */
+ ""
+ ,
+ /* pszOptions. */
+ ""
+ ,
+ /* methods */
+ vgsvcClipboardOs2PreInit,
+ vgsvcClipboardOs2Option,
+ vgsvcClipboardOs2Init,
+ vgsvcClipboardOs2Worker,
+ vgsvcClipboardOs2Stop,
+ vgsvcClipboardOs2Term
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp
new file mode 100644
index 00000000..6e30cdff
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.cpp
@@ -0,0 +1,629 @@
+/* $Id: VBoxServiceControl.cpp $ */
+/** @file
+ * VBoxServiceControl - Host-driven Guest Control.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_vgsvc_gstctrl VBoxService - Guest Control
+ *
+ * The Guest Control subservice helps implementing the IGuest APIs.
+ *
+ * The communication between this service (and its children) and IGuest goes
+ * over the HGCM GuestControl service.
+ *
+ * The IGuest APIs provides means to manipulate (control) files, directories,
+ * symbolic links and processes within the guest. Most of these means requires
+ * credentials of a guest OS user to operate, though some restricted ones
+ * operates directly as the VBoxService user (root / system service account).
+ *
+ * The current design is that a subprocess is spawned for handling operations as
+ * a given user. This process is represented as IGuestSession in the API. The
+ * subprocess will be spawned as the given use, giving up the privileges the
+ * parent subservice had.
+ *
+ * It will try handle as many of the operations directly from within the
+ * subprocess, but for more complicated things (or things that haven't yet been
+ * converted), it will spawn a helper process that does the actual work.
+ *
+ * These helpers are the typically modeled on similar unix core utilities, like
+ * mkdir, rm, rmdir, cat and so on. The helper tools can also be launched
+ * directly from VBoxManage by the user by prepending the 'vbox_' prefix to the
+ * unix command.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceControl.h"
+#include "VBoxServiceUtils.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The control interval (milliseconds). */
+static uint32_t g_msControlInterval = 0;
+/** The semaphore we're blocking our main control thread on. */
+static RTSEMEVENTMULTI g_hControlEvent = NIL_RTSEMEVENTMULTI;
+/** The VM session ID. Changes whenever the VM is restored or reset. */
+static uint64_t g_idControlSession;
+/** The guest control service client ID. */
+uint32_t g_idControlSvcClient = 0;
+/** VBOX_GUESTCTRL_HF_XXX */
+uint64_t g_fControlHostFeatures0 = 0;
+#if 0 /** @todo process limit */
+/** How many started guest processes are kept into memory for supplying
+ * information to the host. Default is 256 processes. If 0 is specified,
+ * the maximum number of processes is unlimited. */
+static uint32_t g_uControlProcsMaxKept = 256;
+#endif
+/** List of guest control session threads (VBOXSERVICECTRLSESSIONTHREAD).
+ * A guest session thread represents a forked guest session process
+ * of VBoxService. */
+RTLISTANCHOR g_lstControlSessionThreads;
+/** The local session object used for handling all session-related stuff.
+ * When using the legacy guest control protocol (< 2), this session runs
+ * under behalf of the VBoxService main process. On newer protocol versions
+ * each session is a forked version of VBoxService using the appropriate
+ * user credentials for opening a guest session. These forked sessions then
+ * are kept in VBOXSERVICECTRLSESSIONTHREAD structures. */
+VBOXSERVICECTRLSESSION g_Session;
+/** Copy of VbglR3GuestCtrlSupportsOptimizations().*/
+bool g_fControlSupportsOptimizations = true;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx);
+static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx);
+static int vgsvcGstCtrlInvalidate(void);
+static void vgsvcGstCtrlShutdown(void);
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlPreInit(void)
+{
+ int rc;
+#ifdef VBOX_WITH_GUEST_PROPS
+ /*
+ * Read the service options from the VM's guest properties.
+ * Note that these options can be overridden by the command line options later.
+ */
+ uint32_t uGuestPropSvcClientID;
+ rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "Guest property service is not available, skipping\n");
+ rc = VINF_SUCCESS;
+ }
+ else
+ VGSvcError("Failed to connect to the guest property service, rc=%Rrc\n", rc);
+ }
+ else
+ VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
+
+ if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
+ rc = VINF_SUCCESS;
+#else
+ /* Nothing to do here yet. */
+ rc = VINF_SUCCESS;
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Init session object. */
+ rc = VGSvcGstCtrlSessionInit(&g_Session, 0 /* Flags */);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlOption(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ int rc = -1;
+ if (ppszShort)
+ /* no short options */;
+ else if (!strcmp(argv[*pi], "--control-interval"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi,
+ &g_msControlInterval, 1, UINT32_MAX - 1);
+#ifdef DEBUG
+ else if (!strcmp(argv[*pi], "--control-dump-stdout"))
+ {
+ g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT;
+ rc = 0; /* Flag this command as parsed. */
+ }
+ else if (!strcmp(argv[*pi], "--control-dump-stderr"))
+ {
+ g_Session.fFlags |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR;
+ rc = 0; /* Flag this command as parsed. */
+ }
+#endif
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlInit(void)
+{
+ /*
+ * If not specified, find the right interval default.
+ * Then create the event sem to block on.
+ */
+ if (!g_msControlInterval)
+ g_msControlInterval = 1000;
+
+ int rc = RTSemEventMultiCreate(&g_hControlEvent);
+ AssertRCReturn(rc, rc);
+
+ VbglR3GetSessionId(&g_idControlSession); /* The status code is ignored as this information is not available with VBox < 3.2.10. */
+
+ RTListInit(&g_lstControlSessionThreads);
+
+ /*
+ * Try connect to the host service and tell it we want to be master (if supported).
+ */
+ rc = VbglR3GuestCtrlConnect(&g_idControlSvcClient);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgsvcGstCtrlInvalidate();
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ else
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "Guest control service is not available\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ VGSvcError("Failed to connect to the guest control service! Error: %Rrc\n", rc);
+ }
+ RTSemEventMultiDestroy(g_hControlEvent);
+ g_hControlEvent = NIL_RTSEMEVENTMULTI;
+ g_idControlSvcClient = 0;
+ return rc;
+}
+
+static int vgsvcGstCtrlInvalidate(void)
+{
+ VGSvcVerbose(1, "Invalidating configuration ...\n");
+
+ int rc = VINF_SUCCESS;
+
+ g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient);
+ if (g_fControlSupportsOptimizations)
+ rc = VbglR3GuestCtrlMakeMeMaster(g_idControlSvcClient);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Guest control service client ID=%RU32%s\n",
+ g_idControlSvcClient, g_fControlSupportsOptimizations ? " w/ optimizations" : "");
+
+ /*
+ * Report features to the host.
+ */
+ const uint64_t fGuestFeatures = VBOX_GUESTCTRL_GF_0_SET_SIZE
+ | VBOX_GUESTCTRL_GF_0_PROCESS_ARGV0
+ | VBOX_GUESTCTRL_GF_0_PROCESS_DYNAMIC_SIZES
+ | VBOX_GUESTCTRL_GF_0_SHUTDOWN;
+
+ rc = VbglR3GuestCtrlReportFeatures(g_idControlSvcClient, fGuestFeatures, &g_fControlHostFeatures0);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Host features: %#RX64\n", g_fControlHostFeatures0);
+ else
+ VGSvcVerbose(1, "Warning! Feature reporing failed: %Rrc\n", rc);
+
+ return VINF_SUCCESS;
+ }
+ VGSvcError("Failed to become guest control master: %Rrc\n", rc);
+ VbglR3GuestCtrlDisconnect(g_idControlSvcClient);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+ Assert(g_idControlSvcClient > 0);
+
+ /* Allocate a scratch buffer for messages which also send
+ * payload data with them. */
+ uint32_t cbScratchBuf = _64K; /** @todo Make buffer size configurable via guest properties/argv! */
+ AssertReturn(RT_IS_POWER_OF_TWO(cbScratchBuf), VERR_INVALID_PARAMETER);
+ uint8_t *pvScratchBuf = (uint8_t*)RTMemAlloc(cbScratchBuf);
+ AssertReturn(pvScratchBuf, VERR_NO_MEMORY);
+
+ int rc = VINF_SUCCESS; /* (shut up compiler warnings) */
+ int cRetrievalFailed = 0; /* Number of failed message retrievals in a row. */
+ while (!*pfShutdown)
+ {
+ VGSvcVerbose(3, "GstCtrl: Waiting for host msg ...\n");
+ VBGLR3GUESTCTRLCMDCTX ctxHost = { g_idControlSvcClient, 0 /*idContext*/, 2 /*uProtocol*/, 0 /*cParms*/ };
+ uint32_t idMsg = 0;
+ rc = VbglR3GuestCtrlMsgPeekWait(g_idControlSvcClient, &idMsg, &ctxHost.uNumParms, &g_idControlSession);
+ if (RT_SUCCESS(rc))
+ {
+ cRetrievalFailed = 0; /* Reset failed retrieval count. */
+ VGSvcVerbose(4, "idMsg=%RU32 (%s) (%RU32 parms) retrieved\n",
+ idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms);
+
+ /*
+ * Handle the host message.
+ */
+ switch (idMsg)
+ {
+ case HOST_MSG_CANCEL_PENDING_WAITS:
+ VGSvcVerbose(1, "We were asked to quit ...\n");
+ break;
+
+ case HOST_MSG_SESSION_CREATE:
+ rc = vgsvcGstCtrlHandleSessionOpen(&ctxHost);
+ break;
+
+ /* This message is also sent to the child session process (by the host). */
+ case HOST_MSG_SESSION_CLOSE:
+ rc = vgsvcGstCtrlHandleSessionClose(&ctxHost);
+ break;
+
+ default:
+ if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient))
+ {
+ rc = VbglR3GuestCtrlMsgSkip(g_idControlSvcClient, VERR_NOT_SUPPORTED, idMsg);
+ VGSvcVerbose(1, "Skipped unexpected message idMsg=%RU32 (%s), cParms=%RU32 (rc=%Rrc)\n",
+ idMsg, GstCtrlHostMsgtoStr((eHostMsg)idMsg), ctxHost.uNumParms, rc);
+ }
+ else
+ {
+ rc = VbglR3GuestCtrlMsgSkipOld(g_idControlSvcClient);
+ VGSvcVerbose(3, "Skipped idMsg=%RU32, cParms=%RU32, rc=%Rrc\n", idMsg, ctxHost.uNumParms, rc);
+ }
+ break;
+ }
+
+ /* Do we need to shutdown? */
+ if (idMsg == HOST_MSG_CANCEL_PENDING_WAITS)
+ break;
+
+ /* Let's sleep for a bit and let others run ... */
+ RTThreadYield();
+ }
+ /*
+ * Handle restore notification from host. All the context IDs (sessions,
+ * files, proceses, etc) are invalidated by a VM restore and must be closed.
+ */
+ else if (rc == VERR_VM_RESTORED)
+ {
+ VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale root session\n");
+
+ /* Make sure that all other session threads are gone.
+ * This is necessary, as the new VM session (NOT to be confused with guest session!) will re-use
+ * the guest session IDs. */
+ int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2);
+
+ /* Make sure to also close the root session (session 0). */
+ rc2 = VGSvcGstCtrlSessionClose(&g_Session);
+ AssertRC(rc2);
+
+ rc2 = VbglR3GuestCtrlSessionHasChanged(g_idControlSvcClient, g_idControlSession);
+ AssertRC(rc2);
+
+ /* Invalidate the internal state to match the current host we got restored from. */
+ rc2 = vgsvcGstCtrlInvalidate();
+ AssertRC(rc2);
+ }
+ else
+ {
+ /* Note: VERR_GEN_IO_FAILURE seems to be normal if ran into timeout. */
+ /** @todo r=bird: Above comment makes no sense. How can you get a timeout in a blocking HGCM call? */
+ VGSvcError("GstCtrl: Getting host message failed with %Rrc\n", rc);
+
+ /* Check for VM session change. */
+ /** @todo We don't need to check the host here. */
+ uint64_t idNewSession = g_idControlSession;
+ int rc2 = VbglR3GetSessionId(&idNewSession);
+ if ( RT_SUCCESS(rc2)
+ && (idNewSession != g_idControlSession))
+ {
+ VGSvcVerbose(1, "GstCtrl: The VM session ID changed\n");
+ g_idControlSession = idNewSession;
+
+ /* Close all opened guest sessions -- all context IDs, sessions etc.
+ * are now invalid. */
+ rc2 = VGSvcGstCtrlSessionClose(&g_Session);
+ AssertRC(rc2);
+
+ /* Do a reconnect. */
+ VGSvcVerbose(1, "Reconnecting to HGCM service ...\n");
+ rc2 = VbglR3GuestCtrlConnect(&g_idControlSvcClient);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(3, "Guest control service client ID=%RU32\n", g_idControlSvcClient);
+ cRetrievalFailed = 0;
+ continue; /* Skip waiting. */
+ }
+ VGSvcError("Unable to re-connect to HGCM service, rc=%Rrc, bailing out\n", rc);
+ break;
+ }
+
+ if (rc == VERR_INTERRUPTED)
+ RTThreadYield(); /* To be on the safe side... */
+ else if (++cRetrievalFailed <= 16) /** @todo Make this configurable? */
+ RTThreadSleep(1000); /* Wait a bit before retrying. */
+ else
+ {
+ VGSvcError("Too many failed attempts in a row to get next message, bailing out\n");
+ break;
+ }
+ }
+ }
+
+ VGSvcVerbose(0, "Guest control service stopped\n");
+
+ /* Delete scratch buffer. */
+ if (pvScratchBuf)
+ RTMemFree(pvScratchBuf);
+
+ VGSvcVerbose(0, "Guest control worker returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlHandleSessionOpen(PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message parameters.
+ */
+ PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pStartupInfo;
+ int rc = VbglR3GuestCtrlSessionGetOpen(pHostCtx, &pStartupInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Flat out refuse to work with protocol v1 hosts.
+ */
+ if (pStartupInfo->uProtocol == 2)
+ {
+ pHostCtx->uProtocol = pStartupInfo->uProtocol;
+ VGSvcVerbose(3, "Client ID=%RU32 now is using protocol %RU32\n", pHostCtx->uClientID, pHostCtx->uProtocol);
+
+/** @todo Someone explain why this code isn't in this file too? v1 support? */
+ rc = VGSvcGstCtrlSessionThreadCreate(&g_lstControlSessionThreads, pStartupInfo, NULL /* ppSessionThread */);
+ /* Report failures to the host (successes are taken care of by the session thread). */
+ }
+ else
+ {
+ VGSvcError("The host wants to use protocol v%u, we only support v2!\n", pStartupInfo->uProtocol);
+ rc = VERR_VERSION_MISMATCH;
+ }
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx, GUEST_SESSION_NOTIFYTYPE_ERROR, rc);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Reporting session error status on open failed with rc=%Rrc\n", rc2);
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for opening guest session: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VbglR3GuestCtrlSessionStartupInfoFree(pStartupInfo);
+ pStartupInfo = NULL;
+
+ VGSvcVerbose(3, "Opening a new guest session returned rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlHandleSessionClose(PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ uint32_t idSession;
+ uint32_t fFlags;
+ int rc = VbglR3GuestCtrlSessionGetClose(pHostCtx, &fFlags, &idSession);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_NOT_FOUND;
+
+ PVBOXSERVICECTRLSESSIONTHREAD pThread;
+ RTListForEach(&g_lstControlSessionThreads, pThread, VBOXSERVICECTRLSESSIONTHREAD, Node)
+ {
+ if ( pThread->pStartupInfo
+ && pThread->pStartupInfo->uSessionID == idSession)
+ {
+ rc = VGSvcGstCtrlSessionThreadDestroy(pThread, fFlags);
+ break;
+ }
+ }
+
+#if 0 /** @todo A bit of a mess here as this message goes to both to this process (master) and the session process. */
+ if (RT_FAILURE(rc))
+ {
+ /* Report back on failure. On success this will be done
+ * by the forked session thread. */
+ int rc2 = VbglR3GuestCtrlSessionNotify(pHostCtx,
+ GUEST_SESSION_NOTIFYTYPE_ERROR, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Reporting session error status on close failed with rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+#endif
+ VGSvcVerbose(2, "Closing guest session %RU32 returned rc=%Rrc\n", idSession, rc);
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for closing guest session: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcGstCtrlStop(void)
+{
+ VGSvcVerbose(3, "Stopping ...\n");
+
+ /** @todo Later, figure what to do if we're in RTProcWait(). It's a very
+ * annoying call since doesn't support timeouts in the posix world. */
+ if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(g_hControlEvent);
+
+ /*
+ * Ask the host service to cancel all pending requests for the main
+ * control thread so that we can shutdown properly here.
+ */
+ if (g_idControlSvcClient)
+ {
+ VGSvcVerbose(3, "Cancelling pending waits (client ID=%u) ...\n",
+ g_idControlSvcClient);
+
+ int rc = VbglR3GuestCtrlCancelPendingWaits(g_idControlSvcClient);
+ if (RT_FAILURE(rc))
+ VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc);
+ }
+}
+
+
+/**
+ * Destroys all guest process threads which are still active.
+ */
+static void vgsvcGstCtrlShutdown(void)
+{
+ VGSvcVerbose(2, "Shutting down ...\n");
+
+ int rc2 = VGSvcGstCtrlSessionThreadDestroyAll(&g_lstControlSessionThreads, 0 /* Flags */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Closing session threads failed with rc=%Rrc\n", rc2);
+
+ rc2 = VGSvcGstCtrlSessionClose(&g_Session);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Closing session failed with rc=%Rrc\n", rc2);
+
+ VGSvcVerbose(2, "Shutting down complete\n");
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcGstCtrlTerm(void)
+{
+ VGSvcVerbose(3, "Terminating ...\n");
+
+ vgsvcGstCtrlShutdown();
+
+ VGSvcVerbose(3, "Disconnecting client ID=%u ...\n", g_idControlSvcClient);
+ VbglR3GuestCtrlDisconnect(g_idControlSvcClient);
+ g_idControlSvcClient = 0;
+
+ if (g_hControlEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_hControlEvent);
+ g_hControlEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'vminfo' service description.
+ */
+VBOXSERVICE g_Control =
+{
+ /* pszName. */
+ "control",
+ /* pszDescription. */
+ "Host-driven Guest Control",
+ /* pszUsage. */
+#ifdef DEBUG
+ " [--control-dump-stderr] [--control-dump-stdout]\n"
+#endif
+ " [--control-interval <ms>]"
+ ,
+ /* pszOptions. */
+#ifdef DEBUG
+ " --control-dump-stderr Dumps all guest proccesses stderr data to the\n"
+ " temporary directory.\n"
+ " --control-dump-stdout Dumps all guest proccesses stdout data to the\n"
+ " temporary directory.\n"
+#endif
+ " --control-interval Specifies the interval at which to check for\n"
+ " new control messages. The default is 1000 ms.\n"
+ ,
+ /* methods */
+ vgsvcGstCtrlPreInit,
+ vgsvcGstCtrlOption,
+ vgsvcGstCtrlInit,
+ vgsvcGstCtrlWorker,
+ vgsvcGstCtrlStop,
+ vgsvcGstCtrlTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h
new file mode 100644
index 00000000..2a0d6513
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControl.h
@@ -0,0 +1,297 @@
+/* $Id: VBoxServiceControl.h $ */
+/** @file
+ * VBoxServiceControl.h - Internal guest control definitions.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/critsect.h>
+#include <iprt/list.h>
+#include <iprt/req.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/GuestControl.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+
+
+/**
+ * Pipe IDs for handling the guest process poll set.
+ */
+typedef enum VBOXSERVICECTRLPIPEID
+{
+ VBOXSERVICECTRLPIPEID_UNKNOWN = 0,
+ VBOXSERVICECTRLPIPEID_STDIN = 10,
+ VBOXSERVICECTRLPIPEID_STDIN_WRITABLE = 11,
+ /** Pipe for reading from guest process' stdout. */
+ VBOXSERVICECTRLPIPEID_STDOUT = 40,
+ /** Pipe for reading from guest process' stderr. */
+ VBOXSERVICECTRLPIPEID_STDERR = 50,
+ /** Notification pipe for waking up the guest process
+ * control thread. */
+ VBOXSERVICECTRLPIPEID_IPC_NOTIFY = 100
+} VBOXSERVICECTRLPIPEID;
+
+/**
+ * Structure for one (opened) guest file.
+ */
+typedef struct VBOXSERVICECTRLFILE
+{
+ /** Pointer to list archor of following
+ * list node.
+ * @todo Would be nice to have a RTListGetAnchor(). */
+ PRTLISTANCHOR pAnchor;
+ /** Node to global guest control file list. */
+ /** @todo Use a map later? */
+ RTLISTNODE Node;
+ /** The file name. */
+ char *pszName;
+ /** The file handle on the guest. */
+ RTFILE hFile;
+ /** File handle to identify this file. */
+ uint32_t uHandle;
+ /** Context ID. */
+ uint32_t uContextID;
+ /** RTFILE_O_XXX flags. */
+ uint64_t fOpen;
+} VBOXSERVICECTRLFILE;
+/** Pointer to thread data. */
+typedef VBOXSERVICECTRLFILE *PVBOXSERVICECTRLFILE;
+
+/**
+ * Structure for a guest session thread to
+ * observe/control the forked session instance from
+ * the VBoxService main executable.
+ */
+typedef struct VBOXSERVICECTRLSESSIONTHREAD
+{
+ /** Node to global guest control session list. */
+ /** @todo Use a map later? */
+ RTLISTNODE Node;
+ /** The sessions's startup info. */
+ PVBGLR3GUESTCTRLSESSIONSTARTUPINFO
+ pStartupInfo;
+ /** Critical section for thread-safe use. */
+ RTCRITSECT CritSect;
+ /** The worker thread. */
+ RTTHREAD Thread;
+ /** Process handle for forked child. */
+ RTPROCESS hProcess;
+ /** Shutdown indicator; will be set when the thread
+ * needs (or is asked) to shutdown. */
+ bool volatile fShutdown;
+ /** Indicator set by the service thread exiting. */
+ bool volatile fStopped;
+ /** Whether the thread was started or not. */
+ bool fStarted;
+#if 0 /* Pipe IPC not used yet. */
+ /** Pollset containing all the pipes. */
+ RTPOLLSET hPollSet;
+ RTPIPE hStdInW;
+ RTPIPE hStdOutR;
+ RTPIPE hStdErrR;
+ struct StdPipe
+ {
+ RTHANDLE hChild;
+ PRTHANDLE phChild;
+ } StdIn,
+ StdOut,
+ StdErr;
+ /** The notification pipe associated with this guest session.
+ * This is NIL_RTPIPE for output pipes. */
+ RTPIPE hNotificationPipeW;
+ /** The other end of hNotificationPipeW. */
+ RTPIPE hNotificationPipeR;
+#endif
+ /** Pipe for handing the secret key to the session process. */
+ RTPIPE hKeyPipe;
+ /** Secret key. */
+ uint8_t abKey[_4K];
+} VBOXSERVICECTRLSESSIONTHREAD;
+/** Pointer to thread data. */
+typedef VBOXSERVICECTRLSESSIONTHREAD *PVBOXSERVICECTRLSESSIONTHREAD;
+
+/** Defines the prefix being used for telling our service executable that we're going
+ * to spawn a new (Guest Control) user session. */
+#define VBOXSERVICECTRLSESSION_GETOPT_PREFIX "guestsession"
+
+/** Flag indicating that this session has been spawned from
+ * the main executable. */
+#define VBOXSERVICECTRLSESSION_FLAG_SPAWN RT_BIT(0)
+/** Flag indicating that this session is anonymous, that is,
+ * it will run start guest processes with the same credentials
+ * as the main executable. */
+#define VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS RT_BIT(1)
+/** Flag indicating that started guest processes will dump their
+ * stdout output to a separate file on disk. For debugging. */
+#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT RT_BIT(2)
+/** Flag indicating that started guest processes will dump their
+ * stderr output to a separate file on disk. For debugging. */
+#define VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR RT_BIT(3)
+
+/**
+ * Structure for maintaining a guest session. This also
+ * contains all started threads (e.g. for guest processes).
+ *
+ * This structure can act in two different ways:
+ * - For legacy guest control handling (protocol version < 2)
+ * this acts as a per-guest process structure containing all
+ * the information needed to get a guest process up and running.
+ * - For newer guest control protocols (>= 2) this structure is
+ * part of the forked session child, maintaining all guest
+ * control objects under it.
+ */
+typedef struct VBOXSERVICECTRLSESSION
+{
+ /* The session's startup information. */
+ VBGLR3GUESTCTRLSESSIONSTARTUPINFO
+ StartupInfo;
+ /** List of active guest process threads
+ * (VBOXSERVICECTRLPROCESS). */
+ RTLISTANCHOR lstProcesses;
+ /** Number of guest processes in the process list. */
+ uint32_t cProcesses;
+ /** List of guest control files (VBOXSERVICECTRLFILE). */
+ RTLISTANCHOR lstFiles;
+ /** Number of guest files in the file list. */
+ uint32_t cFiles;
+ /** The session's critical section. */
+ RTCRITSECT CritSect;
+ /** Internal session flags, not related
+ * to StartupInfo stuff.
+ * @sa VBOXSERVICECTRLSESSION_FLAG_* flags. */
+ uint32_t fFlags;
+ /** How many processes do we allow keeping around at a time? */
+ uint32_t uProcsMaxKept;
+} VBOXSERVICECTRLSESSION;
+/** Pointer to guest session. */
+typedef VBOXSERVICECTRLSESSION *PVBOXSERVICECTRLSESSION;
+
+/**
+ * Structure for holding data for one (started) guest process.
+ */
+typedef struct VBOXSERVICECTRLPROCESS
+{
+ /** Node. */
+ RTLISTNODE Node;
+ /** Process handle. */
+ RTPROCESS hProcess;
+ /** Number of references using this struct. */
+ uint32_t cRefs;
+ /** The worker thread. */
+ RTTHREAD Thread;
+ /** The session this guest process
+ * is bound to. */
+ PVBOXSERVICECTRLSESSION pSession;
+ /** Shutdown indicator; will be set when the thread
+ * needs (or is asked) to shutdown. */
+ bool volatile fShutdown;
+ /** Whether the guest process thread was stopped or not. */
+ bool volatile fStopped;
+ /** Whether the guest process thread was started or not. */
+ bool fStarted;
+ /** Context ID. */
+ uint32_t uContextID;
+ /** Critical section for thread-safe use. */
+ RTCRITSECT CritSect;
+ /** Process startup information. */
+ PVBGLR3GUESTCTRLPROCSTARTUPINFO
+ pStartupInfo;
+ /** The process' PID assigned by the guest OS. */
+ uint32_t uPID;
+ /** The process' request queue to handle requests
+ * from the outside, e.g. the session. */
+ RTREQQUEUE hReqQueue;
+ /** Our pollset, used for accessing the process'
+ * std* pipes + the notification pipe. */
+ RTPOLLSET hPollSet;
+ /** StdIn pipe for addressing writes to the
+ * guest process' stdin.*/
+ RTPIPE hPipeStdInW;
+ /** StdOut pipe for addressing reads from
+ * guest process' stdout.*/
+ RTPIPE hPipeStdOutR;
+ /** StdOut pipe for addressing reads from
+ * guest process' stderr.*/
+ RTPIPE hPipeStdErrR;
+
+ /** The write end of the notification pipe that is used to poke the thread
+ * monitoring the process.
+ * This is NIL_RTPIPE for output pipes. */
+ RTPIPE hNotificationPipeW;
+ /** The other end of hNotificationPipeW, read by vgsvcGstCtrlProcessProcLoop(). */
+ RTPIPE hNotificationPipeR;
+} VBOXSERVICECTRLPROCESS;
+/** Pointer to thread data. */
+typedef VBOXSERVICECTRLPROCESS *PVBOXSERVICECTRLPROCESS;
+
+RT_C_DECLS_BEGIN
+
+extern RTLISTANCHOR g_lstControlSessionThreads;
+extern VBOXSERVICECTRLSESSION g_Session;
+extern uint32_t g_idControlSvcClient;
+extern uint64_t g_fControlHostFeatures0;
+extern bool g_fControlSupportsOptimizations;
+
+
+/** @name Guest session thread handling.
+ * @{ */
+extern int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo, PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread);
+extern int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pSession, uint32_t uFlags);
+extern int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t uFlags);
+extern int VGSvcGstCtrlSessionThreadTerminate(PVBOXSERVICECTRLSESSIONTHREAD pSession);
+extern RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv);
+/** @} */
+/** @name Per-session functions.
+ * @{ */
+extern PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID);
+extern int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession);
+extern int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession);
+extern int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t uFlags);
+extern int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx, void *pvScratchBuf, size_t cbScratchBuf, volatile bool *pfShutdown);
+extern int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed);
+extern int VGSvcGstCtrlSessionReapProcesses(PVBOXSERVICECTRLSESSION pSession);
+/** @} */
+/** @name Per-guest process functions.
+ * @{ */
+extern int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fPendingClose, void *pvBuf, uint32_t cbBuf);
+extern int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx, uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags);
+extern int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess);
+extern void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession, const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContext);
+extern int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess);
+extern int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *pRc);
+/** @} */
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceControl_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp
new file mode 100644
index 00000000..e45fb77e
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlProcess.cpp
@@ -0,0 +1,2201 @@
+/* $Id: VBoxServiceControlProcess.cpp $ */
+/** @file
+ * VBoxServiceControlThread - Guest process handling.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/handle.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceControl.h"
+#include "VBoxServiceToolBox.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pThread, uint32_t uPID);
+static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess);
+static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph,
+ PRTPIPE phPipe);
+static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess);
+/* Request handlers. */
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ bool fPendingClose, void *pvBuf, uint32_t cbBuf);
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ uint32_t uHandle, uint32_t cbToRead, uint32_t uFlags);
+
+
+
+/**
+ * Initialies the passed in thread data structure with the parameters given.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to initialize.
+ * @param pSession Guest session the process is bound to.
+ * @param pStartupInfo Startup information.
+ * @param u32ContextID The context ID bound to this request / command.
+ */
+static int vgsvcGstCtrlProcessInit(PVBOXSERVICECTRLPROCESS pProcess,
+ const PVBOXSERVICECTRLSESSION pSession,
+ const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo,
+ uint32_t u32ContextID)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
+
+ /* General stuff. */
+ pProcess->hProcess = NIL_RTPROCESS;
+ pProcess->pSession = pSession;
+ pProcess->Node.pPrev = NULL;
+ pProcess->Node.pNext = NULL;
+
+ pProcess->fShutdown = false;
+ pProcess->fStarted = false;
+ pProcess->fStopped = false;
+
+ pProcess->uPID = 0; /* Don't have a PID yet. */
+ pProcess->cRefs = 0;
+ /*
+ * Use the initial context ID we got for starting
+ * the process to report back its status with the
+ * same context ID.
+ */
+ pProcess->uContextID = u32ContextID;
+ /*
+ * Note: pProcess->ClientID will be assigned when thread is started;
+ * every guest process has its own client ID to detect crashes on
+ * a per-guest-process level.
+ */
+
+ int rc = RTCritSectInit(&pProcess->CritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pProcess->hPollSet = NIL_RTPOLLSET;
+ pProcess->hPipeStdInW = NIL_RTPIPE;
+ pProcess->hPipeStdOutR = NIL_RTPIPE;
+ pProcess->hPipeStdErrR = NIL_RTPIPE;
+ pProcess->hNotificationPipeW = NIL_RTPIPE;
+ pProcess->hNotificationPipeR = NIL_RTPIPE;
+
+ rc = RTReqQueueCreate(&pProcess->hReqQueue);
+ AssertReleaseRC(rc);
+
+ /* Duplicate startup info. */
+ pProcess->pStartupInfo = VbglR3GuestCtrlProcStartupInfoDup(pStartupInfo);
+ AssertPtrReturn(pProcess->pStartupInfo, VERR_NO_MEMORY);
+
+ /* Adjust timeout value. */
+ if ( pProcess->pStartupInfo->uTimeLimitMS == UINT32_MAX
+ || pProcess->pStartupInfo->uTimeLimitMS == 0)
+ pProcess->pStartupInfo->uTimeLimitMS = RT_INDEFINITE_WAIT;
+
+ if (RT_FAILURE(rc)) /* Clean up on failure. */
+ VGSvcGstCtrlProcessFree(pProcess);
+ return rc;
+}
+
+
+/**
+ * Frees a guest process. On success, pProcess will be
+ * free'd and thus won't be available anymore.
+ *
+ * @return IPRT status code.
+ * @param pProcess Guest process to free.
+ * The pointer will not be valid anymore after return.
+ */
+int VGSvcGstCtrlProcessFree(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pProcess->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Freeing (cRefs=%RU32)...\n", pProcess->uPID, pProcess->cRefs);
+
+ AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER);
+ AssertReturn(pProcess->fStopped, VERR_WRONG_ORDER);
+ AssertReturn(pProcess->fShutdown, VERR_WRONG_ORDER);
+
+ VbglR3GuestCtrlProcStartupInfoFree(pProcess->pStartupInfo);
+ pProcess->pStartupInfo = NULL;
+
+ /*
+ * Destroy other thread data.
+ */
+ rc = RTPollSetDestroy(pProcess->hPollSet);
+ AssertRC(rc);
+
+ rc = RTReqQueueDestroy(pProcess->hReqQueue);
+ AssertRC(rc);
+
+ rc = RTPipeClose(pProcess->hNotificationPipeR);
+ AssertRC(rc);
+ rc = RTPipeClose(pProcess->hNotificationPipeW);
+ AssertRC(rc);
+
+ rc = RTPipeClose(pProcess->hPipeStdInW);
+ AssertRC(rc);
+ rc = RTPipeClose(pProcess->hPipeStdErrR);
+ AssertRC(rc);
+ rc = RTPipeClose(pProcess->hPipeStdOutR);
+ AssertRC(rc);
+
+ rc = RTCritSectLeave(&pProcess->CritSect);
+ AssertRC(rc);
+
+ RTCritSectDelete(&pProcess->CritSect);
+
+ /*
+ * Destroy thread structure as final step.
+ */
+ RTMemFree(pProcess);
+ pProcess = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Signals a guest process thread that we want it to shut down in
+ * a gentle way.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to stop.
+ */
+int VGSvcGstCtrlProcessStop(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ VGSvcVerbose(3, "[PID %RU32]: Stopping ...\n", pProcess->uPID);
+
+ /* Do *not* set pThread->fShutdown or other stuff here!
+ * The guest thread loop will clean up itself. */
+
+ return VGSvcGstCtrlProcessHandleTerm(pProcess);
+}
+
+
+/**
+ * Releases a previously acquired guest process (decreases the refcount).
+ *
+ * @param pProcess Process to release.
+ */
+void VGSvcGstCtrlProcessRelease(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturnVoid(pProcess);
+
+ int rc2 = RTCritSectEnter(&pProcess->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ AssertReturnVoid(pProcess->cRefs);
+ pProcess->cRefs--;
+
+ VGSvcVerbose(3, "[PID %RU32]: cRefs=%RU32, fShutdown=%RTbool, fStopped=%RTbool\n",
+ pProcess->uPID, pProcess->cRefs, pProcess->fShutdown, pProcess->fStopped);
+
+ rc2 = RTCritSectLeave(&pProcess->CritSect);
+ AssertRC(rc2);
+ }
+}
+
+
+/**
+ * Wait for a guest process thread to shut down.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to wait shutting down for.
+ * @param msTimeout Timeout in ms to wait for shutdown.
+ * @param prc Where to store the thread's return code.
+ * Optional.
+ */
+int VGSvcGstCtrlProcessWait(const PVBOXSERVICECTRLPROCESS pProcess, RTMSINTERVAL msTimeout, int *prc)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(prc, VERR_INVALID_POINTER);
+
+ int rc = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTThreadGetState(pProcess->Thread) != RTTHREADSTATE_INVALID) /* Is there a thread we can wait for? */
+ {
+ VGSvcVerbose(2, "[PID %RU32]: Waiting for shutdown (%RU32ms) ...\n", pProcess->uPID, msTimeout);
+
+ AssertMsgReturn(pProcess->fStarted,
+ ("Tried to wait on guest process=%p (PID %RU32) which has not been started yet\n",
+ pProcess, pProcess->uPID), VERR_INVALID_PARAMETER);
+
+ /* Unlock process before waiting. */
+ rc = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc);
+
+ /* Do the actual waiting. */
+ int rcThread;
+ Assert(pProcess->Thread != NIL_RTTHREAD);
+ rc = RTThreadWait(pProcess->Thread, msTimeout, &rcThread);
+
+ int rc2 = vgsvcGstCtrlProcessLock(pProcess);
+ AssertRC(rc2);
+
+ if (RT_SUCCESS(rc))
+ {
+ pProcess->Thread = NIL_RTTHREAD;
+ VGSvcVerbose(3, "[PID %RU32]: Thread shutdown complete, thread rc=%Rrc\n", pProcess->uPID, rcThread);
+ if (prc)
+ *prc = rcThread;
+ }
+ }
+
+ int rc2 = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcError("[PID %RU32]: Waiting for shutting down thread returned error rc=%Rrc\n", pProcess->uPID, rc);
+
+ VGSvcVerbose(3, "[PID %RU32]: Waiting resulted in rc=%Rrc\n", pProcess->uPID, rc);
+ return rc;
+}
+
+
+/**
+ * Closes the stdin pipe of a guest process.
+ *
+ * @return IPRT status code.
+ * @param pProcess The process which input pipe we close.
+ * @param phStdInW The standard input pipe handle.
+ */
+static int vgsvcGstCtrlProcessPollsetCloseInput(PVBOXSERVICECTRLPROCESS pProcess, PRTPIPE phStdInW)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertPtrReturn(phStdInW, VERR_INVALID_POINTER);
+
+ int rc = RTPollSetRemove(pProcess->hPollSet, VBOXSERVICECTRLPIPEID_STDIN);
+ if (rc != VERR_POLL_HANDLE_ID_NOT_FOUND)
+ AssertRC(rc);
+
+ if (*phStdInW != NIL_RTPIPE)
+ {
+ rc = RTPipeClose(*phStdInW);
+ AssertRC(rc);
+ *phStdInW = NIL_RTPIPE;
+ }
+
+ return rc;
+}
+
+
+#ifdef DEBUG
+/**
+ * Names a poll handle ID.
+ *
+ * @returns Pointer to read-only string.
+ * @param idPollHnd What to name.
+ */
+static const char *vgsvcGstCtrlProcessPollHandleToString(uint32_t idPollHnd)
+{
+ switch (idPollHnd)
+ {
+ case VBOXSERVICECTRLPIPEID_UNKNOWN:
+ return "unknown";
+ case VBOXSERVICECTRLPIPEID_STDIN:
+ return "stdin";
+ case VBOXSERVICECTRLPIPEID_STDIN_WRITABLE:
+ return "stdin_writable";
+ case VBOXSERVICECTRLPIPEID_STDOUT:
+ return "stdout";
+ case VBOXSERVICECTRLPIPEID_STDERR:
+ return "stderr";
+ case VBOXSERVICECTRLPIPEID_IPC_NOTIFY:
+ return "ipc_notify";
+ default:
+ return "unknown";
+ }
+}
+#endif /* DEBUG */
+
+
+/**
+ * Handle an error event on standard input.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to handle pollset for.
+ * @param fPollEvt The event mask returned by RTPollNoResume.
+ * @param phStdInW The standard input pipe handle.
+ */
+static int vgsvcGstCtrlProcessPollsetOnInput(PVBOXSERVICECTRLPROCESS pProcess, uint32_t fPollEvt, PRTPIPE phStdInW)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ NOREF(fPollEvt);
+
+ return vgsvcGstCtrlProcessPollsetCloseInput(pProcess, phStdInW);
+}
+
+
+/**
+ * Handle pending output data or error on standard out or standard error.
+ *
+ * @returns IPRT status code from client send.
+ * @param pProcess Process to handle pollset for.
+ * @param fPollEvt The event mask returned by RTPollNoResume.
+ * @param phPipeR The pipe handle.
+ * @param idPollHnd The pipe ID to handle.
+ */
+static int vgsvcGstCtrlProcessHandleOutputError(PVBOXSERVICECTRLPROCESS pProcess,
+ uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd)
+{
+ RT_NOREF1(fPollEvt);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ if (!phPipeR)
+ return VINF_SUCCESS;
+
+#ifdef DEBUG
+ VGSvcVerbose(4, "[PID %RU32]: Output error: idPollHnd=%s, fPollEvt=0x%x\n",
+ pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt);
+#endif
+
+ /* Remove pipe from poll set. */
+ int rc2 = RTPollSetRemove(pProcess->hPollSet, idPollHnd);
+ AssertMsg(RT_SUCCESS(rc2) || rc2 == VERR_POLL_HANDLE_ID_NOT_FOUND, ("%Rrc\n", rc2));
+
+ bool fClosePipe = true; /* By default close the pipe. */
+
+ /* Check if there's remaining data to read from the pipe. */
+ if (*phPipeR != NIL_RTPIPE)
+ {
+ size_t cbReadable;
+ rc2 = RTPipeQueryReadable(*phPipeR, &cbReadable);
+ if ( RT_SUCCESS(rc2)
+ && cbReadable)
+ {
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s has %zu bytes left, vetoing close\n",
+ pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), cbReadable);
+#endif
+ /* Veto closing the pipe yet because there's still stuff to read
+ * from the pipe. This can happen on UNIX-y systems where on
+ * error/hangup there still can be data to be read out. */
+ fClosePipe = false;
+ }
+ }
+#ifdef DEBUG
+ else
+ VGSvcVerbose(3, "[PID %RU32]: idPollHnd=%s will be closed\n",
+ pProcess->uPID, vgsvcGstCtrlProcessPollHandleToString(idPollHnd));
+#endif
+
+ if ( *phPipeR != NIL_RTPIPE
+ && fClosePipe)
+ {
+ rc2 = RTPipeClose(*phPipeR);
+ AssertRC(rc2);
+ *phPipeR = NIL_RTPIPE;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handle pending output data or error on standard out or standard error.
+ *
+ * @returns IPRT status code from client send.
+ * @param pProcess Process to handle pollset for.
+ * @param fPollEvt The event mask returned by RTPollNoResume.
+ * @param phPipeR The pipe handle.
+ * @param idPollHnd The pipe ID to handle.
+ *
+ */
+static int vgsvcGstCtrlProcessPollsetOnOutput(PVBOXSERVICECTRLPROCESS pProcess,
+ uint32_t fPollEvt, PRTPIPE phPipeR, uint32_t idPollHnd)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+#ifdef DEBUG
+ VGSvcVerbose(4, "[PID %RU32]: Output event phPipeR=%p, idPollHnd=%s, fPollEvt=0x%x\n",
+ pProcess->uPID, phPipeR, vgsvcGstCtrlProcessPollHandleToString(idPollHnd), fPollEvt);
+#endif
+
+ if (!phPipeR)
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+#ifdef DEBUG
+ if (*phPipeR != NIL_RTPIPE)
+ {
+ size_t cbReadable;
+ rc = RTPipeQueryReadable(*phPipeR, &cbReadable);
+ if ( RT_SUCCESS(rc)
+ && cbReadable)
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Output event cbReadable=%zu\n", pProcess->uPID, cbReadable);
+ }
+ }
+#endif
+
+#if 0
+ /* Push output to the host. */
+ if (fPollEvt & RTPOLL_EVT_READ)
+ {
+ size_t cbRead = 0;
+ uint8_t byData[_64K];
+ rc = RTPipeRead(*phPipeR, byData, sizeof(byData), &cbRead);
+ VGSvcVerbose(4, "VGSvcGstCtrlProcessHandleOutputEvent cbRead=%u, rc=%Rrc\n", cbRead, rc);
+
+ /* Make sure we go another poll round in case there was too much data
+ for the buffer to hold. */
+ fPollEvt &= RTPOLL_EVT_ERROR;
+ }
+#endif
+
+ if (fPollEvt & RTPOLL_EVT_ERROR)
+ rc = vgsvcGstCtrlProcessHandleOutputError(pProcess, fPollEvt, phPipeR, idPollHnd);
+ return rc;
+}
+
+
+/**
+ * Execution loop which runs in a dedicated per-started-process thread and
+ * handles all pipe input/output and signalling stuff.
+ *
+ * @return IPRT status code.
+ * @param pProcess The guest process to handle.
+ */
+static int vgsvcGstCtrlProcessProcLoop(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc;
+ int rc2;
+ uint64_t const uMsStart = RTTimeMilliTS();
+ RTPROCSTATUS ProcessStatus = { 254, RTPROCEXITREASON_ABEND };
+ bool fProcessAlive = true;
+ bool fProcessTimedOut = false;
+ uint64_t MsProcessKilled = UINT64_MAX;
+ RTMSINTERVAL const cMsPollBase = pProcess->hPipeStdInW != NIL_RTPIPE
+ ? 100 /* Need to poll for input. */
+ : 1000; /* Need only poll for process exit and aborts. */
+ RTMSINTERVAL cMsPollCur = 0;
+
+ /*
+ * Assign PID to thread data.
+ * Also check if there already was a thread with the same PID and shut it down -- otherwise
+ * the first (stale) entry will be found and we get really weird results!
+ */
+ rc = vgsvcGstCtrlProcessAssignPID(pProcess, pProcess->hProcess /* Opaque PID handle */);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Unable to assign PID=%u, to new thread, rc=%Rrc\n", pProcess->hProcess, rc);
+ return rc;
+ }
+
+ /*
+ * Before entering the loop, tell the host that we've started the guest
+ * and that it's now OK to send input to the process.
+ */
+ VGSvcVerbose(2, "[PID %RU32]: Process '%s' started, CID=%u, User=%s, cMsTimeout=%RU32\n",
+ pProcess->uPID, pProcess->pStartupInfo->pszCmd, pProcess->uContextID,
+ pProcess->pStartupInfo->pszUser, pProcess->pStartupInfo->uTimeLimitMS);
+ VBGLR3GUESTCTRLCMDCTX ctxStart = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ };
+ rc = VbglR3GuestCtrlProcCbStatus(&ctxStart,
+ pProcess->uPID, PROC_STS_STARTED, 0 /* u32Flags */,
+ NULL /* pvData */, 0 /* cbData */);
+ if (rc == VERR_INTERRUPTED)
+ rc = VINF_SUCCESS; /* SIGCHLD send by quick childs! */
+ if (RT_FAILURE(rc))
+ VGSvcError("[PID %RU32]: Error reporting starting status to host, rc=%Rrc\n", pProcess->uPID, rc);
+
+ /*
+ * Process input, output, the test pipe and client requests.
+ */
+ while ( RT_SUCCESS(rc)
+ && RT_UNLIKELY(!pProcess->fShutdown))
+ {
+ /*
+ * Wait/Process all pending events.
+ */
+ uint32_t idPollHnd;
+ uint32_t fPollEvt;
+ rc2 = RTPollNoResume(pProcess->hPollSet, cMsPollCur, &fPollEvt, &idPollHnd);
+ if (pProcess->fShutdown)
+ continue;
+
+ cMsPollCur = 0; /* No rest until we've checked everything. */
+
+ if (RT_SUCCESS(rc2))
+ {
+ switch (idPollHnd)
+ {
+ case VBOXSERVICECTRLPIPEID_STDIN:
+ rc = vgsvcGstCtrlProcessPollsetOnInput(pProcess, fPollEvt, &pProcess->hPipeStdInW);
+ break;
+
+ case VBOXSERVICECTRLPIPEID_STDOUT:
+ rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdOutR, idPollHnd);
+ break;
+
+ case VBOXSERVICECTRLPIPEID_STDERR:
+ rc = vgsvcGstCtrlProcessPollsetOnOutput(pProcess, fPollEvt, &pProcess->hPipeStdErrR, idPollHnd);
+ break;
+
+ case VBOXSERVICECTRLPIPEID_IPC_NOTIFY:
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "[PID %RU32]: IPC notify\n", pProcess->uPID);
+#endif
+ rc2 = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Drain the notification pipe. */
+ uint8_t abBuf[8];
+ size_t cbIgnore;
+ rc2 = RTPipeRead(pProcess->hNotificationPipeR, abBuf, sizeof(abBuf), &cbIgnore);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Draining IPC notification pipe failed with rc=%Rrc\n", rc2);
+
+ /* Process all pending requests. */
+ VGSvcVerbose(4, "[PID %RU32]: Processing pending requests ...\n", pProcess->uPID);
+ Assert(pProcess->hReqQueue != NIL_RTREQQUEUE);
+ rc2 = RTReqQueueProcess(pProcess->hReqQueue,
+ 0 /* Only process all pending requests, don't wait for new ones */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ VGSvcError("Processing requests failed with with rc=%Rrc\n", rc2);
+
+ int rc3 = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc3);
+#ifdef DEBUG
+ VGSvcVerbose(4, "[PID %RU32]: Processing pending requests done, rc=%Rrc\n", pProcess->uPID, rc2);
+#endif
+ }
+
+ break;
+
+ default:
+ AssertMsgFailed(("Unknown idPollHnd=%RU32\n", idPollHnd));
+ break;
+ }
+
+ if (RT_FAILURE(rc) || rc == VINF_EOF)
+ break; /* Abort command, or client dead or something. */
+ }
+#if 0
+ VGSvcVerbose(4, "[PID %RU32]: Polling done, pollRc=%Rrc, pollCnt=%RU32, idPollHnd=%s, rc=%Rrc, fProcessAlive=%RTbool, fShutdown=%RTbool\n",
+ pProcess->uPID, rc2, RTPollSetGetCount(hPollSet), vgsvcGstCtrlProcessPollHandleToString(idPollHnd), rc, fProcessAlive, pProcess->fShutdown);
+ VGSvcVerbose(4, "[PID %RU32]: stdOut=%s, stdErrR=%s\n",
+ pProcess->uPID,
+ *phStdOutR == NIL_RTPIPE ? "closed" : "open",
+ *phStdErrR == NIL_RTPIPE ? "closed" : "open");
+#endif
+ if (RT_UNLIKELY(pProcess->fShutdown))
+ break; /* We were asked to shutdown. */
+
+ /*
+ * Check for process death.
+ */
+ if (fProcessAlive)
+ {
+ rc2 = RTProcWaitNoResume(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
+ if (RT_SUCCESS_NP(rc2))
+ {
+ fProcessAlive = false;
+ /* Note: Don't bail out here yet. First check in the next block below
+ * if all needed pipe outputs have been consumed. */
+ }
+ else
+ {
+ if (RT_UNLIKELY(rc2 == VERR_INTERRUPTED))
+ continue;
+ if (RT_UNLIKELY(rc2 == VERR_PROCESS_NOT_FOUND))
+ {
+ fProcessAlive = false;
+ ProcessStatus.enmReason = RTPROCEXITREASON_ABEND;
+ ProcessStatus.iStatus = 255;
+ AssertFailed();
+ }
+ else
+ AssertMsg(rc2 == VERR_PROCESS_RUNNING, ("%Rrc\n", rc2));
+ }
+ }
+
+ /*
+ * If the process has terminated and all output has been consumed,
+ * we should be heading out.
+ */
+ if (!fProcessAlive)
+ {
+ if ( fProcessTimedOut
+ || ( pProcess->hPipeStdOutR == NIL_RTPIPE
+ && pProcess->hPipeStdErrR == NIL_RTPIPE)
+ )
+ {
+ VGSvcVerbose(3, "[PID %RU32]: RTProcWaitNoResume=%Rrc\n", pProcess->uPID, rc2);
+ break;
+ }
+ }
+
+ /*
+ * Check for timed out, killing the process.
+ */
+ uint32_t cMilliesLeft = RT_INDEFINITE_WAIT;
+ if ( pProcess->pStartupInfo->uTimeLimitMS != RT_INDEFINITE_WAIT
+ && pProcess->pStartupInfo->uTimeLimitMS != 0)
+ {
+ uint64_t u64Now = RTTimeMilliTS();
+ uint64_t cMsElapsed = u64Now - uMsStart;
+ if (cMsElapsed >= pProcess->pStartupInfo->uTimeLimitMS)
+ {
+ fProcessTimedOut = true;
+ if ( MsProcessKilled == UINT64_MAX
+ || u64Now - MsProcessKilled > 1000)
+ {
+ if (u64Now - MsProcessKilled > 20*60*1000)
+ break; /* Give up after 20 mins. */
+
+ VGSvcVerbose(3, "[PID %RU32]: Timed out (%RU64ms elapsed > %RU32ms timeout), killing ...\n",
+ pProcess->uPID, cMsElapsed, pProcess->pStartupInfo->uTimeLimitMS);
+
+ rc2 = RTProcTerminate(pProcess->hProcess);
+ VGSvcVerbose(3, "[PID %RU32]: Killing process resulted in rc=%Rrc\n",
+ pProcess->uPID, rc2);
+ MsProcessKilled = u64Now;
+ continue;
+ }
+ cMilliesLeft = 10000;
+ }
+ else
+ cMilliesLeft = pProcess->pStartupInfo->uTimeLimitMS - (uint32_t)cMsElapsed;
+ }
+
+ /* Reset the polling interval since we've done all pending work. */
+ cMsPollCur = fProcessAlive
+ ? cMsPollBase
+ : RT_MS_1MIN;
+ if (cMilliesLeft < cMsPollCur)
+ cMsPollCur = cMilliesLeft;
+ }
+
+ VGSvcVerbose(3, "[PID %RU32]: Loop ended: rc=%Rrc, fShutdown=%RTbool, fProcessAlive=%RTbool, fProcessTimedOut=%RTbool, MsProcessKilled=%RU64 (%RX64)\n",
+ pProcess->uPID, rc, pProcess->fShutdown, fProcessAlive, fProcessTimedOut, MsProcessKilled, MsProcessKilled);
+ VGSvcVerbose(3, "[PID %RU32]: *phStdOutR=%s, *phStdErrR=%s\n",
+ pProcess->uPID,
+ pProcess->hPipeStdOutR == NIL_RTPIPE ? "closed" : "open",
+ pProcess->hPipeStdErrR == NIL_RTPIPE ? "closed" : "open");
+
+ /* Signal that this thread is in progress of shutting down. */
+ ASMAtomicWriteBool(&pProcess->fShutdown, true);
+
+ /*
+ * Try killing the process if it's still alive at this point.
+ */
+ if (fProcessAlive)
+ {
+ if (MsProcessKilled == UINT64_MAX)
+ {
+ VGSvcVerbose(2, "[PID %RU32]: Is still alive and not killed yet\n", pProcess->uPID);
+
+ MsProcessKilled = RTTimeMilliTS();
+ rc2 = RTProcTerminate(pProcess->hProcess);
+ if (rc2 == VERR_NOT_FOUND)
+ {
+ fProcessAlive = false;
+ }
+ else if (RT_FAILURE(rc2))
+ VGSvcError("[PID %RU32]: Killing process failed with rc=%Rrc\n", pProcess->uPID, rc2);
+ RTThreadSleep(500);
+ }
+
+ for (int i = 0; i < 10 && fProcessAlive; i++)
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Waiting to exit ...\n", pProcess->uPID, i + 1);
+ rc2 = RTProcWait(pProcess->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Exited\n", pProcess->uPID, i + 1);
+ fProcessAlive = false;
+ break;
+ }
+ if (i >= 5)
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Kill attempt %d/10: Trying to terminate ...\n", pProcess->uPID, i + 1);
+ rc2 = RTProcTerminate(pProcess->hProcess);
+ if ( RT_FAILURE(rc)
+ && rc2 != VERR_NOT_FOUND)
+ VGSvcError("PID %RU32]: Killing process failed with rc=%Rrc\n",
+ pProcess->uPID, rc2);
+ }
+ RTThreadSleep(i >= 5 ? 2000 : 500);
+ }
+
+ if (fProcessAlive)
+ VGSvcError("[PID %RU32]: Could not be killed\n", pProcess->uPID);
+ }
+
+ /*
+ * Shutdown procedure:
+ * - Set the pProcess->fShutdown indicator to let others know we're
+ * not accepting any new requests anymore.
+ * - After setting the indicator, try to process all outstanding
+ * requests to make sure they're getting delivered.
+ *
+ * Note: After removing the process from the session's list it's not
+ * even possible for the session anymore to control what's
+ * happening to this thread, so be careful and don't mess it up.
+ */
+
+ rc2 = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests ...\n", pProcess->uPID);
+
+ /* Process all pending requests (but don't wait for new ones). */
+ Assert(pProcess->hReqQueue != NIL_RTREQQUEUE);
+ rc2 = RTReqQueueProcess(pProcess->hReqQueue, 0 /* No timeout */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ VGSvcError("[PID %RU32]: Processing outstanding requests failed with with rc=%Rrc\n", pProcess->uPID, rc2);
+
+ VGSvcVerbose(3, "[PID %RU32]: Processing outstanding requests done, rc=%Rrc\n", pProcess->uPID, rc2);
+
+ rc2 = vgsvcGstCtrlProcessUnlock(pProcess);
+ AssertRC(rc2);
+ }
+
+ /*
+ * If we don't have a client problem (RT_FAILURE(rc)) we'll reply to the
+ * clients exec packet now.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uStatus = PROC_STS_UNDEFINED;
+ uint32_t fFlags = 0;
+
+ if ( fProcessTimedOut && !fProcessAlive && MsProcessKilled != UINT64_MAX)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Timed out and got killed\n", pProcess->uPID);
+ uStatus = PROC_STS_TOK;
+ }
+ else if (fProcessTimedOut && fProcessAlive && MsProcessKilled != UINT64_MAX)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Timed out and did *not* get killed\n", pProcess->uPID);
+ uStatus = PROC_STS_TOA;
+ }
+ else if (pProcess->fShutdown && (fProcessAlive || MsProcessKilled != UINT64_MAX))
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Got terminated because system/service is about to shutdown\n", pProcess->uPID);
+ uStatus = PROC_STS_DWN; /* Service is stopping, process was killed. */
+ fFlags = pProcess->pStartupInfo->fFlags; /* Return handed-in execution flags back to the host. */
+ }
+ else if (fProcessAlive)
+ VGSvcError("[PID %RU32]: Is alive when it should not!\n", pProcess->uPID);
+ else if (MsProcessKilled != UINT64_MAX)
+ VGSvcError("[PID %RU32]: Has been killed when it should not!\n", pProcess->uPID);
+ else if (ProcessStatus.enmReason == RTPROCEXITREASON_NORMAL)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_NORMAL (Exit code: %d)\n",
+ pProcess->uPID, ProcessStatus.iStatus);
+ uStatus = PROC_STS_TEN;
+ fFlags = ProcessStatus.iStatus;
+ }
+ else if (ProcessStatus.enmReason == RTPROCEXITREASON_SIGNAL)
+ {
+ VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_SIGNAL (Signal: %u)\n",
+ pProcess->uPID, ProcessStatus.iStatus);
+ uStatus = PROC_STS_TES;
+ fFlags = ProcessStatus.iStatus;
+ }
+ else if (ProcessStatus.enmReason == RTPROCEXITREASON_ABEND)
+ {
+ /* ProcessStatus.iStatus will be undefined. */
+ VGSvcVerbose(3, "[PID %RU32]: Ended with RTPROCEXITREASON_ABEND\n", pProcess->uPID);
+ uStatus = PROC_STS_TEA;
+ fFlags = ProcessStatus.iStatus;
+ }
+ else
+ VGSvcVerbose(1, "[PID %RU32]: Handling process status %u not implemented\n", pProcess->uPID, ProcessStatus.enmReason);
+ VBGLR3GUESTCTRLCMDCTX ctxEnd = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ };
+ VGSvcVerbose(2, "[PID %RU32]: Ended, ClientID=%u, CID=%u, Status=%u, Flags=0x%x\n",
+ pProcess->uPID, ctxEnd.uClientID, pProcess->uContextID, uStatus, fFlags);
+
+ rc2 = VbglR3GuestCtrlProcCbStatus(&ctxEnd, pProcess->uPID, uStatus, fFlags, NULL /* pvData */, 0 /* cbData */);
+ if ( RT_FAILURE(rc2)
+ && rc2 == VERR_NOT_FOUND)
+ VGSvcError("[PID %RU32]: Error reporting final status to host; rc=%Rrc\n", pProcess->uPID, rc2);
+ }
+
+ VGSvcVerbose(3, "[PID %RU32]: Process loop returned with rc=%Rrc\n", pProcess->uPID, rc);
+ return rc;
+}
+
+
+#if 0 /* unused */
+/**
+ * Initializes a pipe's handle and pipe object.
+ *
+ * @return IPRT status code.
+ * @param ph The pipe's handle to initialize.
+ * @param phPipe The pipe's object to initialize.
+ */
+static int vgsvcGstCtrlProcessInitPipe(PRTHANDLE ph, PRTPIPE phPipe)
+{
+ AssertPtrReturn(ph, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phPipe, VERR_INVALID_PARAMETER);
+
+ ph->enmType = RTHANDLETYPE_PIPE;
+ ph->u.hPipe = NIL_RTPIPE;
+ *phPipe = NIL_RTPIPE;
+
+ return VINF_SUCCESS;
+}
+#endif
+
+
+/**
+ * Sets up the redirection / pipe / nothing for one of the standard handles.
+ *
+ * @returns IPRT status code. No client replies made.
+ * @param pszHowTo How to set up this standard handle.
+ * @param fd Which standard handle it is (0 == stdin, 1 ==
+ * stdout, 2 == stderr).
+ * @param ph The generic handle that @a pph may be set
+ * pointing to. Always set.
+ * @param pph Pointer to the RTProcCreateExec argument.
+ * Always set.
+ * @param phPipe Where to return the end of the pipe that we
+ * should service.
+ */
+static int vgsvcGstCtrlProcessSetupPipe(const char *pszHowTo, int fd, PRTHANDLE ph, PRTHANDLE *pph, PRTPIPE phPipe)
+{
+ AssertPtrReturn(ph, VERR_INVALID_POINTER);
+ AssertPtrReturn(pph, VERR_INVALID_POINTER);
+ AssertPtrReturn(phPipe, VERR_INVALID_POINTER);
+
+ int rc;
+
+ ph->enmType = RTHANDLETYPE_PIPE;
+ ph->u.hPipe = NIL_RTPIPE;
+ *pph = NULL;
+ *phPipe = NIL_RTPIPE;
+
+ if (!strcmp(pszHowTo, "|"))
+ {
+ /*
+ * Setup a pipe for forwarding to/from the client.
+ * The ph union struct will be filled with a pipe read/write handle
+ * to represent the "other" end to phPipe.
+ */
+ if (fd == 0) /* stdin? */
+ {
+ /* Connect a wrtie pipe specified by phPipe to stdin. */
+ rc = RTPipeCreate(&ph->u.hPipe, phPipe, RTPIPE_C_INHERIT_READ);
+ }
+ else /* stdout or stderr. */
+ {
+ /* Connect a read pipe specified by phPipe to stdout or stderr. */
+ rc = RTPipeCreate(phPipe, &ph->u.hPipe, RTPIPE_C_INHERIT_WRITE);
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ ph->enmType = RTHANDLETYPE_PIPE;
+ *pph = ph;
+ }
+ else if (!strcmp(pszHowTo, "/dev/null"))
+ {
+ /*
+ * Redirect to/from /dev/null.
+ */
+ RTFILE hFile;
+ rc = RTFileOpenBitBucket(&hFile, fd == 0 ? RTFILE_O_READ : RTFILE_O_WRITE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ ph->enmType = RTHANDLETYPE_FILE;
+ ph->u.hFile = hFile;
+ *pph = ph;
+ }
+ else /* Add other piping stuff here. */
+ rc = VINF_SUCCESS; /* Same as parent (us). */
+
+ return rc;
+}
+
+
+/**
+ * Expands a file name / path to its real content.
+ *
+ * ~~This only works on Windows for now (e.g. translating "%TEMP%\foo.exe" to
+ * "C:\Windows\Temp" when starting with system / administrative rights).~~ See
+ * todo in code.
+ *
+ * @return IPRT status code.
+ * @param pszPath Path to resolve.
+ * @param pszExpanded Pointer to string to store the resolved path in.
+ * @param cbExpanded Size (in bytes) of string to store the resolved path.
+ */
+static int vgsvcGstCtrlProcessMakeFullPath(const char *pszPath, char *pszExpanded, size_t cbExpanded)
+{
+/** @todo r=bird: This feature shall be made optional, i.e. require a
+ * flag to be passed down. Further, it shall work on the environment
+ * block of the new process (i.e. include env changes passed down from
+ * the caller). I would also suggest using the unix variable expansion
+ * syntax, not the DOS one.
+ *
+ * Since this currently not available on non-windows guests, I suggest
+ * we disable it until such a time as it is implemented correctly. */
+#if 0 /*def RT_OS_WINDOWS - see above. Don't know why this wasn't disabled before 7.0, didn't see the @todo yet? */
+ int rc = VINF_SUCCESS;
+ if (!ExpandEnvironmentStrings(pszPath, pszExpanded, (DWORD)cbExpanded))
+ rc = RTErrConvertFromWin32(GetLastError());
+#else
+ /* There is no expansion anywhere yet, see above @todo. */
+ int rc = RTStrCopy(pszExpanded, cbExpanded, pszPath);
+#endif
+#ifdef DEBUG
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessMakeFullPath: %s -> %s\n", pszPath, pszExpanded);
+#endif
+ return rc;
+}
+
+
+/**
+ * Resolves the full path of a specified executable name.
+ *
+ * This function also resolves internal VBoxService tools to its appropriate
+ * executable path + name if VBOXSERVICE_NAME is specified as pszFilename.
+ *
+ * @return IPRT status code.
+ * @param pszFilename File name to resolve.
+ * @param pszResolved Pointer to a string where the resolved file name will be stored.
+ * @param cbResolved Size (in bytes) of resolved file name string.
+ */
+static int vgsvcGstCtrlProcessResolveExecutable(const char *pszFilename, char *pszResolved, size_t cbResolved)
+{
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszResolved, VERR_INVALID_POINTER);
+ AssertReturn(cbResolved, VERR_INVALID_PARAMETER);
+
+ const char * const pszOrgFilename = pszFilename;
+ if ( RTStrICmp(pszFilename, g_pszProgName) == 0
+ || RTStrICmp(pszFilename, VBOXSERVICE_NAME) == 0)
+ pszFilename = RTProcExecutablePath();
+
+ int rc = vgsvcGstCtrlProcessMakeFullPath(pszFilename, pszResolved, cbResolved);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Looked up executable: %s -> %s\n", pszOrgFilename, pszResolved);
+ return rc;
+}
+
+
+/**
+ * Constructs the argv command line by resolving environment variables
+ * and relative paths.
+ *
+ * @return IPRT status code.
+ * @param pszArgv0 First argument (argv0), either original or modified version.
+ * @param papszArgs Original argv command line from the host, starting at argv[1].
+ * @param fFlags The process creation flags pass to us from the host.
+ * @param fExecutingSelf Set if we're executing the VBoxService executable
+ * and should inject the --utf8-argv trick.
+ * @param ppapszArgv Pointer to a pointer with the new argv command line.
+ * Needs to be freed with RTGetOptArgvFree.
+ */
+static int vgsvcGstCtrlProcessAllocateArgv(const char *pszArgv0, const char * const *papszArgs, uint32_t fFlags,
+ bool fExecutingSelf, char ***ppapszArgv)
+{
+ VGSvcVerbose(3, "VGSvcGstCtrlProcessPrepareArgv: pszArgv0=%p, papszArgs=%p, fFlags=%#x, fExecutingSelf=%d, ppapszArgv=%p\n",
+ pszArgv0, papszArgs, fFlags, fExecutingSelf, ppapszArgv);
+
+ AssertPtrReturn(pszArgv0, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppapszArgv, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS), VERR_INVALID_FLAGS); /** @todo implement me */
+
+#ifndef VBOXSERVICE_ARG1_UTF8_ARGV
+ fExecutingSelf = false;
+#endif
+
+ /* Count arguments: */
+ int rc = VINF_SUCCESS;
+ uint32_t cArgs;
+ for (cArgs = 0; papszArgs[cArgs]; cArgs++)
+ {
+ if (cArgs >= UINT32_MAX - 2)
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Allocate new argv vector (adding + 2 for argv0 + termination). */
+ size_t cbSize = (fExecutingSelf + cArgs + 2) * sizeof(char *);
+ char **papszNewArgv = (char **)RTMemAlloc(cbSize);
+ if (!papszNewArgv)
+ return VERR_NO_MEMORY;
+
+ VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: pszArgv0 = '%s', cArgs=%RU32, cbSize=%zu\n", pszArgv0, cArgs, cbSize);
+#ifdef DEBUG /* Never log this stuff in release mode! */
+ if (cArgs)
+ {
+ for (uint32_t i = 0; i < cArgs; i++)
+ VGSvcVerbose(3, "VGSvcGstCtrlProcessAllocateArgv: papszArgs[%RU32] = '%s'\n", i, papszArgs[i]);
+ }
+#endif
+
+ /* HACK ALERT! Older hosts (< VBox 6.1.x) did not allow the user to really specify
+ the first argument separately from the executable image, so we have
+ to fudge a little in the unquoted argument case to deal with executables
+ containing spaces. Windows only, as RTPROC_FLAGS_UNQUOTED_ARGS is
+ ignored on all other hosts. */
+#ifdef RT_OS_WINDOWS
+ if ( (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS)
+ && strpbrk(pszArgv0, " \t\n\r")
+ && pszArgv0[0] == '"')
+ {
+ size_t cchArgv0 = strlen(pszArgv0);
+ AssertReturn(cchArgv0, VERR_INVALID_PARAMETER); /* Paranoia. */
+ rc = RTStrAllocEx(&papszNewArgv[0], 1 + cchArgv0 + 1 + 1);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszDst = papszNewArgv[0];
+ *pszDst++ = '"';
+ memcpy(pszDst, pszArgv0, cchArgv0);
+ pszDst += cchArgv0;
+ *pszDst++ = '"';
+ *pszDst = '\0';
+ }
+ }
+ else
+#endif
+ rc = RTStrDupEx(&papszNewArgv[0], pszArgv0);
+ if (RT_SUCCESS(rc))
+ {
+ size_t iDst = 1;
+
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+ /* Insert --utf8-argv as the first argument if executing the VBoxService binary. */
+ if (fExecutingSelf)
+ {
+ rc = RTStrDupEx(&papszNewArgv[iDst], VBOXSERVICE_ARG1_UTF8_ARGV);
+ if (RT_SUCCESS(rc))
+ iDst++;
+ }
+#endif
+ /* Copy over the other arguments. */
+ if (RT_SUCCESS(rc))
+ for (size_t iSrc = 0; iSrc < cArgs; iSrc++)
+ {
+#if 0 /* Arguments expansion -- untested. */
+ if (fFlags & GUEST_PROC_CREATE_FLAG_EXPAND_ARGUMENTS)
+ {
+/** @todo r=bird: If you want this, we need a generic implementation, preferably in RTEnv or somewhere like that. The marking
+ * up of the variables must be the same on all platforms. */
+ /* According to MSDN the limit on older Windows version is 32K, whereas
+ * Vista+ there are no limits anymore. We still stick to 4K. */
+ char szExpanded[_4K];
+# ifdef RT_OS_WINDOWS
+ if (!ExpandEnvironmentStrings(papszArgs[i], szExpanded, sizeof(szExpanded)))
+ rc = RTErrConvertFromWin32(GetLastError());
+# else
+ /* No expansion for non-Windows yet. */
+ rc = RTStrCopy(papszArgs[i], sizeof(szExpanded), szExpanded);
+# endif
+ if (RT_SUCCESS(rc))
+ rc = RTStrDupEx(&pszArg, szExpanded);
+ }
+ else
+#endif
+ rc = RTStrDupEx(&papszNewArgv[iDst], papszArgs[iSrc]);
+ if (RT_SUCCESS(rc))
+ iDst++;
+ else
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Terminate array. */
+ papszNewArgv[iDst] = NULL;
+
+ *ppapszArgv = papszNewArgv;
+ return VINF_SUCCESS;
+ }
+
+ /* Failed, bail out. */
+ while (iDst-- > 0)
+ RTStrFree(papszNewArgv[iDst]);
+ }
+ RTMemFree(papszNewArgv);
+ return rc;
+}
+
+
+/**
+ * Assigns a valid PID to a guest control thread and also checks if there already was
+ * another (stale) guest process which was using that PID before and destroys it.
+ *
+ * @return IPRT status code.
+ * @param pProcess Process to assign PID to.
+ * @param uPID PID to assign to the specified guest control execution thread.
+ */
+static int vgsvcGstCtrlProcessAssignPID(PVBOXSERVICECTRLPROCESS pProcess, uint32_t uPID)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ AssertReturn(uPID, VERR_INVALID_PARAMETER);
+
+ AssertPtr(pProcess->pSession);
+ int rc = RTCritSectEnter(&pProcess->pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search old threads using the desired PID and shut them down completely -- it's
+ * not used anymore. */
+ bool fTryAgain;
+ do
+ {
+ fTryAgain = false;
+ PVBOXSERVICECTRLPROCESS pProcessCur;
+ RTListForEach(&pProcess->pSession->lstProcesses, pProcessCur, VBOXSERVICECTRLPROCESS, Node)
+ {
+ if (pProcessCur->uPID == uPID)
+ {
+ Assert(pProcessCur != pProcess); /* can't happen */
+ uint32_t uTriedPID = uPID;
+ uPID += 391939;
+ VGSvcVerbose(2, "PID %RU32 was used before (process %p), trying again with %RU32 ...\n",
+ uTriedPID, pProcessCur, uPID);
+ fTryAgain = true;
+ break;
+ }
+ }
+ } while (fTryAgain);
+
+ /* Assign PID to current thread. */
+ pProcess->uPID = uPID;
+
+ rc = RTCritSectLeave(&pProcess->pSession->CritSect);
+ AssertRC(rc);
+ }
+
+ return rc;
+}
+
+
+static void vgsvcGstCtrlProcessFreeArgv(char **papszArgv)
+{
+ if (papszArgv)
+ {
+ size_t i = 0;
+ while (papszArgv[i])
+ RTStrFree(papszArgv[i++]);
+ RTMemFree(papszArgv);
+ }
+}
+
+
+/**
+ * Helper function to create/start a process on the guest.
+ *
+ * @return IPRT status code.
+ * @param pszExec Full qualified path of process to start (without arguments).
+ * @param papszArgs Pointer to array of command line arguments.
+ * @param hEnv Handle to environment block to use.
+ * @param fFlags Process execution flags.
+ * @param phStdIn Handle for the process' stdin pipe.
+ * @param phStdOut Handle for the process' stdout pipe.
+ * @param phStdErr Handle for the process' stderr pipe.
+ * @param pszAsUser User name (account) to start the process under.
+ * @param pszPassword Password of the specified user.
+ * @param pszDomain Domain to use for authentication.
+ * @param phProcess Pointer which will receive the process handle after
+ * successful process start.
+ */
+static int vgsvcGstCtrlProcessCreateProcess(const char *pszExec, const char * const *papszArgs, RTENV hEnv, uint32_t fFlags,
+ PCRTHANDLE phStdIn, PCRTHANDLE phStdOut, PCRTHANDLE phStdErr,
+ const char *pszAsUser, const char *pszPassword, const char *pszDomain,
+ PRTPROCESS phProcess)
+{
+#ifndef RT_OS_WINDOWS
+ RT_NOREF1(pszDomain);
+#endif
+ AssertPtrReturn(pszExec, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(papszArgs, VERR_INVALID_PARAMETER);
+ /* phStdIn is optional. */
+ /* phStdOut is optional. */
+ /* phStdErr is optional. */
+ /* pszPassword is optional. */
+ /* pszDomain is optional. */
+ AssertPtrReturn(phProcess, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ char szExecExp[RTPATH_MAX];
+
+#ifdef DEBUG
+ /* Never log this in release mode! */
+ VGSvcVerbose(4, "pszUser=%s, pszPassword=%s, pszDomain=%s\n", pszAsUser, pszPassword, pszDomain);
+#endif
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * If sysprep should be executed do this in the context of VBoxService, which
+ * (usually, if started by SCM) has administrator rights. Because of that a UI
+ * won't be shown (doesn't have a desktop).
+ */
+ if (!RTStrICmp(pszExec, "sysprep"))
+ {
+ /* Use a predefined sysprep path as default. */
+ char szSysprepCmd[RTPATH_MAX] = "C:\\sysprep\\sysprep.exe";
+ /** @todo Check digital signature of file above before executing it? */
+
+ /*
+ * On Windows Vista (and up) sysprep is located in "system32\\Sysprep\\sysprep.exe",
+ * so detect the OS and use a different path.
+ */
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6,0,0) /* Vista and later */)
+ {
+ rc = RTEnvGetEx(RTENV_DEFAULT, "windir", szSysprepCmd, sizeof(szSysprepCmd), NULL);
+#ifndef RT_ARCH_AMD64
+ /* Don't execute 64-bit sysprep from a 32-bit service host! */
+ char szSysWow64[RTPATH_MAX];
+ if (RTStrPrintf(szSysWow64, sizeof(szSysWow64), "%s", szSysprepCmd))
+ {
+ rc = RTPathAppend(szSysWow64, sizeof(szSysWow64), "SysWow64");
+ AssertRC(rc);
+ }
+ if ( RT_SUCCESS(rc)
+ && RTPathExists(szSysWow64))
+ VGSvcVerbose(0, "Warning: This service is 32-bit; could not execute sysprep on 64-bit OS!\n");
+#endif
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szSysprepCmd, sizeof(szSysprepCmd), "system32\\Sysprep\\sysprep.exe");
+ if (RT_SUCCESS(rc))
+ RTPathChangeToDosSlashes(szSysprepCmd, false /* No forcing necessary */);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Failed to detect sysrep location, rc=%Rrc\n", rc);
+ }
+
+ VGSvcVerbose(3, "Sysprep executable is: %s\n", szSysprepCmd);
+
+ if (RT_SUCCESS(rc))
+ {
+ char **papszArgsExp;
+ rc = vgsvcGstCtrlProcessAllocateArgv(szSysprepCmd /* argv0 */, papszArgs, fFlags,
+ false /*fExecutingSelf*/, &papszArgsExp);
+ if (RT_SUCCESS(rc))
+ {
+ /* As we don't specify credentials for the sysprep process, it will
+ * run under behalf of the account VBoxService was started under, most
+ * likely local system. */
+ rc = RTProcCreateEx(szSysprepCmd, papszArgsExp, hEnv, 0 /* fFlags */,
+ phStdIn, phStdOut, phStdErr, NULL /* pszAsUser */,
+ NULL /* pszPassword */, NULL, phProcess);
+ vgsvcGstCtrlProcessFreeArgv(papszArgsExp);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "Starting sysprep returned rc=%Rrc\n", rc);
+
+ return rc;
+ }
+#endif /* RT_OS_WINDOWS */
+
+ bool fExecutingSelf = false;
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ /* The "vbox_" prefix is reserved for the toolbox (vbox_cat, vbox_mkdir,
+ et al.) and we will replace pszExec with the full VBoxService path instead. */
+ if (RTStrStartsWith(pszExec, "vbox_"))
+ {
+ fExecutingSelf = true;
+ rc = vgsvcGstCtrlProcessResolveExecutable(VBOXSERVICE_NAME, szExecExp, sizeof(szExecExp));
+ }
+ else
+ {
+#endif
+ /*
+ * Do the environment variables expansion on executable and arguments.
+ */
+ rc = vgsvcGstCtrlProcessResolveExecutable(pszExec, szExecExp, sizeof(szExecExp));
+#ifdef VBOX_WITH_VBOXSERVICE_TOOLBOX
+ }
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * This one is a bit tricky to also support older hosts:
+ *
+ * - If the host does not provide a dedicated argv[0] (< VBox 6.1.x), we use the
+ * unmodified executable name (pszExec) as the (default) argv[0]. This is wrong, but we can't do
+ * much about it. The rest (argv[1,2,n]) then gets set starting at papszArgs[0].
+ *
+ * - Newer hosts (>= VBox 6.1.x) provide a correct argv[0] independently of the actual
+ * executable name though, so actually use argv[0] *and* argv[1,2,n] as intended.
+ */
+ const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0);
+
+ const char *pcszArgv0 = (fHasArgv0 && papszArgs[0]) ? papszArgs[0] : pszExec;
+ AssertPtrReturn(pcszArgv0, VERR_INVALID_POINTER); /* Paranoia. */
+
+ const uint32_t uArgvIdx = pcszArgv0 == papszArgs[0] ? 1 : 0;
+
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessCreateProcess: fHasArgv0=%RTbool, pcszArgv0=%p, uArgvIdx=%RU32, "
+ "g_fControlHostFeatures0=%#x\n",
+ fHasArgv0, pcszArgv0, uArgvIdx, g_fControlHostFeatures0);
+
+ char **papszArgsExp;
+ rc = vgsvcGstCtrlProcessAllocateArgv(pcszArgv0, &papszArgs[uArgvIdx], fFlags, fExecutingSelf, &papszArgsExp);
+ if (RT_FAILURE(rc))
+ {
+ /* Don't print any arguments -- may contain passwords or other sensible data! */
+ VGSvcError("Could not prepare arguments, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ uint32_t fProcCreateFlags = 0;
+ if (fExecutingSelf)
+ fProcCreateFlags |= VBOXSERVICE_PROC_F_UTF8_ARGV;
+ if (fFlags)
+ {
+ if (fFlags & GUEST_PROC_CREATE_FLAG_HIDDEN)
+ fProcCreateFlags |= RTPROC_FLAGS_HIDDEN;
+ if (fFlags & GUEST_PROC_CREATE_FLAG_PROFILE)
+ fProcCreateFlags |= RTPROC_FLAGS_PROFILE;
+ if (fFlags & GUEST_PROC_CREATE_FLAG_UNQUOTED_ARGS)
+ fProcCreateFlags |= RTPROC_FLAGS_UNQUOTED_ARGS;
+ }
+
+ /* If no user name specified run with current credentials (e.g.
+ * full service/system rights). This is prohibited via official Main API!
+ *
+ * Otherwise use the RTPROC_FLAGS_SERVICE to use some special authentication
+ * code (at least on Windows) for running processes as different users
+ * started from our system service. */
+ if (pszAsUser && *pszAsUser)
+ fProcCreateFlags |= RTPROC_FLAGS_SERVICE;
+#ifdef DEBUG
+ VGSvcVerbose(3, "Command: %s\n", szExecExp);
+ for (size_t i = 0; papszArgsExp[i]; i++)
+ VGSvcVerbose(3, " argv[%zu]: %s\n", i, papszArgsExp[i]);
+#endif
+ VGSvcVerbose(3, "Starting process '%s' ...\n", szExecExp);
+
+#ifdef RT_OS_WINDOWS
+ /* If a domain name is given, construct an UPN (User Principle Name) with
+ * the domain name built-in, e.g. "joedoe@example.com". */
+ char *pszUserUPN = NULL;
+ if (pszDomain && *pszDomain != '\0')
+ {
+ pszAsUser = pszUserUPN = RTStrAPrintf2("%s@%s", pszAsUser, pszDomain);
+ if (pszAsUser)
+ VGSvcVerbose(3, "Using UPN: %s\n", pszAsUser);
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+#endif
+ {
+ /* Do normal execution. */
+ rc = RTProcCreateEx(szExecExp, papszArgsExp, hEnv, fProcCreateFlags,
+ phStdIn, phStdOut, phStdErr,
+ pszAsUser,
+ pszPassword && *pszPassword ? pszPassword : NULL,
+ NULL /*pvExtraData*/,
+ phProcess);
+
+#ifdef RT_OS_WINDOWS
+ RTStrFree(pszUserUPN);
+#endif
+ VGSvcVerbose(3, "Starting process '%s' returned rc=%Rrc\n", szExecExp, rc);
+ }
+ vgsvcGstCtrlProcessFreeArgv(papszArgsExp);
+ }
+ }
+ return rc;
+}
+
+
+#ifdef DEBUG
+/**
+ * Dumps content to a file in the OS temporary directory.
+ *
+ * @returns VBox status code.
+ * @param pvBuf Buffer of content to dump.
+ * @param cbBuf Size (in bytes) of content to dump.
+ * @param pszFileNmFmt Pointer to the file name format string, @see pg_rt_str_format.
+ * @param ... The format argument.
+ */
+static int vgsvcGstCtrlProcessDbgDumpToFileF(const void *pvBuf, size_t cbBuf, const char *pszFileNmFmt, ...)
+{
+ AssertPtrReturn(pszFileNmFmt, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ if (!cbBuf)
+ return VINF_SUCCESS;
+
+ va_list va;
+ va_start(va, pszFileNmFmt);
+
+ char *pszFileName = NULL;
+ const int cchFileName = RTStrAPrintfV(&pszFileName, pszFileNmFmt, va);
+
+ va_end(va);
+
+ if (!cchFileName)
+ return VERR_NO_MEMORY;
+
+ char szPathFileAbs[RTPATH_MAX];
+ int rc = RTPathTemp(szPathFileAbs, sizeof(szPathFileAbs));
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szPathFileAbs, sizeof(szPathFileAbs), pszFileName);
+
+ RTStrFree(pszFileName);
+
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "Dumping %zu bytes to '%s'\n", cbBuf, szPathFileAbs);
+
+ RTFILE fh;
+ rc = RTFileOpen(&fh, szPathFileAbs, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(fh, pvBuf, cbBuf, NULL /* pcbWritten */);
+ RTFileClose(fh);
+ }
+ }
+
+ return rc;
+}
+#endif /* DEBUG */
+
+
+/**
+ * The actual worker routine (loop) for a started guest process.
+ *
+ * @return IPRT status code.
+ * @param pProcess The process we're servicing and monitoring.
+ */
+static int vgsvcGstCtrlProcessProcessWorker(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ VGSvcVerbose(3, "Thread of process pThread=0x%p = '%s' started\n", pProcess, pProcess->pStartupInfo->pszCmd);
+
+ VGSvcVerbose(3, "Guest process '%s', flags=0x%x\n", pProcess->pStartupInfo->pszCmd, pProcess->pStartupInfo->fFlags);
+
+ int rc = VGSvcGstCtrlSessionProcessAdd(pProcess->pSession, pProcess);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Error while adding guest process '%s' (%p) to session process list, rc=%Rrc\n",
+ pProcess->pStartupInfo->pszCmd, pProcess, rc);
+ RTThreadUserSignal(RTThreadSelf());
+ return rc;
+ }
+
+ bool fSignalled = false; /* Indicator whether we signalled the thread user event already. */
+
+ /*
+ * Prepare argument list.
+ */
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: fHostFeatures0 = %#x\n", g_fControlHostFeatures0);
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szCmd = '%s'\n", pProcess->pStartupInfo->pszCmd);
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.uNumArgs = '%RU32'\n", pProcess->pStartupInfo->cArgs);
+#ifdef DEBUG /* Never log this stuff in release mode! */
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: StartupInfo.szArgs = '%s'\n", pProcess->pStartupInfo->pszArgs);
+#endif
+
+ char **papszArgs;
+ int cArgs = 0; /* Initialize in case of RTGetOptArgvFromString() is failing ... */
+ rc = RTGetOptArgvFromString(&papszArgs, &cArgs,
+ pProcess->pStartupInfo->cArgs > 0 ? pProcess->pStartupInfo->pszArgs : "",
+ RTGETOPTARGV_CNV_QUOTE_BOURNE_SH, NULL);
+
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: cArgs = %d\n", cArgs);
+#ifdef VBOX_STRICT
+ for (int i = 0; i < cArgs; i++)
+ VGSvcVerbose(3, "vgsvcGstCtrlProcessProcessWorker: papszArgs[%d] = '%s'\n", i, papszArgs[i] ? papszArgs[i] : "<NULL>");
+
+ const bool fHasArgv0 = RT_BOOL(g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_PROCESS_ARGV0); RT_NOREF(fHasArgv0);
+ const int cArgsToCheck = cArgs + (fHasArgv0 ? 0 : 1);
+
+ /* Did we get the same result?
+ * Take into account that we might not have supplied a (correct) argv[0] from the host. */
+ AssertMsg((int)pProcess->pStartupInfo->cArgs == cArgsToCheck,
+ ("rc=%Rrc, StartupInfo.uNumArgs=%RU32 != cArgsToCheck=%d, cArgs=%d, fHostFeatures0=%#x\n",
+ rc, pProcess->pStartupInfo->cArgs, cArgsToCheck, cArgs, g_fControlHostFeatures0));
+#endif
+
+ /*
+ * Create the environment.
+ */
+ uint32_t const cbEnv = pProcess->pStartupInfo->cbEnv;
+ if (RT_SUCCESS(rc))
+ AssertStmt( cbEnv <= GUEST_PROC_MAX_ENV_LEN
+ || pProcess->pStartupInfo->cEnvVars == 0,
+ rc = VERR_INVALID_PARAMETER);
+ if (RT_SUCCESS(rc))
+ {
+ RTENV hEnv;
+ rc = RTEnvClone(&hEnv, RTENV_DEFAULT);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Additional environment variables: %RU32 (%RU32 bytes)\n",
+ pProcess->pStartupInfo->cEnvVars, cbEnv);
+
+ if ( pProcess->pStartupInfo->cEnvVars
+ && cbEnv > 0)
+ {
+ size_t offCur = 0;
+ while (offCur < cbEnv)
+ {
+ const char * const pszCur = &pProcess->pStartupInfo->pszEnv[offCur];
+ size_t const cchCur = RTStrNLen(pszCur, cbEnv - offCur);
+ AssertBreakStmt(cchCur < cbEnv - offCur, rc = VERR_INVALID_PARAMETER);
+ VGSvcVerbose(3, "Setting environment variable: '%s'\n", pszCur);
+ rc = RTEnvPutEx(hEnv, pszCur);
+ if (RT_SUCCESS(rc))
+ offCur += cchCur + 1;
+ else
+ {
+ VGSvcError("Setting environment variable '%s' failed: %Rrc\n", pszCur, rc);
+ break;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Setup the redirection of the standard stuff.
+ */
+ /** @todo consider supporting: gcc stuff.c >file 2>&1. */
+ RTHANDLE hStdIn;
+ PRTHANDLE phStdIn;
+ rc = vgsvcGstCtrlProcessSetupPipe("|", 0 /*STDIN_FILENO*/,
+ &hStdIn, &phStdIn, &pProcess->hPipeStdInW);
+ if (RT_SUCCESS(rc))
+ {
+ RTHANDLE hStdOut;
+ PRTHANDLE phStdOut;
+ rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDOUT)
+ ? "|" : "/dev/null",
+ 1 /*STDOUT_FILENO*/,
+ &hStdOut, &phStdOut, &pProcess->hPipeStdOutR);
+ if (RT_SUCCESS(rc))
+ {
+ RTHANDLE hStdErr;
+ PRTHANDLE phStdErr;
+ rc = vgsvcGstCtrlProcessSetupPipe( (pProcess->pStartupInfo->fFlags & GUEST_PROC_CREATE_FLAG_WAIT_STDERR)
+ ? "|" : "/dev/null",
+ 2 /*STDERR_FILENO*/,
+ &hStdErr, &phStdErr, &pProcess->hPipeStdErrR);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a poll set for the pipes and let the
+ * transport layer add stuff to it as well.
+ */
+ rc = RTPollSetCreate(&pProcess->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uFlags = RTPOLL_EVT_ERROR;
+#if 0
+ /* Add reading event to pollset to get some more information. */
+ uFlags |= RTPOLL_EVT_READ;
+#endif
+ /* Stdin. */
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hPipeStdInW, RTPOLL_EVT_ERROR, VBOXSERVICECTRLPIPEID_STDIN);
+ /* Stdout. */
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hPipeStdOutR, uFlags, VBOXSERVICECTRLPIPEID_STDOUT);
+ /* Stderr. */
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hPipeStdErrR, uFlags, VBOXSERVICECTRLPIPEID_STDERR);
+ /* IPC notification pipe. */
+ if (RT_SUCCESS(rc))
+ rc = RTPipeCreate(&pProcess->hNotificationPipeR, &pProcess->hNotificationPipeW, 0 /* Flags */);
+ if (RT_SUCCESS(rc))
+ rc = RTPollSetAddPipe(pProcess->hPollSet,
+ pProcess->hNotificationPipeR, RTPOLL_EVT_READ, VBOXSERVICECTRLPIPEID_IPC_NOTIFY);
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(pProcess->pSession);
+ bool fNeedsImpersonation = !(pProcess->pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_SPAWN);
+
+ rc = vgsvcGstCtrlProcessCreateProcess(pProcess->pStartupInfo->pszCmd, papszArgs, hEnv,
+ pProcess->pStartupInfo->fFlags,
+ phStdIn, phStdOut, phStdErr,
+ fNeedsImpersonation ? pProcess->pStartupInfo->pszUser : NULL,
+ fNeedsImpersonation ? pProcess->pStartupInfo->pszPassword : NULL,
+ fNeedsImpersonation ? pProcess->pStartupInfo->pszDomain : NULL,
+ &pProcess->hProcess);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error starting process, rc=%Rrc\n", rc);
+ /*
+ * Tell the session thread that it can continue
+ * spawning guest processes. This needs to be done after the new
+ * process has been started because otherwise signal handling
+ * on (Open) Solaris does not work correctly (see @bugref{5068}).
+ */
+ int rc2 = RTThreadUserSignal(RTThreadSelf());
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ fSignalled = true;
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Close the child ends of any pipes and redirected files.
+ */
+ rc2 = RTHandleClose(phStdIn); AssertRC(rc2);
+ phStdIn = NULL;
+ rc2 = RTHandleClose(phStdOut); AssertRC(rc2);
+ phStdOut = NULL;
+ rc2 = RTHandleClose(phStdErr); AssertRC(rc2);
+ phStdErr = NULL;
+
+ /* Enter the process main loop. */
+ rc = vgsvcGstCtrlProcessProcLoop(pProcess);
+
+ /*
+ * The handles that are no longer in the set have
+ * been closed by the above call in order to prevent
+ * the guest from getting stuck accessing them.
+ * So, NIL the handles to avoid closing them again.
+ */
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_IPC_NOTIFY, NULL)))
+ pProcess->hNotificationPipeW = NIL_RTPIPE;
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_STDERR, NULL)))
+ pProcess->hPipeStdErrR = NIL_RTPIPE;
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_STDOUT, NULL)))
+ pProcess->hPipeStdOutR = NIL_RTPIPE;
+ if (RT_FAILURE(RTPollSetQueryHandle(pProcess->hPollSet,
+ VBOXSERVICECTRLPIPEID_STDIN, NULL)))
+ pProcess->hPipeStdInW = NIL_RTPIPE;
+ }
+ }
+ RTPollSetDestroy(pProcess->hPollSet);
+ pProcess->hPollSet = NIL_RTPOLLSET;
+
+ RTPipeClose(pProcess->hNotificationPipeR);
+ pProcess->hNotificationPipeR = NIL_RTPIPE;
+ RTPipeClose(pProcess->hNotificationPipeW);
+ pProcess->hNotificationPipeW = NIL_RTPIPE;
+ }
+ RTPipeClose(pProcess->hPipeStdErrR);
+ pProcess->hPipeStdErrR = NIL_RTPIPE;
+ RTHandleClose(&hStdErr);
+ if (phStdErr)
+ RTHandleClose(phStdErr);
+ }
+ RTPipeClose(pProcess->hPipeStdOutR);
+ pProcess->hPipeStdOutR = NIL_RTPIPE;
+ RTHandleClose(&hStdOut);
+ if (phStdOut)
+ RTHandleClose(phStdOut);
+ }
+ RTPipeClose(pProcess->hPipeStdInW);
+ pProcess->hPipeStdInW = NIL_RTPIPE;
+ RTHandleClose(&hStdIn);
+ if (phStdIn)
+ RTHandleClose(phStdIn);
+ }
+ }
+ RTEnvDestroy(hEnv);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ VBGLR3GUESTCTRLCMDCTX ctx = { g_idControlSvcClient, pProcess->uContextID, 0 /* uProtocol */, 0 /* uNumParms */ };
+ int rc2 = VbglR3GuestCtrlProcCbStatus(&ctx,
+ pProcess->uPID, PROC_STS_ERROR, rc,
+ NULL /* pvData */, 0 /* cbData */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_NOT_FOUND)
+ VGSvcError("[PID %RU32]: Could not report process failure error; rc=%Rrc (process error %Rrc)\n",
+ pProcess->uPID, rc2, rc);
+ }
+
+ /* Update stopped status. */
+ ASMAtomicWriteBool(&pProcess->fStopped, true);
+
+ if (cArgs)
+ RTGetOptArgvFree(papszArgs);
+
+ /*
+ * If something went wrong signal the user event so that others don't wait
+ * forever on this thread.
+ */
+ if ( RT_FAILURE(rc)
+ && !fSignalled)
+ {
+ RTThreadUserSignal(RTThreadSelf());
+ }
+
+ /* Set shut down flag in case we've forgotten it. */
+ ASMAtomicWriteBool(&pProcess->fShutdown, true);
+
+ VGSvcVerbose(3, "[PID %RU32]: Thread of process '%s' ended with rc=%Rrc (fSignalled=%RTbool)\n",
+ pProcess->uPID, pProcess->pStartupInfo->pszCmd, rc, fSignalled);
+
+ return rc;
+}
+
+
+static int vgsvcGstCtrlProcessLock(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ int rc = RTCritSectEnter(&pProcess->CritSect);
+ AssertRC(rc);
+ return rc;
+}
+
+
+/**
+ * Thread main routine for a started process.
+ *
+ * @return IPRT status code.
+ * @param hThreadSelf The thread handle.
+ * @param pvUser Pointer to a VBOXSERVICECTRLPROCESS structure.
+ *
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlProcessThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF1(hThreadSelf);
+ PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)pvUser;
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ return vgsvcGstCtrlProcessProcessWorker(pProcess);
+}
+
+
+static int vgsvcGstCtrlProcessUnlock(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ int rc = RTCritSectLeave(&pProcess->CritSect);
+ AssertRC(rc);
+ return rc;
+}
+
+
+/**
+ * Executes (starts) a process on the guest. This causes a new thread to be created
+ * so that this function will not block the overall program execution.
+ *
+ * @return IPRT status code.
+ * @param pSession Guest session.
+ * @param pStartupInfo Startup info.
+ * @param uContextID Context ID to associate the process to start with.
+ */
+int VGSvcGstCtrlProcessStart(const PVBOXSERVICECTRLSESSION pSession,
+ const PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo, uint32_t uContextID)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pStartupInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Allocate new thread data and assign it to our thread list.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = (PVBOXSERVICECTRLPROCESS)RTMemAlloc(sizeof(VBOXSERVICECTRLPROCESS));
+ if (!pProcess)
+ return VERR_NO_MEMORY;
+
+ int rc = vgsvcGstCtrlProcessInit(pProcess, pSession, pStartupInfo, uContextID);
+ if (RT_SUCCESS(rc))
+ {
+ static uint32_t s_uCtrlExecThread = 0;
+ rc = RTThreadCreateF(&pProcess->Thread, vgsvcGstCtrlProcessThread,
+ pProcess /*pvUser*/, 0 /*cbStack*/,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctl%RU32", s_uCtrlExecThread++);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Creating thread for guest process '%s' failed: rc=%Rrc, pProcess=%p\n",
+ pStartupInfo->pszCmd, rc, pProcess);
+
+ /* Process has not been added to the session's process list yet, so skip VGSvcGstCtrlSessionProcessRemove() here. */
+ VGSvcGstCtrlProcessFree(pProcess);
+ }
+ else
+ {
+ VGSvcVerbose(4, "Waiting for thread to initialize ...\n");
+
+ /* Wait for the thread to initialize. */
+ rc = RTThreadUserWait(pProcess->Thread, 60 * 1000 /* 60 seconds max. */);
+ AssertRC(rc);
+ if ( ASMAtomicReadBool(&pProcess->fShutdown)
+ || ASMAtomicReadBool(&pProcess->fStopped)
+ || RT_FAILURE(rc))
+ {
+ VGSvcError("Thread for process '%s' failed to start, rc=%Rrc\n", pStartupInfo->pszCmd, rc);
+ int rc2 = RTThreadWait(pProcess->Thread, RT_MS_1SEC * 30, NULL);
+ if (RT_SUCCESS(rc2))
+ pProcess->Thread = NIL_RTTHREAD;
+
+ VGSvcGstCtrlSessionProcessRemove(pSession, pProcess);
+ VGSvcGstCtrlProcessFree(pProcess);
+ }
+ else
+ {
+ ASMAtomicXchgBool(&pProcess->fStarted, true);
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnInput(PVBOXSERVICECTRLPROCESS pThis,
+ const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ bool fPendingClose, void *pvBuf, uint32_t cbBuf)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ int rc;
+
+ size_t cbWritten = 0;
+ if (pvBuf && cbBuf)
+ {
+ if (pThis->hPipeStdInW != NIL_RTPIPE)
+ rc = RTPipeWrite(pThis->hPipeStdInW, pvBuf, cbBuf, &cbWritten);
+ else
+ rc = VINF_EOF;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ /*
+ * If this is the last write + we have really have written all data
+ * we need to close the stdin pipe on our end and remove it from
+ * the poll set.
+ */
+ if ( fPendingClose
+ && cbBuf == cbWritten)
+ {
+ int rc2 = vgsvcGstCtrlProcessPollsetCloseInput(pThis, &pThis->hPipeStdInW);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ uint32_t uStatus = INPUT_STS_UNDEFINED; /* Status to send back to the host. */
+ uint32_t fFlags = 0; /* No flags at the moment. */
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "[PID %RU32]: Written %RU32 bytes input, CID=%RU32, fPendingClose=%RTbool\n",
+ pThis->uPID, cbWritten, pHostCtx->uContextID, fPendingClose);
+ uStatus = INPUT_STS_WRITTEN;
+ }
+ else
+ {
+ if (rc == VERR_BAD_PIPE)
+ uStatus = INPUT_STS_TERMINATED;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ uStatus = INPUT_STS_OVERFLOW;
+ /* else undefined */
+ }
+
+ /*
+ * If there was an error and we did not set the host status
+ * yet, then do it now.
+ */
+ if ( RT_FAILURE(rc)
+ && uStatus == INPUT_STS_UNDEFINED)
+ {
+ uStatus = INPUT_STS_ERROR;
+ fFlags = rc; /* funny thing to call a "flag"... */
+ }
+ Assert(uStatus > INPUT_STS_UNDEFINED);
+
+ int rc2 = VbglR3GuestCtrlProcCbStatusInput(pHostCtx, pThis->uPID, uStatus, fFlags, (uint32_t)cbWritten);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessOnInput returned with rc=%Rrc\n", pThis->uPID, rc);
+#endif
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnOutput(PVBOXSERVICECTRLPROCESS pThis,
+ const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ const PVBOXSERVICECTRLSESSION pSession = pThis->pSession;
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ int rc;
+
+ uint32_t cbBuf = cbToRead;
+ uint8_t *pvBuf = (uint8_t *)RTMemAlloc(cbBuf);
+ if (pvBuf)
+ {
+ PRTPIPE phPipe = uHandle == GUEST_PROC_OUT_H_STDOUT
+ ? &pThis->hPipeStdOutR
+ : &pThis->hPipeStdErrR;
+ AssertPtr(phPipe);
+
+ size_t cbRead = 0;
+ if (*phPipe != NIL_RTPIPE)
+ {
+ rc = RTPipeRead(*phPipe, pvBuf, cbBuf, &cbRead);
+ if (RT_FAILURE(rc))
+ {
+ RTPollSetRemove(pThis->hPollSet, uHandle == GUEST_PROC_OUT_H_STDERR
+ ? VBOXSERVICECTRLPIPEID_STDERR : VBOXSERVICECTRLPIPEID_STDOUT);
+ RTPipeClose(*phPipe);
+ *phPipe = NIL_RTPIPE;
+ if (rc == VERR_BROKEN_PIPE)
+ rc = VINF_EOF;
+ }
+ }
+ else
+ rc = VINF_EOF;
+
+#ifdef DEBUG
+ if (RT_SUCCESS(rc))
+ {
+ if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT
+ && ( uHandle == GUEST_PROC_OUT_H_STDOUT
+ || uHandle == GUEST_PROC_OUT_H_STDOUT_DEPRECATED)
+ )
+ {
+ rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdOut.txt",
+ pSession->StartupInfo.uSessionID, pThis->uPID);
+ AssertRC(rc);
+ }
+ else if ( pSession->fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR
+ && uHandle == GUEST_PROC_OUT_H_STDERR)
+ {
+ rc = vgsvcGstCtrlProcessDbgDumpToFileF(pvBuf, cbRead, "VBoxService_Session%RU32_PID%RU32_StdErr.txt",
+ pSession->StartupInfo.uSessionID, pThis->uPID);
+ AssertRC(rc);
+ }
+ }
+#endif
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: Read %RU32 bytes output: uHandle=%RU32, CID=%RU32, fFlags=%x\n",
+ pThis->uPID, cbRead, uHandle, pHostCtx->uContextID, fFlags);
+#endif
+ /** Note: Don't convert/touch/modify/whatever the output data here! This might be binary
+ * data which the host needs to work with -- so just pass through all data unfiltered! */
+
+ /* Note: Since the context ID is unique the request *has* to be completed here,
+ * regardless whether we got data or not! Otherwise the waiting events
+ * on the host never will get completed! */
+ Assert((uint32_t)cbRead == cbRead);
+ rc = VbglR3GuestCtrlProcCbOutput(pHostCtx, pThis->uPID, uHandle, fFlags, pvBuf, (uint32_t)cbRead);
+ if ( RT_FAILURE(rc)
+ && rc == VERR_NOT_FOUND) /* Not critical if guest PID is not found on the host (anymore). */
+ rc = VINF_SUCCESS;
+ }
+
+ RTMemFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: Reading output returned with rc=%Rrc\n", pThis->uPID, rc);
+#endif
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vgsvcGstCtrlProcessOnTerm(PVBOXSERVICECTRLPROCESS pThis)
+{
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ if (!ASMAtomicXchgBool(&pThis->fShutdown, true))
+ VGSvcVerbose(3, "[PID %RU32]: Setting shutdown flag ...\n", pThis->uPID);
+
+ return VINF_SUCCESS;
+}
+
+
+static int vgsvcGstCtrlProcessRequestExV(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx, bool fAsync,
+ RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, va_list Args)
+{
+ RT_NOREF1(pHostCtx);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ /* pHostCtx is optional. */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+ if (!fAsync)
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+
+ int rc = vgsvcGstCtrlProcessLock(pProcess);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV fAsync=%RTbool, uTimeoutMS=%RU32, cArgs=%u\n",
+ pProcess->uPID, fAsync, uTimeoutMS, cArgs);
+#endif
+ uint32_t fFlags = RTREQFLAGS_IPRT_STATUS;
+ if (fAsync)
+ {
+ Assert(uTimeoutMS == 0);
+ fFlags |= RTREQFLAGS_NO_WAIT;
+ }
+
+ PRTREQ hReq = NIL_RTREQ;
+ rc = RTReqQueueCallV(pProcess->hReqQueue, &hReq, uTimeoutMS, fFlags, pfnFunction, cArgs, Args);
+ RTReqRelease(hReq);
+ if (RT_SUCCESS(rc))
+ {
+ /* Wake up the process' notification pipe to get
+ * the request being processed. */
+ Assert(pProcess->hNotificationPipeW != NIL_RTPIPE || pProcess->fShutdown /* latter in case of race */);
+ size_t cbWritten = 0;
+ rc = RTPipeWrite(pProcess->hNotificationPipeW, "i", 1, &cbWritten);
+ if ( RT_SUCCESS(rc)
+ && cbWritten != 1)
+ {
+ VGSvcError("[PID %RU32]: Notification pipe got %zu bytes instead of 1\n",
+ pProcess->uPID, cbWritten);
+ }
+ else if (RT_UNLIKELY(RT_FAILURE(rc)))
+ VGSvcError("[PID %RU32]: Writing to notification pipe failed, rc=%Rrc\n",
+ pProcess->uPID, rc);
+ }
+ else
+ VGSvcError("[PID %RU32]: RTReqQueueCallV failed, rc=%Rrc\n",
+ pProcess->uPID, rc);
+
+ int rc2 = vgsvcGstCtrlProcessUnlock(pProcess);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+#ifdef DEBUG
+ VGSvcVerbose(3, "[PID %RU32]: vgsvcGstCtrlProcessRequestExV returned rc=%Rrc\n", pProcess->uPID, rc);
+#endif
+ return rc;
+}
+
+
+static int vgsvcGstCtrlProcessRequestAsync(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ /* pHostCtx is optional. */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+
+ va_list va;
+ va_start(va, cArgs);
+ int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, true /* fAsync */, 0 /* uTimeoutMS */,
+ pfnFunction, cArgs, va);
+ va_end(va);
+
+ return rc;
+}
+
+
+#if 0 /* unused */
+static int vgsvcGstCtrlProcessRequestWait(PVBOXSERVICECTRLPROCESS pProcess, const PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ RTMSINTERVAL uTimeoutMS, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+ /* pHostCtx is optional. */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+
+ va_list va;
+ va_start(va, cArgs);
+ int rc = vgsvcGstCtrlProcessRequestExV(pProcess, pHostCtx, false /* fAsync */, uTimeoutMS,
+ pfnFunction, cArgs, va);
+ va_end(va);
+
+ return rc;
+}
+#endif
+
+
+int VGSvcGstCtrlProcessHandleInput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ bool fPendingClose, void *pvBuf, uint32_t cbBuf)
+{
+ if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped))
+ return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnInput,
+ 5 /* cArgs */, pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf);
+
+ return vgsvcGstCtrlProcessOnInput(pProcess, pHostCtx, fPendingClose, pvBuf, cbBuf);
+}
+
+
+int VGSvcGstCtrlProcessHandleOutput(PVBOXSERVICECTRLPROCESS pProcess, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ uint32_t uHandle, uint32_t cbToRead, uint32_t fFlags)
+{
+ if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped))
+ return vgsvcGstCtrlProcessRequestAsync(pProcess, pHostCtx, (PFNRT)vgsvcGstCtrlProcessOnOutput,
+ 5 /* cArgs */, pProcess, pHostCtx, uHandle, cbToRead, fFlags);
+
+ return vgsvcGstCtrlProcessOnOutput(pProcess, pHostCtx, uHandle, cbToRead, fFlags);
+}
+
+
+int VGSvcGstCtrlProcessHandleTerm(PVBOXSERVICECTRLPROCESS pProcess)
+{
+ if (!ASMAtomicReadBool(&pProcess->fShutdown) && !ASMAtomicReadBool(&pProcess->fStopped))
+ return vgsvcGstCtrlProcessRequestAsync(pProcess, NULL /* pHostCtx */, (PFNRT)vgsvcGstCtrlProcessOnTerm,
+ 1 /* cArgs */, pProcess);
+
+ return vgsvcGstCtrlProcessOnTerm(pProcess);
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp
new file mode 100644
index 00000000..001d468d
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceControlSession.cpp
@@ -0,0 +1,2886 @@
+/* $Id: VBoxServiceControlSession.cpp $ */
+/** @file
+ * VBoxServiceControlSession - Guest session handling. Also handles the spawned session processes.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/handle.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/rand.h>
+#include <iprt/system.h> /* For RTShutdown. */
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServiceControl.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Generic option indices for session spawn arguments. */
+enum
+{
+ VBOXSERVICESESSIONOPT_FIRST = 1000, /* For initialization. */
+ VBOXSERVICESESSIONOPT_DOMAIN,
+#ifdef DEBUG
+ VBOXSERVICESESSIONOPT_DUMP_STDOUT,
+ VBOXSERVICESESSIONOPT_DUMP_STDERR,
+#endif
+ VBOXSERVICESESSIONOPT_LOG_FILE,
+ VBOXSERVICESESSIONOPT_USERNAME,
+ VBOXSERVICESESSIONOPT_SESSION_ID,
+ VBOXSERVICESESSIONOPT_SESSION_PROTO,
+ VBOXSERVICESESSIONOPT_THREAD_ID
+};
+
+
+static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession);
+static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess);
+
+
+/**
+ * Helper that grows the scratch buffer.
+ * @returns Success indicator.
+ */
+static bool vgsvcGstCtrlSessionGrowScratchBuf(void **ppvScratchBuf, uint32_t *pcbScratchBuf, uint32_t cbMinBuf)
+{
+ uint32_t cbNew = *pcbScratchBuf * 2;
+ if ( cbNew <= VMMDEV_MAX_HGCM_DATA_SIZE
+ && cbMinBuf <= VMMDEV_MAX_HGCM_DATA_SIZE)
+ {
+ while (cbMinBuf > cbNew)
+ cbNew *= 2;
+ void *pvNew = RTMemRealloc(*ppvScratchBuf, cbNew);
+ if (pvNew)
+ {
+ *ppvScratchBuf = pvNew;
+ *pcbScratchBuf = cbNew;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+
+static int vgsvcGstCtrlSessionFileFree(PVBOXSERVICECTRLFILE pFile)
+{
+ AssertPtrReturn(pFile, VERR_INVALID_POINTER);
+
+ int rc = RTFileClose(pFile->hFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pFile->pszName);
+
+ /* Remove file entry in any case. */
+ RTListNodeRemove(&pFile->Node);
+ /* Destroy this object. */
+ RTMemFree(pFile);
+ }
+
+ return rc;
+}
+
+
+/** @todo No locking done yet! */
+static PVBOXSERVICECTRLFILE vgsvcGstCtrlSessionFileGetLocked(const PVBOXSERVICECTRLSESSION pSession, uint32_t uHandle)
+{
+ AssertPtrReturn(pSession, NULL);
+
+ /** @todo Use a map later! */
+ PVBOXSERVICECTRLFILE pFileCur;
+ RTListForEach(&pSession->lstFiles, pFileCur, VBOXSERVICECTRLFILE, Node)
+ {
+ if (pFileCur->uHandle == uHandle)
+ return pFileCur;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Recursion worker for vgsvcGstCtrlSessionHandleDirRemove.
+ * Only (recursively) removes directory structures which are not empty. Will fail if not empty.
+ *
+ * @returns IPRT status code.
+ * @param pszDir The directory buffer, RTPATH_MAX in length.
+ * Contains the abs path to the directory to
+ * recurse into. Trailing slash.
+ * @param cchDir The length of the directory we're recursing into,
+ * including the trailing slash.
+ * @param pDirEntry The dir entry buffer. (Shared to save stack.)
+ */
+static int vgsvcGstCtrlSessionHandleDirRemoveSub(char *pszDir, size_t cchDir, PRTDIRENTRY pDirEntry)
+{
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszDir);
+ if (RT_FAILURE(rc))
+ {
+ /* Ignore non-existing directories like RTDirRemoveRecursive does: */
+ if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND)
+ return VINF_SUCCESS;
+ return rc;
+ }
+
+ for (;;)
+ {
+ rc = RTDirRead(hDir, pDirEntry, NULL);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ if (!RTDirEntryIsStdDotLink(pDirEntry))
+ {
+ /* Construct the full name of the entry. */
+ if (cchDir + pDirEntry->cbName + 1 /* dir slash */ < RTPATH_MAX)
+ memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1);
+ else
+ {
+ rc = VERR_FILENAME_TOO_LONG;
+ break;
+ }
+
+ /* Make sure we've got the entry type. */
+ if (pDirEntry->enmType == RTDIRENTRYTYPE_UNKNOWN)
+ RTDirQueryUnknownType(pszDir, false /*fFollowSymlinks*/, &pDirEntry->enmType);
+
+ /* Recurse into subdirs and remove them: */
+ if (pDirEntry->enmType == RTDIRENTRYTYPE_DIRECTORY)
+ {
+ size_t cchSubDir = cchDir + pDirEntry->cbName;
+ pszDir[cchSubDir++] = RTPATH_SLASH;
+ pszDir[cchSubDir] = '\0';
+ rc = vgsvcGstCtrlSessionHandleDirRemoveSub(pszDir, cchSubDir, pDirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ pszDir[cchSubDir] = '\0';
+ rc = RTDirRemove(pszDir);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ break;
+ }
+ /* Not a subdirectory - fail: */
+ else
+ {
+ rc = VERR_DIR_NOT_EMPTY;
+ break;
+ }
+ }
+ }
+
+ RTDirClose(hDir);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleDirRemove(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message.
+ */
+ char szDir[RTPATH_MAX];
+ uint32_t fFlags; /* DIRREMOVE_FLAG_XXX */
+ int rc = VbglR3GuestCtrlDirGetRemove(pHostCtx, szDir, sizeof(szDir), &fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do some validating before executing the job.
+ */
+ if (!(fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK))
+ {
+ if (fFlags & DIRREMOVEREC_FLAG_RECURSIVE)
+ {
+ if (fFlags & (DIRREMOVEREC_FLAG_CONTENT_AND_DIR | DIRREMOVEREC_FLAG_CONTENT_ONLY))
+ {
+ uint32_t fFlagsRemRec = fFlags & DIRREMOVEREC_FLAG_CONTENT_AND_DIR
+ ? RTDIRRMREC_F_CONTENT_AND_DIR : RTDIRRMREC_F_CONTENT_ONLY;
+ rc = RTDirRemoveRecursive(szDir, fFlagsRemRec);
+ }
+ else /* Only remove empty directory structures. Will fail if non-empty. */
+ {
+ RTDIRENTRY DirEntry;
+ RTPathEnsureTrailingSeparator(szDir, sizeof(szDir));
+ rc = vgsvcGstCtrlSessionHandleDirRemoveSub(szDir, strlen(szDir), &DirEntry);
+ }
+ VGSvcVerbose(4, "[Dir %s]: rmdir /s (%#x) -> rc=%Rrc\n", szDir, fFlags, rc);
+ }
+ else
+ {
+ /* Only delete directory if not empty. */
+ rc = RTDirRemove(szDir);
+ VGSvcVerbose(4, "[Dir %s]: rmdir (%#x), rc=%Rrc\n", szDir, fFlags, rc);
+ }
+ }
+ else
+ {
+ VGSvcError("[Dir %s]: Unsupported flags: %#x (all %#x)\n", szDir, (fFlags & ~DIRREMOVEREC_FLAG_VALID_MASK), fFlags);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("[Dir %s]: Failed to report removing status, rc=%Rrc\n", szDir, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for rmdir operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VGSvcVerbose(6, "Removing directory '%s' returned rc=%Rrc\n", szDir, rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileOpen(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message.
+ */
+ char szFile[RTPATH_MAX];
+ char szAccess[64];
+ char szDisposition[64];
+ char szSharing[64];
+ uint32_t uCreationMode = 0;
+ uint64_t offOpen = 0;
+ uint32_t uHandle = 0;
+ int rc = VbglR3GuestCtrlFileGetOpen(pHostCtx,
+ /* File to open. */
+ szFile, sizeof(szFile),
+ /* Open mode. */
+ szAccess, sizeof(szAccess),
+ /* Disposition. */
+ szDisposition, sizeof(szDisposition),
+ /* Sharing. */
+ szSharing, sizeof(szSharing),
+ /* Creation mode. */
+ &uCreationMode,
+ /* Offset. */
+ &offOpen);
+ VGSvcVerbose(4, "[File %s]: szAccess=%s, szDisposition=%s, szSharing=%s, offOpen=%RU64, rc=%Rrc\n",
+ szFile, szAccess, szDisposition, szSharing, offOpen, rc);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICECTRLFILE pFile = (PVBOXSERVICECTRLFILE)RTMemAllocZ(sizeof(VBOXSERVICECTRLFILE));
+ if (pFile)
+ {
+ pFile->hFile = NIL_RTFILE; /* Not zero or NULL! */
+ if (szFile[0])
+ {
+ pFile->pszName = RTStrDup(szFile);
+ if (!pFile->pszName)
+ rc = VERR_NO_MEMORY;
+/** @todo
+ * Implement szSharing!
+ */
+ uint64_t fFlags;
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileModeToFlagsEx(szAccess, szDisposition, NULL /* pszSharing, not used yet */, &fFlags);
+ VGSvcVerbose(4, "[File %s] Opening with fFlags=%#RX64 -> rc=%Rrc\n", pFile->pszName, fFlags, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ fFlags |= (uCreationMode << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK;
+ /* If we're opening a file in read-only mode, strip truncation mode.
+ * rtFileRecalcAndValidateFlags() will validate it anyway, but avoid asserting in debug builds. */
+ if (fFlags & RTFILE_O_READ)
+ fFlags &= ~RTFILE_O_TRUNCATE;
+ rc = RTFileOpen(&pFile->hFile, pFile->pszName, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTFileQueryInfo(pFile->hFile, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make sure that we only open stuff we really support.
+ * Only POSIX / UNIX we could open stuff like directories and sockets as well. */
+ if ( RT_LIKELY(RTFS_IS_FILE(objInfo.Attr.fMode))
+ || RTFS_IS_SYMLINK(objInfo.Attr.fMode))
+ {
+ /* Seeking is optional. However, the whole operation
+ * will fail if we don't succeed seeking to the wanted position. */
+ if (offOpen)
+ rc = RTFileSeek(pFile->hFile, (int64_t)offOpen, RTFILE_SEEK_BEGIN, NULL /* Current offset */);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Succeeded!
+ */
+ uHandle = VBOX_GUESTCTRL_CONTEXTID_GET_OBJECT(pHostCtx->uContextID);
+ pFile->uHandle = uHandle;
+ pFile->fOpen = fFlags;
+ RTListAppend(&pSession->lstFiles, &pFile->Node);
+ VGSvcVerbose(2, "[File %s] Opened (ID=%RU32)\n", pFile->pszName, pFile->uHandle);
+ }
+ else
+ VGSvcError("[File %s] Seeking to offset %RU64 failed: rc=%Rrc\n", pFile->pszName, offOpen, rc);
+ }
+ else
+ {
+ VGSvcError("[File %s] Unsupported mode %#x\n", pFile->pszName, objInfo.Attr.fMode);
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ else
+ VGSvcError("[File %s] Getting mode failed with rc=%Rrc\n", pFile->pszName, rc);
+ }
+ else
+ VGSvcError("[File %s] Opening failed with rc=%Rrc\n", pFile->pszName, rc);
+ }
+ }
+ else
+ {
+ VGSvcError("[File %s] empty filename!\n", szFile);
+ rc = VERR_INVALID_NAME;
+ }
+
+ /* clean up if we failed. */
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(pFile->pszName);
+ if (pFile->hFile != NIL_RTFILE)
+ RTFileClose(pFile->hFile);
+ RTMemFree(pFile);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbOpen(pHostCtx, rc, uHandle);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("[File %s]: Failed to report file open status, rc=%Rrc\n", szFile, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for open file operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VGSvcVerbose(4, "[File %s] Opening (open mode='%s', disposition='%s', creation mode=0x%x) returned rc=%Rrc\n",
+ szFile, szAccess, szDisposition, uCreationMode, rc);
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileClose(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the message.
+ */
+ uint32_t uHandle = 0;
+ int rc = VbglR3GuestCtrlFileGetClose(pHostCtx, &uHandle /* File handle to close */);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ VGSvcVerbose(2, "[File %s] Closing (handle=%RU32)\n", pFile ? pFile->pszName : "<Not found>", uHandle);
+ rc = vgsvcGstCtrlSessionFileFree(pFile);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbClose(pHostCtx, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file close status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for close file operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileRead(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToRead;
+ int rc = VbglR3GuestCtrlFileGetRead(pHostCtx, &uHandle, &cbToRead);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the reading.
+ *
+ * If the request is larger than our scratch buffer, try grow it - just
+ * ignore failure as the host better respect our buffer limits.
+ */
+ uint32_t offNew = 0;
+ size_t cbRead = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ if (*pcbScratchBuf < cbToRead)
+ vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead);
+
+ rc = RTFileRead(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead);
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Read %zu/%RU32 bytes, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, cbToRead, rc, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result and data back to the host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file read status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file read operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileReadAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToRead;
+ uint64_t offReadAt;
+ int rc = VbglR3GuestCtrlFileGetReadAt(pHostCtx, &uHandle, &cbToRead, &offReadAt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the reading.
+ *
+ * If the request is larger than our scratch buffer, try grow it - just
+ * ignore failure as the host better respect our buffer limits.
+ */
+ int64_t offNew = 0;
+ size_t cbRead = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ if (*pcbScratchBuf < cbToRead)
+ vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToRead);
+
+ rc = RTFileReadAt(pFile->hFile, (RTFOFF)offReadAt, *ppvScratchBuf, RT_MIN(cbToRead, *pcbScratchBuf), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ offNew = offReadAt + cbRead;
+ RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL); /* RTFileReadAt does not always change position. */
+ }
+ else
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Read %zu bytes @ %RU64, rc=%Rrc, offNew=%RI64\n", pFile->pszName, cbRead, offReadAt, rc, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result and data back to the host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbReadOffset(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbRead(pHostCtx, rc, *ppvScratchBuf, (uint32_t)cbRead);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file read at status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file read at operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileWrite(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request and data to write.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToWrite;
+ int rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite);
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite))
+ rc = VbglR3GuestCtrlFileGetWrite(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the writing.
+ */
+ int64_t offNew = 0;
+ size_t cbWritten = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileWrite(pFile->hFile, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten);
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 => %Rrc, cbWritten=%zu, offNew=%RI64\n",
+ pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), rc, cbWritten, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file write operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileWriteAt(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request and data to write.
+ */
+ uint32_t uHandle = 0;
+ uint32_t cbToWrite;
+ uint64_t offWriteAt;
+ int rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt);
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbToWrite))
+ rc = VbglR3GuestCtrlFileGetWriteAt(pHostCtx, &uHandle, *ppvScratchBuf, *pcbScratchBuf, &cbToWrite, &offWriteAt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and do the writing.
+ */
+ int64_t offNew = 0;
+ size_t cbWritten = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileWriteAt(pFile->hFile, (RTFOFF)offWriteAt, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ offNew = offWriteAt + cbWritten;
+
+ /* RTFileWriteAt does not always change position: */
+ if (!(pFile->fOpen & RTFILE_O_APPEND))
+ RTFileSeek(pFile->hFile, offNew, RTFILE_SEEK_BEGIN, NULL);
+ else
+ RTFileSeek(pFile->hFile, 0, RTFILE_SEEK_END, (uint64_t *)&offNew);
+ }
+ else
+ offNew = (int64_t)RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s] Writing %p LB %RU32 @ %RU64 => %Rrc, cbWritten=%zu, offNew=%RI64\n",
+ pFile->pszName, *ppvScratchBuf, RT_MIN(cbToWrite, *pcbScratchBuf), offWriteAt, rc, cbWritten, offNew);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2;
+ if (g_fControlHostFeatures0 & VBOX_GUESTCTRL_HF_0_NOTIFY_RDWR_OFFSET)
+ rc2 = VbglR3GuestCtrlFileCbWriteOffset(pHostCtx, rc, (uint32_t)cbWritten, offNew);
+ else
+ rc2 = VbglR3GuestCtrlFileCbWrite(pHostCtx, rc, (uint32_t)cbWritten);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file write status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file write at operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileSeek(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint32_t uSeekMethod;
+ uint64_t offSeek; /* Will be converted to int64_t. */
+ int rc = VbglR3GuestCtrlFileGetSeek(pHostCtx, &uHandle, &uSeekMethod, &offSeek);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t offActual = 0;
+
+ /*
+ * Validate and convert the seek method to IPRT speak.
+ */
+ static const uint8_t s_abMethods[GUEST_FILE_SEEKTYPE_END + 1] =
+ {
+ UINT8_MAX, RTFILE_SEEK_BEGIN, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_CURRENT,
+ UINT8_MAX, UINT8_MAX, UINT8_MAX, RTFILE_SEEK_END
+ };
+ if ( uSeekMethod < RT_ELEMENTS(s_abMethods)
+ && s_abMethods[uSeekMethod] != UINT8_MAX)
+ {
+ /*
+ * Locate the file and do the seek.
+ */
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileSeek(pFile->hFile, (int64_t)offSeek, s_abMethods[uSeekMethod], &offActual);
+ VGSvcVerbose(5, "[File %s]: Seeking to offSeek=%RI64, uSeekMethodIPRT=%u, rc=%Rrc\n",
+ pFile->pszName, offSeek, s_abMethods[uSeekMethod], rc);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ else
+ {
+ VGSvcError("Invalid seek method: %#x\n", uSeekMethod);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbSeek(pHostCtx, rc, offActual);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file seek status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file seek operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileTell(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ int rc = VbglR3GuestCtrlFileGetTell(pHostCtx, &uHandle);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and ask for the current position.
+ */
+ uint64_t offCurrent = 0;
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ offCurrent = RTFileTell(pFile->hFile);
+ VGSvcVerbose(5, "[File %s]: Telling offCurrent=%RU64\n", pFile->pszName, offCurrent);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbTell(pHostCtx, rc, offCurrent);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleFileSetSize(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uHandle = 0;
+ uint64_t cbNew = 0;
+ int rc = VbglR3GuestCtrlFileGetSetSize(pHostCtx, &uHandle, &cbNew);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the file and ask for the current position.
+ */
+ PVBOXSERVICECTRLFILE pFile = vgsvcGstCtrlSessionFileGetLocked(pSession, uHandle);
+ if (pFile)
+ {
+ rc = RTFileSetSize(pFile->hFile, cbNew);
+ VGSvcVerbose(5, "[File %s]: Changing size to %RU64 (%#RX64), rc=%Rrc\n", pFile->pszName, cbNew, cbNew, rc);
+ }
+ else
+ {
+ VGSvcError("File %u (%#x) not found!\n", uHandle, uHandle);
+ cbNew = UINT64_MAX;
+ rc = VERR_NOT_FOUND;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlFileCbSetSize(pHostCtx, rc, cbNew);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report file tell status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for file tell operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandlePathRename(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ char szSource[RTPATH_MAX];
+ char szDest[RTPATH_MAX];
+ uint32_t fFlags = 0; /* PATHRENAME_FLAG_XXX */
+ int rc = VbglR3GuestCtrlPathGetRename(pHostCtx, szSource, sizeof(szSource), szDest, sizeof(szDest), &fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Validate the flags (kudos for using the same as IPRT), then do the renaming.
+ */
+ AssertCompile(PATHRENAME_FLAG_NO_REPLACE == RTPATHRENAME_FLAGS_NO_REPLACE);
+ AssertCompile(PATHRENAME_FLAG_REPLACE == RTPATHRENAME_FLAGS_REPLACE);
+ AssertCompile(PATHRENAME_FLAG_NO_SYMLINKS == RTPATHRENAME_FLAGS_NO_SYMLINKS);
+ AssertCompile(PATHRENAME_FLAG_VALID_MASK == (RTPATHRENAME_FLAGS_NO_REPLACE | RTPATHRENAME_FLAGS_REPLACE | RTPATHRENAME_FLAGS_NO_SYMLINKS));
+ if (!(fFlags & ~PATHRENAME_FLAG_VALID_MASK))
+ {
+ VGSvcVerbose(4, "Renaming '%s' to '%s', fFlags=%#x, rc=%Rrc\n", szSource, szDest, fFlags, rc);
+ rc = RTPathRename(szSource, szDest, fFlags);
+ }
+ else
+ {
+ VGSvcError("Invalid rename flags: %#x\n", fFlags);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Report result back to host.
+ */
+ int rc2 = VbglR3GuestCtrlMsgReply(pHostCtx, rc);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report renaming status, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for rename operation: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ VGSvcVerbose(5, "Renaming '%s' to '%s' returned rc=%Rrc\n", szSource, szDest, rc);
+ return rc;
+}
+
+
+/**
+ * Handles getting the user's documents directory.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandlePathUserDocuments(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ int rc = VbglR3GuestCtrlPathGetUserDocuments(pHostCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the path and pass it back to the host..
+ */
+ char szPath[RTPATH_MAX];
+ rc = RTPathUserDocuments(szPath, sizeof(szPath));
+#ifdef DEBUG
+ VGSvcVerbose(2, "User documents is '%s', rc=%Rrc\n", szPath, rc);
+#endif
+
+ int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath,
+ RT_SUCCESS(rc) ? (uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report user documents, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for user documents path request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+/**
+ * Handles shutting down / rebooting the guest OS.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandleShutdown(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t fAction;
+ int rc = VbglR3GuestCtrlGetShutdown(pHostCtx, &fAction);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(1, "Host requested to %s system ...\n", (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "reboot" : "shutdown");
+
+ /* Reply first to the host, in order to avoid host hangs when issuing the guest shutdown. */
+ rc = VbglR3GuestCtrlMsgReply(pHostCtx, VINF_SUCCESS);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Failed to reply to shutdown / reboot request, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ int fSystemShutdown = RTSYSTEM_SHUTDOWN_PLANNED;
+
+ /* Translate SHUTDOWN_FLAG_ into RTSYSTEM_SHUTDOWN_ flags. */
+ if (fAction & GUEST_SHUTDOWN_FLAG_REBOOT)
+ fSystemShutdown |= RTSYSTEM_SHUTDOWN_REBOOT;
+ else /* SHUTDOWN_FLAG_POWER_OFF */
+ fSystemShutdown |= RTSYSTEM_SHUTDOWN_POWER_OFF;
+
+ if (fAction & GUEST_SHUTDOWN_FLAG_FORCE)
+ fSystemShutdown |= RTSYSTEM_SHUTDOWN_FORCE;
+
+ rc = RTSystemShutdown(0 /*cMsDelay*/, fSystemShutdown, "VBoxService");
+ if (RT_FAILURE(rc))
+ VGSvcError("%s system failed with %Rrc\n",
+ (fAction & RTSYSTEM_SHUTDOWN_REBOOT) ? "Rebooting" : "Shutting down", rc);
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for shutdown / reboot request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Handles getting the user's home directory.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandlePathUserHome(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ int rc = VbglR3GuestCtrlPathGetUserHome(pHostCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the path and pass it back to the host..
+ */
+ char szPath[RTPATH_MAX];
+ rc = RTPathUserHome(szPath, sizeof(szPath));
+
+#ifdef DEBUG
+ VGSvcVerbose(2, "User home is '%s', rc=%Rrc\n", szPath, rc);
+#endif
+ /* Report back in any case. */
+ int rc2 = VbglR3GuestCtrlMsgReplyEx(pHostCtx, rc, 0 /* Type */, szPath,
+ RT_SUCCESS(rc) ?(uint32_t)strlen(szPath) + 1 /* Include terminating zero */ : 0);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Failed to report user home, rc=%Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for user home directory path request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+/**
+ * Handles starting a guest processes.
+ *
+ * @returns VBox status code.
+ * @param pSession Guest session.
+ * @param pHostCtx Host context.
+ */
+static int vgsvcGstCtrlSessionHandleProcExec(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /* Initialize maximum environment block size -- needed as input
+ * parameter to retrieve the stuff from the host. On output this then
+ * will contain the actual block size. */
+ PVBGLR3GUESTCTRLPROCSTARTUPINFO pStartupInfo;
+ int rc = VbglR3GuestCtrlProcGetStart(pHostCtx, &pStartupInfo);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Request to start process szCmd=%s, fFlags=0x%x, szArgs=%s, szEnv=%s, uTimeout=%RU32\n",
+ pStartupInfo->pszCmd, pStartupInfo->fFlags,
+ pStartupInfo->cArgs ? pStartupInfo->pszArgs : "<None>",
+ pStartupInfo->cEnvVars ? pStartupInfo->pszEnv : "<None>",
+ pStartupInfo->uTimeLimitMS);
+
+ bool fStartAllowed = false; /* Flag indicating whether starting a process is allowed or not. */
+ rc = VGSvcGstCtrlSessionProcessStartAllowed(pSession, &fStartAllowed);
+ if (RT_SUCCESS(rc))
+ {
+ vgsvcGstCtrlSessionCleanupProcesses(pSession);
+
+ if (fStartAllowed)
+ rc = VGSvcGstCtrlProcessStart(pSession, pStartupInfo, pHostCtx->uContextID);
+ else
+ rc = VERR_MAX_PROCS_REACHED; /* Maximum number of processes reached. */
+ }
+
+ /* We're responsible for signaling errors to the host (it will wait for ever otherwise). */
+ if (RT_FAILURE(rc))
+ {
+ VGSvcError("Starting process failed with rc=%Rrc, protocol=%RU32, parameters=%RU32\n",
+ rc, pHostCtx->uProtocol, pHostCtx->uNumParms);
+ int rc2 = VbglR3GuestCtrlProcCbStatus(pHostCtx, 0 /*nil-PID*/, PROC_STS_ERROR, rc, NULL /*pvData*/, 0 /*cbData*/);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Error sending start process status to host, rc=%Rrc\n", rc2);
+ }
+
+ VbglR3GuestCtrlProcStartupInfoFree(pStartupInfo);
+ pStartupInfo = NULL;
+ }
+ else
+ {
+ VGSvcError("Failed to retrieve parameters for process start: %Rrc (cParms=%u)\n", rc, pHostCtx->uNumParms);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Sends stdin input to a specific guest process.
+ *
+ * @returns VBox status code.
+ * @param pSession The session which is in charge.
+ * @param pHostCtx The host context to use.
+ * @param ppvScratchBuf The scratch buffer, we may grow it.
+ * @param pcbScratchBuf The scratch buffer size for retrieving the input
+ * data.
+ */
+static int vgsvcGstCtrlSessionHandleProcInput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the data from the host.
+ */
+ uint32_t uPID;
+ uint32_t fFlags;
+ uint32_t cbInput;
+ int rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput);
+ if ( rc == VERR_BUFFER_OVERFLOW
+ && vgsvcGstCtrlSessionGrowScratchBuf(ppvScratchBuf, pcbScratchBuf, cbInput))
+ rc = VbglR3GuestCtrlProcGetInput(pHostCtx, &uPID, &fFlags, *ppvScratchBuf, *pcbScratchBuf, &cbInput);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags & GUEST_PROC_IN_FLAG_EOF)
+ VGSvcVerbose(4, "Got last process input block for PID=%RU32 (%RU32 bytes) ...\n", uPID, cbInput);
+
+ /*
+ * Locate the process and feed it.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VGSvcGstCtrlProcessHandleInput(pProcess, pHostCtx, RT_BOOL(fFlags & GUEST_PROC_IN_FLAG_EOF),
+ *ppvScratchBuf, RT_MIN(cbInput, *pcbScratchBuf));
+ if (RT_FAILURE(rc))
+ VGSvcError("Error handling input message for PID=%RU32, rc=%Rrc\n", uPID, rc);
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ {
+ VGSvcError("Could not find PID %u for feeding %u bytes to it.\n", uPID, cbInput);
+ rc = VERR_PROCESS_NOT_FOUND;
+ VbglR3GuestCtrlProcCbStatusInput(pHostCtx, uPID, INPUT_STS_ERROR, rc, 0);
+ }
+ }
+ else
+ {
+ VGSvcError("Failed to retrieve parameters for process input: %Rrc (scratch %u bytes)\n", rc, *pcbScratchBuf);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+ VGSvcVerbose(6, "Feeding input to PID=%RU32 resulted in rc=%Rrc\n", uPID, rc);
+ return rc;
+}
+
+
+/**
+ * Gets stdout/stderr output of a specific guest process.
+ *
+ * @returns VBox status code.
+ * @param pSession The session which is in charge.
+ * @param pHostCtx The host context to use.
+ */
+static int vgsvcGstCtrlSessionHandleProcOutput(PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uPID;
+ uint32_t uHandleID;
+ uint32_t fFlags;
+ int rc = VbglR3GuestCtrlProcGetOutput(pHostCtx, &uPID, &uHandleID, &fFlags);
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "Getting output for PID=%RU32, CID=%RU32, uHandleID=%RU32, fFlags=%RU32\n",
+ uPID, pHostCtx->uContextID, uHandleID, fFlags);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the process and hand it the output request.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VGSvcGstCtrlProcessHandleOutput(pProcess, pHostCtx, uHandleID, _64K /* cbToRead */, fFlags);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error getting output for PID=%RU32, rc=%Rrc\n", uPID, rc);
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ {
+ VGSvcError("Could not find PID %u for draining handle %u (%#x).\n", uPID, uHandleID, uHandleID);
+ rc = VERR_PROCESS_NOT_FOUND;
+/** @todo r=bird:
+ *
+ * No way to report status status code for output requests?
+ *
+ */
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for process output request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "Getting output for PID=%RU32 resulted in rc=%Rrc\n", uPID, rc);
+#endif
+ return rc;
+}
+
+
+/**
+ * Tells a guest process to terminate.
+ *
+ * @returns VBox status code.
+ * @param pSession The session which is in charge.
+ * @param pHostCtx The host context to use.
+ */
+static int vgsvcGstCtrlSessionHandleProcTerminate(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uPID;
+ int rc = VbglR3GuestCtrlProcGetTerminate(pHostCtx, &uPID);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the process and terminate it.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VGSvcGstCtrlProcessHandleTerm(pProcess);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error terminating PID=%RU32, rc=%Rrc\n", uPID, rc);
+
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ {
+ VGSvcError("Could not find PID %u for termination.\n", uPID);
+ rc = VERR_PROCESS_NOT_FOUND;
+ }
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for process termination request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+#ifdef DEBUG_andy
+ VGSvcVerbose(4, "Terminating PID=%RU32 resulted in rc=%Rrc\n", uPID, rc);
+#endif
+ return rc;
+}
+
+
+static int vgsvcGstCtrlSessionHandleProcWaitFor(const PVBOXSERVICECTRLSESSION pSession, PVBGLR3GUESTCTRLCMDCTX pHostCtx)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+
+ /*
+ * Retrieve the request.
+ */
+ uint32_t uPID;
+ uint32_t uWaitFlags;
+ uint32_t uTimeoutMS;
+ int rc = VbglR3GuestCtrlProcGetWaitFor(pHostCtx, &uPID, &uWaitFlags, &uTimeoutMS);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the process and the realize that this call makes no sense
+ * since we'll notify the host when a process terminates anyway and
+ * hopefully don't need any additional encouragement.
+ */
+ PVBOXSERVICECTRLPROCESS pProcess = VGSvcGstCtrlSessionRetainProcess(pSession, uPID);
+ if (pProcess)
+ {
+ rc = VERR_NOT_IMPLEMENTED; /** @todo */
+ VGSvcGstCtrlProcessRelease(pProcess);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else
+ {
+ VGSvcError("Error fetching parameters for process wait request: %Rrc\n", rc);
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, rc, UINT32_MAX);
+ }
+ return rc;
+}
+
+
+int VGSvcGstCtrlSessionHandler(PVBOXSERVICECTRLSESSION pSession, uint32_t uMsg, PVBGLR3GUESTCTRLCMDCTX pHostCtx,
+ void **ppvScratchBuf, uint32_t *pcbScratchBuf, volatile bool *pfShutdown)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHostCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(*ppvScratchBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfShutdown, VERR_INVALID_POINTER);
+
+
+ /*
+ * Only anonymous sessions (that is, sessions which run with local
+ * service privileges) or spawned session processes can do certain
+ * operations.
+ */
+ bool const fImpersonated = RT_BOOL(pSession->fFlags & ( VBOXSERVICECTRLSESSION_FLAG_SPAWN
+ | VBOXSERVICECTRLSESSION_FLAG_ANONYMOUS));
+ int rc = VERR_NOT_SUPPORTED; /* Play safe by default. */
+
+ switch (uMsg)
+ {
+ case HOST_MSG_SESSION_CLOSE:
+ /* Shutdown (this spawn). */
+ rc = VGSvcGstCtrlSessionClose(pSession);
+ *pfShutdown = true; /* Shutdown in any case. */
+ break;
+
+ case HOST_MSG_DIR_REMOVE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleDirRemove(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_CMD:
+ rc = vgsvcGstCtrlSessionHandleProcExec(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_SET_INPUT:
+ rc = vgsvcGstCtrlSessionHandleProcInput(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_EXEC_GET_OUTPUT:
+ rc = vgsvcGstCtrlSessionHandleProcOutput(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_TERMINATE:
+ rc = vgsvcGstCtrlSessionHandleProcTerminate(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_EXEC_WAIT_FOR:
+ rc = vgsvcGstCtrlSessionHandleProcWaitFor(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_OPEN:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileOpen(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_CLOSE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileClose(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_READ:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileRead(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_READ_AT:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileReadAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_WRITE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileWrite(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_WRITE_AT:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileWriteAt(pSession, pHostCtx, ppvScratchBuf, pcbScratchBuf);
+ break;
+
+ case HOST_MSG_FILE_SEEK:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileSeek(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_TELL:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileTell(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_FILE_SET_SIZE:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandleFileSetSize(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_PATH_RENAME:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandlePathRename(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_PATH_USER_DOCUMENTS:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandlePathUserDocuments(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_PATH_USER_HOME:
+ if (fImpersonated)
+ rc = vgsvcGstCtrlSessionHandlePathUserHome(pSession, pHostCtx);
+ break;
+
+ case HOST_MSG_SHUTDOWN:
+ rc = vgsvcGstCtrlSessionHandleShutdown(pSession, pHostCtx);
+ break;
+
+ default: /* Not supported, see next code block. */
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc != VERR_NOT_SUPPORTED) /* Note: Reply to host must must be sent by above handler. */
+ VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc);
+ else
+ {
+ /* We must skip and notify host here as best we can... */
+ VGSvcVerbose(1, "Unsupported message (uMsg=%RU32, cParms=%RU32) from host, skipping\n", uMsg, pHostCtx->uNumParms);
+ if (VbglR3GuestCtrlSupportsOptimizations(pHostCtx->uClientID))
+ VbglR3GuestCtrlMsgSkip(pHostCtx->uClientID, VERR_NOT_SUPPORTED, uMsg);
+ else
+ VbglR3GuestCtrlMsgSkipOld(pHostCtx->uClientID);
+ rc = VINF_SUCCESS;
+ }
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Error while handling message (uMsg=%RU32, cParms=%RU32), rc=%Rrc\n", uMsg, pHostCtx->uNumParms, rc);
+
+ return rc;
+}
+
+
+/**
+ * Thread main routine for a spawned guest session process.
+ *
+ * This thread runs in the main executable to control the spawned session process.
+ *
+ * @returns VBox status code.
+ * @param hThreadSelf Thread handle.
+ * @param pvUser Pointer to a VBOXSERVICECTRLSESSIONTHREAD structure.
+ *
+ */
+static DECLCALLBACK(int) vgsvcGstCtrlSessionThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PVBOXSERVICECTRLSESSIONTHREAD pThread = (PVBOXSERVICECTRLSESSIONTHREAD)pvUser;
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+
+ uint32_t const idSession = pThread->pStartupInfo->uSessionID;
+ uint32_t const idClient = g_idControlSvcClient;
+ VGSvcVerbose(3, "Session ID=%RU32 thread running\n", idSession);
+
+ /* Let caller know that we're done initializing, regardless of the result. */
+ int rc2 = RTThreadUserSignal(hThreadSelf);
+ AssertRC(rc2);
+
+ /*
+ * Wait for the child process to stop or the shutdown flag to be signalled.
+ */
+ RTPROCSTATUS ProcessStatus = { 0, RTPROCEXITREASON_NORMAL };
+ bool fProcessAlive = true;
+ bool fSessionCancelled = VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient);
+ uint32_t cMsShutdownTimeout = 30 * 1000; /** @todo Make this configurable. Later. */
+ uint64_t msShutdownStart = 0;
+ uint64_t const msStart = RTTimeMilliTS();
+ size_t offSecretKey = 0;
+ int rcWait;
+ for (;;)
+ {
+ /* Secret key feeding. */
+ if (offSecretKey < sizeof(pThread->abKey))
+ {
+ size_t cbWritten = 0;
+ rc2 = RTPipeWrite(pThread->hKeyPipe, &pThread->abKey[offSecretKey], sizeof(pThread->abKey) - offSecretKey, &cbWritten);
+ if (RT_SUCCESS(rc2))
+ offSecretKey += cbWritten;
+ }
+
+ /* Poll child process status. */
+ rcWait = RTProcWaitNoResume(pThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &ProcessStatus);
+ if ( rcWait == VINF_SUCCESS
+ || rcWait == VERR_PROCESS_NOT_FOUND)
+ {
+ fProcessAlive = false;
+ break;
+ }
+ AssertMsgBreak(rcWait == VERR_PROCESS_RUNNING || rcWait == VERR_INTERRUPTED,
+ ("Got unexpected rc=%Rrc while waiting for session process termination\n", rcWait));
+
+ /* Shutting down? */
+ if (ASMAtomicReadBool(&pThread->fShutdown))
+ {
+ if (!msShutdownStart)
+ {
+ VGSvcVerbose(3, "Notifying guest session process (PID=%RU32, session ID=%RU32) ...\n",
+ pThread->hProcess, idSession);
+
+ VBGLR3GUESTCTRLCMDCTX hostCtx =
+ {
+ /* .idClient = */ idClient,
+ /* .idContext = */ VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession),
+ /* .uProtocol = */ pThread->pStartupInfo->uProtocol,
+ /* .cParams = */ 2
+ };
+ rc2 = VbglR3GuestCtrlSessionClose(&hostCtx, 0 /* fFlags */);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Unable to notify guest session process (PID=%RU32, session ID=%RU32), rc=%Rrc\n",
+ pThread->hProcess, idSession, rc2);
+
+ if (rc2 == VERR_NOT_SUPPORTED)
+ {
+ /* Terminate guest session process in case it's not supported by a too old host. */
+ rc2 = RTProcTerminate(pThread->hProcess);
+ VGSvcVerbose(3, "Terminating guest session process (PID=%RU32) ended with rc=%Rrc\n",
+ pThread->hProcess, rc2);
+ }
+ break;
+ }
+
+ VGSvcVerbose(3, "Guest session ID=%RU32 thread was asked to terminate, waiting for session process to exit (%RU32 ms timeout) ...\n",
+ idSession, cMsShutdownTimeout);
+ msShutdownStart = RTTimeMilliTS();
+ continue; /* Don't waste time on waiting. */
+ }
+ if (RTTimeMilliTS() - msShutdownStart > cMsShutdownTimeout)
+ {
+ VGSvcVerbose(3, "Guest session ID=%RU32 process did not shut down within time\n", idSession);
+ break;
+ }
+ }
+
+ /* Cancel the prepared session stuff after 30 seconds. */
+ if ( !fSessionCancelled
+ && RTTimeMilliTS() - msStart >= 30000)
+ {
+ VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession);
+ fSessionCancelled = true;
+ }
+
+/** @todo r=bird: This 100ms sleep is _extremely_ sucky! */
+ RTThreadSleep(100); /* Wait a bit. */
+ }
+
+ if (!fSessionCancelled)
+ VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, idSession);
+
+ if (!fProcessAlive)
+ {
+ VGSvcVerbose(2, "Guest session process (ID=%RU32) terminated with rc=%Rrc, reason=%d, status=%d\n",
+ idSession, rcWait, ProcessStatus.enmReason, ProcessStatus.iStatus);
+ if (ProcessStatus.iStatus == RTEXITCODE_INIT)
+ {
+ VGSvcError("Guest session process (ID=%RU32) failed to initialize. Here some hints:\n", idSession);
+ VGSvcError("- Is logging enabled and the output directory is read-only by the guest session user?\n");
+ /** @todo Add more here. */
+ }
+ }
+
+ uint32_t uSessionStatus = GUEST_SESSION_NOTIFYTYPE_UNDEFINED;
+ int32_t iSessionResult = VINF_SUCCESS;
+
+ if (fProcessAlive)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ if (i)
+ RTThreadSleep(3000);
+
+ VGSvcVerbose(2, "Guest session ID=%RU32 process still alive, killing attempt %d/3\n", idSession, i + 1);
+
+ rc2 = RTProcTerminate(pThread->hProcess);
+ if (RT_SUCCESS(rc2))
+ break;
+ }
+
+ VGSvcVerbose(2, "Guest session ID=%RU32 process termination resulted in rc=%Rrc\n", idSession, rc2);
+ uSessionStatus = RT_SUCCESS(rc2) ? GUEST_SESSION_NOTIFYTYPE_TOK : GUEST_SESSION_NOTIFYTYPE_TOA;
+ }
+ else if (RT_SUCCESS(rcWait))
+ {
+ switch (ProcessStatus.enmReason)
+ {
+ case RTPROCEXITREASON_NORMAL:
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN;
+ iSessionResult = ProcessStatus.iStatus; /* Report back the session's exit code. */
+ break;
+
+ case RTPROCEXITREASON_ABEND:
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA;
+ /* iSessionResult is undefined (0). */
+ break;
+
+ case RTPROCEXITREASON_SIGNAL:
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TES;
+ iSessionResult = ProcessStatus.iStatus; /* Report back the signal number. */
+ break;
+
+ default:
+ AssertMsgFailed(("Unhandled process termination reason (%d)\n", ProcessStatus.enmReason));
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEA;
+ break;
+ }
+ }
+ else
+ {
+ /* If we didn't find the guest process anymore, just assume it terminated normally. */
+ uSessionStatus = GUEST_SESSION_NOTIFYTYPE_TEN;
+ }
+
+ /* Make sure to set stopped state before we let the host know. */
+ ASMAtomicWriteBool(&pThread->fStopped, true);
+
+ /* Report final status, regardless if we failed to wait above, so that the host knows what's going on. */
+ VGSvcVerbose(3, "Reporting final status %RU32 of session ID=%RU32\n", uSessionStatus, idSession);
+ Assert(uSessionStatus != GUEST_SESSION_NOTIFYTYPE_UNDEFINED);
+
+ VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession),
+ 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ };
+ rc2 = VbglR3GuestCtrlSessionNotify(&ctx, uSessionStatus, iSessionResult);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Reporting final status of session ID=%RU32 failed with rc=%Rrc\n", idSession, rc2);
+
+ VGSvcVerbose(3, "Thread for session ID=%RU32 ended with sessionStatus=%#x (%RU32), sessionRc=%#x (%Rrc)\n",
+ idSession, uSessionStatus, uSessionStatus, iSessionResult, iSessionResult);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads the secret key the parent VBoxService instance passed us and pass it
+ * along as a authentication token to the host service.
+ *
+ * For older hosts, this sets up the message filtering.
+ *
+ * @returns VBox status code.
+ * @param idClient The HGCM client ID.
+ * @param idSession The session ID.
+ */
+static int vgsvcGstCtrlSessionReadKeyAndAccept(uint32_t idClient, uint32_t idSession)
+{
+ /*
+ * Read it.
+ */
+ RTHANDLE Handle;
+ int rc = RTHandleGetStandard(RTHANDLESTD_INPUT, true /*fLeaveOpen*/, &Handle);
+ if (RT_SUCCESS(rc))
+ {
+ if (Handle.enmType == RTHANDLETYPE_PIPE)
+ {
+ uint8_t abSecretKey[RT_SIZEOFMEMB(VBOXSERVICECTRLSESSIONTHREAD, abKey)];
+ rc = RTPipeReadBlocking(Handle.u.hPipe, abSecretKey, sizeof(abSecretKey), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Got secret key from standard input.\n");
+
+ /*
+ * Do the accepting, if appropriate.
+ */
+ if (g_fControlSupportsOptimizations)
+ {
+ rc = VbglR3GuestCtrlSessionAccept(idClient, idSession, abSecretKey, sizeof(abSecretKey));
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Session %u accepted (client ID %u)\n", idClient, idSession);
+ else
+ VGSvcError("Failed to accept session %u (client ID %u): %Rrc\n", idClient, idSession, rc);
+ }
+ else
+ {
+ /* For legacy hosts, we do the filtering thingy. */
+ rc = VbglR3GuestCtrlMsgFilterSet(idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(idSession),
+ VBOX_GUESTCTRL_FILTER_BY_SESSION(idSession), 0);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Session %u filtering successfully enabled\n", idSession);
+ else
+ VGSvcError("Failed to set session filter: %Rrc\n", rc);
+ }
+ }
+ else
+ VGSvcError("Error reading secret key from standard input: %Rrc\n", rc);
+ }
+ else
+ {
+ VGSvcError("Standard input is not a pipe!\n");
+ rc = VERR_INVALID_HANDLE;
+ }
+ RTHandleClose(&Handle);
+ }
+ else
+ VGSvcError("RTHandleGetStandard failed on standard input: %Rrc\n", rc);
+ return rc;
+}
+
+/**
+ * Invalidates a guest session by updating all it's internal parameters like host features and stuff.
+ *
+ * @param pSession Session to invalidate.
+ * @param idClient Client ID to use.
+ */
+static void vgsvcGstCtrlSessionInvalidate(PVBOXSERVICECTRLSESSION pSession, uint32_t idClient)
+{
+ RT_NOREF(pSession);
+
+ VGSvcVerbose(1, "Invalidating session %RU32 (client ID=%RU32)\n", idClient, pSession->StartupInfo.uSessionID);
+
+ int rc2 = VbglR3GuestCtrlQueryFeatures(idClient, &g_fControlHostFeatures0);
+ if (RT_SUCCESS(rc2)) /* Querying host features is not fatal -- do not use rc here. */
+ {
+ VGSvcVerbose(1, "g_fControlHostFeatures0=%#x\n", g_fControlHostFeatures0);
+ }
+ else
+ VGSvcVerbose(1, "Querying host features failed with %Rrc\n", rc2);
+}
+
+/**
+ * Main message handler for the guest control session process.
+ *
+ * @returns exit code.
+ * @param pSession Pointer to g_Session.
+ * @thread main.
+ */
+static RTEXITCODE vgsvcGstCtrlSessionSpawnWorker(PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, RTEXITCODE_FAILURE);
+ VGSvcVerbose(0, "Hi, this is guest session ID=%RU32\n", pSession->StartupInfo.uSessionID);
+
+ /*
+ * Connect to the host service.
+ */
+ uint32_t idClient;
+ int rc = VbglR3GuestCtrlConnect(&idClient);
+ if (RT_FAILURE(rc))
+ return VGSvcError("Error connecting to guest control service, rc=%Rrc\n", rc);
+ g_fControlSupportsOptimizations = VbglR3GuestCtrlSupportsOptimizations(idClient);
+ g_idControlSvcClient = idClient;
+
+ VGSvcVerbose(1, "Using client ID=%RU32\n", idClient);
+
+ vgsvcGstCtrlSessionInvalidate(pSession, idClient);
+
+ rc = vgsvcGstCtrlSessionReadKeyAndAccept(idClient, pSession->StartupInfo.uSessionID);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Report started status.
+ * If session status cannot be posted to the host for some reason, bail out.
+ */
+ VBGLR3GUESTCTRLCMDCTX ctx = { idClient, VBOX_GUESTCTRL_CONTEXTID_MAKE_SESSION(pSession->StartupInfo.uSessionID),
+ 0 /* uProtocol, unused */, 0 /* uNumParms, unused */ };
+ rc = VbglR3GuestCtrlSessionNotify(&ctx, GUEST_SESSION_NOTIFYTYPE_STARTED, VINF_SUCCESS);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate a scratch buffer for messages which also send payload data with them.
+ * This buffer may grow if the host sends us larger chunks of data.
+ */
+ uint32_t cbScratchBuf = _64K;
+ void *pvScratchBuf = RTMemAlloc(cbScratchBuf);
+ if (pvScratchBuf)
+ {
+ int cFailedMsgPeeks = 0;
+
+ /*
+ * Message processing loop.
+ */
+ VBGLR3GUESTCTRLCMDCTX CtxHost = { idClient, 0 /* Context ID */, pSession->StartupInfo.uProtocol, 0 };
+ for (;;)
+ {
+ VGSvcVerbose(3, "Waiting for host msg ...\n");
+ uint32_t uMsg = 0;
+ rc = VbglR3GuestCtrlMsgPeekWait(idClient, &uMsg, &CtxHost.uNumParms, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(4, "Msg=%RU32 (%RU32 parms) retrieved (%Rrc)\n", uMsg, CtxHost.uNumParms, rc);
+
+ /*
+ * Pass it on to the session handler.
+ * Note! Only when handling HOST_SESSION_CLOSE is the rc used.
+ */
+ bool fShutdown = false;
+ rc = VGSvcGstCtrlSessionHandler(pSession, uMsg, &CtxHost, &pvScratchBuf, &cbScratchBuf, &fShutdown);
+ if (fShutdown)
+ break;
+
+ cFailedMsgPeeks = 0;
+
+ /* Let others run (guests are often single CPU) ... */
+ RTThreadYield();
+ }
+ /*
+ * Handle restore notification from host. All the context IDs (sessions,
+ * files, proceses, etc) are invalidated by a VM restore and must be closed.
+ */
+ else if (rc == VERR_VM_RESTORED)
+ {
+ VGSvcVerbose(1, "The VM session ID changed (i.e. restored), closing stale session %RU32\n",
+ pSession->StartupInfo.uSessionID);
+
+ /* We currently don't serialize guest sessions, guest processes and other guest control objects
+ * within saved states. So just close this session and report success to the parent process.
+ *
+ * Note: Not notifying the host here is intentional, as it wouldn't have any information
+ * about what to do with it.
+ */
+ rc = VINF_SUCCESS; /* Report success as exit code. */
+ break;
+ }
+ else
+ {
+ VGSvcVerbose(1, "Getting host message failed with %Rrc\n", rc);
+
+ if (cFailedMsgPeeks++ == 3)
+ break;
+
+ RTThreadSleep(3 * RT_MS_1SEC);
+
+ /** @todo Shouldn't we have a plan for handling connection loss and such? */
+ }
+ }
+
+ /*
+ * Shutdown.
+ */
+ RTMemFree(pvScratchBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ VGSvcVerbose(0, "Session %RU32 ended\n", pSession->StartupInfo.uSessionID);
+ }
+ else
+ VGSvcError("Reporting session ID=%RU32 started status failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc);
+ }
+ else
+ VGSvcError("Setting message filterAdd=0x%x failed with rc=%Rrc\n", pSession->StartupInfo.uSessionID, rc);
+
+ VGSvcVerbose(3, "Disconnecting client ID=%RU32 ...\n", idClient);
+ VbglR3GuestCtrlDisconnect(idClient);
+ g_idControlSvcClient = 0;
+
+ VGSvcVerbose(3, "Session worker returned with rc=%Rrc\n", rc);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Finds a (formerly) started guest process given by its PID and increases its
+ * reference count.
+ *
+ * Must be decreased by the caller with VGSvcGstCtrlProcessRelease().
+ *
+ * @returns Guest process if found, otherwise NULL.
+ * @param pSession Pointer to guest session where to search process in.
+ * @param uPID PID to search for.
+ *
+ * @note This does *not lock the process!
+ */
+PVBOXSERVICECTRLPROCESS VGSvcGstCtrlSessionRetainProcess(PVBOXSERVICECTRLSESSION pSession, uint32_t uPID)
+{
+ AssertPtrReturn(pSession, NULL);
+
+ PVBOXSERVICECTRLPROCESS pProcess = NULL;
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICECTRLPROCESS pCurProcess;
+ RTListForEach(&pSession->lstProcesses, pCurProcess, VBOXSERVICECTRLPROCESS, Node)
+ {
+ if (pCurProcess->uPID == uPID)
+ {
+ rc = RTCritSectEnter(&pCurProcess->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pCurProcess->cRefs++;
+ rc = RTCritSectLeave(&pCurProcess->CritSect);
+ AssertRC(rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ pProcess = pCurProcess;
+ break;
+ }
+ }
+
+ rc = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc);
+ }
+
+ return pProcess;
+}
+
+
+int VGSvcGstCtrlSessionClose(PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ VGSvcVerbose(0, "Session %RU32 is about to close ...\n", pSession->StartupInfo.uSessionID);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Close all guest processes.
+ */
+ VGSvcVerbose(0, "Stopping all guest processes ...\n");
+
+ /* Signal all guest processes in the active list that we want to shutdown. */
+ PVBOXSERVICECTRLPROCESS pProcess;
+ RTListForEach(&pSession->lstProcesses, pProcess, VBOXSERVICECTRLPROCESS, Node)
+ VGSvcGstCtrlProcessStop(pProcess);
+
+ VGSvcVerbose(1, "%RU32 guest processes were signalled to stop\n", pSession->cProcesses);
+
+ /* Wait for all active threads to shutdown and destroy the active thread list. */
+ PVBOXSERVICECTRLPROCESS pProcessNext;
+ RTListForEachSafe(&pSession->lstProcesses, pProcess, pProcessNext, VBOXSERVICECTRLPROCESS, Node)
+ {
+ int rc3 = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc3);
+
+ int rc2 = VGSvcGstCtrlProcessWait(pProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */);
+
+ rc3 = RTCritSectEnter(&pSession->CritSect);
+ AssertRC(rc3);
+
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcGstCtrlProcessFree(pProcess);
+ pProcess = NULL;
+ }
+ }
+ }
+
+ AssertMsg(pSession->cProcesses == 0,
+ ("Session process list still contains %RU32 when it should not\n", pSession->cProcesses));
+ AssertMsg(RTListIsEmpty(&pSession->lstProcesses),
+ ("Session process list is not empty when it should\n"));
+
+ /*
+ * Close all left guest files.
+ */
+ VGSvcVerbose(0, "Closing all guest files ...\n");
+
+ PVBOXSERVICECTRLFILE pFile, pFileNext;
+ RTListForEachSafe(&pSession->lstFiles, pFile, pFileNext, VBOXSERVICECTRLFILE, Node)
+ {
+ int rc2 = vgsvcGstCtrlSessionFileFree(pFile);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Unable to close file '%s'; rc=%Rrc\n", pFile->pszName, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+
+ pFile = NULL; /* To make it obvious. */
+ }
+
+ AssertMsg(pSession->cFiles == 0,
+ ("Session file list still contains %RU32 when it should not\n", pSession->cFiles));
+ AssertMsg(RTListIsEmpty(&pSession->lstFiles),
+ ("Session file list is not empty when it should\n"));
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+int VGSvcGstCtrlSessionDestroy(PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ int rc = VGSvcGstCtrlSessionClose(pSession);
+
+ /* Destroy critical section. */
+ RTCritSectDelete(&pSession->CritSect);
+
+ return rc;
+}
+
+
+int VGSvcGstCtrlSessionInit(PVBOXSERVICECTRLSESSION pSession, uint32_t fFlags)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ RTListInit(&pSession->lstProcesses);
+ RTListInit(&pSession->lstFiles);
+
+ pSession->cProcesses = 0;
+ pSession->cFiles = 0;
+
+ pSession->fFlags = fFlags;
+
+ /* Init critical section for protecting the thread lists. */
+ int rc = RTCritSectInit(&pSession->CritSect);
+ AssertRC(rc);
+
+ return rc;
+}
+
+
+/**
+ * Adds a guest process to a session's process list.
+ *
+ * @return VBox status code.
+ * @param pSession Guest session to add process to.
+ * @param pProcess Guest process to add.
+ */
+int VGSvcGstCtrlSessionProcessAdd(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "Adding process (PID %RU32) to session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID);
+
+ /* Add process to session list. */
+ RTListAppend(&pSession->lstProcesses, &pProcess->Node);
+
+ pSession->cProcesses++;
+ VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n",
+ pSession->StartupInfo.uSessionID, pSession->cProcesses);
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Removes a guest process from a session's process list.
+ * Internal version, does not do locking.
+ *
+ * @return VBox status code.
+ * @param pSession Guest session to remove process from.
+ * @param pProcess Guest process to remove.
+ */
+static int vgsvcGstCtrlSessionProcessRemoveInternal(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess)
+{
+ VGSvcVerbose(3, "Removing process (PID %RU32) from session ID=%RU32\n", pProcess->uPID, pSession->StartupInfo.uSessionID);
+ AssertReturn(pProcess->cRefs == 0, VERR_WRONG_ORDER);
+
+ RTListNodeRemove(&pProcess->Node);
+
+ AssertReturn(pSession->cProcesses, VERR_WRONG_ORDER);
+ pSession->cProcesses--;
+ VGSvcVerbose(3, "Now session ID=%RU32 has %RU32 processes total\n",
+ pSession->StartupInfo.uSessionID, pSession->cProcesses);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Removes a guest process from a session's process list.
+ *
+ * @return VBox status code.
+ * @param pSession Guest session to remove process from.
+ * @param pProcess Guest process to remove.
+ */
+int VGSvcGstCtrlSessionProcessRemove(PVBOXSERVICECTRLSESSION pSession, PVBOXSERVICECTRLPROCESS pProcess)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProcess, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgsvcGstCtrlSessionProcessRemoveInternal(pSession, pProcess);
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Determines whether starting a new guest process according to the
+ * maximum number of concurrent guest processes defined is allowed or not.
+ *
+ * @return VBox status code.
+ * @param pSession The guest session.
+ * @param pfAllowed \c True if starting (another) guest process
+ * is allowed, \c false if not.
+ */
+int VGSvcGstCtrlSessionProcessStartAllowed(const PVBOXSERVICECTRLSESSION pSession, bool *pfAllowed)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfAllowed, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectEnter(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check if we're respecting our memory policy by checking
+ * how many guest processes are started and served already.
+ */
+ bool fLimitReached = false;
+ if (pSession->uProcsMaxKept) /* If we allow unlimited processes (=0), take a shortcut. */
+ {
+ VGSvcVerbose(3, "Maximum kept guest processes set to %RU32, acurrent=%RU32\n",
+ pSession->uProcsMaxKept, pSession->cProcesses);
+
+ int32_t iProcsLeft = (pSession->uProcsMaxKept - pSession->cProcesses - 1);
+ if (iProcsLeft < 0)
+ {
+ VGSvcVerbose(3, "Maximum running guest processes reached (%RU32)\n", pSession->uProcsMaxKept);
+ fLimitReached = true;
+ }
+ }
+
+ *pfAllowed = !fLimitReached;
+
+ int rc2 = RTCritSectLeave(&pSession->CritSect);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Cleans up stopped and no longer used processes.
+ *
+ * This will free and remove processes from the session's process list.
+ *
+ * @returns VBox status code.
+ * @param pSession Session to clean up processes for.
+ */
+static int vgsvcGstCtrlSessionCleanupProcesses(const PVBOXSERVICECTRLSESSION pSession)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+
+ VGSvcVerbose(3, "Cleaning up stopped processes for session %RU32 ...\n", pSession->StartupInfo.uSessionID);
+
+ int rc2 = RTCritSectEnter(&pSession->CritSect);
+ AssertRC(rc2);
+
+ int rc = VINF_SUCCESS;
+
+ PVBOXSERVICECTRLPROCESS pCurProcess, pNextProcess;
+ RTListForEachSafe(&pSession->lstProcesses, pCurProcess, pNextProcess, VBOXSERVICECTRLPROCESS, Node)
+ {
+ if (ASMAtomicReadBool(&pCurProcess->fStopped))
+ {
+ rc2 = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc2);
+
+ rc = VGSvcGstCtrlProcessWait(pCurProcess, 30 * 1000 /* Wait 30 seconds max. */, NULL /* rc */);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcGstCtrlSessionProcessRemove(pSession, pCurProcess);
+ VGSvcGstCtrlProcessFree(pCurProcess);
+ }
+
+ rc2 = RTCritSectEnter(&pSession->CritSect);
+ AssertRC(rc2);
+
+ /* If failed, try next time we're being called. */
+ }
+ }
+
+ rc2 = RTCritSectLeave(&pSession->CritSect);
+ AssertRC(rc2);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Cleaning up stopped processes for session %RU32 failed with %Rrc\n", pSession->StartupInfo.uSessionID, rc);
+
+ return rc;
+}
+
+
+/**
+ * Creates the process for a guest session.
+ *
+ * @return VBox status code.
+ * @param pSessionStartupInfo Session startup info.
+ * @param pSessionThread The session thread under construction.
+ * @param uCtrlSessionThread The session thread debug ordinal.
+ */
+static int vgsvcVGSvcGstCtrlSessionThreadCreateProcess(const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo,
+ PVBOXSERVICECTRLSESSIONTHREAD pSessionThread, uint32_t uCtrlSessionThread)
+{
+ RT_NOREF(uCtrlSessionThread);
+
+ /*
+ * Is this an anonymous session? Anonymous sessions run with the same
+ * privileges as the main VBoxService executable.
+ */
+ bool const fAnonymous = pSessionThread->pStartupInfo->pszUser
+ && pSessionThread->pStartupInfo->pszUser[0] == '\0';
+ if (fAnonymous)
+ {
+ Assert(!strlen(pSessionThread->pStartupInfo->pszPassword));
+ Assert(!strlen(pSessionThread->pStartupInfo->pszDomain));
+
+ VGSvcVerbose(3, "New anonymous guest session ID=%RU32 created, fFlags=%x, using protocol %RU32\n",
+ pSessionStartupInfo->uSessionID,
+ pSessionStartupInfo->fFlags,
+ pSessionStartupInfo->uProtocol);
+ }
+ else
+ {
+ VGSvcVerbose(3, "Spawning new guest session ID=%RU32, szUser=%s, szPassword=%s, szDomain=%s, fFlags=%x, using protocol %RU32\n",
+ pSessionStartupInfo->uSessionID,
+ pSessionStartupInfo->pszUser,
+#ifdef DEBUG
+ pSessionStartupInfo->pszPassword,
+#else
+ "XXX", /* Never show passwords in release mode. */
+#endif
+ pSessionStartupInfo->pszDomain,
+ pSessionStartupInfo->fFlags,
+ pSessionStartupInfo->uProtocol);
+ }
+
+ /*
+ * Spawn a child process for doing the actual session handling.
+ * Start by assembling the argument list.
+ */
+ char szExeName[RTPATH_MAX];
+ char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName));
+ AssertPtrReturn(pszExeName, VERR_FILENAME_TOO_LONG);
+
+ char szParmSessionID[32];
+ RTStrPrintf(szParmSessionID, sizeof(szParmSessionID), "--session-id=%RU32", pSessionThread->pStartupInfo->uSessionID);
+
+ char szParmSessionProto[32];
+ RTStrPrintf(szParmSessionProto, sizeof(szParmSessionProto), "--session-proto=%RU32",
+ pSessionThread->pStartupInfo->uProtocol);
+#ifdef DEBUG
+ char szParmThreadId[32];
+ RTStrPrintf(szParmThreadId, sizeof(szParmThreadId), "--thread-id=%RU32", uCtrlSessionThread);
+#endif
+ unsigned idxArg = 0; /* Next index in argument vector. */
+ char const *apszArgs[24];
+
+ apszArgs[idxArg++] = pszExeName;
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+ apszArgs[idxArg++] = VBOXSERVICE_ARG1_UTF8_ARGV; Assert(idxArg == 2);
+#endif
+ apszArgs[idxArg++] = "guestsession";
+ apszArgs[idxArg++] = szParmSessionID;
+ apszArgs[idxArg++] = szParmSessionProto;
+#ifdef DEBUG
+ apszArgs[idxArg++] = szParmThreadId;
+#endif
+ if (!fAnonymous) /* Do we need to pass a user name? */
+ {
+ apszArgs[idxArg++] = "--user";
+ apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszUser;
+
+ if (strlen(pSessionThread->pStartupInfo->pszDomain))
+ {
+ apszArgs[idxArg++] = "--domain";
+ apszArgs[idxArg++] = pSessionThread->pStartupInfo->pszDomain;
+ }
+ }
+
+ /* Add same verbose flags as parent process. */
+ char szParmVerbose[32];
+ if (g_cVerbosity > 0)
+ {
+ unsigned cVs = RT_MIN(g_cVerbosity, RT_ELEMENTS(szParmVerbose) - 2);
+ szParmVerbose[0] = '-';
+ memset(&szParmVerbose[1], 'v', cVs);
+ szParmVerbose[1 + cVs] = '\0';
+ apszArgs[idxArg++] = szParmVerbose;
+ }
+
+ /* Add log file handling. Each session will have an own
+ * log file, naming based on the parent log file. */
+ char szParmLogFile[sizeof(g_szLogFile) + 128];
+ if (g_szLogFile[0])
+ {
+ const char *pszSuffix = RTPathSuffix(g_szLogFile);
+ if (!pszSuffix)
+ pszSuffix = strchr(g_szLogFile, '\0');
+ size_t cchBase = pszSuffix - g_szLogFile;
+
+ RTTIMESPEC Now;
+ RTTimeNow(&Now);
+ char szTime[64];
+ RTTimeSpecToString(&Now, szTime, sizeof(szTime));
+
+ /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */
+ static const RTUNICP s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+ ssize_t cReplaced = RTStrPurgeComplementSet(szTime, s_uszValidRangePairs, '_' /* chReplacement */);
+ AssertReturn(cReplaced, VERR_INVALID_UTF8_ENCODING);
+
+#ifndef DEBUG
+ RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%s-%s%s",
+ cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, pSessionStartupInfo->pszUser, szTime, pszSuffix);
+#else
+ RTStrPrintf(szParmLogFile, sizeof(szParmLogFile), "%.*s-%RU32-%RU32-%s-%s%s",
+ cchBase, g_szLogFile, pSessionStartupInfo->uSessionID, uCtrlSessionThread,
+ pSessionStartupInfo->pszUser, szTime, pszSuffix);
+#endif
+ apszArgs[idxArg++] = "--logfile";
+ apszArgs[idxArg++] = szParmLogFile;
+ }
+
+#ifdef DEBUG
+ if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT)
+ apszArgs[idxArg++] = "--dump-stdout";
+ if (g_Session.fFlags & VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR)
+ apszArgs[idxArg++] = "--dump-stderr";
+#endif
+ apszArgs[idxArg] = NULL;
+ Assert(idxArg < RT_ELEMENTS(apszArgs));
+
+ if (g_cVerbosity > 3)
+ {
+ VGSvcVerbose(4, "Spawning parameters:\n");
+ for (idxArg = 0; apszArgs[idxArg]; idxArg++)
+ VGSvcVerbose(4, " %s\n", apszArgs[idxArg]);
+ }
+
+ /*
+ * Flags.
+ */
+ uint32_t const fProcCreate = RTPROC_FLAGS_PROFILE
+#ifdef RT_OS_WINDOWS
+ | RTPROC_FLAGS_SERVICE
+ | RTPROC_FLAGS_HIDDEN
+#endif
+ | VBOXSERVICE_PROC_F_UTF8_ARGV;
+
+ /*
+ * Configure standard handles.
+ */
+ RTHANDLE hStdIn;
+ int rc = RTPipeCreate(&hStdIn.u.hPipe, &pSessionThread->hKeyPipe, RTPIPE_C_INHERIT_READ);
+ if (RT_SUCCESS(rc))
+ {
+ hStdIn.enmType = RTHANDLETYPE_PIPE;
+
+ RTHANDLE hStdOutAndErr;
+ rc = RTFileOpenBitBucket(&hStdOutAndErr.u.hFile, RTFILE_O_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ hStdOutAndErr.enmType = RTHANDLETYPE_FILE;
+
+ /*
+ * Windows: If a domain name is given, construct an UPN (User Principle Name)
+ * with the domain name built-in, e.g. "joedoe@example.com".
+ */
+ const char *pszUser = pSessionThread->pStartupInfo->pszUser;
+#ifdef RT_OS_WINDOWS
+ char *pszUserUPN = NULL;
+ if (pSessionThread->pStartupInfo->pszDomain[0])
+ {
+ int cchbUserUPN = RTStrAPrintf(&pszUserUPN, "%s@%s",
+ pSessionThread->pStartupInfo->pszUser,
+ pSessionThread->pStartupInfo->pszDomain);
+ if (cchbUserUPN > 0)
+ {
+ pszUser = pszUserUPN;
+ VGSvcVerbose(3, "Using UPN: %s\n", pszUserUPN);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+#endif
+ {
+ /*
+ * Finally, create the process.
+ */
+ rc = RTProcCreateEx(pszExeName, apszArgs, RTENV_DEFAULT, fProcCreate,
+ &hStdIn, &hStdOutAndErr, &hStdOutAndErr,
+ !fAnonymous ? pszUser : NULL,
+ !fAnonymous ? pSessionThread->pStartupInfo->pszPassword : NULL,
+ NULL /*pvExtraData*/,
+ &pSessionThread->hProcess);
+ }
+#ifdef RT_OS_WINDOWS
+ RTStrFree(pszUserUPN);
+#endif
+ RTFileClose(hStdOutAndErr.u.hFile);
+ }
+
+ RTPipeClose(hStdIn.u.hPipe);
+ }
+ return rc;
+}
+
+
+/**
+ * Creates a guest session.
+ *
+ * This will spawn a new VBoxService.exe instance under behalf of the given user
+ * which then will act as a session host. On successful open, the session will
+ * be added to the given session thread list.
+ *
+ * @return VBox status code.
+ * @param pList Which list to use to store the session thread in.
+ * @param pSessionStartupInfo Session startup info.
+ * @param ppSessionThread Returns newly created session thread on success.
+ * Optional.
+ */
+int VGSvcGstCtrlSessionThreadCreate(PRTLISTANCHOR pList, const PVBGLR3GUESTCTRLSESSIONSTARTUPINFO pSessionStartupInfo,
+ PVBOXSERVICECTRLSESSIONTHREAD *ppSessionThread)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSessionStartupInfo, VERR_INVALID_POINTER);
+ /* ppSessionThread is optional. */
+
+#ifdef VBOX_STRICT
+ /* Check for existing session in debug mode. Should never happen because of
+ * Main consistency. */
+ PVBOXSERVICECTRLSESSIONTHREAD pSessionCur;
+ RTListForEach(pList, pSessionCur, VBOXSERVICECTRLSESSIONTHREAD, Node)
+ {
+ AssertMsgReturn( pSessionCur->fStopped == true
+ || pSessionCur->pStartupInfo->uSessionID != pSessionStartupInfo->uSessionID,
+ ("Guest session thread ID=%RU32 already exists (fStopped=%RTbool)\n",
+ pSessionCur->pStartupInfo->uSessionID, pSessionCur->fStopped), VERR_ALREADY_EXISTS);
+ }
+#endif
+
+ /* Static counter to help tracking session thread <-> process relations. */
+ static uint32_t s_uCtrlSessionThread = 0;
+
+ /*
+ * Allocate and initialize the session thread structure.
+ */
+ int rc;
+ PVBOXSERVICECTRLSESSIONTHREAD pSessionThread = (PVBOXSERVICECTRLSESSIONTHREAD)RTMemAllocZ(sizeof(*pSessionThread));
+ if (pSessionThread)
+ {
+ //pSessionThread->fShutdown = false;
+ //pSessionThread->fStarted = false;
+ //pSessionThread->fStopped = false;
+ pSessionThread->hKeyPipe = NIL_RTPIPE;
+ pSessionThread->Thread = NIL_RTTHREAD;
+ pSessionThread->hProcess = NIL_RTPROCESS;
+
+ /* Duplicate startup info. */
+ pSessionThread->pStartupInfo = VbglR3GuestCtrlSessionStartupInfoDup(pSessionStartupInfo);
+ AssertPtrReturn(pSessionThread->pStartupInfo, VERR_NO_MEMORY);
+
+ /* Generate the secret key. */
+ RTRandBytes(pSessionThread->abKey, sizeof(pSessionThread->abKey));
+
+ rc = RTCritSectInit(&pSessionThread->CritSect);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Give the session key to the host so it can validate the client.
+ */
+ if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient))
+ {
+ for (uint32_t i = 0; i < 10; i++)
+ {
+ rc = VbglR3GuestCtrlSessionPrepare(g_idControlSvcClient, pSessionStartupInfo->uSessionID,
+ pSessionThread->abKey, sizeof(pSessionThread->abKey));
+ if (rc != VERR_OUT_OF_RESOURCES)
+ break;
+ RTThreadSleep(100);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ s_uCtrlSessionThread++;
+
+ /*
+ * Start the session child process.
+ */
+ rc = vgsvcVGSvcGstCtrlSessionThreadCreateProcess(pSessionStartupInfo, pSessionThread, s_uCtrlSessionThread);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Start the session thread.
+ */
+ rc = RTThreadCreateF(&pSessionThread->Thread, vgsvcGstCtrlSessionThread, pSessionThread /*pvUser*/, 0 /*cbStack*/,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "gctls%RU32", s_uCtrlSessionThread);
+ if (RT_SUCCESS(rc))
+ {
+ /* Wait for the thread to initialize. */
+ rc = RTThreadUserWait(pSessionThread->Thread, RT_MS_1MIN);
+ if ( RT_SUCCESS(rc)
+ && !ASMAtomicReadBool(&pSessionThread->fShutdown))
+ {
+ VGSvcVerbose(2, "Thread for session ID=%RU32 started\n", pSessionThread->pStartupInfo->uSessionID);
+
+ ASMAtomicXchgBool(&pSessionThread->fStarted, true);
+
+ /* Add session to list. */
+ RTListAppend(pList, &pSessionThread->Node);
+ if (ppSessionThread) /* Return session if wanted. */
+ *ppSessionThread = pSessionThread;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Bail out.
+ */
+ VGSvcError("Thread for session ID=%RU32 failed to start, rc=%Rrc\n",
+ pSessionThread->pStartupInfo->uSessionID, rc);
+ if (RT_SUCCESS_NP(rc))
+ rc = VERR_CANT_CREATE; /** @todo Find a better rc. */
+ }
+ else
+ VGSvcError("Creating session thread failed, rc=%Rrc\n", rc);
+
+ RTProcTerminate(pSessionThread->hProcess);
+ uint32_t cMsWait = 1;
+ while ( RTProcWait(pSessionThread->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, NULL) == VERR_PROCESS_RUNNING
+ && cMsWait <= 9) /* 1023 ms */
+ {
+ RTThreadSleep(cMsWait);
+ cMsWait <<= 1;
+ }
+ }
+
+ if (VbglR3GuestCtrlSupportsOptimizations(g_idControlSvcClient))
+ VbglR3GuestCtrlSessionCancelPrepared(g_idControlSvcClient, pSessionStartupInfo->uSessionID);
+ }
+ else
+ VGSvcVerbose(3, "VbglR3GuestCtrlSessionPrepare failed: %Rrc\n", rc);
+ RTPipeClose(pSessionThread->hKeyPipe);
+ pSessionThread->hKeyPipe = NIL_RTPIPE;
+ RTCritSectDelete(&pSessionThread->CritSect);
+ }
+ RTMemFree(pSessionThread);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ VGSvcVerbose(3, "Spawning session thread returned returned rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Waits for a formerly opened guest session process to close.
+ *
+ * @return VBox status code.
+ * @param pThread Guest session thread to wait for.
+ * @param uTimeoutMS Waiting timeout (in ms).
+ * @param fFlags Closing flags.
+ */
+int VGSvcGstCtrlSessionThreadWait(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t uTimeoutMS, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+ /** @todo Validate closing flags. */
+
+ AssertMsgReturn(pThread->Thread != NIL_RTTHREAD,
+ ("Guest session thread of session %p does not exist when it should\n", pThread),
+ VERR_NOT_FOUND);
+
+ int rc = VINF_SUCCESS;
+
+ /*
+ * The spawned session process should have received the same closing request,
+ * so just wait for the process to close.
+ */
+ if (ASMAtomicReadBool(&pThread->fStarted))
+ {
+ /* Ask the thread to shutdown. */
+ ASMAtomicXchgBool(&pThread->fShutdown, true);
+
+ VGSvcVerbose(3, "Waiting for session thread ID=%RU32 to close (%RU32ms) ...\n",
+ pThread->pStartupInfo->uSessionID, uTimeoutMS);
+
+ int rcThread;
+ rc = RTThreadWait(pThread->Thread, uTimeoutMS, &rcThread);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(pThread->fStopped, ("Thread of session ID=%RU32 not in stopped state when it should\n",
+ pThread->pStartupInfo->uSessionID));
+
+ VGSvcVerbose(3, "Session thread ID=%RU32 ended with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rcThread);
+ }
+ else
+ VGSvcError("Waiting for session thread ID=%RU32 to close failed with rc=%Rrc\n", pThread->pStartupInfo->uSessionID, rc);
+ }
+ else
+ VGSvcVerbose(3, "Thread for session ID=%RU32 not in started state, skipping wait\n", pThread->pStartupInfo->uSessionID);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Waits for the specified session thread to end and remove
+ * it from the session thread list.
+ *
+ * @return VBox status code.
+ * @param pThread Session thread to destroy.
+ * @param fFlags Closing flags.
+ */
+int VGSvcGstCtrlSessionThreadDestroy(PVBOXSERVICECTRLSESSIONTHREAD pThread, uint32_t fFlags)
+{
+ AssertPtrReturn(pThread, VERR_INVALID_POINTER);
+ AssertPtrReturn(pThread->pStartupInfo, VERR_WRONG_ORDER);
+
+ const uint32_t uSessionID = pThread->pStartupInfo->uSessionID;
+
+ VGSvcVerbose(3, "Destroying session ID=%RU32 ...\n", uSessionID);
+
+ int rc = VGSvcGstCtrlSessionThreadWait(pThread, 5 * 60 * 1000 /* 5 minutes timeout */, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ VbglR3GuestCtrlSessionStartupInfoFree(pThread->pStartupInfo);
+ pThread->pStartupInfo = NULL;
+
+ RTPipeClose(pThread->hKeyPipe);
+ pThread->hKeyPipe = NIL_RTPIPE;
+
+ RTCritSectDelete(&pThread->CritSect);
+
+ /* Remove session from list and destroy object. */
+ RTListNodeRemove(&pThread->Node);
+
+ RTMemFree(pThread);
+ pThread = NULL;
+ }
+
+ VGSvcVerbose(3, "Destroyed session ID=%RU32 with %Rrc\n", uSessionID, rc);
+ return rc;
+}
+
+/**
+ * Close all open guest session threads.
+ *
+ * @note Caller is responsible for locking!
+ *
+ * @return VBox status code.
+ * @param pList Which list to close the session threads for.
+ * @param fFlags Closing flags.
+ */
+int VGSvcGstCtrlSessionThreadDestroyAll(PRTLISTANCHOR pList, uint32_t fFlags)
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ /*int rc = VbglR3GuestCtrlClose
+ if (RT_FAILURE(rc))
+ VGSvcError("Cancelling pending waits failed; rc=%Rrc\n", rc);*/
+
+ PVBOXSERVICECTRLSESSIONTHREAD pSessIt;
+ PVBOXSERVICECTRLSESSIONTHREAD pSessItNext;
+ RTListForEachSafe(pList, pSessIt, pSessItNext, VBOXSERVICECTRLSESSIONTHREAD, Node)
+ {
+ int rc2 = VGSvcGstCtrlSessionThreadDestroy(pSessIt, fFlags);
+ if (RT_FAILURE(rc2))
+ {
+ VGSvcError("Closing session thread '%s' failed with rc=%Rrc\n", RTThreadGetName(pSessIt->Thread), rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Keep going. */
+ }
+ }
+
+ VGSvcVerbose(4, "Destroying guest session threads ended with %Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Main function for the session process.
+ *
+ * @returns exit code.
+ * @param argc Argument count.
+ * @param argv Argument vector (UTF-8).
+ */
+RTEXITCODE VGSvcGstCtrlSessionSpawnInit(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--domain", VBOXSERVICESESSIONOPT_DOMAIN, RTGETOPT_REQ_STRING },
+#ifdef DEBUG
+ { "--dump-stdout", VBOXSERVICESESSIONOPT_DUMP_STDOUT, RTGETOPT_REQ_NOTHING },
+ { "--dump-stderr", VBOXSERVICESESSIONOPT_DUMP_STDERR, RTGETOPT_REQ_NOTHING },
+#endif
+ { "--logfile", VBOXSERVICESESSIONOPT_LOG_FILE, RTGETOPT_REQ_STRING },
+ { "--user", VBOXSERVICESESSIONOPT_USERNAME, RTGETOPT_REQ_STRING },
+ { "--session-id", VBOXSERVICESESSIONOPT_SESSION_ID, RTGETOPT_REQ_UINT32 },
+ { "--session-proto", VBOXSERVICESESSIONOPT_SESSION_PROTO, RTGETOPT_REQ_UINT32 },
+#ifdef DEBUG
+ { "--thread-id", VBOXSERVICESESSIONOPT_THREAD_ID, RTGETOPT_REQ_UINT32 },
+#endif /* DEBUG */
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv,
+ s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+
+ uint32_t fSession = VBOXSERVICECTRLSESSION_FLAG_SPAWN;
+
+ /* Protocol and session ID must be specified explicitly. */
+ g_Session.StartupInfo.uProtocol = UINT32_MAX;
+ g_Session.StartupInfo.uSessionID = UINT32_MAX;
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case VBOXSERVICESESSIONOPT_DOMAIN:
+ /* Information not needed right now, skip. */
+ break;
+#ifdef DEBUG
+ case VBOXSERVICESESSIONOPT_DUMP_STDOUT:
+ fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDOUT;
+ break;
+
+ case VBOXSERVICESESSIONOPT_DUMP_STDERR:
+ fSession |= VBOXSERVICECTRLSESSION_FLAG_DUMPSTDERR;
+ break;
+#endif
+ case VBOXSERVICESESSIONOPT_SESSION_ID:
+ g_Session.StartupInfo.uSessionID = ValueUnion.u32;
+ break;
+
+ case VBOXSERVICESESSIONOPT_SESSION_PROTO:
+ g_Session.StartupInfo.uProtocol = ValueUnion.u32;
+ break;
+#ifdef DEBUG
+ case VBOXSERVICESESSIONOPT_THREAD_ID:
+ /* Not handled. Mainly for processs listing. */
+ break;
+#endif
+ case VBOXSERVICESESSIONOPT_LOG_FILE:
+ {
+ int rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error copying log file name: %Rrc", rc);
+ break;
+ }
+
+ case VBOXSERVICESESSIONOPT_USERNAME:
+ /* Information not needed right now, skip. */
+ break;
+
+ /** @todo Implement help? */
+
+ case 'v':
+ g_cVerbosity++;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ if (!RTStrICmp(ValueUnion.psz, VBOXSERVICECTRLSESSION_GETOPT_PREFIX))
+ break;
+ /* else fall through and bail out. */
+ RT_FALL_THROUGH();
+ }
+ default:
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown argument '%s'", ValueUnion.psz);
+ }
+ }
+
+ /* Check that we've got all the required options. */
+ if (g_Session.StartupInfo.uProtocol == UINT32_MAX)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No protocol version specified");
+
+ if (g_Session.StartupInfo.uSessionID == UINT32_MAX)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No session ID specified");
+
+ /* Init the session object. */
+ int rc = VGSvcGstCtrlSessionInit(&g_Session, fSession);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to initialize session object, rc=%Rrc\n", rc);
+
+ rc = VGSvcLogCreate(g_szLogFile[0] ? g_szLogFile : NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_INIT, "Failed to create log file '%s', rc=%Rrc\n",
+ g_szLogFile[0] ? g_szLogFile : "<None>", rc);
+
+ RTEXITCODE rcExit = vgsvcGstCtrlSessionSpawnWorker(&g_Session);
+
+ VGSvcLogDestroy();
+ return rcExit;
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp
new file mode 100644
index 00000000..d1aa77cc
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceCpuHotPlug.cpp
@@ -0,0 +1,672 @@
+/* $Id: VBoxServiceCpuHotPlug.cpp $ */
+/** @file
+ * VBoxService - Guest Additions CPU Hot-Plugging Service.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_vgsvc_cpuhotplug VBoxService - CPU Hot-Plugging
+ *
+ * The CPU Hot-Plugging subservice helps execute and coordinate CPU hot-plugging
+ * between the guest OS and the VMM.
+ *
+ * CPU Hot-Plugging is useful for reallocating CPU resources from one VM to
+ * other VMs or/and the host. It talks to the VMM via VMMDev, new hot-plugging
+ * events being signalled with an interrupt (no polling).
+ *
+ * Currently only supported for linux guests.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+
+#ifdef RT_OS_LINUX
+# include <iprt/linux/sysfs.h>
+# include <errno.h> /* For the sysfs API */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef RT_OS_LINUX
+
+/** @name Paths to access the CPU device
+ * @{
+ */
+# define SYSFS_ACPI_CPU_PATH "/sys/devices"
+# define SYSFS_CPU_PATH "/sys/devices/system/cpu"
+/** @} */
+
+/** Path component for the ACPI CPU path. */
+typedef struct SYSFSCPUPATHCOMP
+{
+ /** Flag whether the name is suffixed with a number */
+ bool fNumberedSuffix;
+ /** Name of the component */
+ const char *pcszName;
+} SYSFSCPUPATHCOMP, *PSYSFSCPUPATHCOMP;
+/** Pointer to a const component. */
+typedef const SYSFSCPUPATHCOMP *PCSYSFSCPUPATHCOMP;
+
+/**
+ * Structure which defines how the entries are assembled.
+ */
+typedef struct SYSFSCPUPATH
+{
+ /** Id when probing for the correct path. */
+ uint32_t uId;
+ /** Array holding the possible components. */
+ PCSYSFSCPUPATHCOMP aComponentsPossible;
+ /** Number of entries in the array, excluding the terminator. */
+ unsigned cComponents;
+ /** Directory handle */
+ RTDIR hDir;
+ /** Current directory to try. */
+ char *pszPath;
+} SYSFSCPUPATH, *PSYSFSCPUPATH;
+
+/** Content of uId if the path wasn't probed yet. */
+# define ACPI_CPU_PATH_NOT_PROBED UINT32_MAX
+#endif /* RT_OS_LINUX*/
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef RT_OS_LINUX
+/** Possible combinations of all path components for level 1. */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl1[] =
+{
+ /** LNXSYSTEM:<id> */
+ { true, "LNXSYSTM:*" }
+};
+
+/** Possible combinations of all path components for level 2. */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl2[] =
+{
+ /** device:<id> */
+ { true, "device:*" },
+ /** LNXSYBUS:<id> */
+ { true, "LNXSYBUS:*" }
+};
+
+/** Possible combinations of all path components for level 3 */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl3[] =
+{
+ /** ACPI0004:<id> */
+ { true, "ACPI0004:*" }
+};
+
+/** Possible combinations of all path components for level 4 */
+static const SYSFSCPUPATHCOMP g_aAcpiCpuPathLvl4[] =
+{
+ /** LNXCPU:<id> */
+ { true, "LNXCPU:*" },
+ /** ACPI_CPU:<id> */
+ { true, "ACPI_CPU:*" }
+};
+
+/** All possible combinations. */
+static SYSFSCPUPATH g_aAcpiCpuPath[] =
+{
+ /** Level 1 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl1, RT_ELEMENTS(g_aAcpiCpuPathLvl1), NULL, NULL },
+ /** Level 2 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl2, RT_ELEMENTS(g_aAcpiCpuPathLvl2), NULL, NULL },
+ /** Level 3 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl3, RT_ELEMENTS(g_aAcpiCpuPathLvl3), NULL, NULL },
+ /** Level 4 */
+ { ACPI_CPU_PATH_NOT_PROBED, g_aAcpiCpuPathLvl4, RT_ELEMENTS(g_aAcpiCpuPathLvl4), NULL, NULL },
+};
+
+/**
+ * Possible directories to get to the topology directory for reading core and package id.
+ *
+ * @remark: This is not part of the path above because the eject file is not in one of the directories
+ * below and would make the hot unplug code fail.
+ */
+static const char *g_apszTopologyPath[] =
+{
+ "sysdev",
+ "physical_node"
+};
+
+#endif /* RT_OS_LINUX*/
+
+
+#ifdef RT_OS_LINUX
+
+/**
+ * Probes for the correct path to the ACPI CPU object in sysfs for the
+ * various different kernel versions and distro's.
+ *
+ * @returns VBox status code.
+ */
+static int vgsvcCpuHotPlugProbePath(void)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Probe for the correct path if we didn't already. */
+ if (RT_UNLIKELY(g_aAcpiCpuPath[0].uId == ACPI_CPU_PATH_NOT_PROBED))
+ {
+ char *pszPath = NULL; /** < Current path, increasing while we dig deeper. */
+
+ pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
+ if (!pszPath)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Simple algorithm to find the path.
+ * Performance is not a real problem because it is
+ * only executed once.
+ */
+ for (unsigned iLvlCurr = 0; iLvlCurr < RT_ELEMENTS(g_aAcpiCpuPath); iLvlCurr++)
+ {
+ PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+
+ for (unsigned iCompCurr = 0; iCompCurr < pAcpiCpuPathLvl->cComponents; iCompCurr++)
+ {
+ PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[iCompCurr];
+
+ /* Open the directory */
+ RTDIR hDirCurr = NIL_RTDIR;
+ char *pszPathTmp = RTPathJoinA(pszPath, pPathComponent->pcszName);
+ if (pszPathTmp)
+ {
+ rc = RTDirOpenFiltered(&hDirCurr, pszPathTmp, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ RTStrFree(pszPathTmp);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Search if the current directory contains one of the possible parts. */
+ size_t cchName = strlen(pPathComponent->pcszName);
+ RTDIRENTRY DirFolderContent;
+ bool fFound = false;
+
+ /* Get rid of the * filter which is in the path component. */
+ if (pPathComponent->fNumberedSuffix)
+ cchName--;
+
+ while (RT_SUCCESS(RTDirRead(hDirCurr, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
+ {
+ if ( DirFolderContent.cbName >= cchName
+ && !strncmp(DirFolderContent.szName, pPathComponent->pcszName, cchName))
+ {
+ /* Found, use the complete name to dig deeper. */
+ fFound = true;
+ pAcpiCpuPathLvl->uId = iCompCurr;
+ char *pszPathLvl = RTPathJoinA(pszPath, DirFolderContent.szName);
+ if (pszPathLvl)
+ {
+ RTStrFree(pszPath);
+ pszPath = pszPathLvl;
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+ }
+ RTDirClose(hDirCurr);
+
+ if (fFound)
+ break;
+ } /* For every possible component. */
+
+ /* No matching component for this part, no need to continue */
+ if (RT_FAILURE(rc))
+ break;
+ } /* For every level */
+
+ VGSvcVerbose(1, "Final path after probing %s rc=%Rrc\n", pszPath, rc);
+ RTStrFree(pszPath);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Returns the path of the ACPI CPU device with the given core and package ID.
+ *
+ * @returns VBox status code.
+ * @param ppszPath Where to store the path.
+ * @param idCpuCore The core ID of the CPU.
+ * @param idCpuPackage The package ID of the CPU.
+ */
+static int vgsvcCpuHotPlugGetACPIDevicePath(char **ppszPath, uint32_t idCpuCore, uint32_t idCpuPackage)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertPtrReturn(ppszPath, VERR_INVALID_PARAMETER);
+
+ rc = vgsvcCpuHotPlugProbePath();
+ if (RT_SUCCESS(rc))
+ {
+ /* Build the path from all components. */
+ bool fFound = false;
+ unsigned iLvlCurr = 0;
+ char *pszPath = NULL;
+ char *pszPathDir = NULL;
+ PSYSFSCPUPATH pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+
+ /* Init everything. */
+ Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
+ pszPath = RTPathJoinA(SYSFS_ACPI_CPU_PATH, pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId].pcszName);
+ if (!pszPath)
+ return VERR_NO_STR_MEMORY;
+
+ pAcpiCpuPathLvl->pszPath = RTStrDup(SYSFS_ACPI_CPU_PATH);
+ if (!pAcpiCpuPathLvl->pszPath)
+ {
+ RTStrFree(pszPath);
+ return VERR_NO_STR_MEMORY;
+ }
+
+ /* Open the directory */
+ rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszPath);
+ pszPath = NULL;
+
+ /* Search for CPU */
+ while (!fFound)
+ {
+ /* Get the next directory. */
+ RTDIRENTRY DirFolderContent;
+ rc = RTDirRead(pAcpiCpuPathLvl->hDir, &DirFolderContent, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create the new path. */
+ char *pszPathCurr = RTPathJoinA(pAcpiCpuPathLvl->pszPath, DirFolderContent.szName);
+ if (!pszPathCurr)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+
+ /* If this is the last level check for the given core and package id. */
+ if (iLvlCurr == RT_ELEMENTS(g_aAcpiCpuPath) - 1)
+ {
+ /* Get the sysdev */
+ uint32_t idCore = 0;
+ uint32_t idPackage = 0;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apszTopologyPath); i++)
+ {
+ int64_t i64Core = 0;
+ int64_t i64Package = 0;
+
+ int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id",
+ pszPathCurr, g_apszTopologyPath[i]);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id",
+ pszPathCurr, g_apszTopologyPath[i]);
+
+ if (RT_SUCCESS(rc2))
+ {
+ idCore = (uint32_t)i64Core;
+ idPackage = (uint32_t)i64Package;
+ break;
+ }
+ }
+
+ if ( idCore == idCpuCore
+ && idPackage == idCpuPackage)
+ {
+ /* Return the path */
+ pszPath = pszPathCurr;
+ fFound = true;
+ VGSvcVerbose(3, "CPU found\n");
+ break;
+ }
+ else
+ {
+ /* Get the next directory. */
+ RTStrFree(pszPathCurr);
+ pszPathCurr = NULL;
+ VGSvcVerbose(3, "CPU doesn't match, next directory\n");
+ }
+ }
+ else
+ {
+ /* Go deeper */
+ iLvlCurr++;
+
+ VGSvcVerbose(3, "Going deeper (iLvlCurr=%u)\n", iLvlCurr);
+
+ pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+
+ Assert(pAcpiCpuPathLvl->hDir == NIL_RTDIR);
+ Assert(!pAcpiCpuPathLvl->pszPath);
+ pAcpiCpuPathLvl->pszPath = pszPathCurr;
+ PCSYSFSCPUPATHCOMP pPathComponent = &pAcpiCpuPathLvl->aComponentsPossible[pAcpiCpuPathLvl->uId];
+
+ Assert(pAcpiCpuPathLvl->uId != ACPI_CPU_PATH_NOT_PROBED);
+
+ pszPathDir = RTPathJoinA(pszPathCurr, pPathComponent->pcszName);
+ if (!pszPathDir)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ break;
+ }
+
+ VGSvcVerbose(3, "New path %s\n", pszPathDir);
+
+ /* Open the directory */
+ rc = RTDirOpenFiltered(&pAcpiCpuPathLvl->hDir, pszPathDir, RTDIRFILTER_WINNT, 0 /*fFlags*/);
+ RTStrFree(pszPathDir);
+ pszPathDir = NULL;
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ {
+ RTDirClose(pAcpiCpuPathLvl->hDir);
+ RTStrFree(pAcpiCpuPathLvl->pszPath);
+ pAcpiCpuPathLvl->hDir = NIL_RTDIR;
+ pAcpiCpuPathLvl->pszPath = NULL;
+
+ /*
+ * If we reached the end we didn't find the matching path
+ * meaning the CPU is already offline.
+ */
+ if (!iLvlCurr)
+ {
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+
+ iLvlCurr--;
+ pAcpiCpuPathLvl = &g_aAcpiCpuPath[iLvlCurr];
+ VGSvcVerbose(3, "Directory not found, going back (iLvlCurr=%u)\n", iLvlCurr);
+ }
+ } /* while not found */
+ } /* Successful init */
+
+ /* Cleanup */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aAcpiCpuPath); i++)
+ {
+ if (g_aAcpiCpuPath[i].hDir)
+ RTDirClose(g_aAcpiCpuPath[i].hDir);
+ if (g_aAcpiCpuPath[i].pszPath)
+ RTStrFree(g_aAcpiCpuPath[i].pszPath);
+ g_aAcpiCpuPath[i].hDir = NIL_RTDIR;
+ g_aAcpiCpuPath[i].pszPath = NULL;
+ }
+ if (pszPathDir)
+ RTStrFree(pszPathDir);
+ if (RT_FAILURE(rc) && pszPath)
+ RTStrFree(pszPath);
+
+ if (RT_SUCCESS(rc))
+ *ppszPath = pszPath;
+ }
+
+ return rc;
+}
+
+#endif /* RT_OS_LINUX */
+
+/**
+ * Handles VMMDevCpuEventType_Plug.
+ *
+ * @param idCpuCore The CPU core ID.
+ * @param idCpuPackage The CPU package ID.
+ */
+static void vgsvcCpuHotPlugHandlePlugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
+{
+#ifdef RT_OS_LINUX
+ /*
+ * The topology directory (containing the physical and core id properties)
+ * is not available until the CPU is online. So we just iterate over all directories
+ * and enable the first CPU which is not online already.
+ * Because the directory might not be available immediately we try a few times.
+ *
+ */
+ /** @todo Maybe use udev to monitor hot-add events from the kernel */
+ bool fCpuOnline = false;
+ unsigned cTries = 5;
+
+ do
+ {
+ RTDIR hDirDevices = NULL;
+ int rc = RTDirOpen(&hDirDevices, SYSFS_CPU_PATH);
+ if (RT_SUCCESS(rc))
+ {
+ RTDIRENTRY DirFolderContent;
+ while (RT_SUCCESS(RTDirRead(hDirDevices, &DirFolderContent, NULL))) /* Assumption that szName has always enough space */
+ {
+ /* Check if this is a CPU object which can be brought online. */
+ if (RTLinuxSysFsExists("%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName))
+ {
+ /* Check the status of the CPU by reading the online flag. */
+ int64_t i64Status = 0;
+ rc = RTLinuxSysFsReadIntFile(10 /*uBase*/, &i64Status, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
+ if ( RT_SUCCESS(rc)
+ && i64Status == 0)
+ {
+ /* CPU is offline, turn it on. */
+ rc = RTLinuxSysFsWriteU8File(10 /*uBase*/, 1, "%s/%s/online", SYSFS_CPU_PATH, DirFolderContent.szName);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was brought online\n", idCpuPackage, idCpuCore);
+ fCpuOnline = true;
+ break;
+ }
+ }
+ else if (RT_FAILURE(rc))
+ VGSvcError("CpuHotPlug: Failed to open '%s/%s/online' rc=%Rrc\n",
+ SYSFS_CPU_PATH, DirFolderContent.szName, rc);
+ else
+ {
+ /*
+ * Check whether the topology matches what we got (which means someone raced us and brought the CPU
+ * online already).
+ */
+ int64_t i64Core = 0;
+ int64_t i64Package = 0;
+
+ int rc2 = RTLinuxSysFsReadIntFile(10, &i64Core, "%s/%s/topology/core_id",
+ SYSFS_CPU_PATH, DirFolderContent.szName);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTLinuxSysFsReadIntFile(10, &i64Package, "%s/%s/topology/physical_package_id",
+ SYSFS_CPU_PATH, DirFolderContent.szName);
+ if ( RT_SUCCESS(rc2)
+ && idCpuPackage == i64Package
+ && idCpuCore == i64Core)
+ {
+ VGSvcVerbose(1, "CpuHotPlug: '%s' is already online\n", DirFolderContent.szName);
+ fCpuOnline = true;
+ break;
+ }
+ }
+ }
+ }
+ RTDirClose(hDirDevices);
+ }
+ else
+ VGSvcError("CpuHotPlug: Failed to open path %s rc=%Rrc\n", SYSFS_CPU_PATH, rc);
+
+ /* Sleep a bit */
+ if (!fCpuOnline)
+ RTThreadSleep(100);
+
+ } while ( !fCpuOnline
+ && cTries-- > 0);
+#else
+# error "Port me"
+#endif
+}
+
+
+/**
+ * Handles VMMDevCpuEventType_Unplug.
+ *
+ * @param idCpuCore The CPU core ID.
+ * @param idCpuPackage The CPU package ID.
+ */
+static void vgsvcCpuHotPlugHandleUnplugEvent(uint32_t idCpuCore, uint32_t idCpuPackage)
+{
+#ifdef RT_OS_LINUX
+ char *pszCpuDevicePath = NULL;
+ int rc = vgsvcCpuHotPlugGetACPIDevicePath(&pszCpuDevicePath, idCpuCore, idCpuPackage);
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hFileCpuEject;
+ rc = RTFileOpenF(&hFileCpuEject, RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, "%s/eject", pszCpuDevicePath);
+ if (RT_SUCCESS(rc))
+ {
+ /* Write a 1 to eject the CPU */
+ rc = RTFileWrite(hFileCpuEject, "1", 1, NULL);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was ejected\n", idCpuPackage, idCpuCore);
+ else
+ VGSvcError("CpuHotPlug: Failed to eject CPU %u/%u rc=%Rrc\n", idCpuPackage, idCpuCore, rc);
+
+ RTFileClose(hFileCpuEject);
+ }
+ else
+ VGSvcError("CpuHotPlug: Failed to open '%s/eject' rc=%Rrc\n", pszCpuDevicePath, rc);
+ RTStrFree(pszCpuDevicePath);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ VGSvcVerbose(1, "CpuHotPlug: CPU %u/%u was aleady ejected by someone else!\n", idCpuPackage, idCpuCore);
+ else
+ VGSvcError("CpuHotPlug: Failed to get CPU device path rc=%Rrc\n", rc);
+#else
+# error "Port me"
+#endif
+}
+
+
+/** @interface_method_impl{VBOXSERVICE,pfnWorker} */
+static DECLCALLBACK(int) vgsvcCpuHotPlugWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Enable the CPU hotplug notifier.
+ */
+ int rc = VbglR3CpuHotPlugInit();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * The Work Loop.
+ */
+ for (;;)
+ {
+ /* Wait for CPU hot-plugging event. */
+ uint32_t idCpuCore;
+ uint32_t idCpuPackage;
+ VMMDevCpuEventType enmEventType;
+ rc = VbglR3CpuHotPlugWaitForEvent(&enmEventType, &idCpuCore, &idCpuPackage);
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcVerbose(3, "CpuHotPlug: Event happened idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
+ idCpuCore, idCpuPackage, enmEventType);
+ switch (enmEventType)
+ {
+ case VMMDevCpuEventType_Plug:
+ vgsvcCpuHotPlugHandlePlugEvent(idCpuCore, idCpuPackage);
+ break;
+
+ case VMMDevCpuEventType_Unplug:
+ vgsvcCpuHotPlugHandleUnplugEvent(idCpuCore, idCpuPackage);
+ break;
+
+ default:
+ {
+ static uint32_t s_iErrors = 0;
+ if (s_iErrors++ < 10)
+ VGSvcError("CpuHotPlug: Unknown event: idCpuCore=%u idCpuPackage=%u enmEventType=%d\n",
+ idCpuCore, idCpuPackage, enmEventType);
+ break;
+ }
+ }
+ }
+ else if (rc != VERR_INTERRUPTED && rc != VERR_TRY_AGAIN)
+ {
+ VGSvcError("CpuHotPlug: VbglR3CpuHotPlugWaitForEvent returned %Rrc\n", rc);
+ break;
+ }
+
+ if (*pfShutdown)
+ break;
+ }
+
+ VbglR3CpuHotPlugTerm();
+ return rc;
+}
+
+
+/** @interface_method_impl{VBOXSERVICE,pfnStop} */
+static DECLCALLBACK(void) vgsvcCpuHotPlugStop(void)
+{
+ VbglR3InterruptEventWaits();
+ return;
+}
+
+
+/**
+ * The 'CpuHotPlug' service description.
+ */
+VBOXSERVICE g_CpuHotPlug =
+{
+ /* pszName. */
+ "cpuhotplug",
+ /* pszDescription. */
+ "CPU hot-plugging monitor",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ VGSvcDefaultInit,
+ vgsvcCpuHotPlugWorker,
+ vgsvcCpuHotPlugStop,
+ VGSvcDefaultTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h
new file mode 100644
index 00000000..cb85b59b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceInternal.h
@@ -0,0 +1,284 @@
+/* $Id: VBoxServiceInternal.h $ */
+/** @file
+ * VBoxService - Guest Additions Services.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+#include <iprt/list.h>
+#include <iprt/critsect.h>
+#include <iprt/path.h> /* RTPATH_MAX */
+#include <iprt/stdarg.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestControlSvc.h>
+
+
+#if !defined(RT_OS_WINDOWS) || defined(DOXYGEN_RUNNING)
+/** Special argv[1] value that indicates that argv is UTF-8.
+ * This causes RTR3Init to be called with RTR3INIT_FLAGS_UTF8_ARGV and helps
+ * work around potential issues caused by a user's locale config not being
+ * UTF-8. See @bugref{10153}.
+ *
+ * @note We don't need this on windows and it would be harmful to enable it
+ * as the argc/argv vs __argc/__argv comparison would fail and we would
+ * not use the unicode command line to create a UTF-8 argv. Since the
+ * original argv is ANSI, it may be missing codepoints not present in
+ * the ANSI code page of the process. */
+# define VBOXSERVICE_ARG1_UTF8_ARGV "--utf8-argv"
+#endif
+/** RTProcCreateEx flags corresponding to VBOXSERVICE_ARG1_UTF8_ARGV. */
+#ifdef VBOXSERVICE_ARG1_UTF8_ARGV
+# define VBOXSERVICE_PROC_F_UTF8_ARGV RTPROC_FLAGS_UTF8_ARGV
+#else
+# define VBOXSERVICE_PROC_F_UTF8_ARGV 0
+#endif
+
+
+/**
+ * A service descriptor.
+ */
+typedef struct
+{
+ /** The short service name. */
+ const char *pszName;
+ /** The longer service name. */
+ const char *pszDescription;
+ /** The usage options stuff for the --help screen. */
+ const char *pszUsage;
+ /** The option descriptions for the --help screen. */
+ const char *pszOptions;
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnPreInit,(void));
+
+ /**
+ * Tries to parse the given command line option.
+ *
+ * @returns 0 if we parsed, -1 if it didn't and anything else means exit.
+ * @param ppszShort If not NULL it points to the short option iterator. a short argument.
+ * If NULL examine argv[*pi].
+ * @param argc The argument count.
+ * @param argv The argument vector.
+ * @param pi The argument vector index. Update if any value(s) are eaten.
+ */
+ DECLCALLBACKMEMBER(int, pfnOption,(const char **ppszShort, int argc, char **argv, int *pi));
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnInit,(void));
+
+ /** Called from the worker thread.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS if exitting because *pfShutdown was set.
+ * @param pfShutdown Pointer to a per service termination flag to check
+ * before and after blocking.
+ */
+ DECLCALLBACKMEMBER(int, pfnWorker,(bool volatile *pfShutdown));
+
+ /**
+ * Stops a service.
+ */
+ DECLCALLBACKMEMBER(void, pfnStop,(void));
+
+ /**
+ * Does termination cleanups.
+ *
+ * @remarks This may be called even if pfnInit hasn't been called!
+ */
+ DECLCALLBACKMEMBER(void, pfnTerm,(void));
+} VBOXSERVICE;
+/** Pointer to a VBOXSERVICE. */
+typedef VBOXSERVICE *PVBOXSERVICE;
+/** Pointer to a const VBOXSERVICE. */
+typedef VBOXSERVICE const *PCVBOXSERVICE;
+
+/* Default call-backs for services which do not need special behaviour. */
+DECLCALLBACK(int) VGSvcDefaultPreInit(void);
+DECLCALLBACK(int) VGSvcDefaultOption(const char **ppszShort, int argc, char **argv, int *pi);
+DECLCALLBACK(int) VGSvcDefaultInit(void);
+DECLCALLBACK(void) VGSvcDefaultTerm(void);
+
+/** The service name.
+ * @note Used on windows to name the service as well as the global mutex. */
+#define VBOXSERVICE_NAME "VBoxService"
+
+#ifdef RT_OS_WINDOWS
+/** The friendly service name. */
+# define VBOXSERVICE_FRIENDLY_NAME "VirtualBox Guest Additions Service"
+/** The service description (only W2K+ atm) */
+# define VBOXSERVICE_DESCRIPTION "Manages VM runtime information, time synchronization, guest control execution and miscellaneous utilities for guest operating systems."
+/** The following constant may be defined by including NtStatus.h. */
+# define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+#endif /* RT_OS_WINDOWS */
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * A guest property cache.
+ */
+typedef struct VBOXSERVICEVEPROPCACHE
+{
+ /** The client ID for HGCM communication. */
+ uint32_t uClientID;
+ /** Head in a list of VBOXSERVICEVEPROPCACHEENTRY nodes. */
+ RTLISTANCHOR NodeHead;
+ /** Critical section for thread-safe use. */
+ RTCRITSECT CritSect;
+} VBOXSERVICEVEPROPCACHE;
+/** Pointer to a guest property cache. */
+typedef VBOXSERVICEVEPROPCACHE *PVBOXSERVICEVEPROPCACHE;
+
+/**
+ * An entry in the property cache (VBOXSERVICEVEPROPCACHE).
+ */
+typedef struct VBOXSERVICEVEPROPCACHEENTRY
+{
+ /** Node to successor.
+ * @todo r=bird: This is not really the node to the successor, but
+ * rather the OUR node in the list. If it helps, remember that
+ * its a doubly linked list. */
+ RTLISTNODE NodeSucc;
+ /** Name (and full path) of guest property. */
+ char *pszName;
+ /** The last value stored (for reference). */
+ char *pszValue;
+ /** Reset value to write if property is temporary. If NULL, it will be
+ * deleted. */
+ char *pszValueReset;
+ /** Flags. */
+ uint32_t fFlags;
+} VBOXSERVICEVEPROPCACHEENTRY;
+/** Pointer to a cached guest property. */
+typedef VBOXSERVICEVEPROPCACHEENTRY *PVBOXSERVICEVEPROPCACHEENTRY;
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+RT_C_DECLS_BEGIN
+
+extern char *g_pszProgName;
+extern unsigned g_cVerbosity;
+extern char g_szLogFile[RTPATH_MAX + 128];
+extern uint32_t g_DefaultInterval;
+extern VBOXSERVICE g_TimeSync;
+#ifdef VBOX_WITH_VBOXSERVICE_CLIPBOARD
+extern VBOXSERVICE g_Clipboard;
+#endif
+extern VBOXSERVICE g_Control;
+extern VBOXSERVICE g_VMInfo;
+extern VBOXSERVICE g_CpuHotPlug;
+#ifdef VBOX_WITH_VBOXSERVICE_MANAGEMENT
+extern VBOXSERVICE g_MemBalloon;
+extern VBOXSERVICE g_VMStatistics;
+#endif
+#ifdef VBOX_WITH_VBOXSERVICE_PAGE_SHARING
+extern VBOXSERVICE g_PageSharing;
+#endif
+#ifdef VBOX_WITH_SHARED_FOLDERS
+extern VBOXSERVICE g_AutoMount;
+#endif
+#ifdef DEBUG
+extern RTCRITSECT g_csLog; /* For guest process stdout dumping. */
+#endif
+
+extern RTEXITCODE VGSvcSyntax(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2);
+extern RTEXITCODE VGSvcError(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2);
+extern void VGSvcVerbose(unsigned iLevel, const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(2, 3);
+extern int VGSvcLogCreate(const char *pszLogFile);
+extern void VGSvcLogV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0);
+extern void VGSvcLogDestroy(void);
+extern int VGSvcArgUInt32(int argc, char **argv, const char *psz, int *pi, uint32_t *pu32,
+ uint32_t u32Min, uint32_t u32Max);
+
+/* Exposing the following bits because of windows: */
+extern int VGSvcStartServices(void);
+extern int VGSvcStopServices(void);
+extern void VGSvcMainWait(void);
+extern int VGSvcReportStatus(VBoxGuestFacilityStatus enmStatus);
+#ifdef RT_OS_WINDOWS
+extern void VGSvcWinResolveApis(void);
+extern RTEXITCODE VGSvcWinInstall(void);
+extern RTEXITCODE VGSvcWinUninstall(void);
+extern RTEXITCODE VGSvcWinEnterCtrlDispatcher(void);
+extern void VGSvcWinSetStopPendingStatus(uint32_t uCheckPoint);
+# ifdef TH32CS_SNAPHEAPLIST
+extern decltype(CreateToolhelp32Snapshot) *g_pfnCreateToolhelp32Snapshot;
+extern decltype(Process32First) *g_pfnProcess32First;
+extern decltype(Process32Next) *g_pfnProcess32Next;
+extern decltype(Module32First) *g_pfnModule32First;
+extern decltype(Module32Next) *g_pfnModule32Next;
+# endif
+extern decltype(GetSystemTimeAdjustment) *g_pfnGetSystemTimeAdjustment;
+extern decltype(SetSystemTimeAdjustment) *g_pfnSetSystemTimeAdjustment;
+# ifdef IPRT_INCLUDED_nt_nt_h
+extern decltype(ZwQuerySystemInformation) *g_pfnZwQuerySystemInformation;
+# endif
+extern ULONG (WINAPI *g_pfnGetAdaptersInfo)(struct _IP_ADAPTER_INFO *, PULONG);
+#ifdef WINSOCK_VERSION
+extern decltype(WSAStartup) *g_pfnWSAStartup;
+extern decltype(WSACleanup) *g_pfnWSACleanup;
+extern decltype(WSASocketA) *g_pfnWSASocketA;
+extern decltype(WSAIoctl) *g_pfnWSAIoctl;
+extern decltype(WSAGetLastError) *g_pfnWSAGetLastError;
+extern decltype(closesocket) *g_pfnclosesocket;
+extern decltype(inet_ntoa) *g_pfninet_ntoa;
+# endif /* WINSOCK_VERSION */
+
+#ifdef SE_INTERACTIVE_LOGON_NAME
+extern decltype(LsaNtStatusToWinError) *g_pfnLsaNtStatusToWinError;
+#endif
+
+# ifdef VBOX_WITH_GUEST_PROPS
+extern int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList);
+extern int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID);
+# endif /* VBOX_WITH_GUEST_PROPS */
+
+#endif /* RT_OS_WINDOWS */
+
+#ifdef VBOX_WITH_MEMBALLOON
+extern uint32_t VGSvcBalloonQueryPages(uint32_t cbPage);
+#endif
+#if defined(VBOX_WITH_VBOXSERVICE_PAGE_SHARING)
+extern RTEXITCODE VGSvcPageSharingWorkerChild(void);
+#endif
+extern int VGSvcVMInfoSignal(void);
+
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceInternal_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp
new file mode 100644
index 00000000..c8e72c62
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServicePageSharing.cpp
@@ -0,0 +1,803 @@
+/* $Id: VBoxServicePageSharing.cpp $ */
+/** @file
+ * VBoxService - Guest page sharing.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_vgsvc_pagesharing VBoxService - Page Sharing
+ *
+ * The Page Sharing subservice is responsible for finding memory mappings
+ * suitable page fusions.
+ *
+ * It is the driving force behind the Page Fusion feature in VirtualBox.
+ * Working with PGM and GMM (ring-0) thru the VMMDev interface. Every so often
+ * it reenumerates the memory mappings (executables and shared libraries) of the
+ * guest OS and reports additions and removals to GMM. For each mapping there
+ * is a filename and version as well as and address range and subsections. GMM
+ * will match the mapping with mapping with the same name and version from other
+ * VMs and see if there are any identical pages between the two.
+ *
+ * To increase the hit rate and reduce the volatility, the service launches a
+ * child process which loads all the Windows system DLLs it can. The child
+ * process is necessary as the DLLs are loaded without running the init code,
+ * and therefore not actually callable for other VBoxService code (may crash).
+ *
+ * This is currently only implemented on Windows. There is no technical reason
+ * for it not to be doable for all the other guests too, it's just a matter of
+ * customer demand and engineering time.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/ldr.h>
+#include <iprt/process.h>
+#include <iprt/env.h>
+#include <iprt/stream.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <VBox/err.h>
+#include <VBox/VMMDev.h>
+#include <VBox/VBoxGuestLib.h>
+
+#ifdef RT_OS_WINDOWS
+#include <iprt/nt/nt-and-windows.h>
+# include <tlhelp32.h>
+# include <psapi.h>
+# include <winternl.h>
+#endif
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+typedef struct
+{
+ AVLPVNODECORE Core;
+#ifdef RT_OS_WINDOWS
+ HMODULE hModule;
+ char szFileVersion[16];
+ MODULEENTRY32 Info;
+#endif
+} VGSVCPGSHKNOWNMOD, *PVGSVCPGSHKNOWNMOD;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+
+static PAVLPVNODECORE g_pKnownModuleTree = NULL;
+static uint64_t g_idSession = 0;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser);
+
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Registers a new module with the VMM
+ * @param pModule Module ptr
+ * @param fValidateMemory Validate/touch memory pages or not
+ */
+static void vgsvcPageSharingRegisterModule(PVGSVCPGSHKNOWNMOD pModule, bool fValidateMemory)
+{
+ VMMDEVSHAREDREGIONDESC aRegions[VMMDEVSHAREDREGIONDESC_MAX];
+ DWORD dwModuleSize = pModule->Info.modBaseSize;
+ BYTE *pBaseAddress = pModule->Info.modBaseAddr;
+
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule\n");
+
+ DWORD dwDummy;
+ DWORD cbVersion = GetFileVersionInfoSize(pModule->Info.szExePath, &dwDummy);
+ if (!cbVersion)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfoSize failed with %d\n", GetLastError());
+ return;
+ }
+ BYTE *pVersionInfo = (BYTE *)RTMemAllocZ(cbVersion);
+ if (!pVersionInfo)
+ return;
+
+ if (!GetFileVersionInfo(pModule->Info.szExePath, 0, cbVersion, pVersionInfo))
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: GetFileVersionInfo failed with %d\n", GetLastError());
+ goto end;
+ }
+
+ /* Fetch default code page. */
+ struct LANGANDCODEPAGE
+ {
+ WORD wLanguage;
+ WORD wCodePage;
+ } *lpTranslate;
+
+ UINT cbTranslate;
+ BOOL fRet = VerQueryValue(pVersionInfo, TEXT("\\VarFileInfo\\Translation"), (LPVOID *)&lpTranslate, &cbTranslate);
+ if ( !fRet
+ || cbTranslate < 4)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VerQueryValue failed with %d (cb=%d)\n", GetLastError(), cbTranslate);
+ goto end;
+ }
+
+ unsigned i;
+ UINT cbFileVersion;
+ char *pszFileVersion = NULL; /* Shut up MSC */
+ unsigned cTranslationBlocks = cbTranslate/sizeof(struct LANGANDCODEPAGE);
+
+ pModule->szFileVersion[0] = '\0';
+ for (i = 0; i < cTranslationBlocks; i++)
+ {
+ /* Fetch file version string. */
+ char szFileVersionLocation[256];
+
+/** @todo r=bird: Mixing ANSI and TCHAR crap again. This code is a mess. We
+ * always use the wide version of the API and convert to UTF-8/whatever. */
+
+ RTStrPrintf(szFileVersionLocation, sizeof(szFileVersionLocation),
+ "\\StringFileInfo\\%04x%04x\\FileVersion", lpTranslate[i].wLanguage, lpTranslate[i].wCodePage);
+ fRet = VerQueryValue(pVersionInfo, szFileVersionLocation, (LPVOID *)&pszFileVersion, &cbFileVersion);
+ if (fRet)
+ {
+ RTStrCopy(pModule->szFileVersion, sizeof(pModule->szFileVersion), pszFileVersion);
+ break;
+ }
+ }
+ if (i == cTranslationBlocks)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: no file version found!\n");
+ goto end;
+ }
+
+ unsigned idxRegion = 0;
+
+ if (fValidateMemory)
+ {
+ do
+ {
+ MEMORY_BASIC_INFORMATION MemInfo;
+ SIZE_T cbRet = VirtualQuery(pBaseAddress, &MemInfo, sizeof(MemInfo));
+ Assert(cbRet);
+ if (!cbRet)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VirtualQueryEx failed with %d\n", GetLastError());
+ break;
+ }
+
+ if ( MemInfo.State == MEM_COMMIT
+ && MemInfo.Type == MEM_IMAGE)
+ {
+ switch (MemInfo.Protect)
+ {
+ case PAGE_EXECUTE:
+ case PAGE_EXECUTE_READ:
+ case PAGE_READONLY:
+ {
+ char *pRegion = (char *)MemInfo.BaseAddress;
+
+ /* Skip the first region as it only contains the image file header. */
+ if (pRegion != (char *)pModule->Info.modBaseAddr)
+ {
+ /* Touch all pages. */
+ while ((uintptr_t)pRegion < (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize)
+ {
+ /* Try to trick the optimizer to leave the page touching code in place. */
+ ASMProbeReadByte(pRegion);
+ pRegion += PAGE_SIZE;
+ }
+ }
+#ifdef RT_ARCH_X86
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)MemInfo.BaseAddress;
+#else
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)MemInfo.BaseAddress;
+#endif
+ aRegions[idxRegion].cbRegion = MemInfo.RegionSize;
+ idxRegion++;
+
+ break;
+ }
+
+ default:
+ break; /* ignore */
+ }
+ }
+
+ pBaseAddress = (BYTE *)MemInfo.BaseAddress + MemInfo.RegionSize;
+ if (dwModuleSize > MemInfo.RegionSize)
+ dwModuleSize -= MemInfo.RegionSize;
+ else
+ {
+ dwModuleSize = 0;
+ break;
+ }
+
+ if (idxRegion >= RT_ELEMENTS(aRegions))
+ break; /* out of room */
+ }
+ while (dwModuleSize);
+ }
+ else
+ {
+ /* We can't probe kernel memory ranges, so pretend it's one big region. */
+#ifdef RT_ARCH_X86
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR32)pBaseAddress;
+#else
+ aRegions[idxRegion].GCRegionAddr = (RTGCPTR64)pBaseAddress;
+#endif
+ aRegions[idxRegion].cbRegion = dwModuleSize;
+ idxRegion++;
+ }
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule %s %s base=%p size=%x cregions=%d\n", pModule->Info.szModule, pModule->szFileVersion, pModule->Info.modBaseAddr, pModule->Info.modBaseSize, idxRegion);
+ int rc = VbglR3RegisterSharedModule(pModule->Info.szModule, pModule->szFileVersion, (uintptr_t)pModule->Info.modBaseAddr,
+ pModule->Info.modBaseSize, idxRegion, aRegions);
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "vgsvcPageSharingRegisterModule: VbglR3RegisterSharedModule failed with %Rrc\n", rc);
+
+end:
+ RTMemFree(pVersionInfo);
+ return;
+}
+
+
+/**
+ * Inspect all loaded modules for the specified process
+ *
+ * @param dwProcessId Process id
+ * @param ppNewTree The module tree we're assembling from modules found
+ * in this process. Modules found are moved from
+ * g_pKnownModuleTree or created new.
+ */
+static void vgsvcPageSharingInspectModules(DWORD dwProcessId, PAVLPVNODECORE *ppNewTree)
+{
+ /* Get a list of all the modules in this process. */
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE /* no child process handle inheritance */, dwProcessId);
+ if (hProcess == NULL)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingInspectModules: OpenProcess %x failed with %d\n", dwProcessId, GetLastError());
+ return;
+ }
+
+ HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingInspectModules: CreateToolhelp32Snapshot failed with %d\n", GetLastError());
+ CloseHandle(hProcess);
+ return;
+ }
+
+ VGSvcVerbose(3, "vgsvcPageSharingInspectModules\n");
+
+ MODULEENTRY32 ModuleInfo;
+ BOOL bRet;
+
+ ModuleInfo.dwSize = sizeof(ModuleInfo);
+ bRet = g_pfnModule32First(hSnapshot, &ModuleInfo);
+ do
+ {
+ /** @todo when changing this make sure VBoxService.exe is excluded! */
+ char *pszDot = strrchr(ModuleInfo.szModule, '.');
+ if ( pszDot
+ && (pszDot[1] == 'e' || pszDot[1] == 'E'))
+ continue; /* ignore executables for now. */
+
+ /* Found it before? */
+ PAVLPVNODECORE pRec = RTAvlPVGet(ppNewTree, ModuleInfo.modBaseAddr);
+ if (!pRec)
+ {
+ pRec = RTAvlPVRemove(&g_pKnownModuleTree, ModuleInfo.modBaseAddr);
+ if (!pRec)
+ {
+ /* New module; register it. */
+ PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule));
+ Assert(pModule);
+ if (!pModule)
+ break;
+
+ pModule->Info = ModuleInfo;
+ pModule->Core.Key = ModuleInfo.modBaseAddr;
+ pModule->hModule = LoadLibraryEx(ModuleInfo.szExePath, 0, DONT_RESOLVE_DLL_REFERENCES);
+ if (pModule->hModule)
+ vgsvcPageSharingRegisterModule(pModule, true /* validate pages */);
+
+ VGSvcVerbose(3, "\n\n MODULE NAME: %s", ModuleInfo.szModule );
+ VGSvcVerbose(3, "\n executable = %s", ModuleInfo.szExePath );
+ VGSvcVerbose(3, "\n process ID = 0x%08X", ModuleInfo.th32ProcessID );
+ VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t) ModuleInfo.modBaseAddr );
+ VGSvcVerbose(3, "\n base size = %d", ModuleInfo.modBaseSize );
+
+ pRec = &pModule->Core;
+ }
+ bool ret = RTAvlPVInsert(ppNewTree, pRec);
+ Assert(ret); NOREF(ret);
+ }
+ } while (g_pfnModule32Next(hSnapshot, &ModuleInfo));
+
+ CloseHandle(hSnapshot);
+ CloseHandle(hProcess);
+}
+
+
+/**
+ * Inspect all running processes for executables and dlls that might be worth sharing
+ * with other VMs.
+ *
+ */
+static void vgsvcPageSharingInspectGuest(void)
+{
+ VGSvcVerbose(3, "vgsvcPageSharingInspectGuest\n");
+ PAVLPVNODECORE pNewTree = NULL;
+
+ /*
+ * Check loaded modules for all running processes.
+ */
+ if ( g_pfnProcess32First
+ && g_pfnProcess32Next
+ && g_pfnModule32First
+ && g_pfnModule32Next
+ && g_pfnCreateToolhelp32Snapshot)
+ {
+ HANDLE hSnapshot = g_pfnCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ VGSvcVerbose(3, "vgsvcPageSharingInspectGuest: CreateToolhelp32Snapshot failed with %d\n", GetLastError());
+ return;
+ }
+
+ DWORD const dwProcessId = GetCurrentProcessId();
+
+ PROCESSENTRY32 ProcessInfo;
+ ProcessInfo.dwSize = sizeof(ProcessInfo);
+ g_pfnProcess32First(hSnapshot, &ProcessInfo);
+
+ do
+ {
+ /* Skip our own process. */
+ if (ProcessInfo.th32ProcessID != dwProcessId)
+ vgsvcPageSharingInspectModules(ProcessInfo.th32ProcessID, &pNewTree);
+ }
+ while (g_pfnProcess32Next(hSnapshot, &ProcessInfo));
+
+ CloseHandle(hSnapshot);
+ }
+
+ /*
+ * Check all loaded kernel modules.
+ */
+ if (g_pfnZwQuerySystemInformation)
+ {
+ ULONG cbBuffer = 0;
+ PVOID pBuffer = NULL;
+ PRTL_PROCESS_MODULES pSystemModules;
+
+ NTSTATUS ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer);
+ if (!cbBuffer)
+ {
+ VGSvcVerbose(1, "ZwQuerySystemInformation returned length 0\n");
+ goto skipkernelmodules;
+ }
+
+ pBuffer = RTMemAllocZ(cbBuffer);
+ if (!pBuffer)
+ goto skipkernelmodules;
+
+ ret = g_pfnZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer);
+ if (ret != STATUS_SUCCESS)
+ {
+ VGSvcVerbose(1, "ZwQuerySystemInformation returned %x (1)\n", ret);
+ goto skipkernelmodules;
+ }
+
+ pSystemModules = (PRTL_PROCESS_MODULES)pBuffer;
+ for (unsigned i = 0; i < pSystemModules->NumberOfModules; i++)
+ {
+ VGSvcVerbose(4, "\n\n KERNEL MODULE NAME: %s", pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName] );
+ VGSvcVerbose(4, "\n executable = %s", pSystemModules->Modules[i].FullPathName );
+ VGSvcVerbose(4, "\n flags = 0x%08X\n", pSystemModules->Modules[i].Flags);
+
+ /* User-mode modules seem to have no flags set; skip them as we detected them above. */
+ if (pSystemModules->Modules[i].Flags == 0)
+ continue;
+
+ /* Found it before? */
+ PAVLPVNODECORE pRec = RTAvlPVGet(&pNewTree, pSystemModules->Modules[i].ImageBase);
+ if (!pRec)
+ {
+ pRec = RTAvlPVRemove(&g_pKnownModuleTree, pSystemModules->Modules[i].ImageBase);
+ if (!pRec)
+ {
+ /* New module; register it. */
+ char szFullFilePath[512];
+ PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)RTMemAllocZ(sizeof(*pModule));
+ Assert(pModule);
+ if (!pModule)
+ break;
+
+/** @todo FullPathName not an UTF-8 string is! An ANSI string it is
+ * according to the SYSTEM locale. Best use RtlAnsiStringToUnicodeString to
+ * convert to UTF-16. */
+ strcpy(pModule->Info.szModule,
+ (const char *)&pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName]);
+ GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath));
+
+ /* skip \Systemroot\system32 */
+ char *lpPath = strchr((char *)&pSystemModules->Modules[i].FullPathName[1], '\\');
+ if (!lpPath)
+ {
+ /* Seen just file names in XP; try to locate the file in the system32 and system32\drivers directories. */
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\");
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName);
+ VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath);
+ if (RTFileExists(szFullFilePath) == false)
+ {
+ GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath));
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), "\\drivers\\");
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), (const char *)pSystemModules->Modules[i].FullPathName);
+ VGSvcVerbose(3, "Unexpected kernel module name try %s\n", szFullFilePath);
+ if (RTFileExists(szFullFilePath) == false)
+ {
+ VGSvcVerbose(1, "Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName);
+ RTMemFree(pModule);
+ continue;
+ }
+ }
+ }
+ else
+ {
+ lpPath = strchr(lpPath + 1, '\\');
+ if (!lpPath)
+ {
+ VGSvcVerbose(1, "Unexpected kernel module name %s (2)\n", pSystemModules->Modules[i].FullPathName);
+ RTMemFree(pModule);
+ continue;
+ }
+
+ RTStrCat(szFullFilePath, sizeof(szFullFilePath), lpPath);
+ }
+
+ strcpy(pModule->Info.szExePath, szFullFilePath);
+ pModule->Info.modBaseAddr = (BYTE *)pSystemModules->Modules[i].ImageBase;
+ pModule->Info.modBaseSize = pSystemModules->Modules[i].ImageSize;
+
+ pModule->Core.Key = pSystemModules->Modules[i].ImageBase;
+ vgsvcPageSharingRegisterModule(pModule, false /* don't check memory pages */);
+
+ VGSvcVerbose(3, "\n\n KERNEL MODULE NAME: %s", pModule->Info.szModule );
+ VGSvcVerbose(3, "\n executable = %s", pModule->Info.szExePath );
+ VGSvcVerbose(3, "\n base address = %#010p", (uintptr_t)pModule->Info.modBaseAddr );
+ VGSvcVerbose(3, "\n flags = 0x%08X", pSystemModules->Modules[i].Flags);
+ VGSvcVerbose(3, "\n base size = %d", pModule->Info.modBaseSize );
+
+ pRec = &pModule->Core;
+ }
+ bool fRet = RTAvlPVInsert(&pNewTree, pRec);
+ Assert(fRet); NOREF(fRet);
+ }
+ }
+skipkernelmodules:
+ if (pBuffer)
+ RTMemFree(pBuffer);
+ }
+
+ /* Delete leftover modules in the old tree. */
+ RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, NULL);
+
+ /* Check all registered modules. */
+ VbglR3CheckSharedModules();
+
+ /* Activate new module tree. */
+ g_pKnownModuleTree = pNewTree;
+}
+
+
+/**
+ * RTAvlPVDestroy callback.
+ */
+static DECLCALLBACK(int) vgsvcPageSharingEmptyTreeCallback(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PVGSVCPGSHKNOWNMOD pModule = (PVGSVCPGSHKNOWNMOD)pNode;
+ bool *pfUnregister = (bool *)pvUser;
+
+ VGSvcVerbose(3, "vgsvcPageSharingEmptyTreeCallback %s %s\n", pModule->Info.szModule, pModule->szFileVersion);
+
+ /* Dereference module in the hypervisor. */
+ if ( !pfUnregister
+ || *pfUnregister)
+ {
+ int rc = VbglR3UnregisterSharedModule(pModule->Info.szModule, pModule->szFileVersion,
+ (uintptr_t)pModule->Info.modBaseAddr, pModule->Info.modBaseSize);
+ AssertRC(rc);
+ }
+
+ if (pModule->hModule)
+ FreeLibrary(pModule->hModule);
+ RTMemFree(pNode);
+ return 0;
+}
+
+
+#else /* !RT_OS_WINDOWS */
+
+static void vgsvcPageSharingInspectGuest(void)
+{
+ /** @todo other platforms */
+}
+
+#endif /* !RT_OS_WINDOWS */
+
+/** @interface_method_impl{VBOXSERVICE,pfnInit} */
+static DECLCALLBACK(int) vgsvcPageSharingInit(void)
+{
+ VGSvcVerbose(3, "vgsvcPageSharingInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_PageSharingEvent);
+ AssertRCReturn(rc, rc);
+
+#ifdef RT_OS_WINDOWS
+ rc = VbglR3GetSessionId(&g_idSession);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_IO_GEN_FAILURE)
+ VGSvcVerbose(0, "PageSharing: Page sharing support is not available by the host\n");
+ else
+ VGSvcError("vgsvcPageSharingInit: Failed with rc=%Rrc\n", rc);
+
+ rc = VERR_SERVICE_DISABLED;
+
+ RTSemEventMultiDestroy(g_PageSharingEvent);
+ g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+
+ }
+#endif
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcPageSharingWorker(bool volatile *pfShutdown)
+{
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ bool fEnabled = VbglR3PageSharingIsEnabled();
+ VGSvcVerbose(3, "vgsvcPageSharingWorker: enabled=%d\n", fEnabled);
+
+ if (fEnabled)
+ vgsvcPageSharingInspectGuest();
+
+ /*
+ * Block for a minute.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc = RTSemEventMultiWait(g_PageSharingEvent, 60000);
+ if (*pfShutdown)
+ break;
+ if (rc != VERR_TIMEOUT && RT_FAILURE(rc))
+ {
+ VGSvcError("vgsvcPageSharingWorker: RTSemEventMultiWait failed; rc=%Rrc\n", rc);
+ break;
+ }
+#ifdef RT_OS_WINDOWS
+ uint64_t idNewSession = g_idSession;
+ rc = VbglR3GetSessionId(&idNewSession);
+ AssertRC(rc);
+
+ if (idNewSession != g_idSession)
+ {
+ bool fUnregister = false;
+
+ VGSvcVerbose(3, "vgsvcPageSharingWorker: VM was restored!!\n");
+ /* The VM was restored, so reregister all modules the next time. */
+ RTAvlPVDestroy(&g_pKnownModuleTree, vgsvcPageSharingEmptyTreeCallback, &fUnregister);
+ g_pKnownModuleTree = NULL;
+
+ g_idSession = idNewSession;
+ }
+#endif
+ }
+
+ RTSemEventMultiDestroy(g_PageSharingEvent);
+ g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+
+ VGSvcVerbose(3, "vgsvcPageSharingWorker: finished thread\n");
+ return 0;
+}
+
+#ifdef RT_OS_WINDOWS
+
+/**
+ * This gets control when VBoxService is launched with "pagefusion" by
+ * vgsvcPageSharingWorkerProcess().
+ *
+ * @returns RTEXITCODE_SUCCESS.
+ *
+ * @remarks It won't normally return since the parent drops the shutdown hint
+ * via RTProcTerminate().
+ */
+RTEXITCODE VGSvcPageSharingWorkerChild(void)
+{
+ VGSvcVerbose(3, "vgsvcPageSharingInitFork\n");
+
+ bool fShutdown = false;
+ vgsvcPageSharingInit();
+ vgsvcPageSharingWorker(&fShutdown);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vgsvcPageSharingWorkerProcess(bool volatile *pfShutdown)
+{
+ RTPROCESS hProcess = NIL_RTPROCESS;
+ int rc;
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ bool fEnabled = VbglR3PageSharingIsEnabled();
+ VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: enabled=%d\n", fEnabled);
+
+ /*
+ * Start a 2nd VBoxService process to deal with page fusion as we do
+ * not wish to dummy load dlls into this process. (First load with
+ * DONT_RESOLVE_DLL_REFERENCES, 2nd normal -> dll init routines not called!)
+ */
+ if ( fEnabled
+ && hProcess == NIL_RTPROCESS)
+ {
+ char szExeName[256];
+ char *pszExeName = RTProcGetExecutablePath(szExeName, sizeof(szExeName));
+ if (pszExeName)
+ {
+ char const *papszArgs[3];
+ papszArgs[0] = pszExeName;
+ papszArgs[1] = "pagefusion";
+ papszArgs[2] = NULL;
+ rc = RTProcCreate(pszExeName, papszArgs, RTENV_DEFAULT, 0 /* normal child */, &hProcess);
+ if (RT_FAILURE(rc))
+ VGSvcError("vgsvcPageSharingWorkerProcess: RTProcCreate %s failed; rc=%Rrc\n", pszExeName, rc);
+ }
+ }
+
+ /*
+ * Block for a minute.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ rc = RTSemEventMultiWait(g_PageSharingEvent, 60000);
+ if (*pfShutdown)
+ break;
+ if (rc != VERR_TIMEOUT && RT_FAILURE(rc))
+ {
+ VGSvcError("vgsvcPageSharingWorkerProcess: RTSemEventMultiWait failed; rc=%Rrc\n", rc);
+ break;
+ }
+ }
+
+ if (hProcess != NIL_RTPROCESS)
+ RTProcTerminate(hProcess);
+
+ VGSvcVerbose(3, "vgsvcPageSharingWorkerProcess: finished thread\n");
+ return 0;
+}
+
+#endif /* RT_OS_WINDOWS */
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcPageSharingStop(void)
+{
+ RTSemEventMultiSignal(g_PageSharingEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcPageSharingTerm(void)
+{
+ if (g_PageSharingEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_PageSharingEvent);
+ g_PageSharingEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'pagesharing' service description.
+ */
+VBOXSERVICE g_PageSharing =
+{
+ /* pszName. */
+ "pagesharing",
+ /* pszDescription. */
+ "Page Sharing",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vgsvcPageSharingInit,
+#ifdef RT_OS_WINDOWS
+ vgsvcPageSharingWorkerProcess,
+#else
+ vgsvcPageSharingWorker,
+#endif
+ vgsvcPageSharingStop,
+ vgsvcPageSharingTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp
new file mode 100644
index 00000000..6df00ba3
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.cpp
@@ -0,0 +1,439 @@
+/* $Id: VBoxServicePropCache.cpp $ */
+/** @file
+ * VBoxServicePropCache - Guest property cache.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServicePropCache.h"
+
+
+
+/** @todo Docs */
+static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheFindInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName,
+ uint32_t fFlags)
+{
+ RT_NOREF1(fFlags);
+ AssertPtrReturn(pCache, NULL);
+ AssertPtrReturn(pszName, NULL);
+
+ /** @todo This is a O(n) lookup, maybe improve this later to O(1) using a
+ * map.
+ * r=bird: Use a string space (RTstrSpace*). That is O(log n) in its current
+ * implementation (AVL tree). However, this is not important at the
+ * moment. */
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = NULL;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ if (strcmp(pNodeIt->pszName, pszName) == 0)
+ {
+ pNode = pNodeIt;
+ break;
+ }
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return pNode;
+}
+
+
+/** @todo Docs */
+static PVBOXSERVICEVEPROPCACHEENTRY vgsvcPropCacheInsertEntryInternal(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName)
+{
+ AssertPtrReturn(pCache, NULL);
+ AssertPtrReturn(pszName, NULL);
+
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = (PVBOXSERVICEVEPROPCACHEENTRY)RTMemAlloc(sizeof(VBOXSERVICEVEPROPCACHEENTRY));
+ if (pNode)
+ {
+ pNode->pszName = RTStrDup(pszName);
+ if (!pNode->pszName)
+ {
+ RTMemFree(pNode);
+ return NULL;
+ }
+ pNode->pszValue = NULL;
+ pNode->fFlags = 0;
+ pNode->pszValueReset = NULL;
+
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&pCache->NodeHead, &pNode->NodeSucc);
+ rc = RTCritSectLeave(&pCache->CritSect);
+ }
+ }
+ return pNode;
+}
+
+
+/** @todo Docs */
+static int vgsvcPropCacheWritePropF(uint32_t u32ClientId, const char *pszName, uint32_t fFlags, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ int rc;
+ if (pszValueFormat != NULL)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+
+ char *pszValue;
+ if (RTStrAPrintfV(&pszValue, pszValueFormat, va) >= 0)
+ {
+ if (fFlags & VGSVCPROPCACHE_FLAGS_TRANSIENT)
+ {
+ /*
+ * Because a value can be temporary we have to make sure it also
+ * gets deleted when the property cache did not have the chance to
+ * gracefully clean it up (due to a hard VM reset etc), so set this
+ * guest property using the TRANSRESET flag..
+ */
+ rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSRESET");
+ if (rc == VERR_PARSE_ERROR)
+ {
+ /* Host does not support the "TRANSRESET" flag, so only
+ * use the "TRANSIENT" flag -- better than nothing :-). */
+ rc = VbglR3GuestPropWrite(u32ClientId, pszName, pszValue, "TRANSIENT");
+ /** @todo r=bird: Remember that the host doesn't support
+ * this. */
+ }
+ }
+ else
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, pszValue /* No transient flags set */);
+ RTStrFree(pszValue);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ va_end(va);
+ }
+ else
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL);
+ return rc;
+}
+
+
+/**
+ * Creates a property cache.
+ *
+ * @returns IPRT status code.
+ * @param pCache Pointer to the cache.
+ * @param uClientId The HGCM handle of to the guest property service.
+ */
+int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ /** @todo Prevent init the cache twice!
+ * r=bird: Use a magic. */
+ RTListInit(&pCache->NodeHead);
+ pCache->uClientID = uClientId;
+ return RTCritSectInit(&pCache->CritSect);
+}
+
+
+/**
+ * Updates a cache entry without submitting any changes to the host.
+ *
+ * This is handy for defining default values/flags.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszName The property name.
+ * @param fFlags The property flags to set.
+ * @param pszValueReset The property reset value.
+ */
+int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0);
+ if (pNode == NULL)
+ pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName);
+
+ int rc;
+ if (pNode != NULL)
+ {
+ rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pNode->fFlags = fFlags;
+ if (pszValueReset)
+ {
+ if (pNode->pszValueReset)
+ RTStrFree(pNode->pszValueReset);
+ pNode->pszValueReset = RTStrDup(pszValueReset);
+ AssertPtr(pNode->pszValueReset);
+ }
+ rc = RTCritSectLeave(&pCache->CritSect);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Updates the local guest property cache and writes it to HGCM if outdated.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszName The property name.
+ * @param pszValueFormat The property format string. If this is NULL then
+ * the property will be deleted (if possible).
+ * @param ... Format arguments.
+ */
+int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+
+ Assert(pCache->uClientID);
+
+ /*
+ * Format the value first.
+ */
+ char *pszValue = NULL;
+ if (pszValueFormat)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+ RTStrAPrintfV(&pszValue, pszValueFormat, va);
+ va_end(va);
+ if (!pszValue)
+ return VERR_NO_STR_MEMORY;
+ }
+
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = vgsvcPropCacheFindInternal(pCache, pszName, 0);
+
+ /* Lock the cache. */
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (pNode == NULL)
+ pNode = vgsvcPropCacheInsertEntryInternal(pCache, pszName);
+
+ AssertPtr(pNode);
+ if (pszValue) /* Do we have a value to check for? */
+ {
+ bool fUpdate = false;
+ /* Always update this property, no matter what? */
+ if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE)
+ fUpdate = true;
+ /* Did the value change so we have to update? */
+ else if (pNode->pszValue && strcmp(pNode->pszValue, pszValue) != 0)
+ fUpdate = true;
+ /* No value stored at the moment but we have a value now? */
+ else if (pNode->pszValue == NULL)
+ fUpdate = true;
+
+ if (fUpdate)
+ {
+ /* Write the update. */
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pszValue);
+ VGSvcVerbose(4, "[PropCache %p]: Written '%s'='%s' (flags: %x), rc=%Rrc\n",
+ pCache, pNode->pszName, pszValue, pNode->fFlags, rc);
+ if (RT_SUCCESS(rc)) /* Only update the node's value on successful write. */
+ {
+ RTStrFree(pNode->pszValue);
+ pNode->pszValue = RTStrDup(pszValue);
+ if (!pNode->pszValue)
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VINF_NO_CHANGE; /* No update needed. */
+ }
+ else
+ {
+ /* No value specified. Deletion (or no action required). */
+ if (pNode->pszValue) /* Did we have a value before? Then the value needs to be deleted. */
+ {
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName,
+ 0, /* Flags */ NULL /* Value */);
+ VGSvcVerbose(4, "[PropCache %p]: Deleted '%s'='%s' (flags: %x), rc=%Rrc\n",
+ pCache, pNode->pszName, pNode->pszValue, pNode->fFlags, rc);
+ if (RT_SUCCESS(rc)) /* Only delete property value on successful Vbgl deletion. */
+ {
+ /* Delete property (but do not remove from cache) if not deleted yet. */
+ RTStrFree(pNode->pszValue);
+ pNode->pszValue = NULL;
+ }
+ }
+ else
+ rc = VINF_NO_CHANGE; /* No update needed. */
+ }
+
+ /* Release cache. */
+ RTCritSectLeave(&pCache->CritSect);
+ }
+
+ VGSvcVerbose(4, "[PropCache %p]: Updating '%s' resulted in rc=%Rrc\n", pCache, pszName, rc);
+
+ /* Delete temp stuff. */
+ RTStrFree(pszValue);
+ return rc;
+}
+
+
+/**
+ * Updates all cache values which are matching the specified path.
+ *
+ * @returns VBox status code.
+ *
+ * @param pCache The property cache.
+ * @param pszValue The value to set. A NULL will delete the value.
+ * @param fFlags Flags to set.
+ * @param pszPathFormat The path format string. May not be null and has
+ * to be an absolute path.
+ * @param ... Format arguments.
+ */
+int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags,
+ const char *pszPathFormat, ...)
+{
+ RT_NOREF1(fFlags);
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPathFormat, VERR_INVALID_POINTER);
+
+ int rc = VERR_NOT_FOUND;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ /*
+ * Format the value first.
+ */
+ char *pszPath = NULL;
+ va_list va;
+ va_start(va, pszPathFormat);
+ RTStrAPrintfV(&pszPath, pszPathFormat, va);
+ va_end(va);
+ if (!pszPath)
+ {
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ {
+ /* Iterate through all nodes and compare their paths. */
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ if (RTStrStr(pNodeIt->pszName, pszPath) == pNodeIt->pszName)
+ {
+ /** @todo Use some internal function to update the node directly, this is slow atm. */
+ rc = VGSvcPropCacheUpdate(pCache, pNodeIt->pszName, pszValue);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTStrFree(pszPath);
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return rc;
+}
+
+
+/**
+ * Flushes the cache by writing every item regardless of its state.
+ *
+ * @param pCache The property cache.
+ */
+int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ if (RT_SUCCESS(RTCritSectEnter(&pCache->CritSect)))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNodeIt;
+ RTListForEach(&pCache->NodeHead, pNodeIt, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc)
+ {
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNodeIt->pszName, pNodeIt->fFlags, pNodeIt->pszValue);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+ return rc;
+}
+
+
+/**
+ * Reset all temporary properties and destroy the cache.
+ *
+ * @param pCache The property cache.
+ */
+void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache)
+{
+ AssertPtrReturnVoid(pCache);
+ Assert(pCache->uClientID);
+
+ /* Lock the cache. */
+ int rc = RTCritSectEnter(&pCache->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNode = RTListGetFirst(&pCache->NodeHead, VBOXSERVICEVEPROPCACHEENTRY, NodeSucc);
+ while (pNode)
+ {
+ PVBOXSERVICEVEPROPCACHEENTRY pNext = RTListNodeIsLast(&pCache->NodeHead, &pNode->NodeSucc)
+ ? NULL :
+ RTListNodeGetNext(&pNode->NodeSucc,
+ VBOXSERVICEVEPROPCACHEENTRY, NodeSucc);
+ RTListNodeRemove(&pNode->NodeSucc);
+
+ if (pNode->fFlags & VGSVCPROPCACHE_FLAGS_TEMPORARY)
+ rc = vgsvcPropCacheWritePropF(pCache->uClientID, pNode->pszName, pNode->fFlags, pNode->pszValueReset);
+
+ AssertPtr(pNode->pszName);
+ RTStrFree(pNode->pszName);
+ RTStrFree(pNode->pszValue);
+ RTStrFree(pNode->pszValueReset);
+ pNode->fFlags = 0;
+
+ RTMemFree(pNode);
+
+ pNode = pNext;
+ }
+ RTCritSectLeave(&pCache->CritSect);
+ }
+
+ /* Destroy critical section. */
+ RTCritSectDelete(&pCache->CritSect);
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h
new file mode 100644
index 00000000..4abc4085
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServicePropCache.h
@@ -0,0 +1,66 @@
+/* $Id: */
+/** @file
+ * VBoxServicePropCache - Guest property cache.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxServiceInternal.h"
+
+#ifdef VBOX_WITH_GUEST_PROPS
+
+/** @name VGSVCPROPCACHE_FLAG_XXX - Guest Property Cache Flags.
+ * @{ */
+/** Indicates wheter a guest property is temporary and either should
+ * - a) get a "reset" value assigned (via VBoxServicePropCacheUpdateEntry)
+ * as soon as the property cache gets destroyed, or
+ * - b) get deleted when no reset value is specified.
+ */
+# define VGSVCPROPCACHE_FLAGS_TEMPORARY RT_BIT(1)
+/** Indicates whether a property every time needs to be updated, regardless
+ * if its real value changed or not. */
+# define VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE RT_BIT(2)
+/** The guest property gets deleted when
+ * - a) the property cache gets destroyed, or
+ * - b) the VM gets reset / shutdown / destroyed.
+ */
+# define VGSVCPROPCACHE_FLAGS_TRANSIENT RT_BIT(3)
+/** @} */
+
+int VGSvcPropCacheCreate(PVBOXSERVICEVEPROPCACHE pCache, uint32_t uClientId);
+int VGSvcPropCacheUpdateEntry(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, uint32_t fFlags, const char *pszValueReset);
+int VGSvcPropCacheUpdate(PVBOXSERVICEVEPROPCACHE pCache, const char *pszName, const char *pszValueFormat, ...);
+int VGSvcPropCacheUpdateByPath(PVBOXSERVICEVEPROPCACHE pCache, const char *pszValue, uint32_t fFlags,
+ const char *pszPathFormat, ...);
+int VGSvcPropCacheFlush(PVBOXSERVICEVEPROPCACHE pCache);
+void VGSvcPropCacheDestroy(PVBOXSERVICEVEPROPCACHE pCache);
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServicePropCache_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h
new file mode 100644
index 00000000..b11e862d
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceResource-win.h
@@ -0,0 +1,37 @@
+/* $Id: VBoxServiceResource-win.h $ */
+/** @file
+ * VBoxService - Guest Additions Service, resource IDs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define IDI_VIRTUALBOX 101
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceResource_win_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp
new file mode 100644
index 00000000..8d68b082
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceStats.cpp
@@ -0,0 +1,747 @@
+/* $Id: VBoxServiceStats.cpp $ */
+/** @file
+ * VBoxStats - Guest statistics notification
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_vgsvc_vmstats VBoxService - VM Statistics
+ *
+ * The VM statistics subservice helps out the performance collector API on the
+ * host side by providing metrics from inside the guest.
+ *
+ * See IPerformanceCollector, CollectorGuest and the "Guest/" submetrics that
+ * gets registered by Machine::i_registerMetrics in Main.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#if defined(RT_OS_WINDOWS)
+# include <iprt/win/windows.h>
+# include <psapi.h>
+# include <winternl.h>
+
+#elif defined(RT_OS_LINUX)
+# include <iprt/ctype.h>
+# include <iprt/stream.h>
+# include <unistd.h>
+
+#elif defined(RT_OS_SOLARIS)
+# include <kstat.h>
+# include <sys/sysinfo.h>
+# include <unistd.h>
+#else
+/** @todo port me. */
+
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/ldr.h>
+#include <VBox/param.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <VBox/err.h>
+#include <VBox/VMMDev.h> /* For VMMDevReportGuestStats and indirectly VbglR3StatReport. */
+#include <VBox/VBoxGuestLib.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct VBOXSTATSCONTEXT
+{
+ RTMSINTERVAL cMsStatInterval;
+
+ uint64_t au64LastCpuLoad_Idle[VMM_MAX_CPU_COUNT];
+ uint64_t au64LastCpuLoad_Kernel[VMM_MAX_CPU_COUNT];
+ uint64_t au64LastCpuLoad_User[VMM_MAX_CPU_COUNT];
+ uint64_t au64LastCpuLoad_Nice[VMM_MAX_CPU_COUNT];
+
+#ifdef RT_OS_WINDOWS
+ DECLCALLBACKMEMBER_EX(NTSTATUS, WINAPI, pfnNtQuerySystemInformation,(SYSTEM_INFORMATION_CLASS SystemInformationClass,
+ PVOID SystemInformation, ULONG SystemInformationLength,
+ PULONG ReturnLength));
+ DECLCALLBACKMEMBER_EX(void, WINAPI, pfnGlobalMemoryStatusEx,(LPMEMORYSTATUSEX lpBuffer));
+ DECLCALLBACKMEMBER_EX(BOOL, WINAPI, pfnGetPerformanceInfo,(PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb));
+#endif
+} VBOXSTATSCONTEXT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Global data. */
+static VBOXSTATSCONTEXT g_VMStat = {0};
+
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_VMStatEvent = NIL_RTSEMEVENTMULTI;
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcVMStatsInit(void)
+{
+ VGSvcVerbose(3, "vgsvcVMStatsInit\n");
+
+ int rc = RTSemEventMultiCreate(&g_VMStatEvent);
+ AssertRCReturn(rc, rc);
+
+ g_VMStat.cMsStatInterval = 0; /* default; update disabled */
+ RT_ZERO(g_VMStat.au64LastCpuLoad_Idle);
+ RT_ZERO(g_VMStat.au64LastCpuLoad_Kernel);
+ RT_ZERO(g_VMStat.au64LastCpuLoad_User);
+ RT_ZERO(g_VMStat.au64LastCpuLoad_Nice);
+
+ rc = VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsInit: New statistics interval %u seconds\n", g_VMStat.cMsStatInterval);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsInit: DeviceIoControl failed with %d\n", rc);
+
+#ifdef RT_OS_WINDOWS
+ /* NtQuerySystemInformation might be dropped in future releases, so load
+ it dynamically as per Microsoft's recommendation. */
+ *(void **)&g_VMStat.pfnNtQuerySystemInformation = RTLdrGetSystemSymbol("ntdll.dll", "NtQuerySystemInformation");
+ if (g_VMStat.pfnNtQuerySystemInformation)
+ VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnNtQuerySystemInformation = %x\n", g_VMStat.pfnNtQuerySystemInformation);
+ else
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsInit: ntdll.NtQuerySystemInformation not found!\n");
+ return VERR_SERVICE_DISABLED;
+ }
+
+ /* GlobalMemoryStatus is win2k and up, so load it dynamically */
+ *(void **)&g_VMStat.pfnGlobalMemoryStatusEx = RTLdrGetSystemSymbol("kernel32.dll", "GlobalMemoryStatusEx");
+ if (g_VMStat.pfnGlobalMemoryStatusEx)
+ VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.GlobalMemoryStatusEx = %x\n", g_VMStat.pfnGlobalMemoryStatusEx);
+ else
+ {
+ /** @todo Now fails in NT4; do we care? */
+ VGSvcVerbose(3, "vgsvcVMStatsInit: kernel32.GlobalMemoryStatusEx not found!\n");
+ return VERR_SERVICE_DISABLED;
+ }
+
+ /* GetPerformanceInfo is xp and up, so load it dynamically */
+ *(void **)&g_VMStat.pfnGetPerformanceInfo = RTLdrGetSystemSymbol("psapi.dll", "GetPerformanceInfo");
+ if (g_VMStat.pfnGetPerformanceInfo)
+ VGSvcVerbose(3, "vgsvcVMStatsInit: g_VMStat.pfnGetPerformanceInfo= %x\n", g_VMStat.pfnGetPerformanceInfo);
+#endif /* RT_OS_WINDOWS */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gathers VM statistics and reports them to the host.
+ */
+static void vgsvcVMStatsReport(void)
+{
+#if defined(RT_OS_WINDOWS)
+ Assert(g_VMStat.pfnGlobalMemoryStatusEx && g_VMStat.pfnNtQuerySystemInformation);
+ if ( !g_VMStat.pfnGlobalMemoryStatusEx
+ || !g_VMStat.pfnNtQuerySystemInformation)
+ return;
+
+ /* Clear the report so we don't report garbage should NtQuerySystemInformation
+ behave in an unexpected manner. */
+ VMMDevReportGuestStats req;
+ RT_ZERO(req);
+
+ /* Query and report guest statistics */
+ SYSTEM_INFO systemInfo;
+ GetSystemInfo(&systemInfo);
+
+ MEMORYSTATUSEX memStatus;
+ memStatus.dwLength = sizeof(memStatus);
+ g_VMStat.pfnGlobalMemoryStatusEx(&memStatus);
+
+ req.guestStats.u32PageSize = systemInfo.dwPageSize;
+ req.guestStats.u32PhysMemTotal = (uint32_t)(memStatus.ullTotalPhys / _4K);
+ req.guestStats.u32PhysMemAvail = (uint32_t)(memStatus.ullAvailPhys / _4K);
+ /* The current size of the committed memory limit, in bytes. This is physical
+ memory plus the size of the page file, minus a small overhead. */
+ req.guestStats.u32PageFileSize = (uint32_t)(memStatus.ullTotalPageFile / _4K) - req.guestStats.u32PhysMemTotal;
+ req.guestStats.u32MemoryLoad = memStatus.dwMemoryLoad;
+ req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL
+ | VBOX_GUEST_STAT_PHYS_MEM_AVAIL
+ | VBOX_GUEST_STAT_PAGE_FILE_SIZE
+ | VBOX_GUEST_STAT_MEMORY_LOAD;
+# ifdef VBOX_WITH_MEMBALLOON
+ req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON;
+# else
+ req.guestStats.u32PhysMemBalloon = 0;
+# endif
+
+ if (g_VMStat.pfnGetPerformanceInfo)
+ {
+ PERFORMANCE_INFORMATION perfInfo;
+
+ if (g_VMStat.pfnGetPerformanceInfo(&perfInfo, sizeof(perfInfo)))
+ {
+ req.guestStats.u32Processes = perfInfo.ProcessCount;
+ req.guestStats.u32Threads = perfInfo.ThreadCount;
+ req.guestStats.u32Handles = perfInfo.HandleCount;
+ req.guestStats.u32MemCommitTotal = perfInfo.CommitTotal; /* already in pages */
+ req.guestStats.u32MemKernelTotal = perfInfo.KernelTotal; /* already in pages */
+ req.guestStats.u32MemKernelPaged = perfInfo.KernelPaged; /* already in pages */
+ req.guestStats.u32MemKernelNonPaged = perfInfo.KernelNonpaged; /* already in pages */
+ req.guestStats.u32MemSystemCache = perfInfo.SystemCache; /* already in pages */
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PROCESSES | VBOX_GUEST_STAT_THREADS | VBOX_GUEST_STAT_HANDLES
+ | VBOX_GUEST_STAT_MEM_COMMIT_TOTAL | VBOX_GUEST_STAT_MEM_KERNEL_TOTAL
+ | VBOX_GUEST_STAT_MEM_KERNEL_PAGED | VBOX_GUEST_STAT_MEM_KERNEL_NONPAGED
+ | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE;
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: GetPerformanceInfo failed with %d\n", GetLastError());
+ }
+
+ /* Query CPU load information */
+ uint32_t cbStruct = systemInfo.dwNumberOfProcessors * sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION);
+ PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION pProcInfo;
+ pProcInfo = (PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)RTMemAlloc(cbStruct);
+ if (!pProcInfo)
+ return;
+
+ /* Unfortunately GetSystemTimes is XP SP1 and up only, so we need to use the semi-undocumented NtQuerySystemInformation */
+ bool fCpuInfoAvail = false;
+ DWORD cbReturned;
+ NTSTATUS rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
+ if ( !rcNt
+ && cbReturned == cbStruct)
+ {
+ for (uint32_t i = 0; i < systemInfo.dwNumberOfProcessors; i++)
+ {
+ if (i >= VMM_MAX_CPU_COUNT)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPUs %u..%u\n", i, systemInfo.dwNumberOfProcessors);
+ break;
+ }
+
+ if (g_VMStat.au64LastCpuLoad_Kernel[i] == 0)
+ {
+ /* first time */
+ g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart;
+
+ Sleep(250);
+
+ rcNt = g_VMStat.pfnNtQuerySystemInformation(SystemProcessorPerformanceInformation, pProcInfo, cbStruct, &cbReturned);
+ Assert(!rcNt);
+ }
+
+ uint64_t deltaIdle = (pProcInfo[i].IdleTime.QuadPart - g_VMStat.au64LastCpuLoad_Idle[i]);
+ uint64_t deltaKernel = (pProcInfo[i].KernelTime.QuadPart - g_VMStat.au64LastCpuLoad_Kernel[i]);
+ uint64_t deltaUser = (pProcInfo[i].UserTime.QuadPart - g_VMStat.au64LastCpuLoad_User[i]);
+ deltaKernel -= deltaIdle; /* idle time is added to kernel time */
+ uint64_t ullTotalTime = deltaIdle + deltaKernel + deltaUser;
+ if (ullTotalTime == 0) /* Prevent division through zero. */
+ ullTotalTime = 1;
+
+ req.guestStats.u32CpuLoad_Idle = (uint32_t)(deltaIdle * 100 / ullTotalTime);
+ req.guestStats.u32CpuLoad_Kernel = (uint32_t)(deltaKernel* 100 / ullTotalTime);
+ req.guestStats.u32CpuLoad_User = (uint32_t)(deltaUser * 100 / ullTotalTime);
+
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE
+ | VBOX_GUEST_STAT_CPU_LOAD_KERNEL
+ | VBOX_GUEST_STAT_CPU_LOAD_USER;
+ req.guestStats.u32CpuId = i;
+ fCpuInfoAvail = true;
+ int rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", i);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: VbglR3StatReport failed with rc=%Rrc\n", rc);
+
+ g_VMStat.au64LastCpuLoad_Idle[i] = pProcInfo[i].IdleTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_Kernel[i] = pProcInfo[i].KernelTime.QuadPart;
+ g_VMStat.au64LastCpuLoad_User[i] = pProcInfo[i].UserTime.QuadPart;
+ }
+ }
+ RTMemFree(pProcInfo);
+
+ if (!fCpuInfoAvail)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n");
+ int rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n");
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+
+#elif defined(RT_OS_LINUX)
+ VMMDevReportGuestStats req;
+ RT_ZERO(req);
+ PRTSTREAM pStrm;
+ char szLine[256];
+ char *psz;
+
+ int rc = RTStrmOpen("/proc/meminfo", "r", &pStrm);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t u64Kb;
+ uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0;
+ for (;;)
+ {
+ rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine));
+ if (RT_FAILURE(rc))
+ break;
+ if (strstr(szLine, "MemTotal:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[9]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Total = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "MemFree:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Free = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "Buffers:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[8]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Buffers = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "Cached:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[7]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64Cached = u64Kb * _1K;
+ }
+ else if (strstr(szLine, "SwapTotal:") == szLine)
+ {
+ rc = RTStrToUInt64Ex(RTStrStripL(&szLine[10]), &psz, 0, &u64Kb);
+ if (RT_SUCCESS(rc))
+ u64PagedTotal = u64Kb * _1K;
+ }
+ }
+ req.guestStats.u32PhysMemTotal = u64Total / _4K;
+ req.guestStats.u32PhysMemAvail = (u64Free + u64Buffers + u64Cached) / _4K;
+ req.guestStats.u32MemSystemCache = (u64Buffers + u64Cached) / _4K;
+ req.guestStats.u32PageFileSize = u64PagedTotal / _4K;
+ RTStrmClose(pStrm);
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: memory info not available!\n");
+
+ req.guestStats.u32PageSize = getpagesize();
+ req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL
+ | VBOX_GUEST_STAT_PHYS_MEM_AVAIL
+ | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE
+ | VBOX_GUEST_STAT_PAGE_FILE_SIZE;
+# ifdef VBOX_WITH_MEMBALLOON
+ req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON;
+# else
+ req.guestStats.u32PhysMemBalloon = 0;
+# endif
+
+
+ /** @todo req.guestStats.u32Threads */
+ /** @todo req.guestStats.u32Processes */
+ /* req.guestStats.u32Handles doesn't make sense here. */
+ /** @todo req.guestStats.u32MemoryLoad */
+ /** @todo req.guestStats.u32MemCommitTotal */
+ /** @todo req.guestStats.u32MemKernelTotal */
+ /** @todo req.guestStats.u32MemKernelPaged, make any sense? = u32MemKernelTotal? */
+ /** @todo req.guestStats.u32MemKernelNonPaged, make any sense? = 0? */
+
+ bool fCpuInfoAvail = false;
+ rc = RTStrmOpen("/proc/stat", "r", &pStrm);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine));
+ if (RT_FAILURE(rc))
+ break;
+ if ( strstr(szLine, "cpu") == szLine
+ && strlen(szLine) > 3
+ && RT_C_IS_DIGIT(szLine[3]))
+ {
+ uint32_t u32CpuId;
+ rc = RTStrToUInt32Ex(&szLine[3], &psz, 0, &u32CpuId);
+ if (u32CpuId < VMM_MAX_CPU_COUNT)
+ {
+ uint64_t u64User = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64User);
+
+ uint64_t u64Nice = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Nice);
+
+ uint64_t u64System = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64System);
+
+ uint64_t u64Idle = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Ex(RTStrStripL(psz), &psz, 0, &u64Idle);
+
+ uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[u32CpuId];
+ uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[u32CpuId];
+ uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[u32CpuId];
+ uint64_t u64DeltaNice = u64Nice - g_VMStat.au64LastCpuLoad_Nice[u32CpuId];
+
+ uint64_t u64DeltaAll = u64DeltaIdle
+ + u64DeltaSystem
+ + u64DeltaUser
+ + u64DeltaNice;
+ if (u64DeltaAll == 0) /* Prevent division through zero. */
+ u64DeltaAll = 1;
+
+ g_VMStat.au64LastCpuLoad_Idle[u32CpuId] = u64Idle;
+ g_VMStat.au64LastCpuLoad_Kernel[u32CpuId] = u64System;
+ g_VMStat.au64LastCpuLoad_User[u32CpuId] = u64User;
+ g_VMStat.au64LastCpuLoad_Nice[u32CpuId] = u64Nice;
+
+ req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_User = (uint32_t)((u64DeltaUser
+ + u64DeltaNice) * 100 / u64DeltaAll);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE
+ | VBOX_GUEST_STAT_CPU_LOAD_KERNEL
+ | VBOX_GUEST_STAT_CPU_LOAD_USER;
+ req.guestStats.u32CpuId = u32CpuId;
+ fCpuInfoAvail = true;
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", u32CpuId);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", u32CpuId);
+ }
+ }
+ RTStrmClose(pStrm);
+ }
+ if (!fCpuInfoAvail)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n");
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n");
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+
+#elif defined(RT_OS_SOLARIS)
+ VMMDevReportGuestStats req;
+ RT_ZERO(req);
+ kstat_ctl_t *pStatKern = kstat_open();
+ if (pStatKern)
+ {
+ /*
+ * Memory statistics.
+ */
+ uint64_t u64Total = 0, u64Free = 0, u64Buffers = 0, u64Cached = 0, u64PagedTotal = 0;
+ int rc = -1;
+ kstat_t *pStatPages = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"system_pages");
+ if (pStatPages)
+ {
+ rc = kstat_read(pStatKern, pStatPages, NULL /* optional-copy-buf */);
+ if (rc != -1)
+ {
+ kstat_named_t *pStat = NULL;
+ pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"pagestotal");
+ if (pStat)
+ u64Total = pStat->value.ul;
+
+ pStat = (kstat_named_t *)kstat_data_lookup(pStatPages, (char *)"freemem");
+ if (pStat)
+ u64Free = pStat->value.ul;
+ }
+ }
+
+ kstat_t *pStatZFS = kstat_lookup(pStatKern, (char *)"zfs", 0 /* instance */, (char *)"arcstats");
+ if (pStatZFS)
+ {
+ rc = kstat_read(pStatKern, pStatZFS, NULL /* optional-copy-buf */);
+ if (rc != -1)
+ {
+ kstat_named_t *pStat = (kstat_named_t *)kstat_data_lookup(pStatZFS, (char *)"size");
+ if (pStat)
+ u64Cached = pStat->value.ul;
+ }
+ }
+
+ /*
+ * The vminfo are accumulative counters updated every "N" ticks. Let's get the
+ * number of stat updates so far and use that to divide the swap counter.
+ */
+ kstat_t *pStatInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"sysinfo");
+ if (pStatInfo)
+ {
+ sysinfo_t SysInfo;
+ rc = kstat_read(pStatKern, pStatInfo, &SysInfo);
+ if (rc != -1)
+ {
+ kstat_t *pStatVMInfo = kstat_lookup(pStatKern, (char *)"unix", 0 /* instance */, (char *)"vminfo");
+ if (pStatVMInfo)
+ {
+ vminfo_t VMInfo;
+ rc = kstat_read(pStatKern, pStatVMInfo, &VMInfo);
+ if (rc != -1)
+ {
+ Assert(SysInfo.updates != 0);
+ u64PagedTotal = VMInfo.swap_avail / SysInfo.updates;
+ }
+ }
+ }
+ }
+
+ req.guestStats.u32PhysMemTotal = u64Total; /* already in pages */
+ req.guestStats.u32PhysMemAvail = u64Free; /* already in pages */
+ req.guestStats.u32MemSystemCache = u64Cached / _4K;
+ req.guestStats.u32PageFileSize = u64PagedTotal; /* already in pages */
+ /** @todo req.guestStats.u32Threads */
+ /** @todo req.guestStats.u32Processes */
+ /** @todo req.guestStats.u32Handles -- ??? */
+ /** @todo req.guestStats.u32MemoryLoad */
+ /** @todo req.guestStats.u32MemCommitTotal */
+ /** @todo req.guestStats.u32MemKernelTotal */
+ /** @todo req.guestStats.u32MemKernelPaged */
+ /** @todo req.guestStats.u32MemKernelNonPaged */
+ req.guestStats.u32PageSize = getpagesize();
+
+ req.guestStats.u32StatCaps = VBOX_GUEST_STAT_PHYS_MEM_TOTAL
+ | VBOX_GUEST_STAT_PHYS_MEM_AVAIL
+ | VBOX_GUEST_STAT_MEM_SYSTEM_CACHE
+ | VBOX_GUEST_STAT_PAGE_FILE_SIZE;
+# ifdef VBOX_WITH_MEMBALLOON
+ req.guestStats.u32PhysMemBalloon = VGSvcBalloonQueryPages(_4K);
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_PHYS_MEM_BALLOON;
+# else
+ req.guestStats.u32PhysMemBalloon = 0;
+# endif
+
+ /*
+ * CPU statistics.
+ */
+ cpu_stat_t StatCPU;
+ RT_ZERO(StatCPU);
+ kstat_t *pStatNode = NULL;
+ uint32_t cCPUs = 0;
+ bool fCpuInfoAvail = false;
+ for (pStatNode = pStatKern->kc_chain; pStatNode != NULL; pStatNode = pStatNode->ks_next)
+ {
+ if (!strcmp(pStatNode->ks_module, "cpu_stat"))
+ {
+ rc = kstat_read(pStatKern, pStatNode, &StatCPU);
+ if (rc == -1)
+ break;
+
+ if (cCPUs < VMM_MAX_CPU_COUNT)
+ {
+ uint64_t u64Idle = StatCPU.cpu_sysinfo.cpu[CPU_IDLE];
+ uint64_t u64User = StatCPU.cpu_sysinfo.cpu[CPU_USER];
+ uint64_t u64System = StatCPU.cpu_sysinfo.cpu[CPU_KERNEL];
+
+ uint64_t u64DeltaIdle = u64Idle - g_VMStat.au64LastCpuLoad_Idle[cCPUs];
+ uint64_t u64DeltaSystem = u64System - g_VMStat.au64LastCpuLoad_Kernel[cCPUs];
+ uint64_t u64DeltaUser = u64User - g_VMStat.au64LastCpuLoad_User[cCPUs];
+
+ uint64_t u64DeltaAll = u64DeltaIdle + u64DeltaSystem + u64DeltaUser;
+ if (u64DeltaAll == 0) /* Prevent division through zero. */
+ u64DeltaAll = 1;
+
+ g_VMStat.au64LastCpuLoad_Idle[cCPUs] = u64Idle;
+ g_VMStat.au64LastCpuLoad_Kernel[cCPUs] = u64System;
+ g_VMStat.au64LastCpuLoad_User[cCPUs] = u64User;
+
+ req.guestStats.u32CpuId = cCPUs;
+ req.guestStats.u32CpuLoad_Idle = (uint32_t)(u64DeltaIdle * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_Kernel = (uint32_t)(u64DeltaSystem * 100 / u64DeltaAll);
+ req.guestStats.u32CpuLoad_User = (uint32_t)(u64DeltaUser * 100 / u64DeltaAll);
+
+ req.guestStats.u32StatCaps |= VBOX_GUEST_STAT_CPU_LOAD_IDLE
+ | VBOX_GUEST_STAT_CPU_LOAD_KERNEL
+ | VBOX_GUEST_STAT_CPU_LOAD_USER;
+ fCpuInfoAvail = true;
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics (CPU %u) reported successfully!\n", cCPUs);
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ cCPUs++;
+ }
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: skipping information for CPU%u\n", cCPUs);
+ }
+ }
+
+ /*
+ * Report whatever statistics were collected.
+ */
+ if (!fCpuInfoAvail)
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsReport: CPU info not available!\n");
+ rc = VbglR3StatReport(&req);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsReport: new statistics reported successfully!\n");
+ else
+ VGSvcVerbose(3, "vgsvcVMStatsReport: stats report failed with rc=%Rrc\n", rc);
+ }
+
+ kstat_close(pStatKern);
+ }
+
+#else
+ /** @todo implement for other platforms. */
+
+#endif
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+DECLCALLBACK(int) vgsvcVMStatsWorker(bool volatile *pfShutdown)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Start monitoring of the stat event change event. */
+ rc = VbglR3CtlFilterMask(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ uint32_t fEvents = 0;
+ RTMSINTERVAL cWaitMillies;
+
+ /* Check if an update interval change is pending. */
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST, 0 /* no wait */, &fEvents);
+ if ( RT_SUCCESS(rc)
+ && (fEvents & VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST))
+ VbglR3StatQueryInterval(&g_VMStat.cMsStatInterval);
+
+ if (g_VMStat.cMsStatInterval)
+ {
+ vgsvcVMStatsReport();
+ cWaitMillies = g_VMStat.cMsStatInterval;
+ }
+ else
+ cWaitMillies = 3000;
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc2 = RTSemEventMultiWait(g_VMStatEvent, cWaitMillies);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("vgsvcVMStatsWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ }
+
+ /* Cancel monitoring of the stat event change event. */
+ rc = VbglR3CtlFilterMask(0, VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST);
+ if (RT_FAILURE(rc))
+ VGSvcVerbose(3, "vgsvcVMStatsWorker: VbglR3CtlFilterMask failed with %d\n", rc);
+
+ VGSvcVerbose(3, "VBoxStatsThread: finished statistics change request thread\n");
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcVMStatsStop(void)
+{
+ RTSemEventMultiSignal(g_VMStatEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcVMStatsTerm(void)
+{
+ if (g_VMStatEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_VMStatEvent);
+ g_VMStatEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'vminfo' service description.
+ */
+VBOXSERVICE g_VMStatistics =
+{
+ /* pszName. */
+ "vmstats",
+ /* pszDescription. */
+ "Virtual Machine Statistics",
+ /* pszUsage. */
+ NULL,
+ /* pszOptions. */
+ NULL,
+ /* methods */
+ VGSvcDefaultPreInit,
+ VGSvcDefaultOption,
+ vgsvcVMStatsInit,
+ vgsvcVMStatsWorker,
+ vgsvcVMStatsStop,
+ vgsvcVMStatsTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp
new file mode 100644
index 00000000..ae5dd69b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceTimeSync.cpp
@@ -0,0 +1,807 @@
+/* $Id: VBoxServiceTimeSync.cpp $ */
+/** @file
+ * VBoxService - Guest Additions TimeSync Service.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_vgsvc_timesync VBoxService - The Time Sync Service
+ *
+ * The time sync subservice synchronizes the guest OS walltime with the host.
+ *
+ * The time sync service plays along with the Time Manager (TM) in the VMM
+ * to keep the guest time accurate using the host machine as a reference.
+ * Communication is facilitated by VMMDev. TM will try its best to make sure
+ * all timer ticks get delivered so that there isn't normally any need to
+ * adjust the guest time.
+ *
+ * There are three normal (= acceptable) cases:
+ * -# When the service starts up. This is because ticks and such might
+ * be lost during VM and OS startup. (Need to figure out exactly why!)
+ * -# When the TM is unable to deliver all the ticks and swallows a
+ * backlog of ticks. The threshold for this is configurable with
+ * a default of 60 seconds.
+ * -# The time is adjusted on the host. This can be caused manually by
+ * the user or by some time sync daemon (NTP, LAN server, etc.).
+ *
+ * There are a number of very odd case where adjusting is needed. Here
+ * are some of them:
+ * -# Timer device emulation inaccuracies (like rounding).
+ * -# Inaccuracies in time source VirtualBox uses.
+ * -# The Guest and/or Host OS doesn't perform proper time keeping. This
+ * can come about as a result of OS and/or hardware issues.
+ *
+ * The TM is our source for the host time and will make adjustments for
+ * current timer delivery lag. The simplistic approach taken by TM is to
+ * adjust the host time by the current guest timer delivery lag, meaning that
+ * if the guest is behind 1 second with PIT/RTC/++ ticks, this should be
+ * reflected in the guest wall time as well.
+ *
+ * Now, there is any amount of trouble we can cause by changing the time.
+ * Most applications probably use the wall time when they need to measure
+ * things. A walltime that is being juggled about every so often, even if just
+ * a little bit, could occasionally upset these measurements by for instance
+ * yielding negative results.
+ *
+ * This bottom line here is that the time sync service isn't really supposed
+ * to do anything and will try avoid having to do anything when possible.
+ *
+ * The implementation uses the latency it takes to query host time as the
+ * absolute maximum precision to avoid messing up under timer tick catchup
+ * and/or heavy host/guest load. (Rationale is that a *lot* of stuff may
+ * happen on our way back from ring-3 and TM/VMMDev since we're taking the
+ * route thru the inner EM loop with its force flag processing.)
+ *
+ * But this latency has to be measured from our perspective, which means it
+ * could just as easily come out as 0. (OS/2 and Windows guests only update
+ * the current time when the timer ticks for instance.) The good thing is
+ * that this isn't really a problem since we won't ever do anything unless
+ * the drift is noticeable.
+ *
+ * It now boils down to these three (configuration) factors:
+ * -# g_cMsTimeSyncMinAdjust - The minimum drift we will ever bother with.
+ * -# g_TimeSyncLatencyFactor - The factor we multiply the latency by to
+ * calculate the dynamic minimum adjust factor.
+ * -# g_cMsTimeSyncMaxLatency - When to start discarding the data as utterly
+ * useless and take a rest (someone is too busy to give us good data).
+ * -# g_TimeSyncSetThreshold - The threshold at which we will just set the time
+ * instead of trying to adjust it (milliseconds).
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#else
+# include <unistd.h>
+# include <errno.h>
+# include <time.h>
+# include <sys/time.h>
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <VBox/err.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The timesync interval (milliseconds). */
+static uint32_t g_TimeSyncInterval = 0;
+/**
+ * @see pg_vgsvc_timesync
+ *
+ * @remark OS/2: There is either a 1 second resolution on the DosSetDateTime
+ * API or a bug in my settimeofday implementation. Thus, don't
+ * bother unless there is at least a 1 second drift.
+ */
+#ifdef RT_OS_OS2
+static uint32_t g_cMsTimeSyncMinAdjust = 1000;
+#else
+static uint32_t g_cMsTimeSyncMinAdjust = 100;
+#endif
+/** @see pg_vgsvc_timesync */
+static uint32_t g_TimeSyncLatencyFactor = 8;
+/** @see pg_vgsvc_timesync */
+static uint32_t g_cMsTimeSyncMaxLatency = 250;
+/** @see pg_vgsvc_timesync */
+static uint32_t g_TimeSyncSetThreshold = 20*60*1000;
+/** Whether the next adjustment should just set the time instead of trying to
+ * adjust it. This is used to implement --timesync-set-start.
+ * For purposes of setting the kernel timezone, OS/2 always starts with this. */
+#ifdef RT_OS_OS2
+static bool volatile g_fTimeSyncSetOnStart = true;
+#else
+static bool volatile g_fTimeSyncSetOnStart = false;
+#endif
+/** Whether to set the time when the VM was restored. */
+static bool g_fTimeSyncSetOnRestore = true;
+/** The logging verbosity level.
+ * This uses the global verbosity level by default. */
+static uint32_t g_cTimeSyncVerbosity = 0;
+
+/** Current error count. Used to decide when to bitch and when not to. */
+static uint32_t g_cTimeSyncErrors = 0;
+
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_TimeSyncEvent = NIL_RTSEMEVENTMULTI;
+
+/** The VM session ID. Changes whenever the VM is restored or reset. */
+static uint64_t g_idTimeSyncSession;
+
+#ifdef RT_OS_WINDOWS
+/** Process token. */
+static HANDLE g_hTokenProcess = NULL;
+/** Old token privileges. */
+static TOKEN_PRIVILEGES g_TkOldPrivileges;
+/** Backup values for time adjustment. */
+static DWORD g_dwWinTimeAdjustment;
+static DWORD g_dwWinTimeIncrement;
+static BOOL g_bWinTimeAdjustmentDisabled;
+#endif
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vgsvcTimeSyncPreInit(void)
+{
+ /* Use global verbosity as default. */
+ g_cTimeSyncVerbosity = g_cVerbosity;
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ /** @todo Merge this function with vgsvcTimeSyncOption() to generalize
+ * the "command line args override guest property values" behavior. */
+
+ /*
+ * Read the service options from the VM's guest properties.
+ * Note that these options can be overridden by the command line options later.
+ */
+ uint32_t uGuestPropSvcClientID;
+ int rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "VMInfo: Guest property service is not available, skipping\n");
+ rc = VINF_SUCCESS;
+ }
+ else
+ VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
+ }
+ else
+ {
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-interval",
+ &g_TimeSyncInterval, 50, UINT32_MAX - 1);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-min-adjust",
+ &g_cMsTimeSyncMinAdjust, 0, 3600000);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-latency-factor",
+ &g_TimeSyncLatencyFactor, 1, 1024);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-max-latency",
+ &g_cMsTimeSyncMaxLatency, 1, 3600000);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-threshold",
+ &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000 /* a week */);
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID,
+ "/VirtualBox/GuestAdd/VBoxService/--timesync-set-start"))
+ g_fTimeSyncSetOnStart = true;
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-start"))
+ g_fTimeSyncSetOnStart = false;
+
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-set-on-restore"))
+ g_fTimeSyncSetOnRestore = true;
+
+ if (VbglR3GuestPropExist(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-no-set-on-restore"))
+ g_fTimeSyncSetOnRestore = false;
+
+ uint32_t uValue;
+ rc = VGSvcReadPropUInt32(uGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--timesync-verbosity",
+ &uValue, 0 /*uMin*/, 255 /*uMax*/);
+ if (RT_SUCCESS(rc))
+ g_cTimeSyncVerbosity = uValue;
+
+ VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
+ }
+
+ if (rc == VERR_NOT_FOUND) /* If a value is not found, don't be sad! */
+ rc = VINF_SUCCESS;
+ return rc;
+#else
+ /* Nothing to do here yet. */
+ return VINF_SUCCESS;
+#endif
+}
+
+
+/**
+ * Displays a verbose message based on the currently
+ * set timesync verbosity level.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+static void vgsvcTimeSyncLog(unsigned iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_cTimeSyncVerbosity)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ VGSvcLogV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vgsvcTimeSyncOption(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ int rc = VINF_SUCCESS;
+ if (ppszShort)
+ rc = -1 ;/* no short options */
+ else if (!strcmp(argv[*pi], "--timesync-interval"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncInterval, 50, UINT32_MAX - 1);
+ else if (!strcmp(argv[*pi], "--timesync-min-adjust"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMinAdjust, 0, 3600000);
+ else if (!strcmp(argv[*pi], "--timesync-latency-factor"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncLatencyFactor, 1, 1024);
+ else if (!strcmp(argv[*pi], "--timesync-max-latency"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsTimeSyncMaxLatency, 1, 3600000);
+ else if (!strcmp(argv[*pi], "--timesync-set-threshold"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_TimeSyncSetThreshold, 0, 7*24*60*60*1000); /* a week */
+ else if (!strcmp(argv[*pi], "--timesync-set-start"))
+ g_fTimeSyncSetOnStart = true;
+ else if (!strcmp(argv[*pi], "--timesync-no-set-start"))
+ g_fTimeSyncSetOnStart = false;
+ else if (!strcmp(argv[*pi], "--timesync-set-on-restore"))
+ g_fTimeSyncSetOnRestore = true;
+ else if (!strcmp(argv[*pi], "--timesync-no-set-on-restore"))
+ g_fTimeSyncSetOnRestore = false;
+ else if (!strcmp(argv[*pi], "--timesync-verbosity"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cTimeSyncVerbosity, 0 /*uMin*/, 255 /*uMax*/);
+ else
+ rc = -1;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vgsvcTimeSyncInit(void)
+{
+ /*
+ * If not specified, find the right interval default.
+ * Then create the event sem to block on.
+ */
+ if (!g_TimeSyncInterval)
+ g_TimeSyncInterval = g_DefaultInterval * 1000;
+ if (!g_TimeSyncInterval)
+ g_TimeSyncInterval = 10 * 1000;
+
+ VbglR3GetSessionId(&g_idTimeSyncSession);
+ /* The status code is ignored as this information is not available with VBox < 3.2.10. */
+
+ int rc = RTSemEventMultiCreate(&g_TimeSyncEvent);
+ AssertRC(rc);
+#ifdef RT_OS_WINDOWS
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Adjust privileges of this process so we can make system time adjustments.
+ */
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &g_hTokenProcess))
+ {
+ TOKEN_PRIVILEGES tkPriv;
+ RT_ZERO(tkPriv);
+ tkPriv.PrivilegeCount = 1;
+ tkPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+ if (LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &tkPriv.Privileges[0].Luid))
+ {
+ DWORD cbRet = sizeof(g_TkOldPrivileges);
+ if (AdjustTokenPrivileges(g_hTokenProcess, FALSE, &tkPriv, sizeof(TOKEN_PRIVILEGES), &g_TkOldPrivileges, &cbRet))
+ rc = VINF_SUCCESS;
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Adjusting token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
+ dwErr, rc);
+ }
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Looking up token privileges (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
+ dwErr, rc);
+ }
+ if (RT_FAILURE(rc))
+ {
+ CloseHandle(g_hTokenProcess);
+ g_hTokenProcess = NULL;
+ }
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Opening process token (SE_SYSTEMTIME_NAME) failed with status code %u/%Rrc!\n",
+ dwErr, rc);
+ g_hTokenProcess = NULL;
+ }
+ }
+
+ if (g_pfnGetSystemTimeAdjustment)
+ {
+ if (g_pfnGetSystemTimeAdjustment(&g_dwWinTimeAdjustment, &g_dwWinTimeIncrement, &g_bWinTimeAdjustmentDisabled))
+ vgsvcTimeSyncLog(0, "vgsvcTimeSyncInit: Initially %ld (100ns) units per %ld (100 ns) units interval, disabled=%d\n",
+ g_dwWinTimeAdjustment, g_dwWinTimeIncrement, g_bWinTimeAdjustmentDisabled ? 1 : 0);
+ else
+ {
+ DWORD dwErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwErr);
+ VGSvcError("vgsvcTimeSyncInit: Could not get time adjustment values! Last error: %ld!\n", dwErr);
+ }
+ }
+#endif /* RT_OS_WINDOWS */
+
+ return rc;
+}
+
+
+/**
+ * Try adjusting the time using adjtime or similar.
+ *
+ * @returns true on success, false on failure.
+ *
+ * @param pDrift The time adjustment.
+ */
+static bool vgsvcTimeSyncAdjust(PCRTTIMESPEC pDrift)
+{
+#ifdef RT_OS_WINDOWS
+/** @todo r=bird: g_hTokenProcess cannot be NULL here.
+ * vgsvcTimeSyncInit will fail and the service will not be started with
+ * it being NULL. vgsvcTimeSyncInit OTOH will *NOT* be called until the
+ * service thread has terminated. If anything
+ * else is the case, there is buggy code somewhere.*/
+ if (g_hTokenProcess == NULL) /* Is the token already closed when shutting down? */
+ return false;
+
+ /* The API appeared in NT 3.50. */
+ if ( !g_pfnSetSystemTimeAdjustment
+ || !g_pfnGetSystemTimeAdjustment)
+ return false;
+
+ DWORD dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwWinTimeIncrement;
+ BOOL fWinTimeAdjustmentDisabled;
+ if (g_pfnGetSystemTimeAdjustment(&dwWinTimeAdjustment, &dwWinTimeIncrement, &fWinTimeAdjustmentDisabled))
+ {
+ DWORD dwDiffMax = g_dwWinTimeAdjustment * 0.50;
+ DWORD dwDiffNew = dwWinTimeAdjustment * 0.10;
+
+ if (RTTimeSpecGetMilli(pDrift) > 0)
+ {
+ dwWinNewTimeAdjustment = dwWinTimeAdjustment + dwDiffNew;
+ if (dwWinNewTimeAdjustment > (g_dwWinTimeAdjustment + dwDiffMax))
+ {
+ dwWinNewTimeAdjustment = g_dwWinTimeAdjustment + dwDiffMax;
+ dwDiffNew = dwDiffMax;
+ }
+ }
+ else
+ {
+ dwWinNewTimeAdjustment = dwWinTimeAdjustment - dwDiffNew;
+ if (dwWinNewTimeAdjustment < (g_dwWinTimeAdjustment - dwDiffMax))
+ {
+ dwWinNewTimeAdjustment = g_dwWinTimeAdjustment - dwDiffMax;
+ dwDiffNew = dwDiffMax;
+ }
+ }
+
+ vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: Drift=%lldms\n", RTTimeSpecGetMilli(pDrift));
+ vgsvcTimeSyncLog(3, "vgsvcTimeSyncAdjust: OrgTA=%ld, CurTA=%ld, NewTA=%ld, DiffNew=%ld, DiffMax=%ld\n",
+ g_dwWinTimeAdjustment, dwWinTimeAdjustment, dwWinNewTimeAdjustment, dwDiffNew, dwDiffMax);
+ if (g_pfnSetSystemTimeAdjustment(dwWinNewTimeAdjustment, FALSE /* Periodic adjustments enabled. */))
+ {
+ g_cTimeSyncErrors = 0;
+ return true;
+ }
+
+ if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncAdjust: SetSystemTimeAdjustment failed, error=%u\n", GetLastError());
+ }
+ else if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncAdjust: GetSystemTimeAdjustment failed, error=%ld\n", GetLastError());
+
+#elif defined(RT_OS_OS2) || defined(RT_OS_HAIKU)
+ /* No API for doing gradual time adjustments. */
+
+#else /* PORTME */
+ /*
+ * Try using adjtime(), most unix-like systems have this.
+ */
+ struct timeval tv;
+ RTTimeSpecGetTimeval(pDrift, &tv);
+ if (adjtime(&tv, NULL) == 0)
+ {
+ vgsvcTimeSyncLog(1, "vgsvcTimeSyncAdjust: adjtime by %RDtimespec\n", pDrift);
+ g_cTimeSyncErrors = 0;
+ return true;
+ }
+#endif
+
+ /* failed */
+ return false;
+}
+
+
+/**
+ * Cancels any pending time adjustment.
+ *
+ * Called when we've caught up and before calls to vgsvcTimeSyncSet.
+ */
+static void vgsvcTimeSyncCancelAdjust(void)
+{
+#ifdef RT_OS_WINDOWS
+/** @todo r=bird: g_hTokenProcess cannot be NULL here. See argumentation in
+ * vgsvcTimeSyncAdjust. */
+ if (g_hTokenProcess == NULL) /* No process token (anymore)? */
+ return;
+ if (!g_pfnSetSystemTimeAdjustment)
+ return;
+ if (g_pfnSetSystemTimeAdjustment(0, TRUE /* Periodic adjustments disabled. */))
+ vgsvcTimeSyncLog(5, "vgsvcTimeSyncCancelAdjust: Windows Time Adjustment is now disabled.\n");
+ else if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncCancelAdjust: SetSystemTimeAdjustment(,disable) failed, error=%u\n", GetLastError());
+#endif /* !RT_OS_WINDOWS */
+}
+
+
+/**
+ * Set the wall clock to compensate for drift.
+ *
+ * @param pDrift The time adjustment.
+ */
+static void vgsvcTimeSyncSet(PCRTTIMESPEC pDrift)
+{
+ /*
+ * Query the current time, adjust it by adding the drift and set it.
+ */
+ RTTIMESPEC NewGuestTime;
+ int rc = RTTimeSet(RTTimeSpecAdd(RTTimeNow(&NewGuestTime), pDrift));
+ if (RT_SUCCESS(rc))
+ {
+ /* Succeeded - reset the error count and log the change. */
+ g_cTimeSyncErrors = 0;
+
+ if (g_cTimeSyncVerbosity >= 1)
+ {
+ char sz[64];
+ RTTIME Time;
+ vgsvcTimeSyncLog(1, "time set to %s\n", RTTimeToString(RTTimeExplode(&Time, &NewGuestTime), sz, sizeof(sz)));
+#ifdef DEBUG
+ RTTIMESPEC Tmp;
+ vgsvcTimeSyncLog(3, " now %s\n", RTTimeToString(RTTimeExplode(&Time, RTTimeNow(&Tmp)), sz, sizeof(sz)));
+#endif
+ }
+ }
+ else if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncSet: RTTimeSet(%RDtimespec) failed: %Rrc\n", &NewGuestTime, rc);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+DECLCALLBACK(int) vgsvcTimeSyncWorker(bool volatile *pfShutdown)
+{
+ RTTIME Time;
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Tell the control thread that it can continue spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /*
+ * Initialize the last host and guest times to prevent log message.
+ * We also track whether we set the time in the previous loop.
+ */
+ RTTIMESPEC HostLast;
+ if (RT_FAILURE(VbglR3GetHostTime(&HostLast)))
+ RTTimeSpecSetNano(&HostLast, 0);
+ RTTIMESPEC GuestLast;
+ RTTimeNow(&GuestLast);
+ bool fSetTimeLastLoop = false;
+
+ /*
+ * The Work Loop.
+ */
+ for (;;)
+ {
+ /*
+ * Try to get a reliable time reading.
+ */
+ int cTries = 3;
+ do
+ {
+ /*
+ * Query the session id (first to keep lantency low) and the time.
+ */
+ uint64_t idNewSession = g_idTimeSyncSession;
+ if (g_fTimeSyncSetOnRestore)
+ VbglR3GetSessionId(&idNewSession);
+
+ RTTIMESPEC GuestNow0;
+ RTTimeNow(&GuestNow0);
+
+ RTTIMESPEC HostNow;
+ int rc2 = VbglR3GetHostTime(&HostNow);
+ if (RT_FAILURE(rc2))
+ {
+ if (g_cTimeSyncErrors++ < 10)
+ VGSvcError("vgsvcTimeSyncWorker: VbglR3GetHostTime failed; rc2=%Rrc\n", rc2);
+ break;
+ }
+
+ RTTIMESPEC GuestNow;
+ RTTimeNow(&GuestNow);
+
+ /*
+ * Calc latency and check if it's ok.
+ */
+ RTTIMESPEC GuestElapsed = GuestNow;
+ RTTimeSpecSub(&GuestElapsed, &GuestNow0);
+ if ((uint32_t)RTTimeSpecGetMilli(&GuestElapsed) < g_cMsTimeSyncMaxLatency)
+ {
+ /*
+ * If we were just restored, set the adjustment threshold to zero to force a resync.
+ */
+ uint32_t TimeSyncSetThreshold = g_TimeSyncSetThreshold;
+ if ( g_fTimeSyncSetOnRestore
+ && idNewSession != g_idTimeSyncSession)
+ {
+ vgsvcTimeSyncLog(2, "vgsvcTimeSyncWorker: The VM session ID changed, forcing resync.\n");
+ g_idTimeSyncSession = idNewSession;
+ TimeSyncSetThreshold = 0;
+ }
+
+ /*
+ * Calculate the adjustment threshold and the current drift.
+ */
+ uint32_t MinAdjust = RTTimeSpecGetMilli(&GuestElapsed) * g_TimeSyncLatencyFactor;
+ if (MinAdjust < g_cMsTimeSyncMinAdjust)
+ MinAdjust = g_cMsTimeSyncMinAdjust;
+
+ RTTIMESPEC Drift = HostNow;
+ RTTimeSpecSub(&Drift, &GuestNow);
+ if (RTTimeSpecGetMilli(&Drift) < 0)
+ MinAdjust += g_cMsTimeSyncMinAdjust; /* extra buffer against moving time backwards. */
+
+ RTTIMESPEC AbsDrift = Drift;
+ RTTimeSpecAbsolute(&AbsDrift);
+
+ if (g_cTimeSyncVerbosity >= 4)
+ {
+ char sz1[64];
+ char sz2[64];
+ vgsvcTimeSyncLog(4, "vgsvcTimeSyncWorker: Host: %s (MinAdjust: %RU32 ms), Guest: %s => %RDtimespec drift\n",
+ RTTimeToString(RTTimeExplode(&Time, &HostNow), sz1, sizeof(sz1)), MinAdjust,
+ RTTimeToString(RTTimeExplode(&Time, &GuestNow), sz2, sizeof(sz2)), &Drift);
+ }
+
+ bool fSetTimeInThisLoop = false;
+ uint64_t AbsDriftMilli = RTTimeSpecGetMilli(&AbsDrift);
+ if ( AbsDriftMilli > MinAdjust
+ || g_fTimeSyncSetOnStart)
+ {
+ /*
+ * Ok, the drift is above the threshold.
+ *
+ * Try a gradual adjustment first, if that fails or the drift is
+ * too big, fall back on just setting the time.
+ */
+ if ( AbsDriftMilli > TimeSyncSetThreshold
+ || g_fTimeSyncSetOnStart
+ || !vgsvcTimeSyncAdjust(&Drift))
+ {
+ vgsvcTimeSyncCancelAdjust();
+ vgsvcTimeSyncSet(&Drift);
+ fSetTimeInThisLoop = true;
+ }
+
+ /*
+ * Log radical host time changes.
+ */
+ int64_t cNsHostDelta = RTTimeSpecGetNano(&HostNow) - RTTimeSpecGetNano(&HostLast);
+ if ((uint64_t)RT_ABS(cNsHostDelta) > RT_NS_1HOUR / 2)
+ vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical host time change: %'RI64ns (HostNow=%RDtimespec HostLast=%RDtimespec)\n",
+ cNsHostDelta, &HostNow, &HostLast);
+ }
+ else
+ vgsvcTimeSyncCancelAdjust();
+ HostLast = HostNow;
+
+ /*
+ * Log radical guest time changes (we could be the cause of these, mind).
+ * Note! Right now we don't care about an extra log line after we called
+ * vgsvcTimeSyncSet. fSetTimeLastLoop helps show it though.
+ */
+ int64_t cNsGuestDelta = RTTimeSpecGetNano(&GuestNow) - RTTimeSpecGetNano(&GuestLast);
+ if ((uint64_t)RT_ABS(cNsGuestDelta) > RT_NS_1HOUR / 2)
+ vgsvcTimeSyncLog(0, "vgsvcTimeSyncWorker: Radical guest time change: %'RI64ns (GuestNow=%RDtimespec GuestLast=%RDtimespec fSetTimeLastLoop=%RTbool)\n",
+ cNsGuestDelta, &GuestNow, &GuestLast, fSetTimeLastLoop);
+ GuestLast = GuestNow;
+ fSetTimeLastLoop = fSetTimeInThisLoop;
+ break;
+ }
+ vgsvcTimeSyncLog(3, "vgsvcTimeSyncWorker: %RDtimespec: latency too high (%RDtimespec, max %ums) sleeping 1s\n",
+ &GuestNow, &GuestElapsed, g_cMsTimeSyncMaxLatency);
+ RTThreadSleep(1000);
+ } while (--cTries > 0);
+
+ /* Clear the set-next/set-start flag. */
+ g_fTimeSyncSetOnStart = false;
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ int rc2 = RTSemEventMultiWait(g_TimeSyncEvent, g_TimeSyncInterval);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("vgsvcTimeSyncWorker: RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ }
+
+ vgsvcTimeSyncCancelAdjust();
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vgsvcTimeSyncStop(void)
+{
+ if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(g_TimeSyncEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vgsvcTimeSyncTerm(void)
+{
+#ifdef RT_OS_WINDOWS
+ /*
+ * Restore the SE_SYSTEMTIME_NAME token privileges (if init succeeded).
+ */
+ if (g_hTokenProcess)
+ {
+ if (!AdjustTokenPrivileges(g_hTokenProcess, FALSE, &g_TkOldPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL))
+ {
+ DWORD dwErr = GetLastError();
+ VGSvcError("vgsvcTimeSyncTerm: Restoring token privileges (SE_SYSTEMTIME_NAME) failed with code %u!\n", dwErr);
+ }
+ CloseHandle(g_hTokenProcess);
+ g_hTokenProcess = NULL;
+ }
+#endif /* !RT_OS_WINDOWS */
+
+ if (g_TimeSyncEvent != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(g_TimeSyncEvent);
+ g_TimeSyncEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'timesync' service description.
+ */
+VBOXSERVICE g_TimeSync =
+{
+ /* pszName. */
+ "timesync",
+ /* pszDescription. */
+ "Time synchronization",
+ /* pszUsage. */
+ " [--timesync-interval <ms>] [--timesync-min-adjust <ms>]\n"
+ " [--timesync-latency-factor <x>] [--timesync-max-latency <ms>]\n"
+ " [--timesync-set-threshold <ms>]\n"
+ " [--timesync-set-start|--timesync-no-set-start]\n"
+ " [--timesync-set-on-restore|--timesync-no-set-on-restore]\n"
+ " [--timesync-verbosity <level>]"
+ ,
+ /* pszOptions. */
+ " --timesync-interval Specifies the interval at which to synchronize the\n"
+ " time with the host. The default is 10000 ms.\n"
+ " --timesync-min-adjust The minimum absolute drift value measured in\n"
+ " milliseconds to make adjustments for.\n"
+ " The default is 1000 ms on OS/2 and 100 ms elsewhere.\n"
+ " --timesync-latency-factor\n"
+ " The factor to multiply the time query latency with\n"
+ " to calculate the dynamic minimum adjust time.\n"
+ " The default is 8 times.\n"
+ " --timesync-max-latency The max host timer query latency to accept.\n"
+ " The default is 250 ms.\n"
+ " --timesync-set-threshold\n"
+ " The absolute drift threshold, given as milliseconds,\n"
+ " where to start setting the time instead of trying to\n"
+ " adjust it. The default is 20 min.\n"
+ " --timesync-set-start, --timesync-no-set-start \n"
+ " Set the time when starting the time sync service.\n"
+#ifdef RT_OS_OS2
+ " Default: --timesync-set-start\n"
+#else
+ " Default: --timesync-no-set-start\n"
+#endif
+ " --timesync-set-on-restore, --timesync-no-set-on-restore\n"
+ " Whether to immediately set the time when the VM is\n"
+ " restored or not. Default: --timesync-set-on-restore\n"
+ " --timesync-verbosity Sets the verbosity level. Defaults to service wide\n"
+ " verbosity level.\n"
+ ,
+ /* methods */
+ vgsvcTimeSyncPreInit,
+ vgsvcTimeSyncOption,
+ vgsvcTimeSyncInit,
+ vgsvcTimeSyncWorker,
+ vgsvcTimeSyncStop,
+ vgsvcTimeSyncTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp
new file mode 100644
index 00000000..a4c9dbe5
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.cpp
@@ -0,0 +1,1769 @@
+/* $Id: VBoxServiceToolBox.cpp $ */
+/** @file
+ * VBoxServiceToolbox - Internal (BusyBox-like) toolbox.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <iprt/symlink.h>
+
+#ifndef RT_OS_WINDOWS
+# include <sys/stat.h> /* need umask */
+#endif
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/version.h>
+
+#include <VBox/GuestHost/GuestControl.h>
+
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceToolBox.h"
+#include "VBoxServiceUtils.h"
+
+using namespace guestControl;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Generic option indices for commands. */
+enum
+{
+ VBOXSERVICETOOLBOXOPT_MACHINE_READABLE = 1000,
+ VBOXSERVICETOOLBOXOPT_VERBOSE
+};
+
+/** Options indices for "vbox_cat". */
+typedef enum VBOXSERVICETOOLBOXCATOPT
+{
+ VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED = 1000
+} VBOXSERVICETOOLBOXCATOPT;
+
+/** Flags for "vbox_ls". */
+typedef enum VBOXSERVICETOOLBOXLSFLAG
+{
+ VBOXSERVICETOOLBOXLSFLAG_NONE,
+ VBOXSERVICETOOLBOXLSFLAG_RECURSIVE,
+ VBOXSERVICETOOLBOXLSFLAG_SYMLINKS
+} VBOXSERVICETOOLBOXLSFLAG;
+
+/** Flags for fs object output. */
+typedef enum VBOXSERVICETOOLBOXOUTPUTFLAG
+{
+ VBOXSERVICETOOLBOXOUTPUTFLAG_NONE,
+ VBOXSERVICETOOLBOXOUTPUTFLAG_LONG,
+ VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE
+} VBOXSERVICETOOLBOXOUTPUTFLAG;
+
+/** The size of the directory entry buffer we're using. */
+#define VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a tool handler function. */
+typedef RTEXITCODE (*PFNHANDLER)(int , char **);
+
+/** Definition for a specific toolbox tool. */
+typedef struct VBOXSERVICETOOLBOXTOOL
+{
+ /** Friendly name of the tool. */
+ const char *pszName;
+ /** Main handler to be invoked to use the tool. */
+ RTEXITCODE (*pfnHandler)(int argc, char **argv);
+ /** Conversion routine to convert the tool's exit code back to an IPRT rc. Optional.
+ *
+ * @todo r=bird: You better revert this, i.e. having pfnHandler return a VBox
+ * status code and have a routine for converting it to RTEXITCODE.
+ * Unless, what you really want to do here is to get a cached status, in
+ * which case you better call it what it is.
+ */
+ int (*pfnExitCodeConvertToRc)(RTEXITCODE rcExit);
+} VBOXSERVICETOOLBOXTOOL;
+/** Pointer to a const tool definition. */
+typedef VBOXSERVICETOOLBOXTOOL const *PCVBOXSERVICETOOLBOXTOOL;
+
+/**
+ * An file/directory entry. Used to cache
+ * file names/paths for later processing.
+ */
+typedef struct VBOXSERVICETOOLBOXPATHENTRY
+{
+ /** Our node. */
+ RTLISTNODE Node;
+ /** Name of the entry. */
+ char *pszName;
+} VBOXSERVICETOOLBOXPATHENTRY, *PVBOXSERVICETOOLBOXPATHENTRY;
+
+/** ID cache entry. */
+typedef struct VGSVCTOOLBOXUIDENTRY
+{
+ /** The identifier name. */
+ uint32_t id;
+ /** Set if UID, clear if GID. */
+ bool fIsUid;
+ /** The name. */
+ char szName[128 - 4 - 1];
+} VGSVCTOOLBOXUIDENTRY;
+typedef VGSVCTOOLBOXUIDENTRY *PVGSVCTOOLBOXUIDENTRY;
+
+
+/** ID cache. */
+typedef struct VGSVCTOOLBOXIDCACHE
+{
+ /** Number of valid cache entries. */
+ uint32_t cEntries;
+ /** The next entry to replace. */
+ uint32_t iNextReplace;
+ /** The cache entries. */
+ VGSVCTOOLBOXUIDENTRY aEntries[16];
+} VGSVCTOOLBOXIDCACHE;
+typedef VGSVCTOOLBOXIDCACHE *PVGSVCTOOLBOXIDCACHE;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static RTEXITCODE vgsvcToolboxCat(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxLs(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxRm(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv);
+static RTEXITCODE vgsvcToolboxStat(int argc, char **argv);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Tool definitions. */
+static VBOXSERVICETOOLBOXTOOL const g_aTools[] =
+{
+ { VBOXSERVICE_TOOL_CAT, vgsvcToolboxCat , NULL },
+ { VBOXSERVICE_TOOL_LS, vgsvcToolboxLs , NULL },
+ { VBOXSERVICE_TOOL_RM, vgsvcToolboxRm , NULL },
+ { VBOXSERVICE_TOOL_MKTEMP, vgsvcToolboxMkTemp, NULL },
+ { VBOXSERVICE_TOOL_MKDIR, vgsvcToolboxMkDir , NULL },
+ { VBOXSERVICE_TOOL_STAT, vgsvcToolboxStat , NULL }
+};
+
+
+
+
+/**
+ * Displays a common header for all help text to stdout.
+ */
+static void vgsvcToolboxShowUsageHeader(void)
+{
+ RTPrintf(VBOX_PRODUCT " Guest Toolbox Version "
+ VBOX_VERSION_STRING "\n"
+ "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+ RTPrintf("Usage:\n\n");
+}
+
+
+/**
+ * Displays a help text to stdout.
+ */
+static void vgsvcToolboxShowUsage(void)
+{
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf(" VBoxService [--use-toolbox] vbox_<command> [<general options>] <parameters>\n\n"
+ "General options:\n\n"
+ " --machinereadable produce all output in machine-readable form\n"
+ " -V print version number and exit\n"
+ "\n"
+ "Commands:\n\n"
+ " vbox_cat [<general options>] <file>...\n"
+ " vbox_ls [<general options>] [--dereference|-L] [-l] [-R]\n"
+ " [--verbose|-v] [<file>...]\n"
+ " vbox_rm [<general options>] [-r|-R] <file>...\n"
+ " vbox_mktemp [<general options>] [--directory|-d] [--mode|-m <mode>]\n"
+ " [--secure|-s] [--tmpdir|-t <path>] <template>\n"
+ " vbox_mkdir [<general options>] [--mode|-m <mode>] [--parents|-p]\n"
+ " [--verbose|-v] <directory>...\n"
+ " vbox_stat [<general options>] [--file-system|-f]\n"
+ " [--dereference|-L] [--terse|-t] [--verbose|-v] <file>...\n"
+ "\n");
+}
+
+
+/**
+ * Displays the program's version number.
+ */
+static void vgsvcToolboxShowVersion(void)
+{
+ RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+}
+
+
+/**
+ * Initializes the parseable stream(s).
+ *
+ * @return IPRT status code.
+ */
+static int vgsvcToolboxStrmInit(void)
+{
+ /* Set stdout's mode to binary. This is required for outputting all the machine-readable
+ * data correctly. */
+ int rc = RTStrmSetMode(g_pStdOut, true /* Binary mode */, -1 /* Current code set, not changed */);
+ if (RT_FAILURE(rc))
+ RTMsgError("Unable to set stdout to binary mode, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+
+/**
+ * Prints a parseable stream header which contains the actual tool
+ * which was called/used along with its stream version.
+ *
+ * @param pszToolName Name of the tool being used, e.g. "vbt_ls".
+ * @param uVersion Stream version name. Handy for distinguishing
+ * different stream versions later.
+ */
+static void vgsvcToolboxPrintStrmHeader(const char *pszToolName, uint32_t uVersion)
+{
+ AssertPtrReturnVoid(pszToolName);
+ RTPrintf("hdr_id=%s%chdr_ver=%u%c", pszToolName, 0, uVersion, 0);
+}
+
+
+/**
+ * Prints a standardized termination sequence indicating that the
+ * parseable stream just ended.
+ */
+static void vgsvcToolboxPrintStrmTermination()
+{
+ RTPrintf("%c%c%c%c", 0, 0, 0, 0);
+}
+
+
+/**
+ * Parse a file mode string from the command line (currently octal only)
+ * and print an error message and return an error if necessary.
+ */
+static int vgsvcToolboxParseMode(const char *pcszMode, RTFMODE *pfMode)
+{
+ int rc = RTStrToUInt32Ex(pcszMode, NULL, 8 /* Base */, pfMode);
+ if (RT_FAILURE(rc)) /* Only octet based values supported right now! */
+ RTMsgError("Mode flag strings not implemented yet! Use octal numbers instead. (%s)\n", pcszMode);
+ return rc;
+}
+
+
+/**
+ * Destroys a path buffer list.
+ *
+ * @param pList Pointer to list to destroy.
+ */
+static void vgsvcToolboxPathBufDestroy(PRTLISTNODE pList)
+{
+ if (!pList)
+ return;
+
+ PVBOXSERVICETOOLBOXPATHENTRY pEntry, pEntryNext;
+ RTListForEachSafe(pList, pEntry, pEntryNext, VBOXSERVICETOOLBOXPATHENTRY, Node)
+ {
+ RTListNodeRemove(&pEntry->Node);
+
+ RTStrFree(pEntry->pszName);
+ RTMemFree(pEntry);
+ }
+}
+
+
+/**
+ * Adds a path entry (file/directory/whatever) to a given path buffer list.
+ *
+ * @return IPRT status code.
+ * @param pList Pointer to list to add entry to.
+ * @param pszName Name of entry to add.
+ */
+static int vgsvcToolboxPathBufAddPathEntry(PRTLISTNODE pList, const char *pszName)
+{
+ AssertPtrReturn(pList, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PVBOXSERVICETOOLBOXPATHENTRY pNode = (PVBOXSERVICETOOLBOXPATHENTRY)RTMemAlloc(sizeof(VBOXSERVICETOOLBOXPATHENTRY));
+ if (pNode)
+ {
+ pNode->pszName = RTStrDup(pszName);
+ AssertPtr(pNode->pszName);
+
+ RTListAppend(pList, &pNode->Node);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Performs the actual output operation of "vbox_cat".
+ *
+ * @return IPRT status code.
+ * @param hInput Handle of input file (if any) to use;
+ * else stdin will be used.
+ * @param hOutput Handle of output file (if any) to use;
+ * else stdout will be used.
+ */
+static int vgsvcToolboxCatOutput(RTFILE hInput, RTFILE hOutput)
+{
+ int rc = VINF_SUCCESS;
+ if (hInput == NIL_RTFILE)
+ {
+ rc = RTFileFromNative(&hInput, RTFILE_NATIVE_STDIN);
+ if (RT_FAILURE(rc))
+ RTMsgError("Could not translate input file to native handle, rc=%Rrc\n", rc);
+ }
+
+ if (hOutput == NIL_RTFILE)
+ {
+ rc = RTFileFromNative(&hOutput, RTFILE_NATIVE_STDOUT);
+ if (RT_FAILURE(rc))
+ RTMsgError("Could not translate output file to native handle, rc=%Rrc\n", rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t abBuf[_64K];
+ size_t cbRead;
+ for (;;)
+ {
+ rc = RTFileRead(hInput, abBuf, sizeof(abBuf), &cbRead);
+ if (RT_SUCCESS(rc) && cbRead > 0)
+ {
+ rc = RTFileWrite(hOutput, abBuf, cbRead, NULL /* Try to write all at once! */);
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Error while writing output, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+ else
+ {
+ if (rc == VERR_BROKEN_PIPE)
+ rc = VINF_SUCCESS;
+ else if (RT_FAILURE(rc))
+ RTMsgError("Error while reading input, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/** @todo Document options! */
+static char g_paszCatHelp[] =
+ " VBoxService [--use-toolbox] vbox_cat [<general options>] <file>...\n\n"
+ "Concatenate files, or standard input, to standard output.\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_cat".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxCat(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* Sorted by short ops. */
+ { "--show-all", 'a', RTGETOPT_REQ_NOTHING },
+ { "--number-nonblank", 'b', RTGETOPT_REQ_NOTHING},
+ { NULL, 'e', RTGETOPT_REQ_NOTHING},
+ { NULL, 'E', RTGETOPT_REQ_NOTHING},
+ { "--flags", 'f', RTGETOPT_REQ_STRING},
+ { "--no-content-indexed", VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED, RTGETOPT_REQ_NOTHING},
+ { "--number", 'n', RTGETOPT_REQ_NOTHING},
+ { "--output", 'o', RTGETOPT_REQ_STRING},
+ { "--squeeze-blank", 's', RTGETOPT_REQ_NOTHING},
+ { NULL, 't', RTGETOPT_REQ_NOTHING},
+ { "--show-tabs", 'T', RTGETOPT_REQ_NOTHING},
+ { NULL, 'u', RTGETOPT_REQ_NOTHING},
+ { "--show-noneprinting", 'v', RTGETOPT_REQ_NOTHING}
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, 0 /*fFlags*/);
+
+ int rc = VINF_SUCCESS;
+
+ const char *pszOutput = NULL;
+ RTFILE hOutput = NIL_RTFILE;
+ uint32_t fFlags = RTFILE_O_CREATE_REPLACE /* Output file flags. */
+ | RTFILE_O_WRITE
+ | RTFILE_O_DENY_WRITE;
+
+ /* Init directory list. */
+ RTLISTANCHOR inputList;
+ RTListInit(&inputList);
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'a':
+ case 'b':
+ case 'e':
+ case 'E':
+ case 'n':
+ case 's':
+ case 't':
+ case 'T':
+ case 'v':
+ RTMsgError("Sorry, option '%s' is not implemented yet!\n",
+ ValueUnion.pDef->pszLong);
+ rc = VERR_INVALID_PARAMETER;
+ break;
+
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszCatHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'o':
+ pszOutput = ValueUnion.psz;
+ break;
+
+ case 'u':
+ /* Ignored. */
+ break;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VBOXSERVICETOOLBOXCATOPT_NO_CONTENT_INDEXED:
+ fFlags |= RTFILE_O_NOT_CONTENT_INDEXED;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* Add file(s) to buffer. This enables processing multiple paths
+ * at once.
+ *
+ * Since the non-options (RTGETOPTINIT_FLAGS_OPTS_FIRST) come last when
+ * processing this loop it's safe to immediately exit on syntax errors
+ * or showing the help text (see above). */
+ rc = vgsvcToolboxPathBufAddPathEntry(&inputList, ValueUnion.psz);
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pszOutput)
+ {
+ rc = RTFileOpen(&hOutput, pszOutput, fFlags);
+ if (RT_FAILURE(rc))
+ RTMsgError("Could not create output file '%s', rc=%Rrc\n", pszOutput, rc);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Process each input file. */
+ RTFILE hInput = NIL_RTFILE;
+ PVBOXSERVICETOOLBOXPATHENTRY pNodeIt;
+ RTListForEach(&inputList, pNodeIt, VBOXSERVICETOOLBOXPATHENTRY, Node)
+ {
+ rc = RTFileOpen(&hInput, pNodeIt->pszName,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vgsvcToolboxCatOutput(hInput, hOutput);
+ RTFileClose(hInput);
+ }
+ else
+ RTMsgError("Could not open input file '%s': %Rrc\n", pNodeIt->pszName, rc);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* If no input files were defined, process stdin. */
+ if (RTListNodeIsFirst(&inputList, &inputList))
+ rc = vgsvcToolboxCatOutput(hInput, hOutput);
+ }
+ }
+
+ if (hOutput != NIL_RTFILE)
+ RTFileClose(hOutput);
+ vgsvcToolboxPathBufDestroy(&inputList);
+
+ if (RT_FAILURE(rc))
+ {
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_ACCESS_DENIED;
+
+ case VERR_FILE_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_FILE_NOT_FOUND;
+
+ case VERR_PATH_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_PATH_NOT_FOUND;
+
+ case VERR_SHARING_VIOLATION:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_SHARING_VIOLATION;
+
+ case VERR_IS_A_DIRECTORY:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_CAT_EXITCODE_IS_A_DIRECTORY;
+
+ default:
+#ifdef DEBUG_andy
+ AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc));
+#endif
+ break;
+ }
+
+ return RTEXITCODE_FAILURE;
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Resolves the UID to a name as best as we can.
+ *
+ * @returns Read-only name string. Only valid till the next cache call.
+ * @param pIdCache The ID cache.
+ * @param uid The UID to resolve.
+ * @param pszEntry The filename of the UID.
+ * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute.
+ */
+static const char *vgsvcToolboxIdCacheGetUidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTUID uid,
+ const char *pszEntry, const char *pszRelativeTo)
+{
+ /* Check cached entries. */
+ for (uint32_t i = 0; i < pIdCache->cEntries; i++)
+ if ( pIdCache->aEntries[i].id == uid
+ && pIdCache->aEntries[i].fIsUid)
+ return pIdCache->aEntries[i].szName;
+
+ /* Miss. */
+ RTFSOBJINFO ObjInfo;
+ RT_ZERO(ObjInfo); /* shut up msc */
+ int rc;
+ if (!pszRelativeTo)
+ rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ else
+ {
+ char szPath[RTPATH_MAX];
+ rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry);
+ if (RT_SUCCESS(rc))
+ rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && ObjInfo.Attr.u.UnixOwner.uid == uid)
+ {
+ uint32_t i = pIdCache->cEntries;
+ if (i < RT_ELEMENTS(pIdCache->aEntries))
+ pIdCache->cEntries = i + 1;
+ else
+ i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries);
+ pIdCache->aEntries[i].id = uid;
+ pIdCache->aEntries[i].fIsUid = true;
+ RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixOwner.szName);
+ return pIdCache->aEntries[i].szName;
+ }
+ return "";
+}
+
+
+/**
+ * Resolves the GID to a name as best as we can.
+ *
+ * @returns Read-only name string. Only valid till the next cache call.
+ * @param pIdCache The ID cache.
+ * @param gid The GID to resolve.
+ * @param pszEntry The filename of the GID.
+ * @param pszRelativeTo What @a pszEntry is relative to, NULL if absolute.
+ */
+static const char *vgsvcToolboxIdCacheGetGidName(PVGSVCTOOLBOXIDCACHE pIdCache, RTGID gid,
+ const char *pszEntry, const char *pszRelativeTo)
+{
+ /* Check cached entries. */
+ for (uint32_t i = 0; i < pIdCache->cEntries; i++)
+ if ( pIdCache->aEntries[i].id == gid
+ && !pIdCache->aEntries[i].fIsUid)
+ return pIdCache->aEntries[i].szName;
+
+ /* Miss. */
+ RTFSOBJINFO ObjInfo;
+ RT_ZERO(ObjInfo); /* shut up msc */
+ int rc;
+ if (!pszRelativeTo)
+ rc = RTPathQueryInfoEx(pszEntry, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ else
+ {
+ char szPath[RTPATH_MAX];
+ rc = RTPathJoin(szPath, sizeof(szPath), pszRelativeTo, pszEntry);
+ if (RT_SUCCESS(rc))
+ rc = RTPathQueryInfoEx(szPath, &ObjInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && ObjInfo.Attr.u.UnixGroup.gid == gid)
+ {
+ uint32_t i = pIdCache->cEntries;
+ if (i < RT_ELEMENTS(pIdCache->aEntries))
+ pIdCache->cEntries = i + 1;
+ else
+ i = pIdCache->iNextReplace++ % RT_ELEMENTS(pIdCache->aEntries);
+ pIdCache->aEntries[i].id = gid;
+ pIdCache->aEntries[i].fIsUid = false;
+ RTStrCopy(pIdCache->aEntries[i].szName, sizeof(pIdCache->aEntries[i].szName), ObjInfo.Attr.u.UnixGroup.szName);
+ return pIdCache->aEntries[i].szName;
+ }
+ return "";
+}
+
+
+/**
+ * Prints information (based on given flags) of a file system object (file/directory/...)
+ * to stdout.
+ *
+ * @return IPRT status code.
+ * @param pszName Object name.
+ * @param cchName Length of pszName.
+ * @param fOutputFlags Output / handling flags of type
+ * VBOXSERVICETOOLBOXOUTPUTFLAG.
+ * @param pszRelativeTo What pszName is relative to.
+ * @param pIdCache The ID cache.
+ * @param pObjInfo Pointer to object information.
+ */
+static int vgsvcToolboxPrintFsInfo(const char *pszName, size_t cchName, uint32_t fOutputFlags, const char *pszRelativeTo,
+ PVGSVCTOOLBOXIDCACHE pIdCache, PRTFSOBJINFO pObjInfo)
+{
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(cchName, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+
+ RTFMODE fMode = pObjInfo->Attr.fMode;
+ char chFileType;
+ switch (fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: chFileType = 'f'; break;
+ case RTFS_TYPE_DEV_CHAR: chFileType = 'c'; break;
+ case RTFS_TYPE_DIRECTORY: chFileType = 'd'; break;
+ case RTFS_TYPE_DEV_BLOCK: chFileType = 'b'; break;
+ case RTFS_TYPE_FILE: chFileType = '-'; break;
+ case RTFS_TYPE_SYMLINK: chFileType = 'l'; break;
+ case RTFS_TYPE_SOCKET: chFileType = 's'; break;
+ case RTFS_TYPE_WHITEOUT: chFileType = 'w'; break;
+ default: chFileType = '?'; break;
+ }
+ /** @todo sticy bits++ */
+
+/** @todo r=bird: turns out the host doesn't use or need cname_len, so perhaps we could drop it? */
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_LONG))
+ {
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ RTPrintf("ftype=%c%cnode_id=%RU64%cinode_dev=%RU32%ccname_len=%zu%cname=%s%c",
+ chFileType, 0, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0,
+ (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0, cchName, 0, pszName, 0);
+ RTPrintf("%c%c", 0, 0);
+ }
+ else
+ RTPrintf("%c %#18llx %3zu %s\n", chFileType, (uint64_t)pObjInfo->Attr.u.Unix.INodeId, cchName, pszName);
+ }
+ else
+ {
+ char szTimeBirth[RTTIME_STR_LEN];
+ char szTimeChange[RTTIME_STR_LEN];
+ char szTimeModification[RTTIME_STR_LEN];
+ char szTimeAccess[RTTIME_STR_LEN];
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ RTPrintf("ftype=%c%c", chFileType, 0);
+ if (pObjInfo->Attr.u.Unix.INodeId || pObjInfo->Attr.u.Unix.INodeIdDevice)
+ RTPrintf("node_id=%RU64%cinode_dev=%RU32%c", (uint64_t)pObjInfo->Attr.u.Unix.INodeId, 0,
+ (uint32_t)pObjInfo->Attr.u.Unix.INodeIdDevice, 0);
+ RTPrintf("owner_mask=%c%c%c%c",
+ fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
+ fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
+ fMode & RTFS_UNIX_IXUSR ? 'x' : '-', 0);
+ RTPrintf("group_mask=%c%c%c%c",
+ fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
+ fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
+ fMode & RTFS_UNIX_IXGRP ? 'x' : '-', 0);
+ RTPrintf("other_mask=%c%c%c%c",
+ fMode & RTFS_UNIX_IROTH ? 'r' : '-',
+ fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
+ fMode & RTFS_UNIX_IXOTH ? 'x' : '-', 0);
+ /** @todo sticky bits. */
+ RTPrintf("dos_mask=%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",
+ fMode & RTFS_DOS_READONLY ? 'R' : '-',
+ fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
+ fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
+ fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
+ fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
+ fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
+ fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
+ fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
+ fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
+ fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
+ fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
+ fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
+ fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
+ fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-', 0);
+ RTPrintf("hlinks=%RU32%cst_size=%RI64%calloc=%RI64%c",
+ pObjInfo->Attr.u.Unix.cHardlinks, 0,
+ pObjInfo->cbObject, 0,
+ pObjInfo->cbAllocated, 0);
+ RTPrintf("st_birthtime=%s%cst_ctime=%s%cst_mtime=%s%cst_atime=%s%c",
+ RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)), 0,
+ RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)), 0,
+ RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)), 0,
+ RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)), 0);
+ if (pObjInfo->Attr.u.Unix.uid != NIL_RTUID)
+ RTPrintf("uid=%RU32%cusername=%s%c", pObjInfo->Attr.u.Unix.uid, 0,
+ vgsvcToolboxIdCacheGetUidName(pIdCache, pObjInfo->Attr.u.Unix.uid, pszName, pszRelativeTo), 0);
+ if (pObjInfo->Attr.u.Unix.gid != NIL_RTGID)
+ RTPrintf("gid=%RU32%cgroupname=%s%c", pObjInfo->Attr.u.Unix.gid, 0,
+ vgsvcToolboxIdCacheGetGidName(pIdCache, pObjInfo->Attr.u.Unix.gid, pszName, pszRelativeTo), 0);
+ if ( (RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode) || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode))
+ && pObjInfo->Attr.u.Unix.Device)
+ RTPrintf("st_rdev=%RU32%c", pObjInfo->Attr.u.Unix.Device, 0);
+ if (pObjInfo->Attr.u.Unix.GenerationId)
+ RTPrintf("st_gen=%RU32%c", pObjInfo->Attr.u.Unix.GenerationId, 0);
+ if (pObjInfo->Attr.u.Unix.fFlags)
+ RTPrintf("st_flags=%RU32%c", pObjInfo->Attr.u.Unix.fFlags, 0);
+ RTPrintf("cname_len=%zu%cname=%s%c", cchName, 0, pszName, 0);
+ RTPrintf("%c%c", 0, 0); /* End of data block. */
+ }
+ else
+ {
+ RTPrintf("%c", chFileType);
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
+ fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
+ fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
+ fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
+ fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IROTH ? 'r' : '-',
+ fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
+ fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
+ RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
+ fMode & RTFS_DOS_READONLY ? 'R' : '-',
+ fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
+ fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
+ fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
+ fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
+ fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
+ fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
+ fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
+ fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
+ fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
+ fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
+ fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
+ fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
+ fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
+ RTPrintf(" %d %4d %4d %10lld %10lld",
+ pObjInfo->Attr.u.Unix.cHardlinks,
+ pObjInfo->Attr.u.Unix.uid,
+ pObjInfo->Attr.u.Unix.gid,
+ pObjInfo->cbObject,
+ pObjInfo->cbAllocated);
+ RTPrintf(" %s %s %s %s",
+ RTTimeSpecToString(&pObjInfo->BirthTime, szTimeBirth, sizeof(szTimeBirth)),
+ RTTimeSpecToString(&pObjInfo->ChangeTime, szTimeChange, sizeof(szTimeChange)),
+ RTTimeSpecToString(&pObjInfo->ModificationTime, szTimeModification, sizeof(szTimeModification)),
+ RTTimeSpecToString(&pObjInfo->AccessTime, szTimeAccess, sizeof(szTimeAccess)) );
+ RTPrintf(" %2zu %s\n", cchName, pszName);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Helper routine for ls tool for handling sub directories.
+ *
+ * @return IPRT status code.
+ * @param pszDir Pointer to the directory buffer.
+ * @param cchDir The length of pszDir in pszDir.
+ * @param pDirEntry Pointer to the directory entry.
+ * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG.
+ * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG.
+ * @param pIdCache The ID cache.
+ */
+static int vgsvcToolboxLsHandleDirSub(char *pszDir, size_t cchDir, PRTDIRENTRYEX pDirEntry,
+ uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache)
+{
+ Assert(cchDir > 0); Assert(pszDir[cchDir] == '\0');
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ RTPrintf("dname=%s%c", pszDir, 0);
+ else if (fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE)
+ RTPrintf("%s:\n", pszDir);
+
+ /* Make sure we've got some room in the path, to save us extra work further down. */
+ if (cchDir + 3 >= RTPATH_MAX)
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Path too long: '%s'\n", pszDir);
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Open directory. */
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszDir);
+ if (RT_FAILURE(rc))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Failed to open directory '%s', rc=%Rrc\n", pszDir, rc);
+ return rc;
+ }
+
+ /* Ensure we've got a trailing slash (there is space for it see above). */
+ if (!RTPATH_IS_SEP(pszDir[cchDir - 1]))
+ {
+ pszDir[cchDir++] = RTPATH_SLASH;
+ pszDir[cchDir] = '\0';
+ }
+
+ /*
+ * Process the files and subdirs.
+ */
+ for (;;)
+ {
+ /* Get the next directory. */
+ size_t cbDirEntry = VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE;
+ rc = RTDirReadEx(hDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Check length. */
+ if (pDirEntry->cbName + cchDir + 3 >= RTPATH_MAX)
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Path too long: '%s' in '%.*s'\n", pDirEntry->szName, cchDir, pszDir);
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_SYMLINK:
+ {
+ if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS))
+ break;
+ RT_FALL_THRU();
+ }
+ case RTFS_TYPE_DIRECTORY:
+ {
+ rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir,
+ pIdCache, &pDirEntry->Info);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ if (!(fFlags & VBOXSERVICETOOLBOXLSFLAG_RECURSIVE))
+ continue;
+
+ memcpy(&pszDir[cchDir], pDirEntry->szName, pDirEntry->cbName + 1);
+ int rc2 = vgsvcToolboxLsHandleDirSub(pszDir, cchDir + pDirEntry->cbName, pDirEntry, fFlags, fOutputFlags, pIdCache);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ rc = vgsvcToolboxPrintFsInfo(pDirEntry->szName, pDirEntry->cbName, fOutputFlags, pszDir,
+ pIdCache, &pDirEntry->Info);
+ break;
+ }
+
+ default:
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Entry '%.*s%s' of mode %#x not supported, skipping",
+ cchDir, pszDir, pDirEntry->szName, pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK);
+ break;
+ }
+ }
+ }
+ if (rc != VERR_NO_MORE_FILES)
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("RTDirReadEx failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir);
+ }
+
+ rc = RTDirClose(hDir);
+ if (RT_FAILURE(rc))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("RTDirClose failed: %Rrc\npszDir=%.*s", rc, cchDir, pszDir);
+ }
+
+ return rc;
+}
+
+/**
+ * Helper routine for ls tool doing the actual parsing and output of
+ * a specified directory.
+ *
+ * @return IPRT status code.
+ * @param pszDir Absolute path to directory to ouptut.
+ * @param fFlags Flags of type VBOXSERVICETOOLBOXLSFLAG.
+ * @param fOutputFlags Flags of type VBOXSERVICETOOLBOXOUTPUTFLAG.
+ * @param pIdCache The ID cache.
+ */
+static int vgsvcToolboxLsHandleDir(const char *pszDir, uint32_t fFlags, uint32_t fOutputFlags, PVGSVCTOOLBOXIDCACHE pIdCache)
+{
+ AssertPtrReturn(pszDir, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pIdCache, VERR_INVALID_PARAMETER);
+
+ char szPath[RTPATH_MAX];
+ int rc = RTPathAbs(pszDir, szPath, sizeof(szPath));
+ if (RT_FAILURE(rc))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("RTPathAbs failed on '%s': %Rrc\n", pszDir, rc);
+ return rc;
+ }
+
+ union
+ {
+ uint8_t abPadding[VBOXSERVICETOOLBOX_DIRENTRY_BUF_SIZE];
+ RTDIRENTRYEX DirEntry;
+ } uBuf;
+ return vgsvcToolboxLsHandleDirSub(szPath, strlen(szPath), &uBuf.DirEntry, fFlags, fOutputFlags, pIdCache);
+}
+
+
+/** @todo Document options! */
+static char g_paszLsHelp[] =
+ " VBoxService [--use-toolbox] vbox_ls [<general options>] [option]...\n"
+ " [<file>...]\n\n"
+ "List information about files (the current directory by default).\n\n"
+ "Options:\n\n"
+ " [--dereference|-L]\n"
+ " [-l][-R]\n"
+ " [--verbose|-v]\n"
+ " [<file>...]\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_ls".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxLs(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { NULL, 'l', RTGETOPT_REQ_NOTHING },
+ { NULL, 'R', RTGETOPT_REQ_NOTHING },
+ { "--verbose", VBOXSERVICETOOLBOXOPT_VERBOSE, RTGETOPT_REQ_NOTHING}
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_INIT);
+
+ bool fVerbose = false;
+ uint32_t fFlags = VBOXSERVICETOOLBOXLSFLAG_NONE;
+ uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_NONE;
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszLsHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'L': /* Dereference symlinks. */
+ fFlags |= VBOXSERVICETOOLBOXLSFLAG_SYMLINKS;
+ break;
+
+ case 'l': /* Print long format. */
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_LONG;
+ break;
+
+ case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
+ break;
+
+ case 'R': /* Recursive processing. */
+ fFlags |= VBOXSERVICETOOLBOXLSFLAG_RECURSIVE;
+ break;
+
+ case VBOXSERVICETOOLBOXOPT_VERBOSE:
+ fVerbose = true;
+ break;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ Assert(GetState.iNext);
+ GetState.iNext--;
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+
+ /* All flags / options processed? Bail out here.
+ * Processing the file / directory list comes down below. */
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ break;
+ }
+
+ /* Print magic/version. */
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ rc = vgsvcToolboxStrmInit();
+ if (RT_FAILURE(rc))
+ RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
+ vgsvcToolboxPrintStrmHeader("vbt_ls", 1 /* Stream version */);
+ }
+
+ VGSVCTOOLBOXIDCACHE IdCache;
+ RT_ZERO(IdCache);
+
+ char szDirCur[RTPATH_MAX];
+ rc = RTPathGetCurrent(szDirCur, sizeof(szDirCur));
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("Getting current directory failed, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ ch = RTGetOpt(&GetState, &ValueUnion);
+ do
+ {
+ char const *pszPath;
+
+ if (ch == 0) /* Use current directory if no element specified. */
+ pszPath = szDirCur;
+ else
+ pszPath = ValueUnion.psz;
+
+ RTFSOBJINFO objInfo;
+ int rc2 = RTPathQueryInfoEx(pszPath, &objInfo,
+ RTFSOBJATTRADD_UNIX,
+ fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc2))
+ {
+ if ( RTFS_IS_FILE(objInfo.Attr.fMode)
+ || ( RTFS_IS_SYMLINK(objInfo.Attr.fMode)
+ && (fFlags & VBOXSERVICETOOLBOXLSFLAG_SYMLINKS)))
+ {
+ rc2 = vgsvcToolboxPrintFsInfo(pszPath, strlen(pszPath), fOutputFlags, NULL, &IdCache, &objInfo);
+ if (RT_SUCCESS(rc)) /* Keep initial failing rc. */
+ rc = rc2;
+ }
+ else if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ rc2 = vgsvcToolboxLsHandleDir(pszPath, fFlags, fOutputFlags, &IdCache);
+ if (RT_SUCCESS(rc)) /* Keep initial failing rc. */
+ rc = rc2;
+ }
+ }
+ else
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Cannot access '%s': No such file or directory\n", pszPath);
+ if (RT_SUCCESS(rc))
+ rc = VERR_FILE_NOT_FOUND;
+ /* Do not break here -- process every element in the list
+ * and keep failing rc. */
+ }
+
+ } while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0);
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ vgsvcToolboxPrintStrmTermination();
+
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/* Try using RTPathRmCmd. */
+static RTEXITCODE vgsvcToolboxRm(int argc, char **argv)
+{
+ return RTPathRmCmd(argc, argv);
+}
+
+
+static char g_paszMkTempHelp[] =
+ " VBoxService [--use-toolbox] vbox_mktemp [<general options>] [<options>]\n"
+ " <template>\n\n"
+ "Create a temporary directory based on the template supplied. The first string\n"
+ "of consecutive 'X' characters in the template will be replaced to form a unique\n"
+ "name for the directory. The template may not contain a path. The default\n"
+ "creation mode is 0600 for files and 0700 for directories. If no path is\n"
+ "specified the default temporary directory will be used.\n"
+ "Options:\n\n"
+ " [--directory|-d] Create a directory instead of a file.\n"
+ " [--mode|-m <mode>] Create the object with mode <mode>.\n"
+ " [--secure|-s] Fail if the object cannot be created securely.\n"
+ " [--tmpdir|-t <path>] Create the object with the absolute path <path>.\n"
+ "\n";
+
+
+/**
+ * Report the result of a vbox_mktemp operation.
+ *
+ * Either errors to stderr (not machine-readable) or everything to stdout as
+ * {name}\0{rc}\0 (machine- readable format). The message may optionally
+ * contain a '%s' for the file name and an %Rrc for the result code in that
+ * order. In future a "verbose" flag may be added, without which nothing will
+ * be output in non-machine- readable mode. Sets prc if rc is a non-success
+ * code.
+ */
+static void toolboxMkTempReport(const char *pcszMessage, const char *pcszFile,
+ bool fActive, int rc, uint32_t fOutputFlags, int *prc)
+{
+ if (!fActive)
+ return;
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ if (RT_SUCCESS(rc))
+ RTPrintf(pcszMessage, pcszFile, rc);
+ else
+ RTMsgError(pcszMessage, pcszFile, rc);
+ else
+ RTPrintf("name=%s%crc=%d%c", pcszFile, 0, rc, 0);
+ if (prc && RT_FAILURE(rc))
+ *prc = rc;
+}
+
+
+/**
+ * Main function for tool "vbox_mktemp".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxMkTemp(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE,
+ RTGETOPT_REQ_NOTHING },
+ { "--directory", 'd', RTGETOPT_REQ_NOTHING },
+ { "--mode", 'm', RTGETOPT_REQ_STRING },
+ { "--secure", 's', RTGETOPT_REQ_NOTHING },
+ { "--tmpdir", 't', RTGETOPT_REQ_STRING },
+ };
+
+ enum
+ {
+ /* Isn't that a bit long? s/VBOXSERVICETOOLBOX/VSTB/ ? */
+ /** Create a temporary directory instead of a temporary file. */
+ VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY = RT_BIT_32(0),
+ /** Only create the temporary object if the operation is expected
+ * to be secure. Not guaranteed to be supported on a particular
+ * set-up. */
+ VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE = RT_BIT_32(1)
+ };
+
+ int ch, rc;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_INIT);
+
+ uint32_t fFlags = 0;
+ uint32_t fOutputFlags = 0;
+ int cNonOptions = 0;
+ RTFMODE fMode = 0700;
+ bool fModeSet = false;
+ const char *pcszPath = NULL;
+ const char *pcszTemplate;
+ char szTemplateWithPath[RTPATH_MAX] = "";
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszMkTempHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
+ break;
+
+ case 'd':
+ fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY;
+ break;
+
+ case 'm':
+ rc = vgsvcToolboxParseMode(ValueUnion.psz, &fMode);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_SYNTAX;
+ fModeSet = true;
+#ifndef RT_OS_WINDOWS
+ umask(0); /* RTDirCreate workaround */
+#endif
+ break;
+ case 's':
+ fFlags |= VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE;
+ break;
+
+ case 't':
+ pcszPath = ValueUnion.psz;
+ break;
+
+ case VINF_GETOPT_NOT_OPTION:
+ /* RTGetOpt will sort these to the end of the argv vector so
+ * that we will deal with them afterwards. */
+ ++cNonOptions;
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+ /* Print magic/version. */
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE)
+ {
+ rc = vgsvcToolboxStrmInit();
+ if (RT_FAILURE(rc))
+ RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
+ vgsvcToolboxPrintStrmHeader("vbt_mktemp", 1 /* Stream version */);
+ }
+
+ if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE && fModeSet)
+ {
+ toolboxMkTempReport("'-s' and '-m' parameters cannot be used together.\n", "",
+ true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_SYNTAX;
+ }
+
+ /* We need exactly one template, containing at least one 'X'. */
+ if (cNonOptions != 1)
+ {
+ toolboxMkTempReport("Please specify exactly one template.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_SYNTAX;
+ }
+ pcszTemplate = argv[argc - 1];
+
+ /* Validate that the template is as IPRT requires (asserted by IPRT). */
+ if ( RTPathHasPath(pcszTemplate)
+ || ( !strstr(pcszTemplate, "XXX")
+ && pcszTemplate[strlen(pcszTemplate) - 1] != 'X'))
+ {
+ toolboxMkTempReport("Template '%s' should contain a file name with no path and at least three consecutive 'X' characters or ending in 'X'.\n",
+ pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ if (pcszPath && !RTPathStartsWithRoot(pcszPath))
+ {
+ toolboxMkTempReport("Path '%s' should be absolute.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ if (pcszPath)
+ {
+ rc = RTStrCopy(szTemplateWithPath, sizeof(szTemplateWithPath), pcszPath);
+ if (RT_FAILURE(rc))
+ {
+ toolboxMkTempReport("Path '%s' too long.\n", pcszPath, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ else
+ {
+ rc = RTPathTemp(szTemplateWithPath, sizeof(szTemplateWithPath));
+ if (RT_FAILURE(rc))
+ {
+ toolboxMkTempReport("Failed to get the temporary directory.\n", "", true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+ rc = RTPathAppend(szTemplateWithPath, sizeof(szTemplateWithPath), pcszTemplate);
+ if (RT_FAILURE(rc))
+ {
+ toolboxMkTempReport("Template '%s' too long for path.\n", pcszTemplate, true, VERR_INVALID_PARAMETER, fOutputFlags, &rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_DIRECTORY)
+ {
+ rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE
+ ? RTDirCreateTempSecure(szTemplateWithPath)
+ : RTDirCreateTemp(szTemplateWithPath, fMode);
+ toolboxMkTempReport("Created temporary directory '%s'.\n",
+ szTemplateWithPath, RT_SUCCESS(rc), rc,
+ fOutputFlags, NULL);
+ /* RTDirCreateTemp[Secure] sets the template to "" on failure. */
+ toolboxMkTempReport("The following error occurred while creating a temporary directory from template '%s': %Rrc.\n",
+ pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/);
+ }
+ else
+ {
+ rc = fFlags & VBOXSERVICETOOLBOXMKTEMPFLAG_SECURE
+ ? RTFileCreateTempSecure(szTemplateWithPath)
+ : RTFileCreateTemp(szTemplateWithPath, fMode);
+ toolboxMkTempReport("Created temporary file '%s'.\n",
+ szTemplateWithPath, RT_SUCCESS(rc), rc,
+ fOutputFlags, NULL);
+ /* RTFileCreateTemp[Secure] sets the template to "" on failure. */
+ toolboxMkTempReport("The following error occurred while creating a temporary file from template '%s': %Rrc.\n",
+ pcszTemplate, RT_FAILURE(rc), rc, fOutputFlags, NULL /*prc*/);
+ }
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ vgsvcToolboxPrintStrmTermination();
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
+
+/** @todo Document options! */
+static char g_paszMkDirHelp[] =
+ " VBoxService [--use-toolbox] vbox_mkdir [<general options>] [<options>]\n"
+ " <directory>...\n\n"
+ "Options:\n\n"
+ " [--mode|-m <mode>] The file mode to set (chmod) on the created\n"
+ " directories. Default: a=rwx & umask.\n"
+ " [--parents|-p] Create parent directories as needed, no\n"
+ " error if the directory already exists.\n"
+ " [--verbose|-v] Display a message for each created directory.\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_mkdir".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxMkDir(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--mode", 'm', RTGETOPT_REQ_STRING },
+ { "--parents", 'p', RTGETOPT_REQ_NOTHING},
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING}
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ AssertRCReturn(rc, RTEXITCODE_INIT);
+
+ bool fMakeParentDirs = false;
+ bool fVerbose = false;
+ RTFMODE fDirMode = RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO;
+ int cDirsCreated = 0;
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'p':
+ fMakeParentDirs = true;
+ break;
+
+ case 'm':
+ rc = vgsvcToolboxParseMode(ValueUnion.psz, &fDirMode);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_SYNTAX;
+#ifndef RT_OS_WINDOWS
+ umask(0); /* RTDirCreate workaround */
+#endif
+ break;
+
+ case 'v':
+ fVerbose = true;
+ break;
+
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszMkDirHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ if (fMakeParentDirs)
+ /** @todo r=bird: If fVerbose is set, we should also show
+ * which directories that get created, parents as well as
+ * omitting existing final dirs. Annoying, but check any
+ * mkdir implementation (try "mkdir -pv asdf/1/2/3/4"
+ * twice). */
+ rc = RTDirCreateFullPath(ValueUnion.psz, fDirMode);
+ else
+ rc = RTDirCreate(ValueUnion.psz, fDirMode, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Could not create directory '%s': %Rra\n",
+ ValueUnion.psz, rc);
+ if (fVerbose)
+ RTMsgInfo("Created directory '%s', mode %#RTfmode\n", ValueUnion.psz, fDirMode);
+ cDirsCreated++;
+ break;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+ AssertRC(rc);
+
+ if (cDirsCreated == 0)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No directory argument.");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/** @todo Document options! */
+static char g_paszStatHelp[] =
+ " VBoxService [--use-toolbox] vbox_stat [<general options>] [<options>]\n"
+ " <file>...\n\n"
+ "Display file or file system status.\n\n"
+ "Options:\n\n"
+ " [--file-system|-f]\n"
+ " [--dereference|-L]\n"
+ " [--terse|-t]\n"
+ " [--verbose|-v]\n"
+ "\n";
+
+
+/**
+ * Main function for tool "vbox_stat".
+ *
+ * @return RTEXITCODE.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ */
+static RTEXITCODE vgsvcToolboxStat(int argc, char **argv)
+{
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--file-system", 'f', RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { "--machinereadable", VBOXSERVICETOOLBOXOPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--terse", 't', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING }
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+
+ int rc = VINF_SUCCESS;
+ uint32_t fOutputFlags = VBOXSERVICETOOLBOXOUTPUTFLAG_LONG; /* Use long mode by default. */
+ uint32_t fQueryInfoFlags = RTPATH_F_ON_LINK;
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'f':
+ RTMsgError("Sorry, option '%s' is not implemented yet!\n", ValueUnion.pDef->pszLong);
+ rc = VERR_INVALID_PARAMETER;
+ break;
+
+ case 'L':
+ fQueryInfoFlags &= ~RTPATH_F_ON_LINK;
+ fQueryInfoFlags |= RTPATH_F_FOLLOW_LINK;
+ break;
+
+ case VBOXSERVICETOOLBOXOPT_MACHINE_READABLE:
+ fOutputFlags |= VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE;
+ break;
+
+ case 'h':
+ vgsvcToolboxShowUsageHeader();
+ RTPrintf("%s", g_paszStatHelp);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ vgsvcToolboxShowVersion();
+ return RTEXITCODE_SUCCESS;
+
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ Assert(GetState.iNext);
+ GetState.iNext--;
+ break;
+ }
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+
+ /* All flags / options processed? Bail out here.
+ * Processing the file / directory list comes down below. */
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ {
+ rc = vgsvcToolboxStrmInit();
+ if (RT_FAILURE(rc))
+ RTMsgError("Error while initializing parseable streams, rc=%Rrc\n", rc);
+ vgsvcToolboxPrintStrmHeader("vbt_stat", 1 /* Stream version */);
+ }
+
+ VGSVCTOOLBOXIDCACHE IdCache;
+ RT_ZERO(IdCache);
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ RTFSOBJINFO objInfo;
+ int rc2 = RTPathQueryInfoEx(ValueUnion.psz, &objInfo, RTFSOBJATTRADD_UNIX, fQueryInfoFlags);
+ if (RT_FAILURE(rc2))
+ {
+ if (!(fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE))
+ RTMsgError("Cannot stat for '%s': %Rrc\n", ValueUnion.psz, rc2);
+ }
+ else
+ rc2 = vgsvcToolboxPrintFsInfo(ValueUnion.psz, strlen(ValueUnion.psz), fOutputFlags, NULL, &IdCache, &objInfo);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ /* Do not break here -- process every element in the list
+ * and keep (initial) failing rc. */
+ }
+
+ if (fOutputFlags & VBOXSERVICETOOLBOXOUTPUTFLAG_PARSEABLE) /* Output termination. */
+ vgsvcToolboxPrintStrmTermination();
+
+ /* At this point the overall result (success/failure) should be in rc. */
+ }
+ else
+ RTMsgError("Failed with rc=%Rrc\n", rc);
+
+ if (RT_FAILURE(rc))
+ {
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_ACCESS_DENIED;
+
+ case VERR_FILE_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_FILE_NOT_FOUND;
+
+ case VERR_PATH_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_PATH_NOT_FOUND;
+
+ case VERR_NET_PATH_NOT_FOUND:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_NET_PATH_NOT_FOUND;
+
+ case VERR_INVALID_NAME:
+ return (RTEXITCODE)VBOXSERVICETOOLBOX_STAT_EXITCODE_INVALID_NAME;
+
+ default:
+#ifdef DEBUG_andy
+ AssertMsgFailed(("Exit code for %Rrc not implemented\n", rc));
+#endif
+ break;
+ }
+
+ return RTEXITCODE_FAILURE;
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Looks up the tool definition entry for the tool give by @a pszTool.
+ *
+ * @returns Pointer to the tool definition. NULL if not found.
+ * @param pszTool The name of the tool.
+ */
+static PCVBOXSERVICETOOLBOXTOOL vgsvcToolboxLookUp(const char *pszTool)
+{
+ AssertPtrReturn(pszTool, NULL);
+
+ /* Do a linear search, since we don't have that much stuff in the table. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aTools); i++)
+ if (!strcmp(g_aTools[i].pszName, pszTool))
+ return &g_aTools[i];
+
+ return NULL;
+}
+
+
+/**
+ * Converts a tool's exit code back to an IPRT error code.
+ *
+ * @return Converted IPRT status code.
+ * @param pszTool Name of the toolbox tool to convert exit code for.
+ * @param rcExit The tool's exit code to convert.
+ */
+int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit)
+{
+ AssertPtrReturn(pszTool, VERR_INVALID_POINTER);
+
+ PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool);
+ if (pTool)
+ return pTool->pfnExitCodeConvertToRc(rcExit);
+
+ AssertMsgFailed(("Tool '%s' not found\n", pszTool));
+ return VERR_GENERAL_FAILURE; /* Lookup failed, should not happen. */
+}
+
+
+/**
+ * Entry point for internal toolbox.
+ *
+ * @return True if an internal tool was handled, false if not.
+ * @param argc Number of arguments.
+ * @param argv Pointer to argument array.
+ * @param prcExit Where to store the exit code when an
+ * internal toolbox command was handled.
+ */
+bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit)
+{
+
+ /*
+ * Check if the file named in argv[0] is one of the toolbox programs.
+ */
+ AssertReturn(argc > 0, false);
+ const char *pszTool = RTPathFilename(argv[0]);
+ PCVBOXSERVICETOOLBOXTOOL pTool = vgsvcToolboxLookUp(pszTool);
+ if (!pTool)
+ {
+ /*
+ * For debugging and testing purposes we also allow toolbox program access
+ * when the first VBoxService argument is --use-toolbox.
+ */
+ if (argc < 2 || strcmp(argv[1], "--use-toolbox"))
+ {
+ /* We must match vgsvcGstCtrlProcessCreateProcess here and claim
+ everything starting with "vbox_". */
+ if (!RTStrStartsWith(pszTool, "vbox_"))
+ return false;
+ RTMsgError("Unknown tool: %s\n", pszTool);
+ *prcExit = RTEXITCODE_SYNTAX;
+ return true;
+ }
+
+ /* No tool specified? Show toolbox help. */
+ if (argc < 3)
+ {
+ RTMsgError("No tool following --use-toolbox\n");
+ *prcExit = RTEXITCODE_SYNTAX;
+ return true;
+ }
+
+ argc -= 2;
+ argv += 2;
+ pszTool = argv[0];
+ pTool = vgsvcToolboxLookUp(pszTool);
+ if (!pTool)
+ {
+ *prcExit = RTEXITCODE_SUCCESS;
+ if ( !strcmp(pszTool, "-V")
+ || !strcmp(pszTool, "version"))
+ vgsvcToolboxShowVersion();
+ else if ( !strcmp(pszTool, "help")
+ || !strcmp(pszTool, "--help")
+ || !strcmp(pszTool, "-h"))
+ vgsvcToolboxShowUsage();
+ else
+ {
+ RTMsgError("Unknown tool: %s\n", pszTool);
+ *prcExit = RTEXITCODE_SYNTAX;
+ }
+ return true;
+ }
+ }
+
+ /*
+ * Invoke the handler.
+ */
+ RTMsgSetProgName("VBoxService/%s", pszTool);
+ AssertPtr(pTool);
+ *prcExit = pTool->pfnHandler(argc, argv);
+
+ return true;
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h
new file mode 100644
index 00000000..e9f1f4ea
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceToolBox.h
@@ -0,0 +1,42 @@
+/* $Id: VBoxServiceToolBox.h $ */
+/** @file
+ * VBoxService - Toolbox header for sharing defines between toolbox binary and VBoxService.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/GuestHost/GuestControl.h>
+
+RT_C_DECLS_BEGIN
+extern bool VGSvcToolboxMain(int argc, char **argv, RTEXITCODE *prcExit);
+extern int VGSvcToolboxExitCodeConvertToRc(const char *pszTool, RTEXITCODE rcExit);
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceToolBox_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp
new file mode 100644
index 00000000..5949dd84
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.cpp
@@ -0,0 +1,324 @@
+/* $Id: VBoxServiceUtils.cpp $ */
+/** @file
+ * VBoxServiceUtils - Some utility functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include <iprt/param.h>
+# include <iprt/path.h>
+#endif
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+
+
+#ifdef VBOX_WITH_GUEST_PROPS
+/**
+ * Reads a guest property as a 32-bit value.
+ *
+ * @returns VBox status code, fully bitched.
+ *
+ * @param u32ClientId The HGCM client ID for the guest property session.
+ * @param pszPropName The property name.
+ * @param pu32 Where to store the 32-bit value.
+ *
+ */
+int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max)
+{
+ char *pszValue;
+ int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, NULL /* ppszFlags */, NULL /* puTimestamp */);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszNext;
+ rc = RTStrToUInt32Ex(pszValue, &pszNext, 0, pu32);
+ if ( RT_SUCCESS(rc)
+ && (*pu32 < u32Min || *pu32 > u32Max))
+ rc = VGSvcError("The guest property value %s = %RU32 is out of range [%RU32..%RU32].\n",
+ pszPropName, *pu32, u32Min, u32Max);
+ RTStrFree(pszValue);
+ }
+ return rc;
+}
+
+/**
+ * Reads a guest property from the host side.
+ *
+ * @returns IPRT status code, fully bitched.
+ * @param u32ClientId The HGCM client ID for the guest property session.
+ * @param pszPropName The property name.
+ * @param fReadOnly Whether or not this property needs to be read only
+ * by the guest side. Otherwise VERR_ACCESS_DENIED will
+ * be returned.
+ * @param ppszValue Where to return the value. This is always set
+ * to NULL. Free it using RTStrFree().
+ * @param ppszFlags Where to return the value flags. Free it
+ * using RTStrFree(). Optional.
+ * @param puTimestamp Where to return the timestamp. This is only set
+ * on success. Optional.
+ */
+int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly,
+ char **ppszValue, char **ppszFlags, uint64_t *puTimestamp)
+{
+ AssertPtrReturn(ppszValue, VERR_INVALID_PARAMETER);
+
+ char *pszValue = NULL;
+ char *pszFlags = NULL;
+ int rc = VbglR3GuestPropReadEx(u32ClientId, pszPropName, &pszValue, &pszFlags, puTimestamp);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check security bits. */
+ if ( fReadOnly /* Do we except a guest read-only property */
+ && !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;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszValue = pszValue;
+
+ if (ppszFlags)
+ *ppszFlags = pszFlags;
+ else if (pszFlags)
+ RTStrFree(pszFlags);
+ }
+ else
+ {
+ if (pszValue)
+ RTStrFree(pszValue);
+ if (pszFlags)
+ RTStrFree(pszFlags);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Wrapper around VbglR3GuestPropWriteValue that does value formatting and
+ * logging.
+ *
+ * @returns VBox status code. Errors will be logged.
+ *
+ * @param u32ClientId The HGCM client ID for the guest property session.
+ * @param pszName The property name.
+ * @param pszValueFormat The property format string. If this is NULL then
+ * the property will be deleted (if possible).
+ * @param ... Format arguments.
+ */
+int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...)
+{
+ AssertPtr(pszName);
+ int rc;
+ if (pszValueFormat != NULL)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+ VGSvcVerbose(3, "Writing guest property '%s' = '%N'\n", pszName, pszValueFormat, &va);
+ va_end(va);
+
+ va_start(va, pszValueFormat);
+ rc = VbglR3GuestPropWriteValueV(u32ClientId, pszName, pszValueFormat, va);
+ va_end(va);
+
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing guest property '%s' (rc=%Rrc)\n", pszName, rc);
+ }
+ else
+ {
+ VGSvcVerbose(3, "Deleting guest property '%s'\n", pszName);
+ rc = VbglR3GuestPropWriteValue(u32ClientId, pszName, NULL);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error deleting guest property '%s' (rc=%Rrc)\n", pszName, rc);
+ }
+ return rc;
+}
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+#ifdef RT_OS_WINDOWS
+
+/**
+ * Helper for vgsvcUtilGetFileVersion and attempts to read and parse
+ * FileVersion.
+ *
+ * @returns Success indicator.
+ */
+static bool vgsvcUtilGetFileVersionOwn(LPSTR pVerData, uint32_t *puMajor, uint32_t *puMinor,
+ uint32_t *puBuildNumber, uint32_t *puRevisionNumber)
+{
+ UINT cchStrValue = 0;
+ LPTSTR pStrValue = NULL;
+ if (!VerQueryValueA(pVerData, "\\StringFileInfo\\040904b0\\FileVersion", (LPVOID *)&pStrValue, &cchStrValue))
+ return false;
+
+ char *pszNext = pStrValue;
+ int rc = RTStrToUInt32Ex(pszNext, &pszNext, 0, puMajor);
+ AssertReturn(rc == VWRN_TRAILING_CHARS, false);
+ AssertReturn(*pszNext == '.', false);
+
+ rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puMinor);
+ AssertReturn(rc == VWRN_TRAILING_CHARS, false);
+ AssertReturn(*pszNext == '.', false);
+
+ rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puBuildNumber);
+ AssertReturn(rc == VWRN_TRAILING_CHARS, false);
+ AssertReturn(*pszNext == '.', false);
+
+ rc = RTStrToUInt32Ex(pszNext + 1, &pszNext, 0, puRevisionNumber);
+ AssertReturn(rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS /*??*/, false);
+
+ return true;
+}
+
+
+/**
+ * Worker for VGSvcUtilWinGetFileVersionString.
+ *
+ * @returns VBox status code.
+ * @param pszFilename ASCII & ANSI & UTF-8 compliant name.
+ * @param puMajor Where to return the major version number.
+ * @param puMinor Where to return the minor version number.
+ * @param puBuildNumber Where to return the build number.
+ * @param puRevisionNumber Where to return the revision number.
+ */
+static int vgsvcUtilGetFileVersion(const char *pszFilename, uint32_t *puMajor, uint32_t *puMinor, uint32_t *puBuildNumber,
+ uint32_t *puRevisionNumber)
+{
+ int rc;
+
+ *puMajor = *puMinor = *puBuildNumber = *puRevisionNumber = 0;
+
+ /*
+ * Get the file version info.
+ */
+ DWORD dwHandleIgnored;
+ DWORD cbVerData = GetFileVersionInfoSizeA(pszFilename, &dwHandleIgnored);
+ if (cbVerData)
+ {
+ LPTSTR pVerData = (LPTSTR)RTMemTmpAllocZ(cbVerData);
+ if (pVerData)
+ {
+ if (GetFileVersionInfoA(pszFilename, dwHandleIgnored, cbVerData, pVerData))
+ {
+ /*
+ * Try query and parse the FileVersion string our selves first
+ * since this will give us the correct revision number when
+ * it goes beyond the range of an uint16_t / WORD.
+ */
+ if (vgsvcUtilGetFileVersionOwn(pVerData, puMajor, puMinor, puBuildNumber, puRevisionNumber))
+ rc = VINF_SUCCESS;
+ else
+ {
+ /* Fall back on VS_FIXEDFILEINFO */
+ UINT cbFileInfoIgnored = 0;
+ VS_FIXEDFILEINFO *pFileInfo = NULL;
+ if (VerQueryValue(pVerData, "\\", (LPVOID *)&pFileInfo, &cbFileInfoIgnored))
+ {
+ *puMajor = HIWORD(pFileInfo->dwFileVersionMS);
+ *puMinor = LOWORD(pFileInfo->dwFileVersionMS);
+ *puBuildNumber = HIWORD(pFileInfo->dwFileVersionLS);
+ *puRevisionNumber = LOWORD(pFileInfo->dwFileVersionLS);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ VGSvcVerbose(3, "No file version value for file '%s' available! (%d / rc=%Rrc)\n",
+ pszFilename, GetLastError(), rc);
+ }
+ }
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ VGSvcVerbose(0, "GetFileVersionInfo(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc);
+ }
+
+ RTMemTmpFree(pVerData);
+ }
+ else
+ {
+ VGSvcVerbose(0, "Failed to allocate %u byte for file version info for '%s'\n", cbVerData, pszFilename);
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ VGSvcVerbose(3, "GetFileVersionInfoSize(%s) -> %u / %Rrc\n", pszFilename, GetLastError(), rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Gets a re-formatted version string from the VS_FIXEDFILEINFO table.
+ *
+ * @returns VBox status code. The output buffer is always valid and the status
+ * code can safely be ignored.
+ *
+ * @param pszPath The base path.
+ * @param pszFilename The filename.
+ * @param pszVersion Where to return the version string.
+ * @param cbVersion The size of the version string buffer. This MUST be
+ * at least 2 bytes!
+ */
+int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFilename, char *pszVersion, size_t cbVersion)
+{
+ /*
+ * We will ALWAYS return with a valid output buffer.
+ */
+ AssertReturn(cbVersion >= 2, VERR_BUFFER_OVERFLOW);
+ pszVersion[0] = '-';
+ pszVersion[1] = '\0';
+
+ /*
+ * Create the path and query the bits.
+ */
+ char szFullPath[RTPATH_MAX];
+ int rc = RTPathJoin(szFullPath, sizeof(szFullPath), pszPath, pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uMajor, uMinor, uBuild, uRev;
+ rc = vgsvcUtilGetFileVersion(szFullPath, &uMajor, &uMinor, &uBuild, &uRev);
+ if (RT_SUCCESS(rc))
+ RTStrPrintf(pszVersion, cbVersion, "%u.%u.%ur%u", uMajor, uMinor, uBuild, uRev);
+ }
+ return rc;
+}
+
+#endif /* RT_OS_WINDOWS */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h
new file mode 100644
index 00000000..8eb4468d
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceUtils.h
@@ -0,0 +1,49 @@
+/* $Id: VBoxServiceUtils.h $ */
+/** @file
+ * VBoxServiceUtils - Guest Additions Services (Utilities).
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxServiceInternal.h"
+
+#ifdef VBOX_WITH_GUEST_PROPS
+int VGSvcReadProp(uint32_t u32ClientId, const char *pszPropName, char **ppszValue, char **ppszFlags, uint64_t *puTimestamp);
+int VGSvcReadPropUInt32(uint32_t u32ClientId, const char *pszPropName, uint32_t *pu32, uint32_t u32Min, uint32_t u32Max);
+int VGSvcReadHostProp(uint32_t u32ClientId, const char *pszPropName, bool fReadOnly, char **ppszValue, char **ppszFlags,
+ uint64_t *puTimestamp);
+int VGSvcWritePropF(uint32_t u32ClientId, const char *pszName, const char *pszValueFormat, ...);
+#endif
+
+#ifdef RT_OS_WINDOWS
+int VGSvcUtilWinGetFileVersionString(const char *pszPath, const char *pszFileName, char *pszVersion, size_t cbVersion);
+#endif
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceUtils_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
new file mode 100644
index 00000000..8560b6d8
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo-win.cpp
@@ -0,0 +1,1363 @@
+/* $Id: VBoxServiceVMInfo-win.cpp $ */
+/** @file
+ * VBoxService - Virtual Machine Information for the Host, Windows specifics.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0600
+# undef _WIN32_WINNT
+# define _WIN32_WINNT 0x0600 /* QueryFullProcessImageNameW in recent SDKs. */
+#endif
+#include <iprt/win/windows.h>
+#include <wtsapi32.h> /* For WTS* calls. */
+#include <psapi.h> /* EnumProcesses. */
+#include <Ntsecapi.h> /* Needed for process security information. */
+
+#include <iprt/assert.h>
+#include <iprt/ldr.h>
+#include <iprt/localipc.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServiceVMInfo.h"
+#include "../../WINNT/VBoxTray/VBoxTrayMsg.h" /* For IPC. */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Structure for storing the looked up user information. */
+typedef struct VBOXSERVICEVMINFOUSER
+{
+ WCHAR wszUser[MAX_PATH];
+ WCHAR wszAuthenticationPackage[MAX_PATH];
+ WCHAR wszLogonDomain[MAX_PATH];
+ /** Number of assigned user processes. */
+ ULONG ulNumProcs;
+ /** Last (highest) session ID. This
+ * is needed for distinguishing old session
+ * process counts from new (current) session
+ * ones. */
+ ULONG ulLastSession;
+} VBOXSERVICEVMINFOUSER, *PVBOXSERVICEVMINFOUSER;
+
+/** Structure for the file information lookup. */
+typedef struct VBOXSERVICEVMINFOFILE
+{
+ char *pszFilePath;
+ char *pszFileName;
+} VBOXSERVICEVMINFOFILE, *PVBOXSERVICEVMINFOFILE;
+
+/** Structure for process information lookup. */
+typedef struct VBOXSERVICEVMINFOPROC
+{
+ /** The PID. */
+ DWORD id;
+ /** The SID. */
+ PSID pSid;
+ /** The LUID. */
+ LUID luid;
+ /** Interactive process. */
+ bool fInteractive;
+} VBOXSERVICEVMINFOPROC, *PVBOXSERVICEVMINFOPROC;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs);
+static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER a_pUserInfo, PLUID a_pSession);
+static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppProc, DWORD *pdwCount);
+static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs);
+static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static uint32_t s_uDebugGuestPropClientID = 0;
+static uint32_t s_uDebugIter = 0;
+/** Whether to skip the logged-in user detection over RDP or not.
+ * See notes in this section why we might want to skip this. */
+static bool s_fSkipRDPDetection = false;
+
+static RTONCE g_vgsvcWinVmInitOnce = RTONCE_INITIALIZER;
+
+/** @name Secur32.dll imports are dynamically resolved because of NT4.
+ * @{ */
+static decltype(LsaGetLogonSessionData) *g_pfnLsaGetLogonSessionData = NULL;
+static decltype(LsaEnumerateLogonSessions) *g_pfnLsaEnumerateLogonSessions = NULL;
+static decltype(LsaFreeReturnBuffer) *g_pfnLsaFreeReturnBuffer = NULL;
+/** @} */
+
+/** @name WtsApi32.dll imports are dynamically resolved because of NT4.
+ * @{ */
+static decltype(WTSFreeMemory) *g_pfnWTSFreeMemory = NULL;
+static decltype(WTSQuerySessionInformationA) *g_pfnWTSQuerySessionInformationA = NULL;
+/** @} */
+
+/** @name PsApi.dll imports are dynamically resolved because of NT4.
+ * @{ */
+static decltype(EnumProcesses) *g_pfnEnumProcesses = NULL;
+static decltype(GetModuleFileNameExW) *g_pfnGetModuleFileNameExW = NULL;
+/** @} */
+
+/** @name New Kernel32.dll APIs we may use when present.
+ * @{ */
+static decltype(QueryFullProcessImageNameW) *g_pfnQueryFullProcessImageNameW = NULL;
+
+/** @} */
+
+
+/**
+ * An RTOnce callback function.
+ */
+static DECLCALLBACK(int) vgsvcWinVmInfoInitOnce(void *pvIgnored)
+{
+ RT_NOREF1(pvIgnored);
+
+ /* SECUR32 */
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrLoadSystem("secur32.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLdrMod, "LsaGetLogonSessionData", (void **)&g_pfnLsaGetLogonSessionData);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "LsaEnumerateLogonSessions", (void **)&g_pfnLsaEnumerateLogonSessions);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "LsaFreeReturnBuffer", (void **)&g_pfnLsaFreeReturnBuffer);
+ AssertRC(rc);
+ RTLdrClose(hLdrMod);
+ }
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(1, "Secur32.dll APIs are not available (%Rrc)\n", rc);
+ g_pfnLsaGetLogonSessionData = NULL;
+ g_pfnLsaEnumerateLogonSessions = NULL;
+ g_pfnLsaFreeReturnBuffer = NULL;
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
+ }
+
+ /* WTSAPI32 */
+ rc = RTLdrLoadSystem("wtsapi32.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLdrMod, "WTSFreeMemory", (void **)&g_pfnWTSFreeMemory);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "WTSQuerySessionInformationA", (void **)&g_pfnWTSQuerySessionInformationA);
+ AssertRC(rc);
+ RTLdrClose(hLdrMod);
+ }
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(1, "WtsApi32.dll APIs are not available (%Rrc)\n", rc);
+ g_pfnWTSFreeMemory = NULL;
+ g_pfnWTSQuerySessionInformationA = NULL;
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
+ }
+
+ /* PSAPI */
+ rc = RTLdrLoadSystem("psapi.dll", true /*fNoUnload*/, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLdrMod, "EnumProcesses", (void **)&g_pfnEnumProcesses);
+ if (RT_SUCCESS(rc))
+ rc = RTLdrGetSymbol(hLdrMod, "GetModuleFileNameExW", (void **)&g_pfnGetModuleFileNameExW);
+ AssertRC(rc);
+ RTLdrClose(hLdrMod);
+ }
+ if (RT_FAILURE(rc))
+ {
+ VGSvcVerbose(1, "psapi.dll APIs are not available (%Rrc)\n", rc);
+ g_pfnEnumProcesses = NULL;
+ g_pfnGetModuleFileNameExW = NULL;
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 0, 0));
+ }
+
+ /* Kernel32: */
+ rc = RTLdrLoadSystem("kernel32.dll", true /*fNoUnload*/, &hLdrMod);
+ AssertRCReturn(rc, rc);
+ rc = RTLdrGetSymbol(hLdrMod, "QueryFullProcessImageNameW", (void **)&g_pfnQueryFullProcessImageNameW);
+ if (RT_FAILURE(rc))
+ {
+ Assert(RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(6, 0, 0));
+ g_pfnQueryFullProcessImageNameW = NULL;
+ }
+ RTLdrClose(hLdrMod);
+
+ return VINF_SUCCESS;
+}
+
+
+static bool vgsvcVMInfoSession0Separation(void)
+{
+ return RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0); /* Vista */
+}
+
+
+/**
+ * Retrieves the module name of a given process.
+ *
+ * @return IPRT status code.
+ */
+static int vgsvcVMInfoWinProcessesGetModuleNameW(PVBOXSERVICEVMINFOPROC const pProc, PRTUTF16 *ppszName)
+{
+ *ppszName = NULL;
+ AssertPtrReturn(ppszName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProc, VERR_INVALID_POINTER);
+ AssertReturn(g_pfnGetModuleFileNameExW || g_pfnQueryFullProcessImageNameW, VERR_NOT_SUPPORTED);
+
+ /*
+ * Open the process.
+ */
+ DWORD dwFlags = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ;
+ if (RTSystemGetNtVersion() >= RTSYSTEM_MAKE_NT_VERSION(6, 0, 0)) /* Vista and later */
+ dwFlags = PROCESS_QUERY_LIMITED_INFORMATION; /* possible to do on more processes */
+
+ HANDLE hProcess = OpenProcess(dwFlags, FALSE, pProc->id);
+ if (hProcess == NULL)
+ {
+ DWORD dwErr = GetLastError();
+ if (g_cVerbosity)
+ VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr);
+ return RTErrConvertFromWin32(dwErr);
+ }
+
+ /*
+ * Since GetModuleFileNameEx has trouble with cross-bitness stuff (32-bit apps
+ * cannot query 64-bit apps and vice verse) we have to use a different code
+ * path for Vista and up.
+ *
+ * So use QueryFullProcessImageNameW when available (Vista+), fall back on
+ * GetModuleFileNameExW on older windows version (
+ */
+ WCHAR wszName[_1K];
+ DWORD dwLen = _1K;
+ BOOL fRc;
+ if (g_pfnQueryFullProcessImageNameW)
+ fRc = g_pfnQueryFullProcessImageNameW(hProcess, 0 /*PROCESS_NAME_NATIVE*/, wszName, &dwLen);
+ else
+ fRc = g_pfnGetModuleFileNameExW(hProcess, NULL /* Get main executable */, wszName, dwLen);
+
+ int rc;
+ if (fRc)
+ rc = RTUtf16DupEx(ppszName, wszName, 0);
+ else
+ {
+ DWORD dwErr = GetLastError();
+ if (g_cVerbosity > 3)
+ VGSvcError("Unable to retrieve process name for PID=%u, LastError=%Rwc\n", pProc->id, dwErr);
+ rc = RTErrConvertFromWin32(dwErr);
+ }
+
+ CloseHandle(hProcess);
+ return rc;
+}
+
+
+/**
+ * Fills in more data for a process.
+ *
+ * @returns VBox status code.
+ * @param pProc The process structure to fill data into.
+ * @param tkClass The kind of token information to get.
+ */
+static int vgsvcVMInfoWinProcessesGetTokenInfo(PVBOXSERVICEVMINFOPROC pProc, TOKEN_INFORMATION_CLASS tkClass)
+{
+ AssertPtrReturn(pProc, VERR_INVALID_POINTER);
+
+ DWORD dwErr = ERROR_SUCCESS;
+ HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pProc->id);
+ if (h == NULL)
+ {
+ dwErr = GetLastError();
+ if (g_cVerbosity > 4)
+ VGSvcError("Unable to open process with PID=%u, error=%u\n", pProc->id, dwErr);
+ return RTErrConvertFromWin32(dwErr);
+ }
+
+ int rc = VINF_SUCCESS;
+ HANDLE hToken;
+ if (OpenProcessToken(h, TOKEN_QUERY, &hToken))
+ {
+ void *pvTokenInfo = NULL;
+ DWORD dwTokenInfoSize;
+ switch (tkClass)
+ {
+ case TokenStatistics:
+ /** @todo r=bird: Someone has been reading too many MSDN examples. You shall
+ * use RTMemAlloc here! There is absolutely not reason for
+ * complicating things uncessarily by using HeapAlloc! */
+ dwTokenInfoSize = sizeof(TOKEN_STATISTICS);
+ pvTokenInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTokenInfoSize);
+ AssertPtr(pvTokenInfo);
+ break;
+
+ case TokenGroups:
+ dwTokenInfoSize = 0;
+ /* Allocation will follow in a second step. */
+ break;
+
+ case TokenUser:
+ dwTokenInfoSize = 0;
+ /* Allocation will follow in a second step. */
+ break;
+
+ default:
+ VGSvcError("Token class not implemented: %d\n", tkClass);
+ rc = VERR_NOT_IMPLEMENTED;
+ dwTokenInfoSize = 0; /* Shut up MSC. */
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ DWORD dwRetLength;
+ if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength))
+ {
+ dwErr = GetLastError();
+ if (dwErr == ERROR_INSUFFICIENT_BUFFER)
+ {
+ dwErr = ERROR_SUCCESS;
+
+ switch (tkClass)
+ {
+ case TokenGroups:
+ pvTokenInfo = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength);
+ if (!pvTokenInfo)
+ dwErr = GetLastError();
+ dwTokenInfoSize = dwRetLength;
+ break;
+
+ case TokenUser:
+ pvTokenInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwRetLength);
+ if (!pvTokenInfo)
+ dwErr = GetLastError();
+ dwTokenInfoSize = dwRetLength;
+ break;
+
+ default:
+ AssertMsgFailed(("Re-allocating of token information for token class not implemented\n"));
+ break;
+ }
+
+ if (dwErr == ERROR_SUCCESS)
+ {
+ if (!GetTokenInformation(hToken, tkClass, pvTokenInfo, dwTokenInfoSize, &dwRetLength))
+ dwErr = GetLastError();
+ }
+ }
+ }
+
+ if (dwErr == ERROR_SUCCESS)
+ {
+ rc = VINF_SUCCESS;
+
+ switch (tkClass)
+ {
+ case TokenStatistics:
+ {
+ PTOKEN_STATISTICS pStats = (PTOKEN_STATISTICS)pvTokenInfo;
+ AssertPtr(pStats);
+ memcpy(&pProc->luid, &pStats->AuthenticationId, sizeof(LUID));
+ /** @todo Add more information of TOKEN_STATISTICS as needed. */
+ break;
+ }
+
+ case TokenGroups:
+ {
+ pProc->fInteractive = false;
+
+ SID_IDENTIFIER_AUTHORITY sidAuthNT = SECURITY_NT_AUTHORITY;
+ PSID pSidInteractive = NULL; /* S-1-5-4 */
+ if (!AllocateAndInitializeSid(&sidAuthNT, 1, 4, 0, 0, 0, 0, 0, 0, 0, &pSidInteractive))
+ dwErr = GetLastError();
+
+ PSID pSidLocal = NULL; /* S-1-2-0 */
+ if (dwErr == ERROR_SUCCESS)
+ {
+ SID_IDENTIFIER_AUTHORITY sidAuthLocal = SECURITY_LOCAL_SID_AUTHORITY;
+ if (!AllocateAndInitializeSid(&sidAuthLocal, 1, 0, 0, 0, 0, 0, 0, 0, 0, &pSidLocal))
+ dwErr = GetLastError();
+ }
+
+ if (dwErr == ERROR_SUCCESS)
+ {
+ PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)pvTokenInfo;
+ AssertPtr(pGroups);
+ for (DWORD i = 0; i < pGroups->GroupCount; i++)
+ {
+ if ( EqualSid(pGroups->Groups[i].Sid, pSidInteractive)
+ || EqualSid(pGroups->Groups[i].Sid, pSidLocal)
+ || pGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID)
+ {
+ pProc->fInteractive = true;
+ break;
+ }
+ }
+ }
+
+ if (pSidInteractive)
+ FreeSid(pSidInteractive);
+ if (pSidLocal)
+ FreeSid(pSidLocal);
+ break;
+ }
+
+ case TokenUser:
+ {
+ PTOKEN_USER pUser = (PTOKEN_USER)pvTokenInfo;
+ AssertPtr(pUser);
+
+ DWORD dwLength = GetLengthSid(pUser->User.Sid);
+ Assert(dwLength);
+ if (dwLength)
+ {
+ pProc->pSid = (PSID)HeapAlloc(GetProcessHeap(),
+ HEAP_ZERO_MEMORY, dwLength);
+ AssertPtr(pProc->pSid);
+ if (CopySid(dwLength, pProc->pSid, pUser->User.Sid))
+ {
+ if (!IsValidSid(pProc->pSid))
+ dwErr = ERROR_INVALID_NAME;
+ }
+ else
+ dwErr = GetLastError();
+ }
+ else
+ dwErr = ERROR_NO_DATA;
+
+ if (dwErr != ERROR_SUCCESS)
+ {
+ VGSvcError("Error retrieving SID of process PID=%u: %u\n", pProc->id, dwErr);
+ if (pProc->pSid)
+ {
+ HeapFree(GetProcessHeap(), 0 /* Flags */, pProc->pSid);
+ pProc->pSid = NULL;
+ }
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Unhandled token information class\n"));
+ break;
+ }
+ }
+
+ if (pvTokenInfo)
+ HeapFree(GetProcessHeap(), 0 /* Flags */, pvTokenInfo);
+ }
+ CloseHandle(hToken);
+ }
+ else
+ dwErr = GetLastError();
+
+ if (dwErr != ERROR_SUCCESS)
+ {
+ if (g_cVerbosity)
+ VGSvcError("Unable to query token information for PID=%u, error=%u\n", pProc->id, dwErr);
+ rc = RTErrConvertFromWin32(dwErr);
+ }
+
+ CloseHandle(h);
+ return rc;
+}
+
+
+/**
+ * Enumerate all the processes in the system and get the logon user IDs for
+ * them.
+ *
+ * @returns VBox status code.
+ * @param ppaProcs Where to return the process snapshot. This must be
+ * freed by calling vgsvcVMInfoWinProcessesFree.
+ *
+ * @param pcProcs Where to store the returned process count.
+ */
+static int vgsvcVMInfoWinProcessesEnumerate(PVBOXSERVICEVMINFOPROC *ppaProcs, PDWORD pcProcs)
+{
+ AssertPtr(ppaProcs);
+ AssertPtr(pcProcs);
+
+ if (!g_pfnEnumProcesses)
+ return VERR_NOT_SUPPORTED;
+
+ /*
+ * Call EnumProcesses with an increasingly larger buffer until it all fits
+ * or we think something is screwed up.
+ */
+ DWORD cProcesses = 64;
+ PDWORD paPID = NULL;
+ int rc = VINF_SUCCESS;
+ do
+ {
+ /* Allocate / grow the buffer first. */
+ cProcesses *= 2;
+ void *pvNew = RTMemRealloc(paPID, cProcesses * sizeof(DWORD));
+ if (!pvNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ paPID = (PDWORD)pvNew;
+
+ /* Query the processes. Not the cbRet == buffer size means there could be more work to be done. */
+ DWORD cbRet;
+ if (!g_pfnEnumProcesses(paPID, cProcesses * sizeof(DWORD), &cbRet))
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ break;
+ }
+ if (cbRet < cProcesses * sizeof(DWORD))
+ {
+ cProcesses = cbRet / sizeof(DWORD);
+ break;
+ }
+ } while (cProcesses <= _32K); /* Should be enough; see: http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx */
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate out process structures and fill data into them.
+ * We currently only try lookup their LUID's.
+ */
+ PVBOXSERVICEVMINFOPROC paProcs;
+ paProcs = (PVBOXSERVICEVMINFOPROC)RTMemAllocZ(cProcesses * sizeof(VBOXSERVICEVMINFOPROC));
+ if (paProcs)
+ {
+ for (DWORD i = 0; i < cProcesses; i++)
+ {
+ paProcs[i].id = paPID[i];
+ paProcs[i].pSid = NULL;
+
+ int rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenUser);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VGSvcError("Get token class 'user' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
+
+ rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenGroups);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VGSvcError("Get token class 'groups' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
+
+ rc2 = vgsvcVMInfoWinProcessesGetTokenInfo(&paProcs[i], TokenStatistics);
+ if (RT_FAILURE(rc2) && g_cVerbosity)
+ VGSvcError("Get token class 'statistics' for process %u failed, rc=%Rrc\n", paProcs[i].id, rc2);
+ }
+
+ /* Save number of processes */
+ if (RT_SUCCESS(rc))
+ {
+ *pcProcs = cProcesses;
+ *ppaProcs = paProcs;
+ }
+ else
+ vgsvcVMInfoWinProcessesFree(cProcesses, paProcs);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ RTMemFree(paPID);
+ return rc;
+}
+
+/**
+ * Frees the process structures returned by
+ * vgsvcVMInfoWinProcessesEnumerate() before.
+ *
+ * @param cProcs Number of processes in paProcs.
+ * @param paProcs The process array.
+ */
+static void vgsvcVMInfoWinProcessesFree(DWORD cProcs, PVBOXSERVICEVMINFOPROC paProcs)
+{
+ for (DWORD i = 0; i < cProcs; i++)
+ if (paProcs[i].pSid)
+ {
+ HeapFree(GetProcessHeap(), 0 /* Flags */, paProcs[i].pSid);
+ paProcs[i].pSid = NULL;
+ }
+ RTMemFree(paProcs);
+}
+
+/**
+ * Determines whether the specified session has processes on the system.
+ *
+ * @returns Number of processes found for a specified session.
+ * @param pSession The current user's SID.
+ * @param paProcs The process snapshot.
+ * @param cProcs The number of processes in the snaphot.
+ * @param puTerminalSession Where to return terminal session number.
+ * Optional.
+ */
+/** @todo r=bird: The 'Has' indicates a predicate function, which this is
+ * not. Predicate functions always returns bool. */
+static uint32_t vgsvcVMInfoWinSessionHasProcesses(PLUID pSession, PVBOXSERVICEVMINFOPROC const paProcs, DWORD cProcs,
+ PULONG puTerminalSession)
+{
+ if (!pSession)
+ {
+ VGSvcVerbose(1, "Session became invalid while enumerating!\n");
+ return 0;
+ }
+ if (!g_pfnLsaGetLogonSessionData)
+ return 0;
+
+ PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
+ NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData);
+ if (rcNt != STATUS_SUCCESS)
+ {
+ VGSvcError("Could not get logon session data! rcNt=%#x\n", rcNt);
+ return 0;
+ }
+
+ if (!IsValidSid(pSessionData->Sid))
+ {
+ VGSvcError("User SID=%p is not valid\n", pSessionData->Sid);
+ if (pSessionData)
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return 0;
+ }
+
+
+ /*
+ * Even if a user seems to be logged in, it could be a stale/orphaned logon
+ * session. So check if we have some processes bound to it by comparing the
+ * session <-> process LUIDs.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cProcessesFound = 0;
+ for (DWORD i = 0; i < cProcs; i++)
+ {
+ PSID pProcSID = paProcs[i].pSid;
+ if ( RT_SUCCESS(rc)
+ && pProcSID
+ && IsValidSid(pProcSID))
+ {
+ if (EqualSid(pSessionData->Sid, paProcs[i].pSid))
+ {
+ if (g_cVerbosity)
+ {
+ PRTUTF16 pszName;
+ int rc2 = vgsvcVMInfoWinProcessesGetModuleNameW(&paProcs[i], &pszName);
+ VGSvcVerbose(4, "Session %RU32: PID=%u (fInt=%RTbool): %ls\n",
+ pSessionData->Session, paProcs[i].id, paProcs[i].fInteractive,
+ RT_SUCCESS(rc2) ? pszName : L"<Unknown>");
+ if (RT_SUCCESS(rc2))
+ RTUtf16Free(pszName);
+ }
+
+ if (paProcs[i].fInteractive)
+ {
+ cProcessesFound++;
+ if (!g_cVerbosity) /* We want a bit more info on higher verbosity. */
+ break;
+ }
+ }
+ }
+ }
+
+ if (puTerminalSession)
+ *puTerminalSession = pSessionData->Session;
+
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+
+ return cProcessesFound;
+}
+
+
+/**
+ * Save and noisy string copy.
+ *
+ * @param pwszDst Destination buffer.
+ * @param cbDst Size in bytes - not WCHAR count!
+ * @param pSrc Source string.
+ * @param pszWhat What this is. For the log.
+ */
+static void vgsvcVMInfoWinSafeCopy(PWCHAR pwszDst, size_t cbDst, LSA_UNICODE_STRING const *pSrc, const char *pszWhat)
+{
+ Assert(RT_ALIGN(cbDst, sizeof(WCHAR)) == cbDst);
+
+ size_t cbCopy = pSrc->Length;
+ if (cbCopy + sizeof(WCHAR) > cbDst)
+ {
+ VGSvcVerbose(0, "%s is too long - %u bytes, buffer %u bytes! It will be truncated.\n", pszWhat, cbCopy, cbDst);
+ cbCopy = cbDst - sizeof(WCHAR);
+ }
+ if (cbCopy)
+ memcpy(pwszDst, pSrc->Buffer, cbCopy);
+ pwszDst[cbCopy / sizeof(WCHAR)] = '\0';
+}
+
+
+/**
+ * Detects whether a user is logged on.
+ *
+ * @returns true if logged in, false if not (or error).
+ * @param pUserInfo Where to return the user information.
+ * @param pSession The session to check.
+ */
+static bool vgsvcVMInfoWinIsLoggedIn(PVBOXSERVICEVMINFOUSER pUserInfo, PLUID pSession)
+{
+ AssertPtrReturn(pUserInfo, false);
+ if (!pSession)
+ return false;
+ if ( !g_pfnLsaGetLogonSessionData
+ || !g_pfnLsaNtStatusToWinError)
+ return false;
+
+ PSECURITY_LOGON_SESSION_DATA pSessionData = NULL;
+ NTSTATUS rcNt = g_pfnLsaGetLogonSessionData(pSession, &pSessionData);
+ if (rcNt != STATUS_SUCCESS)
+ {
+ ULONG ulError = g_pfnLsaNtStatusToWinError(rcNt);
+ switch (ulError)
+ {
+ case ERROR_NOT_ENOUGH_MEMORY:
+ /* If we don't have enough memory it's hard to judge whether the specified user
+ * is logged in or not, so just assume he/she's not. */
+ VGSvcVerbose(3, "Not enough memory to retrieve logon session data!\n");
+ break;
+
+ case ERROR_NO_SUCH_LOGON_SESSION:
+ /* Skip session data which is not valid anymore because it may have been
+ * already terminated. */
+ break;
+
+ default:
+ VGSvcError("LsaGetLogonSessionData failed with error %u\n", ulError);
+ break;
+ }
+ if (pSessionData)
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return false;
+ }
+ if (!pSessionData)
+ {
+ VGSvcError("Invalid logon session data!\n");
+ return false;
+ }
+
+ VGSvcVerbose(3, "Session data: Name=%ls, SessionID=%RU32, LogonID=%d,%u, LogonType=%u\n",
+ pSessionData->UserName.Buffer, pSessionData->Session,
+ pSessionData->LogonId.HighPart, pSessionData->LogonId.LowPart, pSessionData->LogonType);
+
+ if (vgsvcVMInfoSession0Separation())
+ {
+ /* Starting at Windows Vista user sessions begin with session 1, so
+ * ignore (stale) session 0 users. */
+ if ( pSessionData->Session == 0
+ /* Also check the logon time. */
+ || pSessionData->LogonTime.QuadPart == 0)
+ {
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return false;
+ }
+ }
+
+ /*
+ * Only handle users which can login interactively or logged in
+ * remotely over native RDP.
+ */
+ bool fFoundUser = false;
+ if ( IsValidSid(pSessionData->Sid)
+ && ( (SECURITY_LOGON_TYPE)pSessionData->LogonType == Interactive
+ || (SECURITY_LOGON_TYPE)pSessionData->LogonType == RemoteInteractive
+ /* Note: We also need CachedInteractive in case Windows cached the credentials
+ * or just wants to reuse them! */
+ || (SECURITY_LOGON_TYPE)pSessionData->LogonType == CachedInteractive))
+ {
+ VGSvcVerbose(3, "Session LogonType=%u is supported -- looking up SID + type ...\n", pSessionData->LogonType);
+
+ /*
+ * Copy out relevant data.
+ */
+ vgsvcVMInfoWinSafeCopy(pUserInfo->wszUser, sizeof(pUserInfo->wszUser), &pSessionData->UserName, "User name");
+ vgsvcVMInfoWinSafeCopy(pUserInfo->wszAuthenticationPackage, sizeof(pUserInfo->wszAuthenticationPackage),
+ &pSessionData->AuthenticationPackage, "Authentication pkg name");
+ vgsvcVMInfoWinSafeCopy(pUserInfo->wszLogonDomain, sizeof(pUserInfo->wszLogonDomain),
+ &pSessionData->LogonDomain, "Logon domain name");
+
+ TCHAR szOwnerName[MAX_PATH] = { 0 };
+ DWORD dwOwnerNameSize = sizeof(szOwnerName);
+ TCHAR szDomainName[MAX_PATH] = { 0 };
+ DWORD dwDomainNameSize = sizeof(szDomainName);
+ SID_NAME_USE enmOwnerType = SidTypeInvalid;
+ if (!LookupAccountSid(NULL,
+ pSessionData->Sid,
+ szOwnerName,
+ &dwOwnerNameSize,
+ szDomainName,
+ &dwDomainNameSize,
+ &enmOwnerType))
+ {
+ DWORD dwErr = GetLastError();
+ /*
+ * If a network time-out prevents the function from finding the name or
+ * if a SID that does not have a corresponding account name (such as a
+ * logon SID that identifies a logon session), we get ERROR_NONE_MAPPED
+ * here that we just skip.
+ */
+ if (dwErr != ERROR_NONE_MAPPED)
+ VGSvcError("Failed looking up account info for user=%ls, error=$ld!\n", pUserInfo->wszUser, dwErr);
+ }
+ else
+ {
+ if (enmOwnerType == SidTypeUser) /* Only recognize users; we don't care about the rest! */
+ {
+ VGSvcVerbose(3, "Account User=%ls, Session=%u, LogonID=%d,%u, AuthPkg=%ls, Domain=%ls\n",
+ pUserInfo->wszUser, pSessionData->Session, pSessionData->LogonId.HighPart,
+ pSessionData->LogonId.LowPart, pUserInfo->wszAuthenticationPackage, pUserInfo->wszLogonDomain);
+
+ /* KB970910 (check http://support.microsoft.com/kb/970910 on archive.org)
+ * indicates that WTSQuerySessionInformation may leak memory and return the
+ * wrong status code for WTSApplicationName and WTSInitialProgram queries.
+ *
+ * The system must be low on resources, and presumably some internal operation
+ * must fail because of this, triggering an error handling path that forgets
+ * to free memory and set last error.
+ *
+ * bird 2022-08-26: However, we do not query either of those info items. We
+ * query WTSConnectState, which is a rather simple affair. So, I've
+ * re-enabled the code for all systems that includes the API.
+ */
+ if (!s_fSkipRDPDetection)
+ {
+ /* Skip if we don't have the WTS API. */
+ if (!g_pfnWTSQuerySessionInformationA)
+ s_fSkipRDPDetection = true;
+#if 0 /* bird: see above */
+ /* Skip RDP detection on Windows 2000 and older.
+ For Windows 2000 however we don't have any hotfixes, so just skip the
+ RDP detection in any case. */
+ else if (RTSystemGetNtVersion() < RTSYSTEM_MAKE_NT_VERSION(5, 1, 0)) /* older than XP */
+ s_fSkipRDPDetection = true;
+#endif
+ if (s_fSkipRDPDetection)
+ VGSvcVerbose(0, "Detection of logged-in users via RDP is disabled\n");
+ }
+
+ if (!s_fSkipRDPDetection)
+ {
+ Assert(g_pfnWTSQuerySessionInformationA);
+ Assert(g_pfnWTSFreeMemory);
+
+ /* Detect RDP sessions as well. */
+ LPTSTR pBuffer = NULL;
+ DWORD cbRet = 0;
+ int iState = -1;
+ if (g_pfnWTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE,
+ pSessionData->Session,
+ WTSConnectState,
+ &pBuffer,
+ &cbRet))
+ {
+ if (cbRet)
+ iState = *pBuffer;
+ VGSvcVerbose(3, "Account User=%ls, WTSConnectState=%d (%u)\n", pUserInfo->wszUser, iState, cbRet);
+ if ( iState == WTSActive /* User logged on to WinStation. */
+ || iState == WTSShadow /* Shadowing another WinStation. */
+ || iState == WTSDisconnected) /* WinStation logged on without client. */
+ {
+ /** @todo On Vista and W2K, always "old" user name are still
+ * there. Filter out the old one! */
+ VGSvcVerbose(3, "Account User=%ls using TCS/RDP, state=%d \n", pUserInfo->wszUser, iState);
+ fFoundUser = true;
+ }
+ if (pBuffer)
+ g_pfnWTSFreeMemory(pBuffer);
+ }
+ else
+ {
+ DWORD dwLastErr = GetLastError();
+ switch (dwLastErr)
+ {
+ /*
+ * Terminal services don't run (for example in W2K,
+ * nothing to worry about ...). ... or is on the Vista
+ * fast user switching page!
+ */
+ case ERROR_CTX_WINSTATION_NOT_FOUND:
+ VGSvcVerbose(3, "No WinStation found for user=%ls\n", pUserInfo->wszUser);
+ break;
+
+ default:
+ VGSvcVerbose(3, "Cannot query WTS connection state for user=%ls, error=%u\n",
+ pUserInfo->wszUser, dwLastErr);
+ break;
+ }
+
+ fFoundUser = true;
+ }
+ }
+ }
+ else
+ VGSvcVerbose(3, "SID owner type=%d not handled, skipping\n", enmOwnerType);
+ }
+
+ VGSvcVerbose(3, "Account User=%ls %s logged in\n", pUserInfo->wszUser, fFoundUser ? "is" : "is not");
+ }
+
+ if (fFoundUser)
+ pUserInfo->ulLastSession = pSessionData->Session;
+
+ g_pfnLsaFreeReturnBuffer(pSessionData);
+ return fFoundUser;
+}
+
+
+static int vgsvcVMInfoWinWriteLastInput(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
+ /* pszDomain is optional. */
+
+ char szPipeName[512 + sizeof(VBOXTRAY_IPC_PIPE_PREFIX)];
+ memcpy(szPipeName, VBOXTRAY_IPC_PIPE_PREFIX, sizeof(VBOXTRAY_IPC_PIPE_PREFIX));
+ int rc = RTStrCat(szPipeName, sizeof(szPipeName), pszUser);
+ if (RT_SUCCESS(rc))
+ {
+ bool fReportToHost = false;
+ VBoxGuestUserState userState = VBoxGuestUserState_Unknown;
+
+ RTLOCALIPCSESSION hSession;
+ rc = RTLocalIpcSessionConnect(&hSession, szPipeName, RTLOCALIPC_FLAGS_NATIVE_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ VBOXTRAYIPCHEADER ipcHdr =
+ {
+ /* .uMagic = */ VBOXTRAY_IPC_HDR_MAGIC,
+ /* .uVersion = */ VBOXTRAY_IPC_HDR_VERSION,
+ /* .enmMsgType = */ VBOXTRAYIPCMSGTYPE_USER_LAST_INPUT,
+ /* .cbPayload = */ 0 /* No payload */
+ };
+
+ rc = RTLocalIpcSessionWrite(hSession, &ipcHdr, sizeof(ipcHdr));
+ if (RT_SUCCESS(rc))
+ {
+ VBOXTRAYIPCREPLY_USER_LAST_INPUT_T ipcReply;
+ rc = RTLocalIpcSessionRead(hSession, &ipcReply, sizeof(ipcReply), NULL /* Exact read */);
+ if ( RT_SUCCESS(rc)
+ /* If uLastInput is set to UINT32_MAX VBoxTray was not able to retrieve the
+ * user's last input time. This might happen when running on Windows NT4 or older. */
+ && ipcReply.cSecSinceLastInput != UINT32_MAX)
+ {
+ userState = ipcReply.cSecSinceLastInput * 1000 < g_uVMInfoUserIdleThresholdMS
+ ? VBoxGuestUserState_InUse
+ : VBoxGuestUserState_Idle;
+
+ rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState",
+ userState == VBoxGuestUserState_InUse ? "InUse" : "Idle");
+
+ /*
+ * Note: vboxServiceUserUpdateF can return VINF_NO_CHANGE in case there wasn't anything
+ * to update. So only report the user's status to host when we really got something
+ * new.
+ */
+ fReportToHost = rc == VINF_SUCCESS;
+ VGSvcVerbose(4, "User '%s' (domain '%s') is idle for %RU32, fReportToHost=%RTbool\n",
+ pszUser, pszDomain ? pszDomain : "<None>", ipcReply.cSecSinceLastInput, fReportToHost);
+
+#if 0 /* Do we want to write the idle time as well? */
+ /* Also write the user's current idle time, if there is any. */
+ if (userState == VBoxGuestUserState_Idle)
+ rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", "%RU32", ipcReply.cSecSinceLastInput);
+ else
+ rc = vgsvcUserUpdateF(pCache, pszUser, pszDomain, "IdleTimeMs", NULL /* Delete property */);
+
+ if (RT_SUCCESS(rc))
+#endif
+ }
+#ifdef DEBUG
+ else if (RT_SUCCESS(rc) && ipcReply.cSecSinceLastInput == UINT32_MAX)
+ VGSvcVerbose(4, "Last input for user '%s' is not supported, skipping\n", pszUser, rc);
+#endif
+ }
+#ifdef DEBUG
+ VGSvcVerbose(4, "Getting last input for user '%s' ended with rc=%Rrc\n", pszUser, rc);
+#endif
+ int rc2 = RTLocalIpcSessionClose(hSession);
+ if (RT_SUCCESS(rc) && RT_FAILURE(rc2))
+ rc = rc2;
+ }
+ else
+ {
+ switch (rc)
+ {
+ case VERR_FILE_NOT_FOUND:
+ {
+ /* No VBoxTray (or too old version which does not support IPC) running
+ for the given user. Not much we can do then. */
+ VGSvcVerbose(4, "VBoxTray for user '%s' not running (anymore), no last input available\n", pszUser);
+
+ /* Overwrite rc from above. */
+ rc = VGSvcUserUpdateF(pCache, pszUser, pszDomain, "UsageState", "Idle");
+
+ fReportToHost = rc == VINF_SUCCESS;
+ if (fReportToHost)
+ userState = VBoxGuestUserState_Idle;
+ break;
+ }
+
+ default:
+ VGSvcError("Error querying last input for user '%s', rc=%Rrc\n", pszUser, rc);
+ break;
+ }
+ }
+
+ if (fReportToHost)
+ {
+ Assert(userState != VBoxGuestUserState_Unknown);
+ int rc2 = VbglR3GuestUserReportState(pszUser, pszDomain, userState, NULL /* No details */, 0);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Error reporting usage state %d for user '%s' to host, rc=%Rrc\n", userState, pszUser, rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Retrieves the currently logged in users and stores their names along with the
+ * user count.
+ *
+ * @returns VBox status code.
+ * @param pCache Property cache to use for storing some of the lookup
+ * data in between calls.
+ * @param ppszUserList Where to store the user list (separated by commas).
+ * Must be freed with RTStrFree().
+ * @param pcUsersInList Where to store the number of users in the list.
+ */
+int VGSvcVMInfoWinWriteUsers(PVBOXSERVICEVEPROPCACHE pCache, char **ppszUserList, uint32_t *pcUsersInList)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszUserList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcUsersInList, VERR_INVALID_POINTER);
+
+ int rc = RTOnce(&g_vgsvcWinVmInitOnce, vgsvcWinVmInfoInitOnce, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!g_pfnLsaEnumerateLogonSessions || !g_pfnEnumProcesses || !g_pfnLsaNtStatusToWinError)
+ return VERR_NOT_SUPPORTED;
+
+ rc = VbglR3GuestPropConnect(&s_uDebugGuestPropClientID);
+ AssertRC(rc);
+
+ char *pszUserList = NULL;
+ uint32_t cUsersInList = 0;
+
+ /* This function can report stale or orphaned interactive logon sessions
+ of already logged off users (especially in Windows 2000). */
+ PLUID paSessions = NULL;
+ ULONG cSessions = 0;
+ NTSTATUS rcNt = g_pfnLsaEnumerateLogonSessions(&cSessions, &paSessions);
+ if (rcNt != STATUS_SUCCESS)
+ {
+ ULONG uError = g_pfnLsaNtStatusToWinError(rcNt);
+ switch (uError)
+ {
+ case ERROR_NOT_ENOUGH_MEMORY:
+ VGSvcError("Not enough memory to enumerate logon sessions!\n");
+ break;
+
+ case ERROR_SHUTDOWN_IN_PROGRESS:
+ /* If we're about to shutdown when we were in the middle of enumerating the logon
+ * sessions, skip the error to not confuse the user with an unnecessary log message. */
+ VGSvcVerbose(3, "Shutdown in progress ...\n");
+ uError = ERROR_SUCCESS;
+ break;
+
+ default:
+ VGSvcError("LsaEnumerate failed with error %RU32\n", uError);
+ break;
+ }
+
+ if (paSessions)
+ g_pfnLsaFreeReturnBuffer(paSessions);
+
+ return RTErrConvertFromWin32(uError);
+ }
+ VGSvcVerbose(3, "Found %u sessions\n", cSessions);
+
+ PVBOXSERVICEVMINFOPROC paProcs;
+ DWORD cProcs;
+ rc = vgsvcVMInfoWinProcessesEnumerate(&paProcs, &cProcs);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MEMORY)
+ VGSvcError("Not enough memory to enumerate processes\n");
+ else
+ VGSvcError("Failed to enumerate processes, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ PVBOXSERVICEVMINFOUSER pUserInfo;
+ pUserInfo = (PVBOXSERVICEVMINFOUSER)RTMemAllocZ(cSessions * sizeof(VBOXSERVICEVMINFOUSER) + 1);
+ if (!pUserInfo)
+ VGSvcError("Not enough memory to store enumerated users!\n");
+ else
+ {
+ ULONG cUniqueUsers = 0;
+
+ /*
+ * Note: The cSessions loop variable does *not* correlate with
+ * the Windows session ID!
+ */
+ for (ULONG i = 0; i < cSessions; i++)
+ {
+ VGSvcVerbose(3, "Handling session %RU32 (of %RU32)\n", i + 1, cSessions);
+
+ VBOXSERVICEVMINFOUSER userSession;
+ if (vgsvcVMInfoWinIsLoggedIn(&userSession, &paSessions[i]))
+ {
+ VGSvcVerbose(4, "Handling user=%ls, domain=%ls, package=%ls, session=%RU32\n",
+ userSession.wszUser, userSession.wszLogonDomain, userSession.wszAuthenticationPackage,
+ userSession.ulLastSession);
+
+ /* Retrieve assigned processes of current session. */
+ uint32_t cCurSessionProcs = vgsvcVMInfoWinSessionHasProcesses(&paSessions[i], paProcs, cProcs,
+ NULL /* Terminal session ID */);
+ /* Don't return here when current session does not have assigned processes
+ * anymore -- in that case we have to search through the unique users list below
+ * and see if got a stale user/session entry. */
+
+ if (g_cVerbosity > 3)
+ {
+ char szDebugSessionPath[255];
+ RTStrPrintf(szDebugSessionPath, sizeof(szDebugSessionPath),
+ "/VirtualBox/GuestInfo/Debug/LSA/Session/%RU32", userSession.ulLastSession);
+ VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugSessionPath,
+ "#%RU32: cSessionProcs=%RU32 (of %RU32 procs total)",
+ s_uDebugIter, cCurSessionProcs, cProcs);
+ }
+
+ bool fFoundUser = false;
+ for (ULONG a = 0; a < cUniqueUsers; a++)
+ {
+ PVBOXSERVICEVMINFOUSER pCurUser = &pUserInfo[a];
+ AssertPtr(pCurUser);
+
+ if ( !RTUtf16Cmp(userSession.wszUser, pCurUser->wszUser)
+ && !RTUtf16Cmp(userSession.wszLogonDomain, pCurUser->wszLogonDomain)
+ && !RTUtf16Cmp(userSession.wszAuthenticationPackage, pCurUser->wszAuthenticationPackage))
+ {
+ /*
+ * Only respect the highest session for the current user.
+ */
+ if (userSession.ulLastSession > pCurUser->ulLastSession)
+ {
+ VGSvcVerbose(4, "Updating user=%ls to %u processes (last used session: %RU32)\n",
+ pCurUser->wszUser, cCurSessionProcs, userSession.ulLastSession);
+
+ if (!cCurSessionProcs)
+ VGSvcVerbose(3, "Stale session for user=%ls detected! Processes: %RU32 -> %RU32, Session: %RU32 -> %RU32\n",
+ pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs,
+ pCurUser->ulLastSession, userSession.ulLastSession);
+
+ pCurUser->ulNumProcs = cCurSessionProcs;
+ pCurUser->ulLastSession = userSession.ulLastSession;
+ }
+ /* There can be multiple session objects using the same session ID for the
+ * current user -- so when we got the same session again just add the found
+ * processes to it. */
+ else if (pCurUser->ulLastSession == userSession.ulLastSession)
+ {
+ VGSvcVerbose(4, "Updating processes for user=%ls (old procs=%RU32, new procs=%RU32, session=%RU32)\n",
+ pCurUser->wszUser, pCurUser->ulNumProcs, cCurSessionProcs, pCurUser->ulLastSession);
+
+ pCurUser->ulNumProcs = cCurSessionProcs;
+ }
+
+ fFoundUser = true;
+ break;
+ }
+ }
+
+ if (!fFoundUser)
+ {
+ VGSvcVerbose(4, "Adding new user=%ls (session=%RU32) with %RU32 processes\n",
+ userSession.wszUser, userSession.ulLastSession, cCurSessionProcs);
+
+ memcpy(&pUserInfo[cUniqueUsers], &userSession, sizeof(VBOXSERVICEVMINFOUSER));
+ pUserInfo[cUniqueUsers].ulNumProcs = cCurSessionProcs;
+ cUniqueUsers++;
+ Assert(cUniqueUsers <= cSessions);
+ }
+ }
+ }
+
+ if (g_cVerbosity > 3)
+ VGSvcWritePropF(s_uDebugGuestPropClientID, "/VirtualBox/GuestInfo/Debug/LSA",
+ "#%RU32: cSessions=%RU32, cProcs=%RU32, cUniqueUsers=%RU32",
+ s_uDebugIter, cSessions, cProcs, cUniqueUsers);
+
+ VGSvcVerbose(3, "Found %u unique logged-in user(s)\n", cUniqueUsers);
+
+ for (ULONG i = 0; i < cUniqueUsers; i++)
+ {
+ if (g_cVerbosity > 3)
+ {
+ char szDebugUserPath[255]; RTStrPrintf(szDebugUserPath, sizeof(szDebugUserPath), "/VirtualBox/GuestInfo/Debug/LSA/User/%RU32", i);
+ VGSvcWritePropF(s_uDebugGuestPropClientID, szDebugUserPath,
+ "#%RU32: szName=%ls, sessionID=%RU32, cProcs=%RU32",
+ s_uDebugIter, pUserInfo[i].wszUser, pUserInfo[i].ulLastSession, pUserInfo[i].ulNumProcs);
+ }
+
+ bool fAddUser = false;
+ if (pUserInfo[i].ulNumProcs)
+ fAddUser = true;
+
+ if (fAddUser)
+ {
+ VGSvcVerbose(3, "User '%ls' has %RU32 interactive processes (session=%RU32)\n",
+ pUserInfo[i].wszUser, pUserInfo[i].ulNumProcs, pUserInfo[i].ulLastSession);
+
+ if (cUsersInList > 0)
+ {
+ rc = RTStrAAppend(&pszUserList, ",");
+ AssertRCBreakStmt(rc, RTStrFree(pszUserList));
+ }
+
+ cUsersInList += 1;
+
+ char *pszUser = NULL;
+ char *pszDomain = NULL;
+ rc = RTUtf16ToUtf8(pUserInfo[i].wszUser, &pszUser);
+ if ( RT_SUCCESS(rc)
+ && pUserInfo[i].wszLogonDomain)
+ rc = RTUtf16ToUtf8(pUserInfo[i].wszLogonDomain, &pszDomain);
+ if (RT_SUCCESS(rc))
+ {
+ /* Append user to users list. */
+ rc = RTStrAAppend(&pszUserList, pszUser);
+
+ /* Do idle detection. */
+ if (RT_SUCCESS(rc))
+ rc = vgsvcVMInfoWinWriteLastInput(pCache, pszUser, pszDomain);
+ }
+ else
+ rc = RTStrAAppend(&pszUserList, "<string-conversion-error>");
+
+ RTStrFree(pszUser);
+ RTStrFree(pszDomain);
+
+ AssertRCBreakStmt(rc, RTStrFree(pszUserList));
+ }
+ }
+
+ RTMemFree(pUserInfo);
+ }
+ vgsvcVMInfoWinProcessesFree(cProcs, paProcs);
+ }
+ if (paSessions)
+ g_pfnLsaFreeReturnBuffer(paSessions);
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszUserList = pszUserList;
+ *pcUsersInList = cUsersInList;
+ }
+
+ s_uDebugIter++;
+ VbglR3GuestPropDisconnect(s_uDebugGuestPropClientID);
+
+ return rc;
+}
+
+
+int VGSvcVMInfoWinGetComponentVersions(uint32_t uClientID)
+{
+ int rc;
+ char szSysDir[MAX_PATH] = {0};
+ char szWinDir[MAX_PATH] = {0};
+ char szDriversDir[MAX_PATH + 32] = {0};
+
+ /* ASSUME: szSysDir and szWinDir and derivatives are always ASCII compatible. */
+ GetSystemDirectory(szSysDir, MAX_PATH);
+ GetWindowsDirectory(szWinDir, MAX_PATH);
+ RTStrPrintf(szDriversDir, sizeof(szDriversDir), "%s\\drivers", szSysDir);
+#ifdef RT_ARCH_AMD64
+ char szSysWowDir[MAX_PATH + 32] = {0};
+ RTStrPrintf(szSysWowDir, sizeof(szSysWowDir), "%s\\SysWow64", szWinDir);
+#endif
+
+ /* The file information table. */
+ const VBOXSERVICEVMINFOFILE aVBoxFiles[] =
+ {
+ { szSysDir, "VBoxControl.exe" },
+ { szSysDir, "VBoxHook.dll" },
+ { szSysDir, "VBoxDisp.dll" },
+ { szSysDir, "VBoxTray.exe" },
+ { szSysDir, "VBoxService.exe" },
+ { szSysDir, "VBoxMRXNP.dll" },
+ { szSysDir, "VBoxGINA.dll" },
+ { szSysDir, "VBoxCredProv.dll" },
+
+ /* On 64-bit we don't yet have the OpenGL DLLs in native format.
+ So just enumerate the 32-bit files in the SYSWOW directory. */
+#ifdef RT_ARCH_AMD64
+ { szSysWowDir, "VBoxOGL-x86.dll" },
+#else /* !RT_ARCH_AMD64 */
+ { szSysDir, "VBoxOGL.dll" },
+#endif /* !RT_ARCH_AMD64 */
+
+ { szDriversDir, "VBoxGuest.sys" },
+ { szDriversDir, "VBoxMouseNT.sys" },
+ { szDriversDir, "VBoxMouse.sys" },
+ { szDriversDir, "VBoxSF.sys" },
+ { szDriversDir, "VBoxVideo.sys" },
+ };
+
+ for (unsigned i = 0; i < RT_ELEMENTS(aVBoxFiles); i++)
+ {
+ char szVer[128];
+ rc = VGSvcUtilWinGetFileVersionString(aVBoxFiles[i].pszFilePath, aVBoxFiles[i].pszFileName, szVer, sizeof(szVer));
+ char szPropPath[256];
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestAdd/Components/%s", aVBoxFiles[i].pszFileName);
+ if ( rc != VERR_FILE_NOT_FOUND
+ && rc != VERR_PATH_NOT_FOUND)
+ VGSvcWritePropF(uClientID, szPropPath, "%s", szVer);
+ else
+ VGSvcWritePropF(uClientID, szPropPath, NULL);
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp
new file mode 100644
index 00000000..f7e42756
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.cpp
@@ -0,0 +1,1707 @@
+/* $Id: VBoxServiceVMInfo.cpp $ */
+/** @file
+ * VBoxService - Virtual Machine Information for the Host.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_vgsvc_vminfo VBoxService - VM Information
+ *
+ * The VM Information subservice provides heaps of useful information about the
+ * VM via guest properties.
+ *
+ * Guest properties is a limited database maintained by the HGCM GuestProperties
+ * service in cooperation with the Main API (VBoxSVC). Properties have a name
+ * (ours are path like), a string value, and a nanosecond timestamp (unix
+ * epoch). The timestamp lets the user see how recent the information is. As
+ * an laternative to polling on changes, it is also possible to wait on changes
+ * via the Main API or VBoxManage on the host side and VBoxControl in the guest.
+ *
+ * The namespace "/VirtualBox/" is reserved for value provided by VirtualBox.
+ * This service provides all the information under "/VirtualBox/GuestInfo/".
+ *
+ *
+ * @section sec_vgsvc_vminfo_beacons Beacons
+ *
+ * The subservice does not write properties unless there are changes. So, in
+ * order for the host side to know that information is up to date despite an
+ * oldish timestamp we define a couple of values that are always updated and can
+ * reliably used to figure how old the information actually is.
+ *
+ * For the networking part "/VirtualBox/GuestInfo/Net/Count" is the value to
+ * watch out for.
+ *
+ * For the login part, it's possible that we intended to use
+ * "/VirtualBox/GuestInfo/OS/LoggedInUsers" for this, however it is not defined
+ * correctly and current does NOT work as a beacon.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/winsock2.h>
+# include <iprt/win/iphlpapi.h>
+# include <iprt/win/ws2tcpip.h>
+# include <iprt/win/windows.h>
+# include <Ntsecapi.h>
+#else
+# define __STDC_LIMIT_MACROS
+# include <arpa/inet.h>
+# include <errno.h>
+# include <netinet/in.h>
+# include <sys/ioctl.h>
+# include <sys/socket.h>
+# include <net/if.h>
+# include <pwd.h> /* getpwuid */
+# include <unistd.h>
+# if !defined(RT_OS_OS2) && !defined(RT_OS_FREEBSD) && !defined(RT_OS_HAIKU)
+# include <utmpx.h> /** @todo FreeBSD 9 should have this. */
+# endif
+# ifdef RT_OS_OS2
+# include <net/if_dl.h>
+# endif
+# ifdef RT_OS_SOLARIS
+# include <sys/sockio.h>
+# include <net/if_arp.h>
+# endif
+# if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD)
+# include <ifaddrs.h> /* getifaddrs, freeifaddrs */
+# include <net/if_dl.h> /* LLADDR */
+# include <netdb.h> /* getnameinfo */
+# endif
+# ifdef VBOX_WITH_DBUS
+# include <VBox/dbus.h>
+# endif
+#endif
+
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+#include <iprt/assert.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+#include <VBox/VBoxGuestLib.h>
+#include "VBoxServiceInternal.h"
+#include "VBoxServiceUtils.h"
+#include "VBoxServicePropCache.h"
+
+
+/** Structure containing information about a location awarness
+ * client provided by the host. */
+/** @todo Move this (and functions) into VbglR3. */
+typedef struct VBOXSERVICELACLIENTINFO
+{
+ uint32_t uID;
+ char *pszName;
+ char *pszLocation;
+ char *pszDomain;
+ bool fAttached;
+ uint64_t uAttachedTS;
+} VBOXSERVICELACLIENTINFO, *PVBOXSERVICELACLIENTINFO;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The vminfo interval (milliseconds). */
+static uint32_t g_cMsVMInfoInterval = 0;
+/** The semaphore we're blocking on. */
+static RTSEMEVENTMULTI g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
+/** The guest property service client ID. */
+static uint32_t g_uVMInfoGuestPropSvcClientID = 0;
+/** Number of currently logged in users in OS. */
+static uint32_t g_cVMInfoLoggedInUsers = 0;
+/** The guest property cache. */
+static VBOXSERVICEVEPROPCACHE g_VMInfoPropCache;
+static const char *g_pszPropCacheValLoggedInUsersList = "/VirtualBox/GuestInfo/OS/LoggedInUsersList";
+static const char *g_pszPropCacheValLoggedInUsers = "/VirtualBox/GuestInfo/OS/LoggedInUsers";
+static const char *g_pszPropCacheValNoLoggedInUsers = "/VirtualBox/GuestInfo/OS/NoLoggedInUsers";
+static const char *g_pszPropCacheValNetCount = "/VirtualBox/GuestInfo/Net/Count";
+/** A guest user's guest property root key. */
+static const char *g_pszPropCacheValUser = "/VirtualBox/GuestInfo/User/";
+/** The VM session ID. Changes whenever the VM is restored or reset. */
+static uint64_t g_idVMInfoSession;
+/** The last attached locartion awareness (LA) client timestamp. */
+static uint64_t g_LAClientAttachedTS = 0;
+/** The current LA client info. */
+static VBOXSERVICELACLIENTINFO g_LAClientInfo;
+/** User idle threshold (in ms). This specifies the minimum time a user is considered
+ * as being idle and then will be reported to the host. Default is 5s. */
+uint32_t g_uVMInfoUserIdleThresholdMS = 5 * 1000;
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+static const char *g_pszLAActiveClient = "/VirtualBox/HostInfo/VRDP/ActiveClient";
+
+#ifdef VBOX_WITH_DBUS
+/** @name ConsoleKit defines (taken from 0.4.5).
+ * @{ */
+# define CK_NAME "org.freedesktop.ConsoleKit"
+# define CK_PATH "/org/freedesktop/ConsoleKit"
+# define CK_INTERFACE "org.freedesktop.ConsoleKit"
+# define CK_MANAGER_PATH "/org/freedesktop/ConsoleKit/Manager"
+# define CK_MANAGER_INTERFACE "org.freedesktop.ConsoleKit.Manager"
+# define CK_SEAT_INTERFACE "org.freedesktop.ConsoleKit.Seat"
+# define CK_SESSION_INTERFACE "org.freedesktop.ConsoleKit.Session"
+/** @} */
+#endif
+
+
+
+/**
+ * Signals the event so that a re-enumeration of VM-specific
+ * information (like logged in users) can happen.
+ *
+ * @return IPRT status code.
+ */
+int VGSvcVMInfoSignal(void)
+{
+ /* Trigger a re-enumeration of all logged-in users by unblocking
+ * the multi event semaphore of the VMInfo thread. */
+ if (g_hVMInfoEvent)
+ return RTSemEventMultiSignal(g_hVMInfoEvent);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnPreInit}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoPreInit(void)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnOption}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoOption(const char **ppszShort, int argc, char **argv, int *pi)
+{
+ /** @todo Use RTGetOpt here. */
+
+ int rc = -1;
+ if (ppszShort)
+ /* no short options */;
+ else if (!strcmp(argv[*pi], "--vminfo-interval"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_cMsVMInfoInterval, 1, UINT32_MAX - 1);
+ else if (!strcmp(argv[*pi], "--vminfo-user-idle-threshold"))
+ rc = VGSvcArgUInt32(argc, argv, "", pi, &g_uVMInfoUserIdleThresholdMS, 1, UINT32_MAX - 1);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoInit(void)
+{
+ /*
+ * If not specified, find the right interval default.
+ * Then create the event sem to block on.
+ */
+ if (!g_cMsVMInfoInterval)
+ g_cMsVMInfoInterval = g_DefaultInterval * 1000;
+ if (!g_cMsVMInfoInterval)
+ {
+ /* Set it to 5s by default for location awareness checks. */
+ g_cMsVMInfoInterval = 5 * 1000;
+ }
+
+ int rc = RTSemEventMultiCreate(&g_hVMInfoEvent);
+ AssertRCReturn(rc, rc);
+
+ VbglR3GetSessionId(&g_idVMInfoSession);
+ /* The status code is ignored as this information is not available with VBox < 3.2.10. */
+
+ /* Initialize the LA client object. */
+ RT_ZERO(g_LAClientInfo);
+
+ rc = VbglR3GuestPropConnect(&g_uVMInfoGuestPropSvcClientID);
+ if (RT_SUCCESS(rc))
+ VGSvcVerbose(3, "Property Service Client ID: %#x\n", g_uVMInfoGuestPropSvcClientID);
+ else
+ {
+ /* If the service was not found, we disable this service without
+ causing VBoxService to fail. */
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND) /* Host service is not available. */
+ {
+ VGSvcVerbose(0, "Guest property service is not available, disabling the service\n");
+ rc = VERR_SERVICE_DISABLED;
+ }
+ else
+ VGSvcError("Failed to connect to the guest property service! Error: %Rrc\n", rc);
+ RTSemEventMultiDestroy(g_hVMInfoEvent);
+ g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ VGSvcPropCacheCreate(&g_VMInfoPropCache, g_uVMInfoGuestPropSvcClientID);
+
+ /*
+ * Declare some guest properties with flags and reset values.
+ */
+ int rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT,
+ NULL /* Delete on exit */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsersList, rc2);
+
+ rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "0");
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValLoggedInUsers, rc2);
+
+ rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT, "true");
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNoLoggedInUsers, rc2);
+
+ rc2 = VGSvcPropCacheUpdateEntry(&g_VMInfoPropCache, g_pszPropCacheValNetCount,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE,
+ NULL /* Delete on exit */);
+ if (RT_FAILURE(rc2))
+ VGSvcError("Failed to init property cache value '%s', rc=%Rrc\n", g_pszPropCacheValNetCount, rc2);
+
+ /*
+ * Get configuration guest properties from the host.
+ * Note: All properties should have sensible defaults in case the lookup here fails.
+ */
+ char *pszValue;
+ rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VBoxService/--vminfo-user-idle-threshold",
+ true /* Read only */, &pszValue, NULL /* Flags */, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc2))
+ {
+ AssertPtr(pszValue);
+ g_uVMInfoUserIdleThresholdMS = RT_CLAMP(RTStrToUInt32(pszValue), 1000, UINT32_MAX - 1);
+ RTStrFree(pszValue);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Retrieves a specifiy client LA property.
+ *
+ * @return IPRT status code.
+ * @param uClientID LA client ID to retrieve property for.
+ * @param pszProperty Property (without path) to retrieve.
+ * @param ppszValue Where to store value of property.
+ * @param puTimestamp Timestamp of property to retrieve. Optional.
+ */
+static int vgsvcGetLAClientValue(uint32_t uClientID, const char *pszProperty, char **ppszValue, uint64_t *puTimestamp)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszProperty, VERR_INVALID_POINTER);
+
+ int rc;
+
+ char pszClientPath[255];
+/** @todo r=bird: Another pointless RTStrPrintf test with wrong status code to boot. */
+ if (RTStrPrintf(pszClientPath, sizeof(pszClientPath),
+ "/VirtualBox/HostInfo/VRDP/Client/%RU32/%s", uClientID, pszProperty))
+ {
+ rc = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, pszClientPath, true /* Read only */,
+ ppszValue, NULL /* Flags */, puTimestamp);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Retrieves LA client information. On success the returned structure will have allocated
+ * objects which need to be free'd with vboxServiceFreeLAClientInfo.
+ *
+ * @return IPRT status code.
+ * @param uClientID Client ID to retrieve information for.
+ * @param pClient Pointer where to store the client information.
+ */
+static int vgsvcGetLAClientInfo(uint32_t uClientID, PVBOXSERVICELACLIENTINFO pClient)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pClient, VERR_INVALID_POINTER);
+
+ int rc = vgsvcGetLAClientValue(uClientID, "Name", &pClient->pszName,
+ NULL /* Timestamp */);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszAttach;
+ rc = vgsvcGetLAClientValue(uClientID, "Attach", &pszAttach, &pClient->uAttachedTS);
+ if (RT_SUCCESS(rc))
+ {
+ AssertPtr(pszAttach);
+ pClient->fAttached = RTStrICmp(pszAttach, "1") == 0;
+
+ RTStrFree(pszAttach);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ rc = vgsvcGetLAClientValue(uClientID, "Location", &pClient->pszLocation, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc))
+ rc = vgsvcGetLAClientValue(uClientID, "Domain", &pClient->pszDomain, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc))
+ pClient->uID = uClientID;
+
+ return rc;
+}
+
+
+/**
+ * Frees all allocated LA client information of a structure.
+ *
+ * @param pClient Pointer to client information structure to free.
+ */
+static void vgsvcFreeLAClientInfo(PVBOXSERVICELACLIENTINFO pClient)
+{
+ if (pClient)
+ {
+ if (pClient->pszName)
+ {
+ RTStrFree(pClient->pszName);
+ pClient->pszName = NULL;
+ }
+ if (pClient->pszLocation)
+ {
+ RTStrFree(pClient->pszLocation);
+ pClient->pszLocation = NULL;
+ }
+ if (pClient->pszDomain)
+ {
+ RTStrFree(pClient->pszDomain);
+ pClient->pszDomain = NULL;
+ }
+ }
+}
+
+
+/**
+ * Updates a per-guest user guest property inside the given property cache.
+ *
+ * @return IPRT status code.
+ * @param pCache Pointer to guest property cache to update user in.
+ * @param pszUser Name of guest user to update.
+ * @param pszDomain Domain of guest user to update. Optional.
+ * @param pszKey Key name of guest property to update.
+ * @param pszValueFormat Guest property value to set. Pass NULL for deleting
+ * the property.
+ */
+int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
+ const char *pszKey, const char *pszValueFormat, ...)
+{
+ AssertPtrReturn(pCache, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszUser, VERR_INVALID_POINTER);
+ /* pszDomain is optional. */
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ /* pszValueFormat is optional. */
+
+ int rc = VINF_SUCCESS;
+
+ char *pszName;
+ if (pszDomain)
+ {
+ if (RTStrAPrintf(&pszName, "%s%s@%s/%s", g_pszPropCacheValUser, pszUser, pszDomain, pszKey) < 0)
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ if (RTStrAPrintf(&pszName, "%s%s/%s", g_pszPropCacheValUser, pszUser, pszKey) < 0)
+ rc = VERR_NO_MEMORY;
+ }
+
+ char *pszValue = NULL;
+ if ( RT_SUCCESS(rc)
+ && pszValueFormat)
+ {
+ va_list va;
+ va_start(va, pszValueFormat);
+ if (RTStrAPrintfV(&pszValue, pszValueFormat, va) < 0)
+ rc = VERR_NO_MEMORY;
+ va_end(va);
+ if ( RT_SUCCESS(rc)
+ && !pszValue)
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = VGSvcPropCacheUpdate(pCache, pszName, pszValue);
+ if (rc == VINF_SUCCESS) /* VGSvcPropCacheUpdate will also return VINF_NO_CHANGE. */
+ {
+ /** @todo Combine updating flags w/ updating the actual value. */
+ rc = VGSvcPropCacheUpdateEntry(pCache, pszName,
+ VGSVCPROPCACHE_FLAGS_TEMPORARY | VGSVCPROPCACHE_FLAGS_TRANSIENT,
+ NULL /* Delete on exit */);
+ }
+
+ RTStrFree(pszValue);
+ RTStrFree(pszName);
+ return rc;
+}
+
+
+/**
+ * Writes the properties that won't change while the service is running.
+ *
+ * Errors are ignored.
+ */
+static void vgsvcVMInfoWriteFixedProperties(void)
+{
+ /*
+ * First get OS information that won't change.
+ */
+ char szInfo[256];
+ int rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Product",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Release",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/Version",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szInfo, sizeof(szInfo));
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestInfo/OS/ServicePack",
+ "%s", RT_FAILURE(rc) ? "" : szInfo);
+
+ /*
+ * Retrieve version information about Guest Additions and installed files (components).
+ */
+ char *pszAddVer;
+ char *pszAddVerExt;
+ char *pszAddRev;
+ rc = VbglR3GetAdditionsVersion(&pszAddVer, &pszAddVerExt, &pszAddRev);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Version",
+ "%s", RT_FAILURE(rc) ? "" : pszAddVer);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/VersionExt",
+ "%s", RT_FAILURE(rc) ? "" : pszAddVerExt);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/Revision",
+ "%s", RT_FAILURE(rc) ? "" : pszAddRev);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pszAddVer);
+ RTStrFree(pszAddVerExt);
+ RTStrFree(pszAddRev);
+ }
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Do windows specific properties.
+ */
+ char *pszInstDir;
+ rc = VbglR3GetAdditionsInstallationPath(&pszInstDir);
+ VGSvcWritePropF(g_uVMInfoGuestPropSvcClientID, "/VirtualBox/GuestAdd/InstallDir",
+ "%s", RT_FAILURE(rc) ? "" : pszInstDir);
+ if (RT_SUCCESS(rc))
+ RTStrFree(pszInstDir);
+
+ VGSvcVMInfoWinGetComponentVersions(g_uVMInfoGuestPropSvcClientID);
+#endif
+}
+
+
+#if defined(VBOX_WITH_DBUS) && defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */
+/*
+ * Simple wrapper to work around compiler-specific va_list madness.
+ */
+static dbus_bool_t vboxService_dbus_message_get_args(DBusMessage *message, DBusError *error, int first_arg_type, ...)
+{
+ va_list va;
+ va_start(va, first_arg_type);
+ dbus_bool_t ret = dbus_message_get_args_valist(message, error, first_arg_type, va);
+ va_end(va);
+ return ret;
+}
+#endif
+
+
+/**
+ * Provide information about active users.
+ */
+static int vgsvcVMInfoWriteUsers(void)
+{
+ int rc;
+ char *pszUserList = NULL;
+ uint32_t cUsersInList = 0;
+
+#ifdef RT_OS_WINDOWS
+ rc = VGSvcVMInfoWinWriteUsers(&g_VMInfoPropCache, &pszUserList, &cUsersInList);
+
+#elif defined(RT_OS_FREEBSD)
+ /** @todo FreeBSD: Port logged on user info retrieval.
+ * However, FreeBSD 9 supports utmpx, so we could use the code
+ * block below (?). */
+ rc = VERR_NOT_IMPLEMENTED;
+
+#elif defined(RT_OS_HAIKU)
+ /** @todo Haiku: Port logged on user info retrieval. */
+ rc = VERR_NOT_IMPLEMENTED;
+
+#elif defined(RT_OS_OS2)
+ /** @todo OS/2: Port logged on (LAN/local/whatever) user info retrieval. */
+ rc = VERR_NOT_IMPLEMENTED;
+
+#else
+ setutxent();
+ utmpx *ut_user;
+ uint32_t cListSize = 32;
+
+ /* Allocate a first array to hold 32 users max. */
+ char **papszUsers = (char **)RTMemAllocZ(cListSize * sizeof(char *));
+ if (papszUsers)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Process all entries in the utmp file.
+ * Note: This only handles */
+ while ( (ut_user = getutxent())
+ && RT_SUCCESS(rc))
+ {
+# ifdef RT_OS_DARWIN /* No ut_user->ut_session on Darwin */
+ VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32)\n", ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid);
+# else
+ VGSvcVerbose(4, "Found entry '%s' (type: %d, PID: %RU32, session: %RU32)\n",
+ ut_user->ut_user, ut_user->ut_type, ut_user->ut_pid, ut_user->ut_session);
+# endif
+ if (cUsersInList > cListSize)
+ {
+ cListSize += 32;
+ void *pvNew = RTMemRealloc(papszUsers, cListSize * sizeof(char*));
+ AssertBreakStmt(pvNew, cListSize -= 32);
+ papszUsers = (char **)pvNew;
+ }
+
+ /* Make sure we don't add user names which are not
+ * part of type USER_PROCES. */
+ if (ut_user->ut_type == USER_PROCESS) /* Regular user process. */
+ {
+ bool fFound = false;
+ for (uint32_t i = 0; i < cUsersInList && !fFound; i++)
+ fFound = strncmp(papszUsers[i], ut_user->ut_user, sizeof(ut_user->ut_user)) == 0;
+
+ if (!fFound)
+ {
+ VGSvcVerbose(4, "Adding user '%s' (type: %d) to list\n", ut_user->ut_user, ut_user->ut_type);
+
+ rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ut_user->ut_user);
+ if (RT_FAILURE(rc))
+ break;
+ cUsersInList++;
+ }
+ }
+ }
+
+# ifdef VBOX_WITH_DBUS
+# if defined(RT_OS_LINUX) /* Not yet for Solaris/FreeBSB. */
+ DBusError dbErr;
+ DBusConnection *pConnection = NULL;
+ int rc2 = RTDBusLoadLib();
+ bool fHaveLibDbus = false;
+ if (RT_SUCCESS(rc2))
+ {
+ /* Handle desktop sessions using ConsoleKit. */
+ VGSvcVerbose(4, "Checking ConsoleKit sessions ...\n");
+ fHaveLibDbus = true;
+ dbus_error_init(&dbErr);
+ pConnection = dbus_bus_get(DBUS_BUS_SYSTEM, &dbErr);
+ }
+
+ if ( pConnection
+ && !dbus_error_is_set(&dbErr))
+ {
+ /* Get all available sessions. */
+/** @todo r=bird: What's the point of hardcoding things here when we've taken the pain of defining CK_XXX constants at the top of the file (or vice versa)? */
+ DBusMessage *pMsgSessions = dbus_message_new_method_call("org.freedesktop.ConsoleKit",
+ "/org/freedesktop/ConsoleKit/Manager",
+ "org.freedesktop.ConsoleKit.Manager",
+ "GetSessions");
+ if ( pMsgSessions
+ && dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL)
+ {
+ DBusMessage *pReplySessions = dbus_connection_send_with_reply_and_block(pConnection,
+ pMsgSessions, 30 * 1000 /* 30s timeout */,
+ &dbErr);
+ if ( pReplySessions
+ && !dbus_error_is_set(&dbErr))
+ {
+ char **ppszSessions;
+ int cSessions;
+ if ( dbus_message_get_type(pMsgSessions) == DBUS_MESSAGE_TYPE_METHOD_CALL
+ && vboxService_dbus_message_get_args(pReplySessions, &dbErr, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_OBJECT_PATH, &ppszSessions, &cSessions,
+ DBUS_TYPE_INVALID /* Termination */))
+ {
+ VGSvcVerbose(4, "ConsoleKit: retrieved %RU16 session(s)\n", cSessions);
+
+ char **ppszCurSession = ppszSessions;
+ for (ppszCurSession; ppszCurSession && *ppszCurSession; ppszCurSession++)
+ {
+ VGSvcVerbose(4, "ConsoleKit: processing session '%s' ...\n", *ppszCurSession);
+
+ /* Only respect active sessions .*/
+ bool fActive = false;
+ DBusMessage *pMsgSessionActive = dbus_message_new_method_call("org.freedesktop.ConsoleKit",
+ *ppszCurSession,
+ "org.freedesktop.ConsoleKit.Session",
+ "IsActive");
+ if ( pMsgSessionActive
+ && dbus_message_get_type(pMsgSessionActive) == DBUS_MESSAGE_TYPE_METHOD_CALL)
+ {
+ DBusMessage *pReplySessionActive = dbus_connection_send_with_reply_and_block(pConnection,
+ pMsgSessionActive,
+ 30 * 1000 /*sec*/,
+ &dbErr);
+ if ( pReplySessionActive
+ && !dbus_error_is_set(&dbErr))
+ {
+ DBusMessageIter itMsg;
+ if ( dbus_message_iter_init(pReplySessionActive, &itMsg)
+ && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_BOOLEAN)
+ {
+ /* Get uid from message. */
+ int val;
+ dbus_message_iter_get_basic(&itMsg, &val);
+ fActive = val >= 1;
+ }
+
+ if (pReplySessionActive)
+ dbus_message_unref(pReplySessionActive);
+ }
+
+ if (pMsgSessionActive)
+ dbus_message_unref(pMsgSessionActive);
+ }
+
+ VGSvcVerbose(4, "ConsoleKit: session '%s' is %s\n",
+ *ppszCurSession, fActive ? "active" : "not active");
+
+ /* *ppszCurSession now contains the object path
+ * (e.g. "/org/freedesktop/ConsoleKit/Session1"). */
+ DBusMessage *pMsgUnixUser = dbus_message_new_method_call("org.freedesktop.ConsoleKit",
+ *ppszCurSession,
+ "org.freedesktop.ConsoleKit.Session",
+ "GetUnixUser");
+ if ( fActive
+ && pMsgUnixUser
+ && dbus_message_get_type(pMsgUnixUser) == DBUS_MESSAGE_TYPE_METHOD_CALL)
+ {
+ DBusMessage *pReplyUnixUser = dbus_connection_send_with_reply_and_block(pConnection,
+ pMsgUnixUser,
+ 30 * 1000 /* 30s timeout */,
+ &dbErr);
+ if ( pReplyUnixUser
+ && !dbus_error_is_set(&dbErr))
+ {
+ DBusMessageIter itMsg;
+ if ( dbus_message_iter_init(pReplyUnixUser, &itMsg)
+ && dbus_message_iter_get_arg_type(&itMsg) == DBUS_TYPE_UINT32)
+ {
+ /* Get uid from message. */
+ uint32_t uid;
+ dbus_message_iter_get_basic(&itMsg, &uid);
+
+ /** @todo Add support for getting UID_MIN (/etc/login.defs on
+ * Debian). */
+ uint32_t uid_min = 1000;
+
+ /* Look up user name (realname) from uid. */
+ setpwent();
+ struct passwd *ppwEntry = getpwuid(uid);
+ if ( ppwEntry
+ && ppwEntry->pw_name)
+ {
+ if (ppwEntry->pw_uid >= uid_min /* Only respect users, not daemons etc. */)
+ {
+ VGSvcVerbose(4, "ConsoleKit: session '%s' -> %s (uid: %RU32)\n",
+ *ppszCurSession, ppwEntry->pw_name, uid);
+
+ bool fFound = false;
+ for (uint32_t i = 0; i < cUsersInList && !fFound; i++)
+ fFound = strcmp(papszUsers[i], ppwEntry->pw_name) == 0;
+
+ if (!fFound)
+ {
+ VGSvcVerbose(4, "ConsoleKit: adding user '%s' to list\n", ppwEntry->pw_name);
+
+ rc = RTStrDupEx(&papszUsers[cUsersInList], (const char *)ppwEntry->pw_name);
+ if (RT_FAILURE(rc))
+ break;
+ cUsersInList++;
+ }
+ }
+ /* else silently ignore the user */
+ }
+ else
+ VGSvcError("ConsoleKit: unable to lookup user name for uid=%RU32\n", uid);
+ }
+ else
+ AssertMsgFailed(("ConsoleKit: GetUnixUser returned a wrong argument type\n"));
+ }
+
+ if (pReplyUnixUser)
+ dbus_message_unref(pReplyUnixUser);
+ }
+ else if (fActive) /* don't bitch about inactive users */
+ {
+ static int s_iBitchedAboutConsoleKit = 0;
+ if (s_iBitchedAboutConsoleKit < 1)
+ {
+ s_iBitchedAboutConsoleKit++;
+ VGSvcError("ConsoleKit: unable to retrieve user for session '%s' (msg type=%d): %s\n",
+ *ppszCurSession, dbus_message_get_type(pMsgUnixUser),
+ dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
+ }
+ }
+
+ if (pMsgUnixUser)
+ dbus_message_unref(pMsgUnixUser);
+ }
+
+ dbus_free_string_array(ppszSessions);
+ }
+ else
+ VGSvcError("ConsoleKit: unable to retrieve session parameters (msg type=%d): %s\n",
+ dbus_message_get_type(pMsgSessions),
+ dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
+ dbus_message_unref(pReplySessions);
+ }
+
+ if (pMsgSessions)
+ {
+ dbus_message_unref(pMsgSessions);
+ pMsgSessions = NULL;
+ }
+ }
+ else
+ {
+ static int s_iBitchedAboutConsoleKit = 0;
+ if (s_iBitchedAboutConsoleKit < 3)
+ {
+ s_iBitchedAboutConsoleKit++;
+ VGSvcError("Unable to invoke ConsoleKit (%d/3) -- maybe not installed / used? Error: %s\n",
+ s_iBitchedAboutConsoleKit,
+ dbus_error_is_set(&dbErr) ? dbErr.message : "No error information available");
+ }
+ }
+
+ if (pMsgSessions)
+ dbus_message_unref(pMsgSessions);
+ }
+ else
+ {
+ static int s_iBitchedAboutDBus = 0;
+ if (s_iBitchedAboutDBus < 3)
+ {
+ s_iBitchedAboutDBus++;
+ VGSvcError("Unable to connect to system D-Bus (%d/3): %s\n", s_iBitchedAboutDBus,
+ fHaveLibDbus && dbus_error_is_set(&dbErr) ? dbErr.message : "D-Bus not installed");
+ }
+ }
+
+ if ( fHaveLibDbus
+ && dbus_error_is_set(&dbErr))
+ dbus_error_free(&dbErr);
+# endif /* RT_OS_LINUX */
+# endif /* VBOX_WITH_DBUS */
+
+ /** @todo Fedora/others: Handle systemd-loginctl. */
+
+ /* Calc the string length. */
+ size_t cchUserList = 0;
+ if (RT_SUCCESS(rc))
+ for (uint32_t i = 0; i < cUsersInList; i++)
+ cchUserList += (i != 0) + strlen(papszUsers[i]);
+
+ /* Build the user list. */
+ if (cchUserList > 0)
+ {
+ if (RT_SUCCESS(rc))
+ rc = RTStrAllocEx(&pszUserList, cchUserList + 1);
+ if (RT_SUCCESS(rc))
+ {
+ char *psz = pszUserList;
+ for (uint32_t i = 0; i < cUsersInList; i++)
+ {
+ if (i != 0)
+ *psz++ = ',';
+ size_t cch = strlen(papszUsers[i]);
+ memcpy(psz, papszUsers[i], cch);
+ psz += cch;
+ }
+ *psz = '\0';
+ }
+ }
+
+ /* Cleanup. */
+ for (uint32_t i = 0; i < cUsersInList; i++)
+ RTStrFree(papszUsers[i]);
+ RTMemFree(papszUsers);
+
+ endutxent(); /* Close utmpx file. */
+#endif /* !RT_OS_WINDOWS && !RT_OS_FREEBSD && !RT_OS_HAIKU && !RT_OS_OS2 */
+
+ Assert(RT_FAILURE(rc) || cUsersInList == 0 || (pszUserList && *pszUserList));
+
+ /*
+ * If the user enumeration above failed, reset the user count to 0 except
+ * we didn't have enough memory anymore. In that case we want to preserve
+ * the previous user count in order to not confuse third party tools which
+ * rely on that count.
+ */
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MEMORY)
+ {
+ static int s_iVMInfoBitchedOOM = 0;
+ if (s_iVMInfoBitchedOOM++ < 3)
+ VGSvcVerbose(0, "Warning: Not enough memory available to enumerate users! Keeping old value (%RU32)\n",
+ g_cVMInfoLoggedInUsers);
+ cUsersInList = g_cVMInfoLoggedInUsers;
+ }
+ else
+ cUsersInList = 0;
+ }
+ else /* Preserve logged in users count. */
+ g_cVMInfoLoggedInUsers = cUsersInList;
+
+ VGSvcVerbose(4, "cUsersInList=%RU32, pszUserList=%s, rc=%Rrc\n", cUsersInList, pszUserList ? pszUserList : "<NULL>", rc);
+
+ if (pszUserList)
+ {
+ AssertMsg(cUsersInList, ("pszUserList contains users whereas cUsersInList is 0\n"));
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, "%s", pszUserList);
+ }
+ else
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsersList, NULL);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing logged in users list, rc=%Rrc\n", rc);
+
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValLoggedInUsers, "%RU32", cUsersInList);
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing logged in users count, rc=%Rrc\n", rc);
+
+/** @todo r=bird: What's this 'beacon' nonsense here? It's _not_ defined with
+ * the VGSVCPROPCACHE_FLAGS_ALWAYS_UPDATE flag set!! */
+ rc = VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNoLoggedInUsers, cUsersInList == 0 ? "true" : "false");
+ if (RT_FAILURE(rc))
+ VGSvcError("Error writing no logged in users beacon, rc=%Rrc\n", rc);
+
+ if (pszUserList)
+ RTStrFree(pszUserList);
+
+ VGSvcVerbose(4, "Writing users returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+
+/**
+ * Provide information about the guest network.
+ */
+static int vgsvcVMInfoWriteNetwork(void)
+{
+ uint32_t cIfsReported = 0;
+ char szPropPath[256];
+
+#ifdef RT_OS_WINDOWS
+ /*
+ * Check that the APIs we need are present.
+ */
+ if ( !g_pfnWSAIoctl
+ || !g_pfnWSASocketA
+ || !g_pfnWSAGetLastError
+ || !g_pfninet_ntoa
+ || !g_pfnclosesocket)
+ return VINF_SUCCESS;
+
+ /*
+ * Query the IP adapter info first, if we have the API.
+ */
+ IP_ADAPTER_INFO *pAdpInfo = NULL;
+ if (g_pfnGetAdaptersInfo)
+ {
+ ULONG cbAdpInfo = RT_MAX(sizeof(IP_ADAPTER_INFO) * 2, _2K);
+ pAdpInfo = (IP_ADAPTER_INFO *)RTMemAllocZ(cbAdpInfo);
+ if (!pAdpInfo)
+ {
+ VGSvcError("VMInfo/Network: Failed to allocate two IP_ADAPTER_INFO structures\n");
+ return VERR_NO_MEMORY;
+ }
+
+ DWORD dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo);
+ if (dwRet == ERROR_BUFFER_OVERFLOW)
+ {
+ IP_ADAPTER_INFO *pAdpInfoNew = (IP_ADAPTER_INFO*)RTMemRealloc(pAdpInfo, cbAdpInfo);
+ if (pAdpInfoNew)
+ {
+ pAdpInfo = pAdpInfoNew;
+ RT_BZERO(pAdpInfo, cbAdpInfo);
+ dwRet = g_pfnGetAdaptersInfo(pAdpInfo, &cbAdpInfo);
+ }
+ }
+ if (dwRet != NO_ERROR)
+ {
+ RTMemFree(pAdpInfo);
+ pAdpInfo = NULL;
+ if (dwRet == ERROR_NO_DATA)
+ /* If no network adapters available / present in the
+ system we pretend success to not bail out too early. */
+ VGSvcVerbose(3, "VMInfo/Network: No network adapters present according to GetAdaptersInfo.\n");
+ else
+ {
+ VGSvcError("VMInfo/Network: Failed to get adapter info: Error %d\n", dwRet);
+ return RTErrConvertFromWin32(dwRet);
+ }
+ }
+ }
+
+ /*
+ * Ask the TCP/IP stack for an interface list.
+ */
+ SOCKET sd = g_pfnWSASocketA(AF_INET, SOCK_DGRAM, 0, 0, 0, 0);
+ if (sd == SOCKET_ERROR) /* Socket invalid. */
+ {
+ int const wsaErr = g_pfnWSAGetLastError();
+ RTMemFree(pAdpInfo);
+
+ /* Don't complain/bail out with an error if network stack is not up; can happen
+ * on NT4 due to start up when not connected shares dialogs pop up. */
+ if (wsaErr == WSAENETDOWN)
+ {
+ VGSvcVerbose(0, "VMInfo/Network: Network is not up yet.\n");
+ return VINF_SUCCESS;
+ }
+ VGSvcError("VMInfo/Network: Failed to get a socket: Error %d\n", wsaErr);
+ return RTErrConvertFromWin32(wsaErr);
+ }
+
+ INTERFACE_INFO aInterfaces[20] = {{0}};
+ DWORD cbReturned = 0;
+# ifdef RT_ARCH_X86
+ /* Workaround for uninitialized variable used in memcpy in GetTcpipInterfaceList
+ (NT4SP1 at least). It seems to be happy enough with garbages, no failure
+ returns so far, so we just need to prevent it from crashing by filling the
+ stack with valid pointer values prior to the API call. */
+ _asm
+ {
+ mov edx, edi
+ lea eax, aInterfaces
+ mov [esp - 0x1000], eax
+ mov [esp - 0x2000], eax
+ mov ecx, 0x2000/4 - 1
+ cld
+ lea edi, [esp - 0x2000]
+ rep stosd
+ mov edi, edx
+ }
+# endif
+ int rc = g_pfnWSAIoctl(sd,
+ SIO_GET_INTERFACE_LIST,
+ NULL, /* pvInBuffer */
+ 0, /* cbInBuffer */
+ &aInterfaces[0], /* pvOutBuffer */
+ sizeof(aInterfaces), /* cbOutBuffer */
+ &cbReturned,
+ NULL, /* pOverlapped */
+ NULL); /* pCompletionRoutine */
+ if (rc == SOCKET_ERROR)
+ {
+ VGSvcError("VMInfo/Network: Failed to WSAIoctl() on socket: Error: %d\n", g_pfnWSAGetLastError());
+ RTMemFree(pAdpInfo);
+ g_pfnclosesocket(sd);
+ return RTErrConvertFromWin32(g_pfnWSAGetLastError());
+ }
+ g_pfnclosesocket(sd);
+ int cIfacesSystem = cbReturned / sizeof(INTERFACE_INFO);
+
+ /*
+ * Iterate the inteface list we got back from the TCP/IP,
+ * using the pAdpInfo list to supply the MAC address.
+ */
+ /** @todo Use GetAdaptersInfo() and GetAdapterAddresses (IPv4 + IPv6) for more information. */
+ for (int i = 0; i < cIfacesSystem; ++i)
+ {
+ if (aInterfaces[i].iiFlags & IFF_LOOPBACK) /* Skip loopback device. */
+ continue;
+ sockaddr_in *pAddress = &aInterfaces[i].iiAddress.AddressIn;
+ char szIp[32];
+ RTStrPrintf(szIp, sizeof(szIp), "%s", g_pfninet_ntoa(pAddress->sin_addr));
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szIp);
+
+ pAddress = &aInterfaces[i].iiBroadcastAddress.AddressIn;
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr));
+
+ pAddress = (sockaddr_in *)&aInterfaces[i].iiNetmask;
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", g_pfninet_ntoa(pAddress->sin_addr));
+
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, aInterfaces[i].iiFlags & IFF_UP ? "Up" : "Down");
+
+ if (pAdpInfo)
+ {
+ IP_ADAPTER_INFO *pAdp;
+ for (pAdp = pAdpInfo; pAdp; pAdp = pAdp->Next)
+ if (!strcmp(pAdp->IpAddressList.IpAddress.String, szIp))
+ break;
+
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported);
+ if (pAdp)
+ {
+ char szMac[32];
+ RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X",
+ pAdp->Address[0], pAdp->Address[1], pAdp->Address[2],
+ pAdp->Address[3], pAdp->Address[4], pAdp->Address[5]);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac);
+ }
+ else
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, NULL);
+ }
+
+ cIfsReported++;
+ }
+
+ RTMemFree(pAdpInfo);
+
+#elif defined(RT_OS_HAIKU)
+ /** @todo Haiku: implement network info. retreival */
+ return VERR_NOT_IMPLEMENTED;
+
+#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD)
+ struct ifaddrs *pIfHead = NULL;
+
+ /* Get all available interfaces */
+ int rc = getifaddrs(&pIfHead);
+ if (rc < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to get all interfaces: Error %Rrc\n");
+ return rc;
+ }
+
+ /* Loop through all interfaces and set the data. */
+ for (struct ifaddrs *pIfCurr = pIfHead; pIfCurr; pIfCurr = pIfCurr->ifa_next)
+ {
+ /*
+ * Only AF_INET and no loopback interfaces
+ */
+ /** @todo IPv6 interfaces */
+ if ( pIfCurr->ifa_addr->sa_family == AF_INET
+ && !(pIfCurr->ifa_flags & IFF_LOOPBACK))
+ {
+ char szInetAddr[NI_MAXHOST];
+
+ memset(szInetAddr, 0, NI_MAXHOST);
+ getnameinfo(pIfCurr->ifa_addr, sizeof(struct sockaddr_in),
+ szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/IP", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
+
+ memset(szInetAddr, 0, NI_MAXHOST);
+ getnameinfo(pIfCurr->ifa_broadaddr, sizeof(struct sockaddr_in),
+ szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Broadcast", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
+
+ memset(szInetAddr, 0, NI_MAXHOST);
+ getnameinfo(pIfCurr->ifa_netmask, sizeof(struct sockaddr_in),
+ szInetAddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/V4/Netmask", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szInetAddr);
+
+ /* Search for the AF_LINK interface of the current AF_INET one and get the mac. */
+ for (struct ifaddrs *pIfLinkCurr = pIfHead; pIfLinkCurr; pIfLinkCurr = pIfLinkCurr->ifa_next)
+ {
+ if ( pIfLinkCurr->ifa_addr->sa_family == AF_LINK
+ && !strcmp(pIfCurr->ifa_name, pIfLinkCurr->ifa_name))
+ {
+ char szMac[32];
+ uint8_t *pu8Mac = NULL;
+ struct sockaddr_dl *pLinkAddress = (struct sockaddr_dl *)pIfLinkCurr->ifa_addr;
+
+ AssertPtr(pLinkAddress);
+ pu8Mac = (uint8_t *)LLADDR(pLinkAddress);
+ RTStrPrintf(szMac, sizeof(szMac), "%02X%02X%02X%02X%02X%02X",
+ pu8Mac[0], pu8Mac[1], pu8Mac[2], pu8Mac[3], pu8Mac[4], pu8Mac[5]);
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/MAC", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", szMac);
+ break;
+ }
+ }
+
+ RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32/Status", cIfsReported);
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, pIfCurr->ifa_flags & IFF_UP ? "Up" : "Down");
+
+ cIfsReported++;
+ }
+ }
+
+ /* Free allocated resources. */
+ freeifaddrs(pIfHead);
+
+#else /* !RT_OS_WINDOWS && !RT_OS_FREEBSD */
+ /*
+ * Use SIOCGIFCONF to get a list of interface/protocol configurations.
+ *
+ * See "UNIX Network Programming Volume 1" by W. R. Stevens, section 17.6
+ * for details on this ioctl.
+ */
+ int sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ {
+ int rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to get a socket: Error %Rrc\n", rc);
+ return rc;
+ }
+
+ /* Call SIOCGIFCONF with the right sized buffer (remember the size). */
+ static int s_cbBuf = 256; // 1024
+ int cbBuf = s_cbBuf;
+ char *pchBuf;
+ struct ifconf IfConf;
+ int rc = VINF_SUCCESS;
+ for (;;)
+ {
+ pchBuf = (char *)RTMemTmpAllocZ(cbBuf);
+ if (!pchBuf)
+ {
+ rc = VERR_NO_TMP_MEMORY;
+ break;
+ }
+
+ IfConf.ifc_len = cbBuf;
+ IfConf.ifc_buf = pchBuf;
+ if (ioctl(sd, SIOCGIFCONF, &IfConf) >= 0)
+ {
+ /* Hard to anticipate how space an address might possibly take, so
+ making some generous assumptions here to avoid performing the
+ query twice with different buffer sizes. */
+ if (IfConf.ifc_len + 128 < cbBuf)
+ break;
+ }
+ else if (errno != EOVERFLOW)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ break;
+ }
+
+ /* grow the buffer */
+ s_cbBuf = cbBuf *= 2;
+ RTMemFree(pchBuf);
+ }
+ if (RT_FAILURE(rc))
+ {
+ close(sd);
+ RTMemTmpFree(pchBuf);
+ VGSvcError("VMInfo/Network: Error doing SIOCGIFCONF (cbBuf=%d): %Rrc\n", cbBuf, rc);
+ return rc;
+ }
+
+ /*
+ * Iterate the interface/protocol configurations.
+ *
+ * Note! The current code naively assumes one IPv4 address per interface.
+ * This means that guest assigning more than one address to an
+ * interface will get multiple entries for one physical interface.
+ */
+# ifdef RT_OS_OS2
+ struct ifreq *pPrevLinkAddr = NULL;
+# endif
+ struct ifreq *pCur = IfConf.ifc_req;
+ size_t cbLeft = IfConf.ifc_len;
+ while (cbLeft >= sizeof(*pCur))
+ {
+# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
+ /* These two do not provide the sa_len member but only support address
+ * families which do not need extra bytes on the end. */
+# define SA_LEN(pAddr) sizeof(struct sockaddr)
+# elif !defined(SA_LEN)
+# define SA_LEN(pAddr) (pAddr)->sa_len
+# endif
+ /* Figure the size of the current request. */
+ size_t cbCur = RT_UOFFSETOF(struct ifreq, ifr_addr)
+ + SA_LEN(&pCur->ifr_addr);
+ cbCur = RT_MAX(cbCur, sizeof(struct ifreq));
+# if defined(RT_OS_SOLARIS) || defined(RT_OS_LINUX)
+ Assert(pCur->ifr_addr.sa_family == AF_INET);
+# endif
+ AssertBreak(cbCur <= cbLeft);
+
+# ifdef RT_OS_OS2
+ /* On OS/2 we get the MAC address in the AF_LINK that the BSD 4.4 stack
+ emits. We boldly ASSUME these always comes first. */
+ if ( pCur->ifr_addr.sa_family == AF_LINK
+ && ((struct sockaddr_dl *)&pCur->ifr_addr)->sdl_alen == 6)
+ pPrevLinkAddr = pCur;
+# endif
+
+ /* Skip it if it's not the kind of address we're looking for. */
+ struct ifreq IfReqTmp;
+ bool fIfUp = false;
+ bool fSkip = false;
+ if (pCur->ifr_addr.sa_family != AF_INET)
+ fSkip = true;
+ else
+ {
+ /* Get the interface flags so we can detect loopback and check if it's up. */
+ IfReqTmp = *pCur;
+ if (ioctl(sd, SIOCGIFFLAGS, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFFLAGS,%s) on socket: Error %Rrc\n", pCur->ifr_name, rc);
+ break;
+ }
+ fIfUp = !!(IfReqTmp.ifr_flags & IFF_UP);
+ if (IfReqTmp.ifr_flags & IFF_LOOPBACK) /* Skip the loopback device. */
+ fSkip = true;
+ }
+ if (!fSkip)
+ {
+ size_t offSubProp = RTStrPrintf(szPropPath, sizeof(szPropPath), "/VirtualBox/GuestInfo/Net/%RU32", cIfsReported);
+
+ sockaddr_in *pAddress = (sockaddr_in *)&pCur->ifr_addr;
+ strcpy(&szPropPath[offSubProp], "/V4/IP");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
+
+ /* Get the broadcast address. */
+ IfReqTmp = *pCur;
+ if (ioctl(sd, SIOCGIFBRDADDR, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFBRDADDR) on socket: Error %Rrc\n", rc);
+ break;
+ }
+ pAddress = (sockaddr_in *)&IfReqTmp.ifr_broadaddr;
+ strcpy(&szPropPath[offSubProp], "/V4/Broadcast");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
+
+ /* Get the net mask. */
+ IfReqTmp = *pCur;
+ if (ioctl(sd, SIOCGIFNETMASK, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFNETMASK) on socket: Error %Rrc\n", rc);
+ break;
+ }
+# if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS)
+ pAddress = (sockaddr_in *)&IfReqTmp.ifr_addr;
+# else
+ pAddress = (sockaddr_in *)&IfReqTmp.ifr_netmask;
+# endif
+ strcpy(&szPropPath[offSubProp], "/V4/Netmask");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%s", inet_ntoa(pAddress->sin_addr));
+
+# if defined(RT_OS_SOLARIS)
+ /*
+ * "ifreq" is obsolete on Solaris. We use the recommended "lifreq".
+ * We might fail if the interface has not been assigned an IP address.
+ * That doesn't matter; as long as it's plumbed we can pick it up.
+ * But, if it has not acquired an IP address we cannot obtain it's MAC
+ * address this way, so we just use all zeros there.
+ */
+ RTMAC IfMac;
+ struct lifreq IfReq;
+ RT_ZERO(IfReq);
+ AssertCompile(sizeof(IfReq.lifr_name) >= sizeof(pCur->ifr_name));
+ strncpy(IfReq.lifr_name, pCur->ifr_name, sizeof(IfReq.lifr_name));
+ if (ioctl(sd, SIOCGLIFADDR, &IfReq) >= 0)
+ {
+ struct arpreq ArpReq;
+ RT_ZERO(ArpReq);
+ memcpy(&ArpReq.arp_pa, &IfReq.lifr_addr, sizeof(struct sockaddr_in));
+
+ if (ioctl(sd, SIOCGARP, &ArpReq) >= 0)
+ memcpy(&IfMac, ArpReq.arp_ha.sa_data, sizeof(IfMac));
+ else
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: failed to ioctl(SIOCGARP) on socket: Error %Rrc\n", rc);
+ break;
+ }
+ }
+ else
+ {
+ VGSvcVerbose(2, "VMInfo/Network: Interface '%s' has no assigned IP address, skipping ...\n", pCur->ifr_name);
+ continue;
+ }
+# elif defined(RT_OS_OS2)
+ RTMAC IfMac;
+ if ( pPrevLinkAddr
+ && strncmp(pCur->ifr_name, pPrevLinkAddr->ifr_name, sizeof(pCur->ifr_name)) == 0)
+ {
+ struct sockaddr_dl *pDlAddr = (struct sockaddr_dl *)&pPrevLinkAddr->ifr_addr;
+ IfMac = *(PRTMAC)&pDlAddr->sdl_data[pDlAddr->sdl_nlen];
+ }
+ else
+ RT_ZERO(IfMac);
+#else
+ if (ioctl(sd, SIOCGIFHWADDR, &IfReqTmp) < 0)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ VGSvcError("VMInfo/Network: Failed to ioctl(SIOCGIFHWADDR) on socket: Error %Rrc\n", rc);
+ break;
+ }
+ RTMAC IfMac = *(PRTMAC)&IfReqTmp.ifr_hwaddr.sa_data[0];
+# endif
+ strcpy(&szPropPath[offSubProp], "/MAC");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%02X%02X%02X%02X%02X%02X",
+ IfMac.au8[0], IfMac.au8[1], IfMac.au8[2], IfMac.au8[3], IfMac.au8[4], IfMac.au8[5]);
+
+ strcpy(&szPropPath[offSubProp], "/Status");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, fIfUp ? "Up" : "Down");
+
+ /* The name. */
+ int rc2 = RTStrValidateEncodingEx(pCur->ifr_name, sizeof(pCur->ifr_name), 0);
+ if (RT_SUCCESS(rc2))
+ {
+ strcpy(&szPropPath[offSubProp], "/Name");
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, szPropPath, "%.*s", sizeof(pCur->ifr_name), pCur->ifr_name);
+ }
+
+ cIfsReported++;
+ }
+
+ /*
+ * Next interface/protocol configuration.
+ */
+ pCur = (struct ifreq *)((uintptr_t)pCur + cbCur);
+ cbLeft -= cbCur;
+ }
+
+ RTMemTmpFree(pchBuf);
+ close(sd);
+ if (RT_FAILURE(rc))
+ VGSvcError("VMInfo/Network: Network enumeration for interface %RU32 failed with error %Rrc\n", cIfsReported, rc);
+
+#endif /* !RT_OS_WINDOWS */
+
+#if 0 /* Zapping not enabled yet, needs more testing first. */
+ /*
+ * Zap all stale network interface data if the former (saved) network ifaces count
+ * is bigger than the current one.
+ */
+
+ /* Get former count. */
+ uint32_t cIfsReportedOld;
+ rc = VGSvcReadPropUInt32(g_uVMInfoGuestPropSvcClientID, g_pszPropCacheValNetCount, &cIfsReportedOld,
+ 0 /* Min */, UINT32_MAX /* Max */);
+ if ( RT_SUCCESS(rc)
+ && cIfsReportedOld > cIfsReported) /* Are some ifaces not around anymore? */
+ {
+ VGSvcVerbose(3, "VMInfo/Network: Stale interface data detected (%RU32 old vs. %RU32 current)\n",
+ cIfsReportedOld, cIfsReported);
+
+ uint32_t uIfaceDeleteIdx = cIfsReported;
+ do
+ {
+ VGSvcVerbose(3, "VMInfo/Network: Deleting stale data of interface %d ...\n", uIfaceDeleteIdx);
+ rc = VGSvcPropCacheUpdateByPath(&g_VMInfoPropCache, NULL /* Value, delete */, 0 /* Flags */, "/VirtualBox/GuestInfo/Net/%RU32", uIfaceDeleteIdx++);
+ } while (RT_SUCCESS(rc));
+ }
+ else if ( RT_FAILURE(rc)
+ && rc != VERR_NOT_FOUND)
+ {
+ VGSvcError("VMInfo/Network: Failed retrieving old network interfaces count with error %Rrc\n", rc);
+ }
+#endif
+
+ /*
+ * This property is a beacon which is _always_ written, even if the network configuration
+ * does not change. If this property is missing, the host assumes that all other GuestInfo
+ * properties are no longer valid.
+ */
+ VGSvcPropCacheUpdate(&g_VMInfoPropCache, g_pszPropCacheValNetCount, "%RU32", cIfsReported);
+
+ /* Don't fail here; just report everything we got. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbsvcVMInfoWorker(bool volatile *pfShutdown)
+{
+ int rc;
+
+ /*
+ * Tell the control thread that it can continue
+ * spawning services.
+ */
+ RTThreadUserSignal(RTThreadSelf());
+
+#ifdef RT_OS_WINDOWS
+ /* Required for network information (must be called per thread). */
+ if (g_pfnWSAStartup)
+ {
+ WSADATA wsaData;
+ RT_ZERO(wsaData);
+ if (g_pfnWSAStartup(MAKEWORD(2, 2), &wsaData))
+ VGSvcError("VMInfo/Network: WSAStartup failed! Error: %Rrc\n", RTErrConvertFromWin32(g_pfnWSAGetLastError()));
+ }
+#endif
+
+ /*
+ * Write the fixed properties first.
+ */
+ vgsvcVMInfoWriteFixedProperties();
+
+ /*
+ * Now enter the loop retrieving runtime data continuously.
+ */
+ for (;;)
+ {
+ rc = vgsvcVMInfoWriteUsers();
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = vgsvcVMInfoWriteNetwork();
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Whether to wait for event semaphore or not. */
+ bool fWait = true;
+
+ /* Check for location awareness. This most likely only
+ * works with VBox (latest) 4.1 and up. */
+
+ /* Check for new connection. */
+ char *pszLAClientID = NULL;
+ int rc2 = VGSvcReadHostProp(g_uVMInfoGuestPropSvcClientID, g_pszLAActiveClient, true /* Read only */,
+ &pszLAClientID, NULL /* Flags */, NULL /* Timestamp */);
+ if (RT_SUCCESS(rc2))
+ {
+ AssertPtr(pszLAClientID);
+ if (RTStrICmp(pszLAClientID, "0")) /* Is a client connected? */
+ {
+ uint32_t uLAClientID = RTStrToInt32(pszLAClientID);
+ uint64_t uLAClientAttachedTS;
+
+ /* Peek at "Attach" value to figure out if hotdesking happened. */
+ char *pszAttach = NULL;
+ rc2 = vgsvcGetLAClientValue(uLAClientID, "Attach", &pszAttach,
+ &uLAClientAttachedTS);
+
+ if ( RT_SUCCESS(rc2)
+ && ( !g_LAClientAttachedTS
+ || (g_LAClientAttachedTS != uLAClientAttachedTS)))
+ {
+ vgsvcFreeLAClientInfo(&g_LAClientInfo);
+
+ /* Note: There is a race between setting the guest properties by the host and getting them by
+ * the guest. */
+ rc2 = vgsvcGetLAClientInfo(uLAClientID, &g_LAClientInfo);
+ if (RT_SUCCESS(rc2))
+ {
+ VGSvcVerbose(1, "VRDP: Hotdesk client %s with ID=%RU32, Name=%s, Domain=%s\n",
+ /* If g_LAClientAttachedTS is 0 this means there already was an active
+ * hotdesk session when VBoxService started. */
+ !g_LAClientAttachedTS ? "already active" : g_LAClientInfo.fAttached ? "connected" : "disconnected",
+ uLAClientID, g_LAClientInfo.pszName, g_LAClientInfo.pszDomain);
+
+ g_LAClientAttachedTS = g_LAClientInfo.uAttachedTS;
+
+ /* Don't wait for event semaphore below anymore because we now know that the client
+ * changed. This means we need to iterate all VM information again immediately. */
+ fWait = false;
+ }
+ else
+ {
+ static int s_iBitchedAboutLAClientInfo = 0;
+ if (s_iBitchedAboutLAClientInfo < 10)
+ {
+ s_iBitchedAboutLAClientInfo++;
+ VGSvcError("Error getting active location awareness client info, rc=%Rrc\n", rc2);
+ }
+ }
+ }
+ else if (RT_FAILURE(rc2))
+ VGSvcError("Error getting attached value of location awareness client %RU32, rc=%Rrc\n", uLAClientID, rc2);
+ if (pszAttach)
+ RTStrFree(pszAttach);
+ }
+ else
+ {
+ VGSvcVerbose(1, "VRDP: UTTSC disconnected from VRDP server\n");
+ vgsvcFreeLAClientInfo(&g_LAClientInfo);
+ }
+
+ RTStrFree(pszLAClientID);
+ }
+ else
+ {
+ static int s_iBitchedAboutLAClient = 0;
+ if ( (rc2 != VERR_NOT_FOUND) /* No location awareness installed, skip. */
+ && s_iBitchedAboutLAClient < 3)
+ {
+ s_iBitchedAboutLAClient++;
+ VGSvcError("VRDP: Querying connected location awareness client failed with rc=%Rrc\n", rc2);
+ }
+ }
+
+ VGSvcVerbose(3, "VRDP: Handling location awareness done\n");
+
+ /*
+ * Flush all properties if we were restored.
+ */
+ uint64_t idNewSession = g_idVMInfoSession;
+ VbglR3GetSessionId(&idNewSession);
+ if (idNewSession != g_idVMInfoSession)
+ {
+ VGSvcVerbose(3, "The VM session ID changed, flushing all properties\n");
+ vgsvcVMInfoWriteFixedProperties();
+ VGSvcPropCacheFlush(&g_VMInfoPropCache);
+ g_idVMInfoSession = idNewSession;
+ }
+
+ /*
+ * Block for a while.
+ *
+ * The event semaphore takes care of ignoring interruptions and it
+ * allows us to implement service wakeup later.
+ */
+ if (*pfShutdown)
+ break;
+ if (fWait)
+ rc2 = RTSemEventMultiWait(g_hVMInfoEvent, g_cMsVMInfoInterval);
+ if (*pfShutdown)
+ break;
+ if (rc2 != VERR_TIMEOUT && RT_FAILURE(rc2))
+ {
+ VGSvcError("RTSemEventMultiWait failed; rc2=%Rrc\n", rc2);
+ rc = rc2;
+ break;
+ }
+ else if (RT_LIKELY(RT_SUCCESS(rc2)))
+ {
+ /* Reset event semaphore if it got triggered. */
+ rc2 = RTSemEventMultiReset(g_hVMInfoEvent);
+ if (RT_FAILURE(rc2))
+ rc2 = VGSvcError("RTSemEventMultiReset failed; rc2=%Rrc\n", rc2);
+ }
+ }
+
+#ifdef RT_OS_WINDOWS
+ if (g_pfnWSACleanup)
+ g_pfnWSACleanup();
+#endif
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbsvcVMInfoStop(void)
+{
+ RTSemEventMultiSignal(g_hVMInfoEvent);
+}
+
+
+/**
+ * @interface_method_impl{VBOXSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(void) vbsvcVMInfoTerm(void)
+{
+ if (g_hVMInfoEvent != NIL_RTSEMEVENTMULTI)
+ {
+ /** @todo temporary solution: Zap all values which are not valid
+ * anymore when VM goes down (reboot/shutdown ). Needs to
+ * be replaced with "temporary properties" later.
+ *
+ * One idea is to introduce a (HGCM-)session guest property
+ * flag meaning that a guest property is only valid as long
+ * as the HGCM session isn't closed (e.g. guest application
+ * terminates). [don't remove till implemented]
+ */
+ /** @todo r=bird: Drop the VbglR3GuestPropDelSet call here and use the cache
+ * since it remembers what we've written. */
+ /* Delete the "../Net" branch. */
+ const char *apszPat[1] = { "/VirtualBox/GuestInfo/Net/*" };
+ int rc = VbglR3GuestPropDelSet(g_uVMInfoGuestPropSvcClientID, &apszPat[0], RT_ELEMENTS(apszPat));
+
+ /* Destroy LA client info. */
+ vgsvcFreeLAClientInfo(&g_LAClientInfo);
+
+ /* Destroy property cache. */
+ VGSvcPropCacheDestroy(&g_VMInfoPropCache);
+
+ /* Disconnect from guest properties service. */
+ rc = VbglR3GuestPropDisconnect(g_uVMInfoGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ VGSvcError("Failed to disconnect from guest property service! Error: %Rrc\n", rc);
+ g_uVMInfoGuestPropSvcClientID = 0;
+
+ RTSemEventMultiDestroy(g_hVMInfoEvent);
+ g_hVMInfoEvent = NIL_RTSEMEVENTMULTI;
+ }
+}
+
+
+/**
+ * The 'vminfo' service description.
+ */
+VBOXSERVICE g_VMInfo =
+{
+ /* pszName. */
+ "vminfo",
+ /* pszDescription. */
+ "Virtual Machine Information",
+ /* pszUsage. */
+ " [--vminfo-interval <ms>] [--vminfo-user-idle-threshold <ms>]"
+ ,
+ /* pszOptions. */
+ " --vminfo-interval Specifies the interval at which to retrieve the\n"
+ " VM information. The default is 10000 ms.\n"
+ " --vminfo-user-idle-threshold <ms>\n"
+ " Specifies the user idle threshold (in ms) for\n"
+ " considering a guest user as being idle. The default\n"
+ " is 5000 (5 seconds).\n"
+ ,
+ /* methods */
+ vbsvcVMInfoPreInit,
+ vbsvcVMInfoOption,
+ vbsvcVMInfoInit,
+ vbsvcVMInfoWorker,
+ vbsvcVMInfoStop,
+ vbsvcVMInfoTerm
+};
+
diff --git a/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h
new file mode 100644
index 00000000..10e380be
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/VBoxServiceVMInfo.h
@@ -0,0 +1,42 @@
+/* $Id: VBoxServiceVMInfo.h $ */
+/** @file
+ * VBoxServiceVMInfo.h - Internal VM info definitions.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h
+#define GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+extern int VGSvcUserUpdateF(PVBOXSERVICEVEPROPCACHE pCache, const char *pszUser, const char *pszDomain,
+ const char *pszKey, const char *pszValueFormat, ...);
+
+
+extern uint32_t g_uVMInfoUserIdleThresholdMS;
+
+#endif /* !GA_INCLUDED_SRC_common_VBoxService_VBoxServiceVMInfo_h */
+
diff --git a/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk
new file mode 100644
index 00000000..9a92d243
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/testcase/Makefile.kmk
@@ -0,0 +1,42 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxServicec test cases.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# tstUserInfo
+#
+PROGRAMS.win += tstUserInfo
+tstUserInfo_TEMPLATE = VBoxGuestR3Exe
+tstUserInfo_SOURCES = \
+ tstUserInfo.cpp
+tstUserInfo_VBOX_IMPORT_CHECKER.win.x86 = xp
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp
new file mode 100644
index 00000000..3fa93638
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxService/testcase/tstUserInfo.cpp
@@ -0,0 +1,87 @@
+/* $Id: tstUserInfo.cpp $ */
+/** @file
+ * Test case for correct user environment.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include <iprt/win/shlobj.h>
+#endif
+
+#include <iprt/initterm.h>
+#include <iprt/path.h>
+#include <iprt/env.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <VBox/VBoxGuestLib.h>
+
+
+int main()
+{
+ /*
+ * Init globals and such.
+ */
+ RTR3InitExeNoArguments(0);
+
+ int rc = VbglR3Init();
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("VbglR3Init failed with rc=%Rrc.\n", rc);
+ return -1;
+ }
+#ifdef RT_OS_WINDOWS
+ WCHAR wszPath[MAX_PATH];
+ HRESULT hRes = SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, wszPath);
+
+ if (SUCCEEDED(hRes))
+ {
+ RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) = %ls\n", wszPath);
+ hRes = SHGetFolderPathW(0, CSIDL_PERSONAL, 0, 0, wszPath);
+ if (SUCCEEDED(hRes))
+ {
+ RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) = %ls\n", wszPath);
+ }
+ else
+ RTPrintf("SHGetFolderPathW (CSIDL_PERSONAL) returned error: 0x%x\n", hRes);
+ }
+ else
+ RTPrintf("SHGetFolderPathW (CSIDL_APPDATA) returned error: 0x%x\n", hRes);
+
+ if (FAILED(hRes))
+ rc = RTErrConvertFromWin32(hRes);
+
+ /* Dump env bits. */
+ RTPrintf("Environment:\n\n");
+ RTPrintf("APPDATA = %s\n", RTEnvGet("APPDATA"));
+#endif
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
diff --git a/src/VBox/Additions/common/VBoxVideo/.scm-settings b/src/VBox/Additions/common/VBoxVideo/.scm-settings
new file mode 100644
index 00000000..706de605
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxVideo/.scm-settings
@@ -0,0 +1,33 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for common/VBoxVideo
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+
+# This graphics stuff is using MIT to encourage kernel upstreaming.
+--license-mit
+
diff --git a/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp b/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp
new file mode 100644
index 00000000..7a5467a9
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp
@@ -0,0 +1,300 @@
+/* $Id: HGSMIBase.cpp $ */
+/** @file
+ * VirtualBox Video driver, common code - HGSMI guest-to-host communication.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <HGSMIBase.h>
+#include <VBoxVideoIPRT.h>
+#include <VBoxVideoGuest.h>
+#include <VBoxVideoVBE.h>
+#include <HGSMIChannels.h>
+#include <HGSMIChSetup.h>
+
+/** Detect whether HGSMI is supported by the host. */
+DECLHIDDEN(bool) VBoxHGSMIIsSupported(void)
+{
+ uint16_t DispiId;
+
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ID);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_ID_HGSMI);
+
+ DispiId = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+
+ return (DispiId == VBE_DISPI_ID_HGSMI);
+}
+
+
+/**
+ * Inform the host of the location of the host flags in VRAM via an HGSMI command.
+ * @returns IPRT status value.
+ * @returns VERR_NOT_IMPLEMENTED if the host does not support the command.
+ * @returns VERR_NO_MEMORY if a heap allocation fails.
+ * @param pCtx the context of the guest heap to use.
+ * @param offLocation the offset chosen for the flags withing guest VRAM.
+ */
+DECLHIDDEN(int) VBoxHGSMIReportFlagsLocation(PHGSMIGUESTCOMMANDCONTEXT pCtx, HGSMIOFFSET offLocation)
+{
+
+ /* Allocate the IO buffer. */
+ HGSMIBUFFERLOCATION RT_UNTRUSTED_VOLATILE_HOST *p =
+ (HGSMIBUFFERLOCATION RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_HGSMI,
+ HGSMI_CC_HOST_FLAGS_LOCATION);
+ if (!p)
+ return VERR_NO_MEMORY;
+
+ /* Prepare data to be sent to the host. */
+ p->offLocation = offLocation;
+ p->cbLocation = sizeof(HGSMIHOSTFLAGS);
+ /* No need to check that the buffer is valid as we have just allocated it. */
+ VBoxHGSMIBufferSubmit(pCtx, p);
+ /* Free the IO buffer. */
+ VBoxHGSMIBufferFree(pCtx, p);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Notify the host of HGSMI-related guest capabilities via an HGSMI command.
+ * @returns IPRT status value.
+ * @returns VERR_NOT_IMPLEMENTED if the host does not support the command.
+ * @returns VERR_NO_MEMORY if a heap allocation fails.
+ * @param pCtx the context of the guest heap to use.
+ * @param fCaps the capabilities to report, see VBVACAPS.
+ */
+DECLHIDDEN(int) VBoxHGSMISendCapsInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx, uint32_t fCaps)
+{
+
+ /* Allocate the IO buffer. */
+ VBVACAPS RT_UNTRUSTED_VOLATILE_HOST *p =
+ (VBVACAPS RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_VBVA, VBVA_INFO_CAPS);
+
+ if (!p)
+ return VERR_NO_MEMORY;
+
+ /* Prepare data to be sent to the host. */
+ p->rc = VERR_NOT_IMPLEMENTED;
+ p->fCaps = fCaps;
+ /* No need to check that the buffer is valid as we have just allocated it. */
+ VBoxHGSMIBufferSubmit(pCtx, p);
+
+ AssertRC(p->rc);
+ /* Free the IO buffer. */
+ VBoxHGSMIBufferFree(pCtx, p);
+ return p->rc;
+}
+
+
+/**
+ * Get the information needed to map the basic communication structures in
+ * device memory into our address space. All pointer parameters are optional.
+ *
+ * @param cbVRAM how much video RAM is allocated to the device
+ * @param poffVRAMBaseMapping where to save the offset from the start of the
+ * device VRAM of the whole area to map
+ * @param pcbMapping where to save the mapping size
+ * @param poffGuestHeapMemory where to save the offset into the mapped area
+ * of the guest heap backing memory
+ * @param pcbGuestHeapMemory where to save the size of the guest heap
+ * backing memory
+ * @param poffHostFlags where to save the offset into the mapped area
+ * of the host flags
+ */
+DECLHIDDEN(void) VBoxHGSMIGetBaseMappingInfo(uint32_t cbVRAM,
+ uint32_t *poffVRAMBaseMapping,
+ uint32_t *pcbMapping,
+ uint32_t *poffGuestHeapMemory,
+ uint32_t *pcbGuestHeapMemory,
+ uint32_t *poffHostFlags)
+{
+ AssertPtrNullReturnVoid(poffVRAMBaseMapping);
+ AssertPtrNullReturnVoid(pcbMapping);
+ AssertPtrNullReturnVoid(poffGuestHeapMemory);
+ AssertPtrNullReturnVoid(pcbGuestHeapMemory);
+ AssertPtrNullReturnVoid(poffHostFlags);
+ if (poffVRAMBaseMapping)
+ *poffVRAMBaseMapping = cbVRAM - VBVA_ADAPTER_INFORMATION_SIZE;
+ if (pcbMapping)
+ *pcbMapping = VBVA_ADAPTER_INFORMATION_SIZE;
+ if (poffGuestHeapMemory)
+ *poffGuestHeapMemory = 0;
+ if (pcbGuestHeapMemory)
+ *pcbGuestHeapMemory = VBVA_ADAPTER_INFORMATION_SIZE
+ - sizeof(HGSMIHOSTFLAGS);
+ if (poffHostFlags)
+ *poffHostFlags = VBVA_ADAPTER_INFORMATION_SIZE
+ - sizeof(HGSMIHOSTFLAGS);
+}
+
+/**
+ * Query the host for an HGSMI configuration parameter via an HGSMI command.
+ * @returns iprt status value
+ * @param pCtx the context containing the heap used
+ * @param u32Index the index of the parameter to query,
+ * @see VBVACONF32::u32Index
+ * @param pulValue where to store the value of the parameter on success
+ */
+DECLHIDDEN(int) VBoxQueryConfHGSMI(PHGSMIGUESTCOMMANDCONTEXT pCtx, uint32_t u32Index, uint32_t *pulValue)
+{
+ VBVACONF32 *p;
+
+ /* Allocate the IO buffer. */
+ p = (VBVACONF32 *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_VBVA, VBVA_QUERY_CONF32);
+ if (!p)
+ return VERR_NO_MEMORY;
+
+ /* Prepare data to be sent to the host. */
+ p->u32Index = u32Index;
+ p->u32Value = UINT32_MAX;
+ /* No need to check that the buffer is valid as we have just allocated it. */
+ VBoxHGSMIBufferSubmit(pCtx, p);
+ *pulValue = p->u32Value;
+ /* Free the IO buffer. */
+ VBoxHGSMIBufferFree(pCtx, p);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Pass the host a new mouse pointer shape via an HGSMI command.
+ *
+ * @returns success or failure
+ * @param pCtx the context containing the heap to be used
+ * @param fFlags cursor flags, @see VMMDevReqMousePointer::fFlags
+ * @param cHotX horizontal position of the hot spot
+ * @param cHotY vertical position of the hot spot
+ * @param cWidth width in pixels of the cursor
+ * @param cHeight height in pixels of the cursor
+ * @param pPixels pixel data, @see VMMDevReqMousePointer for the format
+ * @param cbLength size in bytes of the pixel data
+ */
+DECLHIDDEN(int) VBoxHGSMIUpdatePointerShape(PHGSMIGUESTCOMMANDCONTEXT pCtx, uint32_t fFlags,
+ uint32_t cHotX, uint32_t cHotY, uint32_t cWidth, uint32_t cHeight,
+ uint8_t *pPixels, uint32_t cbLength)
+{
+ VBVAMOUSEPOINTERSHAPE *p;
+ uint32_t cbPixels = 0;
+ int rc;
+
+ if (fFlags & VBOX_MOUSE_POINTER_SHAPE)
+ {
+ /*
+ * Size of the pointer data:
+ * sizeof (AND mask) + sizeof (XOR_MASK)
+ */
+ cbPixels = ((((cWidth + 7) / 8) * cHeight + 3) & ~3)
+ + cWidth * 4 * cHeight;
+ if (cbPixels > cbLength)
+ return VERR_INVALID_PARAMETER;
+ /*
+ * If shape is supplied, then always create the pointer visible.
+ * See comments in 'vboxUpdatePointerShape'
+ */
+ fFlags |= VBOX_MOUSE_POINTER_VISIBLE;
+ }
+ /* Allocate the IO buffer. */
+ p = (VBVAMOUSEPOINTERSHAPE *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p) + cbPixels, HGSMI_CH_VBVA,
+ VBVA_MOUSE_POINTER_SHAPE);
+ if (!p)
+ return VERR_NO_MEMORY;
+ /* Prepare data to be sent to the host. */
+ /* Will be updated by the host. */
+ p->i32Result = VINF_SUCCESS;
+ /* We have our custom flags in the field */
+ p->fu32Flags = fFlags;
+ p->u32HotX = cHotX;
+ p->u32HotY = cHotY;
+ p->u32Width = cWidth;
+ p->u32Height = cHeight;
+ if (cbPixels)
+ /* Copy the actual pointer data. */
+ memcpy (p->au8Data, pPixels, cbPixels);
+ /* No need to check that the buffer is valid as we have just allocated it. */
+ VBoxHGSMIBufferSubmit(pCtx, p);
+ rc = p->i32Result;
+ /* Free the IO buffer. */
+ VBoxHGSMIBufferFree(pCtx, p);
+ return rc;
+}
+
+
+/**
+ * Report the guest cursor position. The host may wish to use this information
+ * to re-position its own cursor (though this is currently unlikely). The
+ * current host cursor position is returned.
+ * @param pCtx The context containing the heap used.
+ * @param fReportPosition Are we reporting a position?
+ * @param x Guest cursor X position.
+ * @param y Guest cursor Y position.
+ * @param pxHost Host cursor X position is stored here. Optional.
+ * @param pyHost Host cursor Y position is stored here. Optional.
+ * @returns iprt status code.
+ * @returns VERR_NO_MEMORY HGSMI heap allocation failed.
+ */
+DECLHIDDEN(int) VBoxHGSMICursorPosition(PHGSMIGUESTCOMMANDCONTEXT pCtx, bool fReportPosition,
+ uint32_t x, uint32_t y, uint32_t *pxHost, uint32_t *pyHost)
+{
+ VBVACURSORPOSITION *p;
+
+ /* Allocate the IO buffer. */
+ p = (VBVACURSORPOSITION *)VBoxHGSMIBufferAlloc(pCtx, sizeof(*p), HGSMI_CH_VBVA,
+ VBVA_CURSOR_POSITION);
+ if (!p)
+ return VERR_NO_MEMORY;
+ /* Prepare data to be sent to the host. */
+ p->fReportPosition = fReportPosition;
+ p->x = x;
+ p->y = y;
+ /* No need to check that the buffer is valid as we have just allocated it. */
+ VBoxHGSMIBufferSubmit(pCtx, p);
+ if (pxHost)
+ *pxHost = p->x;
+ if (pyHost)
+ *pyHost = p->y;
+ /* Free the IO buffer. */
+ VBoxHGSMIBufferFree(pCtx, p);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @todo Mouse pointer position to be read from VMMDev memory, address of the
+ * memory region can be queried from VMMDev via an IOCTL. This VMMDev memory
+ * region will contain host information which is needed by the guest.
+ *
+ * Reading will not cause a switch to the host.
+ *
+ * Have to take into account:
+ * * synchronization: host must write to the memory only from EMT,
+ * large structures must be read under flag, which tells the host
+ * that the guest is currently reading the memory (OWNER flag?).
+ * * guest writes: may be allocate a page for the host info and make
+ * the page readonly for the guest.
+ * * the information should be available only for additions drivers.
+ * * VMMDev additions driver will inform the host which version of the info
+ * it expects, host must support all versions.
+ */
diff --git a/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp b/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp
new file mode 100644
index 00000000..84a35a4a
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp
@@ -0,0 +1,124 @@
+/* $Id: HGSMIBuffers.cpp $ */
+/** @file
+ * VirtualBox Video driver, common code - HGSMI buffer management.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <VBoxVideoGuest.h>
+#include <VBoxVideoVBE.h>
+#include <VBoxVideoIPRT.h>
+
+/**
+ * Set up the HGSMI guest-to-host command context.
+ * @returns iprt status value
+ * @param pCtx the context to set up
+ * @param pvGuestHeapMemory a pointer to the mapped backing memory for
+ * the guest heap
+ * @param cbGuestHeapMemory the size of the backing memory area
+ * @param offVRAMGuestHeapMemory the offset of the memory pointed to by
+ * @a pvGuestHeapMemory within the video RAM
+ * @param pEnv HGSMI environment.
+ */
+DECLHIDDEN(int) VBoxHGSMISetupGuestContext(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ void *pvGuestHeapMemory,
+ uint32_t cbGuestHeapMemory,
+ uint32_t offVRAMGuestHeapMemory,
+ const HGSMIENV *pEnv)
+{
+ /** @todo should we be using a fixed ISA port value here? */
+ pCtx->port = (RTIOPORT)VGA_PORT_HGSMI_GUEST;
+#ifdef VBOX_WDDM_MINIPORT
+ return VBoxSHGSMIInit(&pCtx->heapCtx, pvGuestHeapMemory,
+ cbGuestHeapMemory, offVRAMGuestHeapMemory, pEnv);
+#else
+ return HGSMIHeapSetup(&pCtx->heapCtx, pvGuestHeapMemory,
+ cbGuestHeapMemory, offVRAMGuestHeapMemory, pEnv);
+#endif
+}
+
+
+/**
+ * Allocate and initialise a command descriptor in the guest heap for a
+ * guest-to-host command.
+ *
+ * @returns pointer to the descriptor's command data buffer
+ * @param pCtx the context containing the heap to be used
+ * @param cbData the size of the command data to go into the descriptor
+ * @param u8Ch the HGSMI channel to be used, set to the descriptor
+ * @param u16Op the HGSMI command to be sent, set to the descriptor
+ */
+DECLHIDDEN(void RT_UNTRUSTED_VOLATILE_HOST *) VBoxHGSMIBufferAlloc(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ HGSMISIZE cbData,
+ uint8_t u8Ch,
+ uint16_t u16Op)
+{
+#ifdef VBOX_WDDM_MINIPORT
+ return VBoxSHGSMIHeapAlloc(&pCtx->heapCtx, cbData, u8Ch, u16Op);
+#else
+ return HGSMIHeapAlloc(&pCtx->heapCtx, cbData, u8Ch, u16Op);
+#endif
+}
+
+
+/**
+ * Free a descriptor allocated by @a VBoxHGSMIBufferAlloc.
+ *
+ * @param pCtx the context containing the heap used
+ * @param pvBuffer the pointer returned by @a VBoxHGSMIBufferAlloc
+ */
+DECLHIDDEN(void) VBoxHGSMIBufferFree(PHGSMIGUESTCOMMANDCONTEXT pCtx, void RT_UNTRUSTED_VOLATILE_HOST *pvBuffer)
+{
+#ifdef VBOX_WDDM_MINIPORT
+ VBoxSHGSMIHeapFree(&pCtx->heapCtx, pvBuffer);
+#else
+ HGSMIHeapFree(&pCtx->heapCtx, pvBuffer);
+#endif
+}
+
+/**
+ * Submit a command descriptor allocated by @a VBoxHGSMIBufferAlloc.
+ *
+ * @param pCtx the context containing the heap used
+ * @param pvBuffer the pointer returned by @a VBoxHGSMIBufferAlloc
+ */
+DECLHIDDEN(int) VBoxHGSMIBufferSubmit(PHGSMIGUESTCOMMANDCONTEXT pCtx, void RT_UNTRUSTED_VOLATILE_HOST *pvBuffer)
+{
+ /* Initialize the buffer and get the offset for port IO. */
+ HGSMIOFFSET offBuffer = HGSMIHeapBufferOffset(HGSMIGUESTCMDHEAP_GET(&pCtx->heapCtx), pvBuffer);
+
+ Assert(offBuffer != HGSMIOFFSET_VOID);
+ if (offBuffer != HGSMIOFFSET_VOID)
+ {
+ /* Submit the buffer to the host. */
+ VBVO_PORT_WRITE_U32(pCtx->port, offBuffer);
+ /* Make the compiler aware that the host has changed memory. */
+ ASMCompilerBarrier();
+ return VINF_SUCCESS;
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
diff --git a/src/VBox/Additions/common/VBoxVideo/HGSMIHostCmd.cpp b/src/VBox/Additions/common/VBoxVideo/HGSMIHostCmd.cpp
new file mode 100644
index 00000000..bd1733e5
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxVideo/HGSMIHostCmd.cpp
@@ -0,0 +1,245 @@
+/* $Id: HGSMIHostCmd.cpp $ */
+/** @file
+ * VirtualBox Video driver, common code - HGSMI host-to-guest communication.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <VBoxVideoGuest.h>
+#include <VBoxVideoVBE.h>
+#include <VBoxVideoIPRT.h>
+#include <HGSMIHostCmd.h>
+
+/**
+ * Initialise the host context structure.
+ *
+ * @param pCtx the context structure to initialise
+ * @param pvBaseMapping where the basic HGSMI structures are mapped at
+ * @param offHostFlags the offset of the host flags into the basic HGSMI
+ * structures
+ * @param pvHostAreaMapping where the area for the host heap is mapped at
+ * @param offVRAMHostArea offset of the host heap area into VRAM
+ * @param cbHostArea size in bytes of the host heap area
+ */
+DECLHIDDEN(void) VBoxHGSMISetupHostContext(PHGSMIHOSTCOMMANDCONTEXT pCtx,
+ void *pvBaseMapping,
+ uint32_t offHostFlags,
+ void *pvHostAreaMapping,
+ uint32_t offVRAMHostArea,
+ uint32_t cbHostArea)
+{
+ uint8_t *pu8HostFlags = ((uint8_t *)pvBaseMapping) + offHostFlags;
+ pCtx->pfHostFlags = (HGSMIHOSTFLAGS *)pu8HostFlags;
+ /** @todo should we really be using a fixed ISA port value here? */
+ pCtx->port = (RTIOPORT)VGA_PORT_HGSMI_HOST;
+ HGSMIAreaInitialize(&pCtx->areaCtx, pvHostAreaMapping, cbHostArea,
+ offVRAMHostArea);
+}
+
+
+/** Send completion notification to the host for the command located at offset
+ * @a offt into the host command buffer. */
+static void HGSMINotifyHostCmdComplete(PHGSMIHOSTCOMMANDCONTEXT pCtx, HGSMIOFFSET offt)
+{
+ VBVO_PORT_WRITE_U32(pCtx->port, offt);
+}
+
+
+/**
+ * Inform the host that a command has been handled.
+ *
+ * @param pCtx the context containing the heap to be used
+ * @param pvMem pointer into the heap as mapped in @a pCtx to the command to
+ * be completed
+ */
+DECLHIDDEN(void) VBoxHGSMIHostCmdComplete(PHGSMIHOSTCOMMANDCONTEXT pCtx, void RT_UNTRUSTED_VOLATILE_HOST *pvMem)
+{
+ HGSMIBUFFERHEADER RT_UNTRUSTED_VOLATILE_GUEST *pHdr = HGSMIBufferHeaderFromData(pvMem);
+ HGSMIOFFSET offMem = HGSMIPointerToOffset(&pCtx->areaCtx, pHdr);
+ Assert(offMem != HGSMIOFFSET_VOID);
+ if (offMem != HGSMIOFFSET_VOID)
+ HGSMINotifyHostCmdComplete(pCtx, offMem);
+}
+
+
+/** Submit an incoming host command to the appropriate handler. */
+static void hgsmiHostCmdProcess(PHGSMIHOSTCOMMANDCONTEXT pCtx,
+ HGSMIOFFSET offBuffer)
+{
+ int rc = HGSMIBufferProcess(&pCtx->areaCtx, &pCtx->channels, offBuffer);
+ Assert(!RT_FAILURE(rc));
+ if(RT_FAILURE(rc))
+ {
+ /* failure means the command was not submitted to the handler for some reason
+ * it's our responsibility to notify its completion in this case */
+ HGSMINotifyHostCmdComplete(pCtx, offBuffer);
+ }
+ /* if the cmd succeeded it's responsibility of the callback to complete it */
+}
+
+/** Get the next command from the host. */
+static HGSMIOFFSET hgsmiGetHostBuffer(PHGSMIHOSTCOMMANDCONTEXT pCtx)
+{
+ return VBVO_PORT_READ_U32(pCtx->port);
+}
+
+
+/** Get and handle the next command from the host. */
+static void hgsmiHostCommandQueryProcess(PHGSMIHOSTCOMMANDCONTEXT pCtx)
+{
+ HGSMIOFFSET offset = hgsmiGetHostBuffer(pCtx);
+ AssertReturnVoid(offset != HGSMIOFFSET_VOID);
+ hgsmiHostCmdProcess(pCtx, offset);
+}
+
+
+/** Drain the host command queue. */
+DECLHIDDEN(void) VBoxHGSMIProcessHostQueue(PHGSMIHOSTCOMMANDCONTEXT pCtx)
+{
+ while (pCtx->pfHostFlags->u32HostFlags & HGSMIHOSTFLAGS_COMMANDS_PENDING)
+ {
+ if (!ASMAtomicCmpXchgBool(&pCtx->fHostCmdProcessing, true, false))
+ return;
+ hgsmiHostCommandQueryProcess(pCtx);
+ ASMAtomicWriteBool(&pCtx->fHostCmdProcessing, false);
+ }
+}
+
+
+/** Tell the host about the location of the area of VRAM set aside for the host
+ * heap. */
+static int vboxHGSMIReportHostArea(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ uint32_t u32AreaOffset, uint32_t u32AreaSize)
+{
+ VBVAINFOHEAP *p;
+ int rc = VINF_SUCCESS;
+
+ /* Allocate the IO buffer. */
+ p = (VBVAINFOHEAP *)VBoxHGSMIBufferAlloc(pCtx,
+ sizeof (VBVAINFOHEAP), HGSMI_CH_VBVA,
+ VBVA_INFO_HEAP);
+ if (p)
+ {
+ /* Prepare data to be sent to the host. */
+ p->u32HeapOffset = u32AreaOffset;
+ p->u32HeapSize = u32AreaSize;
+ rc = VBoxHGSMIBufferSubmit(pCtx, p);
+ /* Free the IO buffer. */
+ VBoxHGSMIBufferFree(pCtx, p);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Get the information needed to map the area used by the host to send back
+ * requests.
+ *
+ * @param pCtx the context containing the heap to use
+ * @param cbVRAM how much video RAM is allocated to the device
+ * @param offVRAMBaseMapping the offset of the basic communication structures
+ * into the guest's VRAM
+ * @param poffVRAMHostArea where to store the offset into VRAM of the host
+ * heap area
+ * @param pcbHostArea where to store the size of the host heap area
+ */
+DECLHIDDEN(void) VBoxHGSMIGetHostAreaMapping(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ uint32_t cbVRAM,
+ uint32_t offVRAMBaseMapping,
+ uint32_t *poffVRAMHostArea,
+ uint32_t *pcbHostArea)
+{
+ uint32_t offVRAMHostArea = offVRAMBaseMapping, cbHostArea = 0;
+
+ AssertPtrReturnVoid(poffVRAMHostArea);
+ AssertPtrReturnVoid(pcbHostArea);
+ VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_HOST_HEAP_SIZE, &cbHostArea);
+ if (cbHostArea != 0)
+ {
+ uint32_t cbHostAreaMaxSize = cbVRAM / 4;
+ /** @todo what is the idea of this? */
+ if (cbHostAreaMaxSize >= VBVA_ADAPTER_INFORMATION_SIZE)
+ {
+ cbHostAreaMaxSize -= VBVA_ADAPTER_INFORMATION_SIZE;
+ }
+ if (cbHostArea > cbHostAreaMaxSize)
+ {
+ cbHostArea = cbHostAreaMaxSize;
+ }
+ /* Round up to 4096 bytes. */
+ cbHostArea = (cbHostArea + 0xFFF) & ~0xFFF;
+ offVRAMHostArea = offVRAMBaseMapping - cbHostArea;
+ }
+
+ *pcbHostArea = cbHostArea;
+ *poffVRAMHostArea = offVRAMHostArea;
+ // LogFunc(("offVRAMHostArea = 0x%08X, cbHostArea = 0x%08X\n",
+ // offVRAMHostArea, cbHostArea));
+}
+
+
+/**
+ * Tell the host about the ways it can use to communicate back to us via an
+ * HGSMI command
+ *
+ * @returns iprt status value
+ * @param pCtx the context containing the heap to use
+ * @param offVRAMFlagsLocation where we wish the host to place its flags
+ * relative to the start of the VRAM
+ * @param fCaps additions HGSMI capabilities the guest
+ * supports
+ * @param offVRAMHostArea offset into VRAM of the host heap area
+ * @param cbHostArea size in bytes of the host heap area
+ */
+DECLHIDDEN(int) VBoxHGSMISendHostCtxInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ HGSMIOFFSET offVRAMFlagsLocation,
+ uint32_t fCaps,
+ uint32_t offVRAMHostArea,
+ uint32_t cbHostArea)
+{
+ // Log(("VBoxVideo::vboxSetupAdapterInfo\n"));
+
+ /* setup the flags first to ensure they are initialized by the time the
+ * host heap is ready */
+ int rc = VBoxHGSMIReportFlagsLocation(pCtx, offVRAMFlagsLocation);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc) && fCaps)
+ {
+ /* Inform about caps */
+ rc = VBoxHGSMISendCapsInfo(pCtx, fCaps);
+ AssertRC(rc);
+ }
+ if (RT_SUCCESS (rc))
+ {
+ /* Report the host heap location. */
+ rc = vboxHGSMIReportHostArea(pCtx, offVRAMHostArea, cbHostArea);
+ AssertRC(rc);
+ }
+ // Log(("VBoxVideo::vboxSetupAdapterInfo finished rc = %d\n", rc));
+ return rc;
+}
diff --git a/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp b/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp
new file mode 100644
index 00000000..a84613a7
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp
@@ -0,0 +1,419 @@
+/* $Id: Modesetting.cpp $ */
+/** @file
+ * VirtualBox Video driver, common code - HGSMI initialisation and helper
+ * functions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <VBoxVideoGuest.h>
+#include <VBoxVideoVBE.h>
+#include <HGSMIChannels.h>
+
+#ifndef VBOX_GUESTR3XF86MOD
+# include <VBoxVideoIPRT.h>
+#endif
+
+/**
+ * Gets the count of virtual monitors attached to the guest via an HGSMI
+ * command
+ *
+ * @returns the right count on success or 1 on failure.
+ * @param pCtx the context containing the heap to use
+ */
+DECLHIDDEN(uint32_t) VBoxHGSMIGetMonitorCount(PHGSMIGUESTCOMMANDCONTEXT pCtx)
+{
+ /* Query the configured number of displays. */
+ uint32_t cDisplays = 0;
+ VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_MONITOR_COUNT, &cDisplays);
+ // LogFunc(("cDisplays = %d\n", cDisplays));
+ if (cDisplays == 0 || cDisplays > VBOX_VIDEO_MAX_SCREENS)
+ /* Host reported some bad value. Continue in the 1 screen mode. */
+ cDisplays = 1;
+ return cDisplays;
+}
+
+
+/**
+ * Query whether the virtual hardware supports VBE_DISPI_ID_CFG
+ * and set the interface.
+ *
+ * @returns Whether the interface is supported.
+ */
+DECLHIDDEN(bool) VBoxVGACfgAvailable(void)
+{
+ uint16_t DispiId;
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ID);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_ID_CFG);
+ DispiId = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+ return (DispiId == VBE_DISPI_ID_CFG);
+}
+
+
+/**
+ * Query a configuration value from the virtual hardware which supports VBE_DISPI_ID_CFG.
+ * I.e. use this function only if VBoxVGACfgAvailable returns true.
+ *
+ * @returns Whether the value is supported.
+ * @param u16Id Identifier of the configuration value (VBE_DISPI_CFG_ID_*).
+ * @param pu32Value Where to store value from the host.
+ * @param u32DefValue What to assign to *pu32Value if the value is not supported.
+ */
+DECLHIDDEN(bool) VBoxVGACfgQuery(uint16_t u16Id, uint32_t *pu32Value, uint32_t u32DefValue)
+{
+ uint32_t u32;
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_CFG);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_CFG_MASK_SUPPORT | u16Id);
+ u32 = VBVO_PORT_READ_U32(VBE_DISPI_IOPORT_DATA);
+ if (u32)
+ {
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, u16Id);
+ *pu32Value = VBVO_PORT_READ_U32(VBE_DISPI_IOPORT_DATA);
+ return true;
+ }
+
+ *pu32Value = u32DefValue;
+ return false;
+}
+
+
+/**
+ * Returns the size of the video RAM in bytes.
+ *
+ * @returns the size
+ */
+DECLHIDDEN(uint32_t) VBoxVideoGetVRAMSize(void)
+{
+ /** @note A 32bit read on this port returns the VRAM size if interface is older than VBE_DISPI_ID_CFG. */
+ return VBVO_PORT_READ_U32(VBE_DISPI_IOPORT_DATA);
+}
+
+
+/**
+ * Check whether this hardware allows the display width to have non-multiple-
+ * of-eight values.
+ *
+ * @returns true if any width is allowed, false otherwise.
+ */
+DECLHIDDEN(bool) VBoxVideoAnyWidthAllowed(void)
+{
+ unsigned DispiId;
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ID);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, VBE_DISPI_ID_ANYX);
+ DispiId = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+ return (DispiId == VBE_DISPI_ID_ANYX);
+}
+
+
+/**
+ * Tell the host about how VRAM is divided up between each screen via an HGSMI
+ * command. It is acceptable to specifiy identical data for each screen if
+ * they share a single framebuffer.
+ *
+ * @returns iprt status code, either VERR_NO_MEMORY or the status returned by
+ * @a pfnFill
+ * @todo What was I thinking of with that callback function? It
+ * would be much simpler to just pass in a structure in normal
+ * memory and copy it.
+ * @param pCtx the context containing the heap to use
+ * @param u32Count the number of screens we are activating
+ * @param pfnFill a callback which initialises the VBVAINFOVIEW structures
+ * for all screens
+ * @param pvData context data for @a pfnFill
+ */
+DECLHIDDEN(int) VBoxHGSMISendViewInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ uint32_t u32Count,
+ PFNHGSMIFILLVIEWINFO pfnFill,
+ void *pvData)
+{
+ int rc;
+ /* Issue the screen info command. */
+ VBVAINFOVIEW RT_UNTRUSTED_VOLATILE_HOST *pInfo =
+ (VBVAINFOVIEW RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAINFOVIEW) * u32Count,
+ HGSMI_CH_VBVA, VBVA_INFO_VIEW);
+ if (pInfo)
+ {
+ rc = pfnFill(pvData, (VBVAINFOVIEW *)pInfo /* lazy bird */, u32Count);
+ if (RT_SUCCESS(rc))
+ VBoxHGSMIBufferSubmit(pCtx, pInfo);
+ VBoxHGSMIBufferFree(pCtx, pInfo);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Set a video mode using port registers. This must be done for the first
+ * screen before every HGSMI modeset and also works when HGSM is not enabled.
+ * @param cWidth the mode width
+ * @param cHeight the mode height
+ * @param cVirtWidth the mode pitch
+ * @param cBPP the colour depth of the mode
+ * @param fFlags flags for the mode. These will be or-ed with the
+ * default _ENABLED flag, so unless you are restoring
+ * a saved mode or have special requirements you can pass
+ * zero here.
+ * @param cx the horizontal panning offset
+ * @param cy the vertical panning offset
+ */
+DECLHIDDEN(void) VBoxVideoSetModeRegisters(uint16_t cWidth, uint16_t cHeight,
+ uint16_t cVirtWidth, uint16_t cBPP,
+ uint16_t fFlags, uint16_t cx,
+ uint16_t cy)
+{
+ /* set the mode characteristics */
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_XRES);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cWidth);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_YRES);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cHeight);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_VIRT_WIDTH);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cVirtWidth);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_BPP);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cBPP);
+ /* enable the mode */
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ENABLE);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, fFlags | VBE_DISPI_ENABLED);
+ /* Panning registers */
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_X_OFFSET);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cx);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_Y_OFFSET);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, cy);
+ /** @todo read from the port to see if the mode switch was successful */
+}
+
+
+/**
+ * Get the video mode for the first screen using the port registers. All
+ * parameters are optional
+ * @returns true if the VBE mode returned is active, false if we are in VGA
+ * mode
+ * @note If anyone else needs additional register values just extend the
+ * function with additional parameters and fix any existing callers.
+ * @param pcWidth where to store the mode width
+ * @param pcHeight where to store the mode height
+ * @param pcVirtWidth where to store the mode pitch
+ * @param pcBPP where to store the colour depth of the mode
+ * @param pfFlags where to store the flags for the mode
+ */
+DECLHIDDEN(bool) VBoxVideoGetModeRegisters(uint16_t *pcWidth, uint16_t *pcHeight,
+ uint16_t *pcVirtWidth, uint16_t *pcBPP,
+ uint16_t *pfFlags)
+{
+ uint16_t fFlags;
+
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ENABLE);
+ fFlags = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+ if (pcWidth)
+ {
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_XRES);
+ *pcWidth = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+ }
+ if (pcHeight)
+ {
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_YRES);
+ *pcHeight = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+ }
+ if (pcVirtWidth)
+ {
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_VIRT_WIDTH);
+ *pcVirtWidth = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+ }
+ if (pcBPP)
+ {
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_BPP);
+ *pcBPP = VBVO_PORT_READ_U16(VBE_DISPI_IOPORT_DATA);
+ }
+ if (pfFlags)
+ *pfFlags = fFlags;
+ return RT_BOOL(fFlags & VBE_DISPI_ENABLED);
+}
+
+
+/**
+ * Disable our extended graphics mode and go back to VGA mode.
+ */
+DECLHIDDEN(void) VBoxVideoDisableVBE(void)
+{
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_INDEX, VBE_DISPI_INDEX_ENABLE);
+ VBVO_PORT_WRITE_U16(VBE_DISPI_IOPORT_DATA, 0);
+}
+
+
+/**
+ * Set a video mode via an HGSMI request. The views must have been
+ * initialised first using @a VBoxHGSMISendViewInfo and if the mode is being
+ * set on the first display then it must be set first using registers.
+ * @param pCtx The context containing the heap to use.
+ * @param cDisplay the screen number
+ * @param cOriginX the horizontal displacement relative to the first screen
+ * @param cOriginY the vertical displacement relative to the first screen
+ * @param offStart the offset of the visible area of the framebuffer
+ * relative to the framebuffer start
+ * @param cbPitch the offset in bytes between the starts of two adjecent
+ * scan lines in video RAM
+ * @param cWidth the mode width
+ * @param cHeight the mode height
+ * @param cBPP the colour depth of the mode
+ * @param fFlags flags
+ */
+DECLHIDDEN(void) VBoxHGSMIProcessDisplayInfo(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ uint32_t cDisplay,
+ int32_t cOriginX,
+ int32_t cOriginY,
+ uint32_t offStart,
+ uint32_t cbPitch,
+ uint32_t cWidth,
+ uint32_t cHeight,
+ uint16_t cBPP,
+ uint16_t fFlags)
+{
+ /* Issue the screen info command. */
+ VBVAINFOSCREEN RT_UNTRUSTED_VOLATILE_HOST *pScreen =
+ (VBVAINFOSCREEN RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAINFOSCREEN),
+ HGSMI_CH_VBVA, VBVA_INFO_SCREEN);
+ if (pScreen != NULL)
+ {
+ pScreen->u32ViewIndex = cDisplay;
+ pScreen->i32OriginX = cOriginX;
+ pScreen->i32OriginY = cOriginY;
+ pScreen->u32StartOffset = offStart;
+ pScreen->u32LineSize = cbPitch;
+ pScreen->u32Width = cWidth;
+ pScreen->u32Height = cHeight;
+ pScreen->u16BitsPerPixel = cBPP;
+ pScreen->u16Flags = fFlags;
+
+ VBoxHGSMIBufferSubmit(pCtx, pScreen);
+
+ VBoxHGSMIBufferFree(pCtx, pScreen);
+ }
+ else
+ {
+ // LogFunc(("HGSMIHeapAlloc failed\n"));
+ }
+}
+
+
+/** Report the rectangle relative to which absolute pointer events should be
+ * expressed. This information remains valid until the next VBVA resize event
+ * for any screen, at which time it is reset to the bounding rectangle of all
+ * virtual screens.
+ * @param pCtx The context containing the heap to use.
+ * @param cOriginX Upper left X co-ordinate relative to the first screen.
+ * @param cOriginY Upper left Y co-ordinate relative to the first screen.
+ * @param cWidth Rectangle width.
+ * @param cHeight Rectangle height.
+ * @returns iprt status code.
+ * @returns VERR_NO_MEMORY HGSMI heap allocation failed.
+ */
+DECLHIDDEN(int) VBoxHGSMIUpdateInputMapping(PHGSMIGUESTCOMMANDCONTEXT pCtx, int32_t cOriginX, int32_t cOriginY,
+ uint32_t cWidth, uint32_t cHeight)
+{
+ int rc;
+ VBVAREPORTINPUTMAPPING *p;
+ // Log(("%s: cOriginX=%d, cOriginY=%d, cWidth=%u, cHeight=%u\n", __PRETTY_FUNCTION__, (int)cOriginX, (int)cOriginX,
+ // (unsigned)cWidth, (unsigned)cHeight));
+
+ /* Allocate the IO buffer. */
+ p = (VBVAREPORTINPUTMAPPING *)VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAREPORTINPUTMAPPING), HGSMI_CH_VBVA,
+ VBVA_REPORT_INPUT_MAPPING);
+ if (p)
+ {
+ /* Prepare data to be sent to the host. */
+ p->x = cOriginX;
+ p->y = cOriginY;
+ p->cx = cWidth;
+ p->cy = cHeight;
+ rc = VBoxHGSMIBufferSubmit(pCtx, p);
+ /* Free the IO buffer. */
+ VBoxHGSMIBufferFree(pCtx, p);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ // LogFunc(("rc = %d\n", rc));
+ return rc;
+}
+
+
+/**
+ * Get most recent video mode hints.
+ * @param pCtx the context containing the heap to use
+ * @param cScreens the number of screens to query hints for, starting at 0.
+ * @param paHints array of VBVAMODEHINT structures for receiving the hints.
+ * @returns iprt status code
+ * @returns VERR_NO_MEMORY HGSMI heap allocation failed.
+ * @returns VERR_NOT_SUPPORTED Host does not support this command.
+ */
+DECLHIDDEN(int) VBoxHGSMIGetModeHints(PHGSMIGUESTCOMMANDCONTEXT pCtx,
+ unsigned cScreens, VBVAMODEHINT *paHints)
+{
+ int rc;
+ VBVAQUERYMODEHINTS RT_UNTRUSTED_VOLATILE_HOST *pQuery;
+
+ AssertPtrReturn(paHints, VERR_INVALID_POINTER);
+ pQuery = (VBVAQUERYMODEHINTS RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pCtx,
+ sizeof(VBVAQUERYMODEHINTS)
+ + cScreens * sizeof(VBVAMODEHINT),
+ HGSMI_CH_VBVA, VBVA_QUERY_MODE_HINTS);
+ if (pQuery != NULL)
+ {
+ pQuery->cHintsQueried = cScreens;
+ pQuery->cbHintStructureGuest = sizeof(VBVAMODEHINT);
+ pQuery->rc = VERR_NOT_SUPPORTED;
+
+ VBoxHGSMIBufferSubmit(pCtx, pQuery);
+ rc = pQuery->rc;
+ if (RT_SUCCESS(rc))
+ memcpy(paHints, (void *)(pQuery + 1), cScreens * sizeof(VBVAMODEHINT));
+
+ VBoxHGSMIBufferFree(pCtx, pQuery);
+ }
+ else
+ {
+ // LogFunc(("HGSMIHeapAlloc failed\n"));
+ rc = VERR_NO_MEMORY;
+ }
+ return rc;
+}
+
+
+/**
+ * Query the supported flags in VBVAINFOSCREEN::u16Flags.
+ *
+ * @returns The mask of VBVA_SCREEN_F_* flags or 0 if host does not support the request.
+ * @param pCtx the context containing the heap to use
+ */
+DECLHIDDEN(uint16_t) VBoxHGSMIGetScreenFlags(PHGSMIGUESTCOMMANDCONTEXT pCtx)
+{
+ uint32_t u32Flags = 0;
+ int rc = VBoxQueryConfHGSMI(pCtx, VBOX_VBVA_CONF32_SCREEN_FLAGS, &u32Flags);
+ // LogFunc(("u32Flags = 0x%x rc %Rrc\n", u32Flags, rc));
+ if (RT_FAILURE(rc) || u32Flags > UINT16_MAX)
+ u32Flags = 0;
+ return (uint16_t)u32Flags;
+}
diff --git a/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp b/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp
new file mode 100644
index 00000000..9bb0269f
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp
@@ -0,0 +1,378 @@
+/* $Id: VBVABase.cpp $ */
+/** @file
+ * VirtualBox Video driver, common code - VBVA initialisation and helper
+ * functions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <VBoxVideoGuest.h>
+#include <VBoxVideoIPRT.h>
+#include <HGSMIChannels.h>
+
+/*
+ * There is a hardware ring buffer in the graphics device video RAM, formerly
+ * in the VBox VMMDev PCI memory space.
+ * All graphics commands go there serialized by VBoxVBVABufferBeginUpdate.
+ * and vboxHwBufferEndUpdate.
+ *
+ * off32Free is writing position. off32Data is reading position.
+ * off32Free == off32Data means buffer is empty.
+ * There must be always gap between off32Data and off32Free when data
+ * are in the buffer.
+ * Guest only changes off32Free, host changes off32Data.
+ */
+
+/* Forward declarations of internal functions. */
+static void vboxHwBufferFlush(PHGSMIGUESTCOMMANDCONTEXT pCtx);
+static void vboxHwBufferPlaceDataAt(PVBVABUFFERCONTEXT pCtx, const void *p,
+ uint32_t cb, uint32_t offset);
+static bool vboxHwBufferWrite(PVBVABUFFERCONTEXT pCtx,
+ PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx,
+ const void *p, uint32_t cb);
+
+
+static bool vboxVBVAInformHost(PVBVABUFFERCONTEXT pCtx, PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx, int32_t cScreen, bool fEnable)
+{
+ bool fRc = false;
+
+#if 0 /* All callers check this */
+ if (ppdev->bHGSMISupported)
+#endif
+ {
+ VBVAENABLE_EX RT_UNTRUSTED_VOLATILE_HOST *pEnable =
+ (VBVAENABLE_EX RT_UNTRUSTED_VOLATILE_HOST *)VBoxHGSMIBufferAlloc(pHGSMICtx, sizeof(VBVAENABLE_EX),
+ HGSMI_CH_VBVA, VBVA_ENABLE);
+ if (pEnable != NULL)
+ {
+ pEnable->Base.u32Flags = fEnable ? VBVA_F_ENABLE : VBVA_F_DISABLE;
+ pEnable->Base.u32Offset = pCtx->offVRAMBuffer;
+ pEnable->Base.i32Result = VERR_NOT_SUPPORTED;
+ if (cScreen >= 0)
+ {
+ pEnable->Base.u32Flags |= VBVA_F_EXTENDED | VBVA_F_ABSOFFSET;
+ pEnable->u32ScreenId = cScreen;
+ }
+
+ VBoxHGSMIBufferSubmit(pHGSMICtx, pEnable);
+
+ if (fEnable)
+ fRc = RT_SUCCESS(pEnable->Base.i32Result);
+ else
+ fRc = true;
+
+ VBoxHGSMIBufferFree(pHGSMICtx, pEnable);
+ }
+ else
+ {
+ // LogFunc(("HGSMIHeapAlloc failed\n"));
+ }
+ }
+
+ return fRc;
+}
+
+/*
+ * Public hardware buffer methods.
+ */
+DECLHIDDEN(bool) VBoxVBVAEnable(PVBVABUFFERCONTEXT pCtx,
+ PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx,
+ VBVABUFFER *pVBVA, int32_t cScreen)
+{
+ bool fRc = false;
+
+ // LogFlowFunc(("pVBVA %p\n", pVBVA));
+
+#if 0 /* All callers check this */
+ if (ppdev->bHGSMISupported)
+#endif
+ {
+ // LogFunc(("pVBVA %p vbva off 0x%x\n", pVBVA, pCtx->offVRAMBuffer));
+
+ pVBVA->hostFlags.u32HostEvents = 0;
+ pVBVA->hostFlags.u32SupportedOrders = 0;
+ pVBVA->off32Data = 0;
+ pVBVA->off32Free = 0;
+ memset(pVBVA->aRecords, 0, sizeof (pVBVA->aRecords));
+ pVBVA->indexRecordFirst = 0;
+ pVBVA->indexRecordFree = 0;
+ pVBVA->cbPartialWriteThreshold = 256;
+ pVBVA->cbData = pCtx->cbBuffer - sizeof (VBVABUFFER) + sizeof (pVBVA->au8Data);
+
+ pCtx->fHwBufferOverflow = false;
+ pCtx->pRecord = NULL;
+ pCtx->pVBVA = pVBVA;
+
+ fRc = vboxVBVAInformHost(pCtx, pHGSMICtx, cScreen, true);
+ }
+
+ if (!fRc)
+ {
+ VBoxVBVADisable(pCtx, pHGSMICtx, cScreen);
+ }
+
+ return fRc;
+}
+
+DECLHIDDEN(void) VBoxVBVADisable(PVBVABUFFERCONTEXT pCtx,
+ PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx,
+ int32_t cScreen)
+{
+ // LogFlowFunc(("\n"));
+
+ pCtx->fHwBufferOverflow = false;
+ pCtx->pRecord = NULL;
+ pCtx->pVBVA = NULL;
+
+ vboxVBVAInformHost(pCtx, pHGSMICtx, cScreen, false);
+}
+
+DECLHIDDEN(bool) VBoxVBVABufferBeginUpdate(PVBVABUFFERCONTEXT pCtx,
+ PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx)
+{
+ bool fRc = false;
+
+ // LogFunc(("flags = 0x%08X\n", pCtx->pVBVA? pCtx->pVBVA->u32HostEvents: -1));
+
+ if ( pCtx->pVBVA
+ && (pCtx->pVBVA->hostFlags.u32HostEvents & VBVA_F_MODE_ENABLED))
+ {
+ uint32_t indexRecordNext;
+
+ Assert(!pCtx->fHwBufferOverflow);
+ Assert(pCtx->pRecord == NULL);
+
+ indexRecordNext = (pCtx->pVBVA->indexRecordFree + 1) % VBVA_MAX_RECORDS;
+
+ if (indexRecordNext == pCtx->pVBVA->indexRecordFirst)
+ {
+ /* All slots in the records queue are used. */
+ vboxHwBufferFlush (pHGSMICtx);
+ }
+
+ if (indexRecordNext == pCtx->pVBVA->indexRecordFirst)
+ {
+ /* Even after flush there is no place. Fail the request. */
+ // LogFunc(("no space in the queue of records!!! first %d, last %d\n",
+ // pCtx->pVBVA->indexRecordFirst, pCtx->pVBVA->indexRecordFree));
+ }
+ else
+ {
+ /* Initialize the record. */
+ VBVARECORD *pRecord = &pCtx->pVBVA->aRecords[pCtx->pVBVA->indexRecordFree];
+
+ pRecord->cbRecord = VBVA_F_RECORD_PARTIAL;
+
+ pCtx->pVBVA->indexRecordFree = indexRecordNext;
+
+ // LogFunc(("indexRecordNext = %d\n", indexRecordNext));
+
+ /* Remember which record we are using. */
+ pCtx->pRecord = pRecord;
+
+ fRc = true;
+ }
+ }
+
+ return fRc;
+}
+
+DECLHIDDEN(void) VBoxVBVABufferEndUpdate(PVBVABUFFERCONTEXT pCtx)
+{
+ VBVARECORD *pRecord;
+
+ // LogFunc(("\n"));
+
+ Assert(pCtx->pVBVA);
+
+ pRecord = pCtx->pRecord;
+ Assert(pRecord && (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL));
+
+ /* Mark the record completed. */
+ pRecord->cbRecord &= ~VBVA_F_RECORD_PARTIAL;
+
+ pCtx->fHwBufferOverflow = false;
+ pCtx->pRecord = NULL;
+}
+
+/*
+ * Private operations.
+ */
+static uint32_t vboxHwBufferAvail (const VBVABUFFER *pVBVA)
+{
+ int32_t i32Diff = pVBVA->off32Data - pVBVA->off32Free;
+
+ return i32Diff > 0? i32Diff: pVBVA->cbData + i32Diff;
+}
+
+static void vboxHwBufferFlush(PHGSMIGUESTCOMMANDCONTEXT pCtx)
+{
+ /* Issue the flush command. */
+ VBVAFLUSH RT_UNTRUSTED_VOLATILE_HOST *pFlush =
+ (VBVAFLUSH RT_UNTRUSTED_VOLATILE_HOST * )VBoxHGSMIBufferAlloc(pCtx, sizeof(VBVAFLUSH), HGSMI_CH_VBVA, VBVA_FLUSH);
+ if (pFlush != NULL)
+ {
+ pFlush->u32Reserved = 0;
+
+ VBoxHGSMIBufferSubmit(pCtx, pFlush);
+
+ VBoxHGSMIBufferFree(pCtx, pFlush);
+ }
+ else
+ {
+ // LogFunc(("HGSMIHeapAlloc failed\n"));
+ }
+}
+
+static void vboxHwBufferPlaceDataAt(PVBVABUFFERCONTEXT pCtx, const void *p,
+ uint32_t cb, uint32_t offset)
+{
+ VBVABUFFER *pVBVA = pCtx->pVBVA;
+ uint32_t u32BytesTillBoundary = pVBVA->cbData - offset;
+ uint8_t *dst = &pVBVA->au8Data[offset];
+ int32_t i32Diff = cb - u32BytesTillBoundary;
+
+ if (i32Diff <= 0)
+ {
+ /* Chunk will not cross buffer boundary. */
+ memcpy (dst, p, cb);
+ }
+ else
+ {
+ /* Chunk crosses buffer boundary. */
+ memcpy (dst, p, u32BytesTillBoundary);
+ memcpy (&pVBVA->au8Data[0], (uint8_t *)p + u32BytesTillBoundary, i32Diff);
+ }
+}
+
+static bool vboxHwBufferWrite(PVBVABUFFERCONTEXT pCtx,
+ PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx,
+ const void *p, uint32_t cb)
+{
+ VBVARECORD *pRecord;
+ uint32_t cbHwBufferAvail;
+
+ uint32_t cbWritten = 0;
+
+ VBVABUFFER *pVBVA = pCtx->pVBVA;
+ Assert(pVBVA);
+
+ if (!pVBVA || pCtx->fHwBufferOverflow)
+ {
+ return false;
+ }
+
+ Assert(pVBVA->indexRecordFirst != pVBVA->indexRecordFree);
+
+ pRecord = pCtx->pRecord;
+ Assert(pRecord && (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL));
+
+ // LogFunc(("%d\n", cb));
+
+ cbHwBufferAvail = vboxHwBufferAvail (pVBVA);
+
+ while (cb > 0)
+ {
+ uint32_t cbChunk = cb;
+
+ // LogFunc(("pVBVA->off32Free %d, pRecord->cbRecord 0x%08X, cbHwBufferAvail %d, cb %d, cbWritten %d\n",
+ // pVBVA->off32Free, pRecord->cbRecord, cbHwBufferAvail, cb, cbWritten));
+
+ if (cbChunk >= cbHwBufferAvail)
+ {
+ // LogFunc(("1) avail %d, chunk %d\n", cbHwBufferAvail, cbChunk));
+
+ vboxHwBufferFlush (pHGSMICtx);
+
+ cbHwBufferAvail = vboxHwBufferAvail (pVBVA);
+
+ if (cbChunk >= cbHwBufferAvail)
+ {
+ // LogFunc(("no place for %d bytes. Only %d bytes available after flush. Going to partial writes.\n",
+ // cb, cbHwBufferAvail));
+
+ if (cbHwBufferAvail <= pVBVA->cbPartialWriteThreshold)
+ {
+ // LogFunc(("Buffer overflow!!!\n"));
+ pCtx->fHwBufferOverflow = true;
+ Assert(false);
+ return false;
+ }
+
+ cbChunk = cbHwBufferAvail - pVBVA->cbPartialWriteThreshold;
+ }
+ }
+
+ Assert(cbChunk <= cb);
+ Assert(cbChunk <= vboxHwBufferAvail (pVBVA));
+
+ vboxHwBufferPlaceDataAt (pCtx, (uint8_t *)p + cbWritten, cbChunk, pVBVA->off32Free);
+
+ pVBVA->off32Free = (pVBVA->off32Free + cbChunk) % pVBVA->cbData;
+ pRecord->cbRecord += cbChunk;
+ cbHwBufferAvail -= cbChunk;
+
+ cb -= cbChunk;
+ cbWritten += cbChunk;
+ }
+
+ return true;
+}
+
+/*
+ * Public writer to the hardware buffer.
+ */
+DECLHIDDEN(bool) VBoxVBVAWrite(PVBVABUFFERCONTEXT pCtx,
+ PHGSMIGUESTCOMMANDCONTEXT pHGSMICtx,
+ const void *pv, uint32_t cb)
+{
+ return vboxHwBufferWrite (pCtx, pHGSMICtx, pv, cb);
+}
+
+DECLHIDDEN(bool) VBoxVBVAOrderSupported(PVBVABUFFERCONTEXT pCtx, unsigned code)
+{
+ VBVABUFFER *pVBVA = pCtx->pVBVA;
+
+ if (!pVBVA)
+ {
+ return false;
+ }
+
+ if (pVBVA->hostFlags.u32SupportedOrders & (1 << code))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+DECLHIDDEN(void) VBoxVBVASetupBufferContext(PVBVABUFFERCONTEXT pCtx,
+ uint32_t offVRAMBuffer,
+ uint32_t cbBuffer)
+{
+ pCtx->offVRAMBuffer = offVRAMBuffer;
+ pCtx->cbBuffer = cbBuffer;
+}
diff --git a/src/VBox/Additions/common/VBoxVideo/todo-create-library-from-these-files-for-windows b/src/VBox/Additions/common/VBoxVideo/todo-create-library-from-these-files-for-windows
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/common/VBoxVideo/todo-create-library-from-these-files-for-windows
diff --git a/src/VBox/Additions/common/pam/Makefile.kmk b/src/VBox/Additions/common/pam/Makefile.kmk
new file mode 100644
index 00000000..60c5962e
--- /dev/null
+++ b/src/VBox/Additions/common/pam/Makefile.kmk
@@ -0,0 +1,40 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for VBox PAM module for automated logons.
+#
+
+#
+# Copyright (C) 2011-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# The PAM module.
+DLLS += pam_vbox
+
+pam_vbox_TEMPLATE := VBoxGuestR3Dll
+pam_vbox_DEFS := LOG_TO_BACKDOOR VBOX_WITH_HGCM
+pam_vbox_DEFS += VBOX_WITH_GUEST_PROPS
+pam_vbox_SOURCES := pam_vbox.cpp
+pam_vbox_LIBS := pam
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/common/pam/pam_vbox.cpp b/src/VBox/Additions/common/pam/pam_vbox.cpp
new file mode 100644
index 00000000..6e1b6a7c
--- /dev/null
+++ b/src/VBox/Additions/common/pam/pam_vbox.cpp
@@ -0,0 +1,879 @@
+/* $Id: pam_vbox.cpp $ */
+/** @file
+ * pam_vbox - PAM module for auto logons.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define PAM_SM_AUTH
+#define PAM_SM_ACCOUNT
+#define PAM_SM_PASSWORD
+#define PAM_SM_SESSION
+
+#ifdef DEBUG
+# define PAM_DEBUG
+#endif
+
+#include <security/pam_appl.h>
+#ifdef RT_OS_LINUX
+# include <security/_pam_macros.h>
+#endif
+
+#include <pwd.h>
+#include <syslog.h>
+#include <stdlib.h>
+
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/env.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <VBox/log.h>
+#include <VBox/HostServices/GuestPropertySvc.h>
+
+#define VBOX_MODULE_NAME "pam_vbox"
+
+#define VBOX_PAM_FLAG_SILENT "PAM_SILENT"
+#define VBOX_PAM_FLAG_DISALLOW_NULL_AUTHTOK "PAM_DISALLOW_NULL_AUTHTOK"
+#define VBOX_PAM_FLAG_ESTABLISH_CRED "PAM_ESTABLISH_CRED"
+#define VBOX_PAM_FLAG_DELETE_CRED "PAM_DELETE_CRED"
+#define VBOX_PAM_FLAG_REINITIALIZE_CRED "PAM_REINITIALIZE_CRED"
+#define VBOX_PAM_FLAG_REFRESH_CRED "PAM_REFRESH_CRED"
+
+RT_C_DECLS_BEGIN
+DECLEXPORT(int) pam_sm_authenticate(pam_handle_t *hPAM, int iFlags, int argc, const char **argv);
+DECLEXPORT(int) pam_sm_setcred(pam_handle_t *hPAM, int iFlags, int argc, const char **argv);
+DECLEXPORT(int) pam_sm_acct_mgmt(pam_handle_t *hPAM, int iFlags, int argc, const char **argv);
+DECLEXPORT(int) pam_sm_open_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv);
+DECLEXPORT(int) pam_sm_close_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv);
+DECLEXPORT(int) pam_sm_chauthtok(pam_handle_t *hPAM, int iFlags, int argc, const char **argv);
+RT_C_DECLS_END
+
+/** For debugging. */
+#ifdef DEBUG
+static pam_handle_t *g_pam_handle;
+static int g_verbosity = 99;
+#else
+static int g_verbosity = 0;
+#endif
+
+/**
+ * User-provided thread data for the credentials waiting thread.
+ */
+typedef struct PAMVBOXTHREAD
+{
+ /** The PAM handle. */
+ pam_handle_t *hPAM;
+ /** The timeout (in ms) to wait for credentials. */
+ uint32_t uTimeoutMS;
+ /** The overall result of the thread operation. */
+ int rc;
+} PAMVBOXTHREAD, *PPAMVBOXTHREAD;
+
+/**
+ * Write to system log.
+ *
+ * @param pszBuf Buffer to write to the log (NULL-terminated)
+ */
+static void pam_vbox_writesyslog(char *pszBuf)
+{
+#ifdef RT_OS_LINUX
+ openlog("pam_vbox", LOG_PID, LOG_AUTHPRIV);
+ syslog(LOG_ERR, "%s", pszBuf);
+ closelog();
+#elif defined(RT_OS_SOLARIS)
+ syslog(LOG_ERR, "pam_vbox: %s\n", pszBuf);
+#endif
+}
+
+
+/**
+ * Displays an error message.
+ *
+ * @param hPAM PAM handle.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+static void pam_vbox_error(pam_handle_t *hPAM, const char *pszFormat, ...)
+{
+ RT_NOREF1(hPAM);
+ va_list va;
+ char *buf;
+ va_start(va, pszFormat);
+ if (RT_SUCCESS(RTStrAPrintfV(&buf, pszFormat, va)))
+ {
+ LogRel(("%s: Error: %s", VBOX_MODULE_NAME, buf));
+ pam_vbox_writesyslog(buf);
+ RTStrFree(buf);
+ }
+ va_end(va);
+}
+
+
+/**
+ * Displays a debug message.
+ *
+ * @param hPAM PAM handle.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+static void pam_vbox_log(pam_handle_t *hPAM, const char *pszFormat, ...)
+{
+ RT_NOREF1(hPAM);
+ if (g_verbosity)
+ {
+ va_list va;
+ char *buf;
+ va_start(va, pszFormat);
+ if (RT_SUCCESS(RTStrAPrintfV(&buf, pszFormat, va)))
+ {
+ /* Only do normal logging in debug mode; could contain
+ * sensitive data! */
+ LogRel(("%s: %s", VBOX_MODULE_NAME, buf));
+ /* Log to syslog */
+ pam_vbox_writesyslog(buf);
+ RTStrFree(buf);
+ }
+ va_end(va);
+ }
+}
+
+
+/**
+ * Sets a message using PAM's conversation function.
+ *
+ * @return IPRT status code.
+ * @param hPAM PAM handle.
+ * @param iStyle Style of message (0 = Information, 1 = Prompt
+ * echo off, 2 = Prompt echo on, 3 = Error).
+ * @param pszText Message text to set.
+ */
+static int vbox_set_msg(pam_handle_t *hPAM, int iStyle, const char *pszText)
+{
+ AssertPtrReturn(hPAM, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszText, VERR_INVALID_POINTER);
+
+ if (!iStyle)
+ iStyle = PAM_TEXT_INFO;
+
+ int rc = VINF_SUCCESS;
+
+ pam_message msg;
+ msg.msg_style = iStyle;
+#ifdef RT_OS_SOLARIS
+ msg.msg = (char*)pszText;
+#else
+ msg.msg = pszText;
+#endif
+
+#ifdef RT_OS_SOLARIS
+ pam_conv *conv = NULL;
+ int pamrc = pam_get_item(hPAM, PAM_CONV, (void **)&conv);
+#else
+ const pam_conv *conv = NULL;
+ int pamrc = pam_get_item(hPAM, PAM_CONV, (const void **)&conv);
+#endif
+ if ( pamrc == PAM_SUCCESS
+ && conv)
+ {
+ pam_response *resp = NULL;
+#ifdef RT_OS_SOLARIS
+ pam_message *msg_p = &msg;
+#else
+ const pam_message *msg_p = &msg;
+#endif
+ pam_vbox_log(hPAM, "Showing message \"%s\" (type %d)", pszText, iStyle);
+
+ pamrc = conv->conv(1 /* One message only */, &msg_p, &resp, conv->appdata_ptr);
+ if (resp != NULL) /* If we use PAM_TEXT_INFO we never will get something back! */
+ {
+ if (resp->resp)
+ {
+ pam_vbox_log(hPAM, "Response to message \"%s\" was \"%s\"",
+ pszText, resp->resp);
+ /** @todo Save response! */
+ free(resp->resp);
+ }
+ free(resp);
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ return rc;
+}
+
+
+/**
+ * Initializes pam_vbox.
+ *
+ * @return IPRT status code.
+ * @param hPAM PAM handle.
+ */
+static int pam_vbox_init(pam_handle_t *hPAM)
+{
+#ifdef DEBUG
+ g_pam_handle = hPAM; /* hack for getting assertion text */
+#endif
+
+ /* Don't make assertions panic because the PAM stack +
+ * the current logon module won't work anymore (or just restart).
+ * This could result in not able to log into the system anymore. */
+ RTAssertSetMayPanic(false);
+
+ pam_vbox_log(hPAM, "pam_vbox: %sr%s, running on %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), RTBldCfgTargetArch());
+
+ int rc = RTR3InitDll(0);
+ if (RT_FAILURE(rc))
+ {
+ pam_vbox_error(hPAM, "pam_vbox_init: could not init runtime! rc=%Rrc. Aborting\n", rc);
+ return PAM_SUCCESS; /* Jump out as early as we can to not mess around. */
+ }
+
+ pam_vbox_log(hPAM, "pam_vbox_init: runtime initialized\n");
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3InitUser();
+ if (RT_FAILURE(rc))
+ {
+ switch(rc)
+ {
+ case VERR_ACCESS_DENIED:
+ pam_vbox_error(hPAM, "pam_vbox_init: access is denied to guest driver! Please make sure you run with sufficient rights. Aborting\n");
+ break;
+
+ case VERR_FILE_NOT_FOUND:
+ pam_vbox_error(hPAM, "pam_vbox_init: guest driver not found! Guest Additions installed? Aborting\n");
+ break;
+
+ default:
+ pam_vbox_error(hPAM, "pam_vbox_init: could not init VbglR3 library! rc=%Rrc. Aborting\n", rc);
+ break;
+ }
+ }
+ pam_vbox_log(hPAM, "pam_vbox_init: guest lib initialized\n");
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ char *rhost = NULL;
+ char *tty = NULL;
+ char *prompt = NULL;
+#ifdef RT_OS_SOLARIS
+ pam_get_item(hPAM, PAM_RHOST, (void**) &rhost);
+ pam_get_item(hPAM, PAM_TTY, (void**) &tty);
+ pam_get_item(hPAM, PAM_USER_PROMPT, (void**) &prompt);
+#else
+ pam_get_item(hPAM, PAM_RHOST, (const void**) &rhost);
+ pam_get_item(hPAM, PAM_TTY, (const void**) &tty);
+ pam_get_item(hPAM, PAM_USER_PROMPT, (const void**) &prompt);
+#endif
+ pam_vbox_log(hPAM, "pam_vbox_init: rhost=%s, tty=%s, prompt=%s\n",
+ rhost ? rhost : "<none>", tty ? tty : "<none>", prompt ? prompt : "<none>");
+ }
+
+ return rc;
+}
+
+
+/**
+ * Shuts down pam_vbox.
+ *
+ * @param hPAM PAM handle.
+ */
+static void pam_vbox_shutdown(pam_handle_t *hPAM)
+{
+ RT_NOREF1(hPAM);
+ VbglR3Term();
+}
+
+
+/**
+ * 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 hPAM PAM handle.
+ */
+static int pam_vbox_check_creds(pam_handle_t *hPAM)
+{
+ int rc = VbglR3CredentialsQueryAvailability();
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_NOT_FOUND)
+ pam_vbox_error(hPAM, "pam_vbox_check_creds: could not query for credentials! rc=%Rrc. Aborting\n", rc);
+#ifdef DEBUG
+ else
+ pam_vbox_log(hPAM, "pam_vbox_check_creds: no credentials available\n");
+#endif
+ }
+ else
+ {
+ char *pszUsername;
+ char *pszPassword;
+ char *pszDomain;
+
+ rc = VbglR3CredentialsRetrieve(&pszUsername, &pszPassword, &pszDomain);
+ if (RT_FAILURE(rc))
+ {
+ pam_vbox_error(hPAM, "pam_vbox_check_creds: could not retrieve credentials! rc=%Rrc. Aborting\n", rc);
+ }
+ else
+ {
+#ifdef DEBUG
+ pam_vbox_log(hPAM, "pam_vbox_check_creds: credentials retrieved: user=%s, password=%s, domain=%s\n",
+ pszUsername, pszPassword, pszDomain);
+#else
+ /* Don't log passwords in release mode! */
+ pam_vbox_log(hPAM, "pam_vbox_check_creds: credentials retrieved: user=%s, password=XXX, domain=%s\n",
+ pszUsername, pszDomain);
+#endif
+ /* Fill credentials into PAM. */
+ int pamrc = pam_set_item(hPAM, PAM_USER, pszUsername);
+ if (pamrc != PAM_SUCCESS)
+ {
+ pam_vbox_error(hPAM, "pam_vbox_check_creds: could not set user name! pamrc=%d, msg=%s. Aborting\n",
+ pamrc, pam_strerror(hPAM, pamrc));
+ }
+ else
+ {
+ pamrc = pam_set_item(hPAM, PAM_AUTHTOK, pszPassword);
+ if (pamrc != PAM_SUCCESS)
+ pam_vbox_error(hPAM, "pam_vbox_check_creds: could not set password! pamrc=%d, msg=%s. Aborting\n",
+ pamrc, pam_strerror(hPAM, pamrc));
+
+ }
+ /** @todo Add handling domains as well. */
+ VbglR3CredentialsDestroy(pszUsername, pszPassword, pszDomain,
+ 3 /* Three wipe passes */);
+ pam_vbox_log(hPAM, "pam_vbox_check_creds: returned with pamrc=%d, msg=%s\n",
+ pamrc, pam_strerror(hPAM, pamrc));
+ }
+ }
+
+#ifdef DEBUG
+ pam_vbox_log(hPAM, "pam_vbox_check_creds: returned with rc=%Rrc\n", rc);
+#endif
+ return rc;
+}
+
+/**
+ * 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).
+ */
+static int pam_vbox_read_prop(pam_handle_t *hPAM, uint32_t uClientID,
+ const char *pszKey, bool fReadOnly,
+ char *pszValue, size_t cbValue)
+{
+ AssertPtrReturn(hPAM, VERR_INVALID_POINTER);
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+
+ int rc;
+
+ uint64_t u64Timestamp = 0;
+ char *pszValTemp;
+ 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++)
+ {
+ void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvTmpBuf)
+ {
+ pvBuf = pvTmpBuf;
+ rc = VbglR3GuestPropRead(uClientID, pszKey, pvBuf, cbBuf,
+ &pszValTemp, &u64Timestamp, &pszFlags,
+ &cbBuf);
+ if (rc == VERR_BUFFER_OVERFLOW && i < 10)
+ {
+ /* Buffer too small, try it with a bigger one next time. */
+ cbBuf += _1K;
+ continue; /* Try next round. */
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break; /* Everything except VERR_BUFFER_OVERFLOW makes us bail out ... */
+ }
+
+ 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! */
+ pam_vbox_error(hPAM, "pam_vbox_read_prop: key \"%s\" should be read-only on guest but it is not\n", pszKey);
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else /* No flags, no access! */
+ {
+ pam_vbox_error(hPAM, "pam_vbox_read_prop: key \"%s\" contains no/wrong flags (%s)\n", pszKey, pszFlags);
+ 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))
+ {
+ pam_vbox_error(hPAM, "pam_vbox_read_prop: could not store value of key \"%s\"\n", pszKey);
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_SUCCESS(rc))
+ pam_vbox_log(hPAM, "pam_vbox_read_prop: read key \"%s\"=\"%s\"\n", pszKey, pszValue);
+ }
+ }
+
+ RTMemFree(pvBuf);
+ pam_vbox_log(hPAM, "pam_vbox_read_prop: read key \"%s\" with rc=%Rrc\n",
+ pszKey, rc);
+ return rc;
+}
+
+
+/**
+ * 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 pam_vbox_wait_prop(pam_handle_t *hPAM, uint32_t uClientID,
+ const char *pszKey, uint32_t uTimeoutMS)
+{
+ AssertPtrReturn(hPAM, VERR_INVALID_POINTER);
+ 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 = GUEST_PROP_MAX_NAME_LEN + GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + _1K;
+
+ for (int i = 0; ; i++)
+ {
+ void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvTmpBuf)
+ {
+ char *pszName = NULL;
+ char *pszValue = NULL;
+ uint64_t u64TimestampOut = 0;
+ char *pszFlags = NULL;
+
+ pvBuf = pvTmpBuf;
+ rc = VbglR3GuestPropWait(uClientID, pszKey, pvBuf, cbBuf,
+ 0 /* Last timestamp; just wait for next event */, uTimeoutMS,
+ &pszName, &pszValue, &u64TimestampOut,
+ &pszFlags, &cbBuf, NULL /* pfWasDeleted */);
+ if (rc == VERR_BUFFER_OVERFLOW && i < 10)
+ {
+ cbBuf += _1K; /* Buffer too small, try it with a bigger one more time. */
+ continue;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ RTMemFree(pvBuf);
+ return rc;
+}
+
+/**
+ * Thread function waiting for credentials to arrive.
+ *
+ * @return IPRT status code.
+ * @param hThreadSelf Thread handle.
+ * @param pvUser Pointer to a PAMVBOXTHREAD structure providing
+ * required data used / set by the thread.
+ */
+static DECLCALLBACK(int) pam_vbox_wait_thread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF1(hThreadSelf);
+ PPAMVBOXTHREAD pUserData = (PPAMVBOXTHREAD)pvUser;
+ AssertPtr(pUserData);
+
+ int rc = VINF_SUCCESS;
+ /* Get current time stamp to later calculate rest of timeout left. */
+ uint64_t u64StartMS = RTTimeMilliTS();
+
+ uint32_t uClientID = 0;
+ rc = VbglR3GuestPropConnect(&uClientID);
+ if (RT_FAILURE(rc))
+ {
+ pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: Unable to connect to guest property service, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: clientID=%u\n", uClientID);
+
+ for (;;)
+ {
+
+ if (uClientID)
+ {
+ rc = pam_vbox_wait_prop(pUserData->hPAM, uClientID,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitAbort",
+ 500 /* Wait 500ms, same as VBoxGINA/VBoxCredProv. */);
+ switch (rc)
+ {
+ case VINF_SUCCESS:
+ /* Somebody (guest/host) wants to abort waiting for credentials. */
+ break;
+
+ case VERR_INTERRUPTED:
+ pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: The abort notification request timed out or was interrupted\n");
+ break;
+
+ case VERR_TIMEOUT:
+ /* We did not receive an abort message within time. */
+ break;
+
+ case VERR_TOO_MUCH_DATA:
+ pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: Temporarily unable to get abort notification\n");
+ break;
+
+ default:
+ pam_vbox_error(pUserData->hPAM, "pam_vbox_wait_thread: The abort notification request failed with rc=%Rrc\n", rc);
+ break;
+ }
+
+ if (RT_SUCCESS(rc)) /* Abort waiting. */
+ {
+ pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: Got notification to abort waiting\n");
+ rc = VERR_CANCELLED;
+ break;
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_TIMEOUT)
+ {
+ rc = pam_vbox_check_creds(pUserData->hPAM);
+ if (RT_SUCCESS(rc))
+ {
+ /* Credentials retrieved. */
+ break; /* Thread no longer is required, bail out. */
+ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ /* No credentials found, but try next round (if there's
+ * time left for) ... */
+ RTThreadSleep(500); /* Wait 500 ms. */
+ }
+ else
+ break; /* Something bad happend ... */
+ }
+ else
+ break;
+
+ /* Calculate timeout value left after process has been started. */
+ uint64_t u64Elapsed = RTTimeMilliTS() - u64StartMS;
+ /* Is it time to bail out? */
+ if (pUserData->uTimeoutMS < u64Elapsed)
+ {
+ pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: Waiting thread has reached timeout (%dms), exiting ...\n",
+ pUserData->uTimeoutMS);
+ rc = VERR_TIMEOUT;
+ break;
+ }
+ }
+ }
+ VbglR3GuestPropDisconnect(uClientID);
+
+ /* Save result. */
+ pUserData->rc = rc; /** @todo Use ASMAtomicXXX? */
+
+ int rc2 = RTThreadUserSignal(RTThreadSelf());
+ AssertRC(rc2);
+
+ pam_vbox_log(pUserData->hPAM, "pam_vbox_wait_thread: Waiting thread returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+/**
+ * Waits for credentials to arrive by creating and waiting for a thread.
+ *
+ * @return IPRT status code.
+ * @param hPAM PAM handle.
+ * @param uClientID Guest property service client ID.
+ * @param uTimeoutMS Timeout (in ms) to wait for the change. Specify
+ * RT_INDEFINITE_WAIT to wait indefinitly.
+ */
+static int pam_vbox_wait_for_creds(pam_handle_t *hPAM, uint32_t uClientID, uint32_t uTimeoutMS)
+{
+ RT_NOREF1(uClientID);
+ PAMVBOXTHREAD threadData;
+ threadData.hPAM = hPAM;
+ threadData.uTimeoutMS = uTimeoutMS;
+
+ RTTHREAD threadWait;
+ int rc = RTThreadCreate(&threadWait, pam_vbox_wait_thread,
+ (void *)&threadData, 0,
+ RTTHREADTYPE_DEFAULT, 0 /* Flags */, "pam_vbox");
+ if (RT_SUCCESS(rc))
+ {
+ pam_vbox_log(hPAM, "pam_vbox_wait_for_creds: Waiting for credentials (%dms) ...\n", uTimeoutMS);
+ /* Wait for thread to initialize. */
+ /** @todo We can do something else here in the meantime later. */
+ rc = RTThreadUserWait(threadWait, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ rc = threadData.rc; /* Get back thread result to take further actions. */
+ }
+ else
+ pam_vbox_error(hPAM, "pam_vbox_wait_for_creds: Creating thread failed with rc=%Rrc\n", rc);
+
+ pam_vbox_log(hPAM, "pam_vbox_wait_for_creds: Waiting for credentials returned with rc=%Rrc\n", rc);
+ return rc;
+}
+
+DECLEXPORT(int) pam_sm_authenticate(pam_handle_t *hPAM, int iFlags, int argc, const char **argv)
+{
+ RT_NOREF1(iFlags);
+
+ /* Parse arguments. */
+ for (int i = 0; i < argc; i++)
+ {
+ if (!RTStrICmp(argv[i], "debug"))
+ g_verbosity = 1;
+ else
+ pam_vbox_error(hPAM, "pam_vbox_authenticate: unknown command line argument \"%s\"\n", argv[i]);
+ }
+ pam_vbox_log(hPAM, "pam_vbox_authenticate called\n");
+
+ int rc = pam_vbox_init(hPAM);
+ if (RT_FAILURE(rc))
+ return PAM_SUCCESS; /* Jump out as early as we can to not mess around. */
+
+ bool fFallback = true;
+
+ uint32_t uClientId;
+ rc = VbglR3GuestPropConnect(&uClientId);
+ if (RT_SUCCESS(rc))
+ {
+ char szVal[256];
+ rc = pam_vbox_read_prop(hPAM, uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWait",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal));
+ if (RT_SUCCESS(rc))
+ {
+ /* All calls which are checked against rc2 are not critical, e.g. it does
+ * not matter if they succeed or not. */
+ uint32_t uTimeoutMS = RT_INDEFINITE_WAIT; /* Wait infinite by default. */
+ int rc2 = pam_vbox_read_prop(hPAM, uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitTimeout",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal));
+ if (RT_SUCCESS(rc2))
+ {
+ uTimeoutMS = RTStrToUInt32(szVal);
+ if (!uTimeoutMS)
+ {
+ pam_vbox_error(hPAM, "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. */
+ }
+
+ rc2 = pam_vbox_read_prop(hPAM, uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaiting",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal));
+ const char *pszWaitMsg = NULL;
+ if (RT_SUCCESS(rc2))
+ pszWaitMsg = szVal;
+
+ rc2 = vbox_set_msg(hPAM, 0 /* Info message */,
+ pszWaitMsg ? pszWaitMsg : "Waiting for credentials ...");
+ if (RT_FAILURE(rc2)) /* Not critical. */
+ pam_vbox_error(hPAM, "pam_vbox_authenticate: error setting waiting information message, rc=%Rrc\n", rc2);
+
+ 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 ... */
+ rc = pam_vbox_check_creds(hPAM);
+ if (rc == VERR_NOT_FOUND)
+ {
+ rc = pam_vbox_wait_for_creds(hPAM, uClientId, uTimeoutMS);
+ if (rc == VERR_TIMEOUT)
+ {
+ pam_vbox_log(hPAM, "pam_vbox_authenticate: no credentials given within time\n");
+
+ rc2 = pam_vbox_read_prop(hPAM, uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaitTimeout",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal));
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = vbox_set_msg(hPAM, 0 /* Info message */, szVal);
+ AssertRC(rc2);
+ }
+ }
+ else if (rc == VERR_CANCELLED)
+ {
+ pam_vbox_log(hPAM, "pam_vbox_authenticate: waiting aborted\n");
+
+ rc2 = pam_vbox_read_prop(hPAM, uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaitAbort",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal));
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = vbox_set_msg(hPAM, 0 /* Info message */, szVal);
+ AssertRC(rc2);
+ }
+ }
+ }
+
+ /* If we got here we don't need the fallback, so just deactivate it. */
+ fFallback = false;
+ }
+ }
+
+ VbglR3GuestPropDisconnect(uClientId);
+ }
+
+ if (fFallback)
+ {
+ pam_vbox_log(hPAM, "pam_vbox_authenticate: falling back to old method\n");
+
+ /* If anything went wrong in the code above we just do a credentials
+ * check like it was before: Try retrieving the stuff and authenticating. */
+ int rc2 = pam_vbox_check_creds(hPAM);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ pam_vbox_shutdown(hPAM);
+
+ pam_vbox_log(hPAM, "pam_vbox_authenticate: overall result rc=%Rrc\n", rc);
+
+ /* Never report an error here because if no credentials from the host are available or something
+ * went wrong we then let do the authentication by the next module in the stack. */
+
+ /* We report success here because this is all we can do right now -- we passed the credentials
+ * to the next PAM module in the block above which then might do a shadow (like pam_unix/pam_unix2)
+ * password verification to "really" authenticate the user. */
+ return PAM_SUCCESS;
+}
+
+
+DECLEXPORT(int) pam_sm_setcred(pam_handle_t *hPAM, int iFlags, int argc, const char **argv)
+{
+ pam_vbox_log(hPAM, "pam_vbox_setcred called, iFlags=0x%x\n", iFlags);
+ for (int i = 0; i < argc; i++)
+ pam_vbox_log(hPAM, "pam_vbox_setcred: argv[%d] = %s\n", i, argv[i]);
+ return PAM_SUCCESS;
+}
+
+
+DECLEXPORT(int) pam_sm_acct_mgmt(pam_handle_t *hPAM, int iFlags, int argc, const char **argv)
+{
+ RT_NOREF3(iFlags, argc, argv);
+ pam_vbox_log(hPAM, "pam_vbox_acct_mgmt called\n");
+ return PAM_SUCCESS;
+}
+
+
+DECLEXPORT(int) pam_sm_open_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv)
+{
+ RT_NOREF3(iFlags, argc, argv);
+ pam_vbox_log(hPAM, "pam_vbox_open_session called\n");
+ RTPrintf("This session was provided by VirtualBox Guest Additions. Have a lot of fun!\n");
+ return PAM_SUCCESS;
+}
+
+
+DECLEXPORT(int) pam_sm_close_session(pam_handle_t *hPAM, int iFlags, int argc, const char **argv)
+{
+ RT_NOREF3(iFlags, argc, argv);
+ pam_vbox_log(hPAM, "pam_vbox_close_session called\n");
+ return PAM_SUCCESS;
+}
+
+
+DECLEXPORT(int) pam_sm_chauthtok(pam_handle_t *hPAM, int iFlags, int argc, const char **argv)
+{
+ RT_NOREF3(iFlags, argc, argv);
+ pam_vbox_log(hPAM, "pam_vbox_sm_chauthtok called\n");
+ return PAM_SUCCESS;
+}
+
+
+#ifdef DEBUG
+RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ pam_vbox_log(g_pam_handle,
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ pszExpr, pszFile, uLine, pszFunction);
+ RTAssertMsg1(pszExpr, uLine, pszFile, pszFunction);
+}
+#endif
+
diff --git a/src/VBox/Additions/common/testcase/Makefile.kmk b/src/VBox/Additions/common/testcase/Makefile.kmk
new file mode 100644
index 00000000..f18504b2
--- /dev/null
+++ b/src/VBox/Additions/common/testcase/Makefile.kmk
@@ -0,0 +1,53 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Cross Platform Guest Addition test cases.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Target lists.
+#
+PROGRAMS += tstPageFusion
+
+#
+# tstPageFusion
+#
+tstPageFusion_TEMPLATE = VBoxGuestR3Exe
+tstPageFusion_DEFS.win += _WIN32_WINNT=0x0501
+tstPageFusion_SOURCES = \
+ tstPageFusion.cpp
+
+#
+# Install the LED test script to bin.
+#
+INSTALLS += lights-test-script
+lights-test-script_INST = $(INST_BIN)
+lights-test-script_MODE = a+rx,u+w
+lights-test-script_SOURCES = led-lights.sh
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/common/testcase/led-lights.sh b/src/VBox/Additions/common/testcase/led-lights.sh
new file mode 100755
index 00000000..6d7e6504
--- /dev/null
+++ b/src/VBox/Additions/common/testcase/led-lights.sh
@@ -0,0 +1,276 @@
+#!/bin/bash
+# $Id: led-lights.sh $
+## @file
+# VirtualBox guest LED demonstration test
+#
+
+#
+# Copyright (C) 2021-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# Usage:
+# led-lights.sh [-a | -r]
+#
+
+#
+# Test script to twiddle the console LEDs of a VirtualBox VM.
+#
+# This is not an automated test, just something for humans to look
+# at, to convince themselves that the VM console LEDs are working.
+# By default it cycles through the LED types in a specific order.
+#
+# '-a' twiddles all possible LEDs at the same time
+# '-r' reverses the default order
+#
+# For instance, run the script in 2 VMs at once, one with '-r'.
+#
+# LEDs are not expected to track perfectly, as other OS activities
+# will light them (and buffer cache effects can delay things). Just
+# make sure that all the tested ones (hard disk, optical, USB storage,
+# floppy, shared folders, net) are working. Expected activity:
+#
+# - Disk & optical devices show solid 'read'
+# - Virtual USB disk & optical devices show 'write' on the USB LED
+# - Floppy devices and shared folders alternate 'read/write'
+# - Net blinks 'write'
+#
+# Pre-VM setup:
+#
+# Download or locate a bootable Linux ISO able to be used 'live'.
+# Make a tarball of this script + extra junk:
+#
+# $ dd if=/dev/zero of=junk bs=100k count=1
+# $ tar cf floppy.img led-lights.sh junk
+#
+# NOTE: floppy.img must be >= 20KiB or you will get I/O errors!
+#
+# VM setup:
+#
+# New VM; type: Linux (subtype to match ISO); create default HD.
+# VM Settings:
+# System > raise base memory to 4GiB
+# Storage > insert 'Live bootable' Linux ISO image;
+# turn on 'Live CD/DVD'
+# Storage > add floppy controller (i82078); insert floppy.img
+# Storage > add USB controller; insert USB HD & USB CD
+# System > move Optical before Floppy in boot order
+# Shared Folders > set up one or more Shared Folders if desired
+# (they should be permanent, auto-mount,
+# writable, with at least 10MB free space)
+#
+# Boot the VM. Open a shell, become root, optionally install
+# VirtualBox Guest Utilities to access Shared Folders, then extract
+# and run this script:
+#
+# $ sudo bash
+# # yum install virtualbox-guest-utils # for Shared Folders
+# # tar xf /dev/fd0 led-lights.sh
+# # ./led-lights.sh [-a | -r]
+
+if [ ! -w / ]; then
+ echo "Must be run as root!" 1>&2
+ exit 1
+fi
+
+all_all=false
+reverse=false
+
+if [ "x$1" = "x-a" ]; then
+ all_all=true
+fi
+
+if [ "x$1" = "x-r" ]; then
+ reverse=true
+fi
+
+# Copy binaries to RAM tmpfs to avoid CD I/O after cache purges
+MYTMP=/tmp/led-lights.$$
+mkdir $MYTMP
+for bin in $(which dd sleep sync); do
+ case $bin in
+ /*)
+ cp -p $bin $MYTMP
+ ;;
+ esac
+done
+export MYTMP PATH=$MYTMP:$PATH
+
+set -o monitor
+
+# Make device reads keep hitting the 'hardware'
+# even if the whole medium fits in cache...
+drop_cache()
+{
+ echo 1 >/proc/sys/vm/drop_caches
+}
+
+activate()
+{
+ kill -CONT -$1 2>/dev/null
+}
+
+suppress()
+{
+ $all_all || kill -STOP -$1 2>/dev/null
+}
+
+declare -a pids pidnames
+cpids=0
+
+twiddle()
+{
+ let ++cpids
+ new_pid=$!
+ pidname=$1
+ pids[$cpids]=$new_pid
+ pidnames[$cpids]=$pidname
+ suppress $new_pid
+}
+
+hide_stderr()
+{
+ exec 3>&2 2>/dev/null
+}
+
+show_stderr()
+{
+ exec 2>&3 3>&-
+}
+
+bail()
+{
+ hide_stderr
+ for pid in ${pids[*]}; do
+ activate $pid
+ kill -TERM -$pid
+ kill -TERM $pid
+ done 2>/dev/null
+ rm -rf $MYTMP
+ kill $$
+}
+
+trap "bail" INT
+
+drives()
+{
+ echo $(
+ awk '$NF ~/^('$1')$/ { print $NF }' /proc/partitions
+ )
+}
+
+# Prevent job control 'stopped' msgs during twiddler startup
+hide_stderr
+
+# Hard disks
+for hdd in $(drives '[sh]d.'); do
+ while :; do
+ dd if=/dev/$hdd of=/dev/null
+ drop_cache
+ done 2>/dev/null &
+ twiddle disk:$hdd
+done
+
+# Optical drives
+for odd in $(drives 'sr.|scd.'); do
+ while :; do
+ dd if=/dev/$odd of=/dev/null
+ drop_cache
+ done 2>/dev/null &
+ twiddle optical:$odd
+done
+
+# Floppy drives
+for fdd in $(drives 'fd.'); do
+ while :; do
+ dd if=/dev/$fdd of=$MYTMP/$fdd bs=1k count=20
+ dd of=/dev/$fdd if=$MYTMP/$fdd bs=1k count=20
+ done 2>/dev/null &
+ twiddle floppy:$fdd
+done
+
+# Shared folders
+if ! lsmod | grep -q vboxsf; then
+ echo
+ echo "Note: to test the Shared Folders LED, install this"
+ echo "distro's VirtualBox Guest Utilities package, e.g.:"
+ echo
+ echo " # yum install virtualbox-guest-utils (Red Hat family)"
+ echo " # apt install virtualbox-guest-utils (Debian family)"
+ echo
+fi >&3 # original stderr
+for shf in $(mount -t vboxsf | awk '{ print $3 }'); do
+ while :; do
+ dd if=/dev/urandom of=$shf/tmp.led-lights.$$ bs=100k count=100
+ for rep in $(seq 1 10); do
+ drop_cache
+ dd of=/dev/zero if=$shf/tmp.led-lights.$$ bs=100k count=100
+ done
+ sync
+ rm -f $shf/tmp.led-lights.$$
+ done >/dev/null 2>&1 &
+ twiddle sharedfs:$shf
+done
+
+# Network
+ping -i.2 1.2.3.4 >/dev/null &
+twiddle net
+
+# Untested LED: Graphics3D -- add some day?
+
+sleep 0.1
+show_stderr
+
+if $reverse; then
+ seq=$(seq $cpids -1 1)
+else
+ seq=$(seq 1 $cpids)
+fi
+
+show_intr()
+{
+ intr=$(stty -a | sed -n '/intr/ { s/.*intr *=* *//; s/[ ;].*//p }')
+ echo
+ echo "[ Hit $intr to stop ]"
+ echo
+}
+
+if $all_all; then
+ printf "%s ...\n" ${pidnames[*]}
+ show_intr
+ wait
+else
+ CEOL=$(tput el)
+ show_intr
+ while :; do
+ for pidx in $seq; do
+ pid=${pids[$pidx]}
+ pidname=${pidnames[$pidx]}
+ echo -e -n "$pidname$CEOL\r"
+ shift
+ activate $pid
+ sleep 2
+ suppress $pid
+ sync
+ sleep .5
+ done
+ done
+fi
diff --git a/src/VBox/Additions/common/testcase/tstPageFusion.cpp b/src/VBox/Additions/common/testcase/tstPageFusion.cpp
new file mode 100644
index 00000000..264d887a
--- /dev/null
+++ b/src/VBox/Additions/common/testcase/tstPageFusion.cpp
@@ -0,0 +1,389 @@
+/* $Id: tstPageFusion.cpp $ */
+/** @file
+ * VBoxService - Guest page sharing testcase
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/messages.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/initterm.h>
+#include <VBox/VBoxGuestLib.h>
+#include <iprt/x86.h>
+#include <stdio.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+#ifdef RT_OS_WINDOWS
+#include <iprt/win/windows.h>
+#include <process.h> /* Needed for file version information. */
+#include <tlhelp32.h>
+#include <psapi.h>
+#include <winternl.h>
+
+#define SystemModuleInformation 11
+
+typedef struct _RTL_PROCESS_MODULE_INFORMATION
+{
+ ULONG Section;
+ PVOID MappedBase;
+ PVOID ImageBase;
+ ULONG ImageSize;
+ ULONG Flags;
+ USHORT LoadOrderIndex;
+ USHORT InitOrderIndex;
+ USHORT LoadCount;
+ USHORT OffsetToFileName;
+ CHAR FullPathName[256];
+} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;
+
+typedef struct _RTL_PROCESS_MODULES
+{
+ ULONG NumberOfModules;
+ RTL_PROCESS_MODULE_INFORMATION Modules[1];
+} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;
+
+typedef NTSTATUS (WINAPI *PFNZWQUERYSYSTEMINFORMATION)(ULONG, PVOID, ULONG, PULONG);
+static PFNZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
+static HMODULE hNtdll = 0;
+
+#define PAGE_STATE_INVALID 0
+#define PAGE_STATE_SHARED 1
+#define PAGE_STATE_READ_WRITE 2
+#define PAGE_STATE_READ_ONLY 3
+#define PAGE_STATE_NOT_PRESENT 4
+
+/* Page counters. */
+static unsigned cNotPresentPages = 0;
+static unsigned cWritablePages = 0;
+static unsigned cSharedPages = 0;
+static unsigned cPrivatePages = 0;
+
+/**
+ * Registers a new module with the VMM
+ * @param pModule Module ptr
+ */
+void VBoxServicePageSharingCheckModule(MODULEENTRY32 *pModule)
+{
+ DWORD dwModuleSize = pModule->modBaseSize;
+ BYTE *pBaseAddress = pModule->modBaseAddr;
+ bool fFirstLine = true;
+ unsigned uPageState, uLastPageState;
+ bool fLastWritable = false;
+ BYTE *pLastBaseAddress = pBaseAddress;
+
+ uPageState = uLastPageState = PAGE_STATE_INVALID;
+
+ printf("Check module %s base %p size %x\n", pModule->szModule, pBaseAddress, dwModuleSize);
+ do
+ {
+ bool fShared;
+ uint64_t uPageFlags;
+
+#ifdef RT_ARCH_X86
+ int rc = VbglR3PageIsShared((uint32_t)pLastBaseAddress, &fShared, &uPageFlags);
+#else
+ int rc = VbglR3PageIsShared((RTGCPTR)pLastBaseAddress, &fShared, &uPageFlags);
+#endif
+ if (RT_FAILURE(rc))
+ printf("VbglR3PageIsShared %p failed with %d\n", pLastBaseAddress, rc);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (uPageFlags & X86_PTE_P)
+ {
+ if (uPageFlags & X86_PTE_RW)
+ {
+ cWritablePages++;
+ uPageState = PAGE_STATE_READ_WRITE;
+ }
+ else
+ if (fShared)
+ {
+ cSharedPages++;
+ uPageState = PAGE_STATE_SHARED;
+ }
+ else
+ {
+ cPrivatePages++;
+ uPageState = PAGE_STATE_READ_ONLY;
+ }
+ }
+ else
+ {
+ cNotPresentPages++;
+ uPageState = PAGE_STATE_NOT_PRESENT;
+ }
+
+ if ( !fFirstLine
+ && uPageState != uLastPageState)
+ {
+ printf("0x%p\n", pLastBaseAddress + 0xfff);
+ }
+
+ if (uPageState != uLastPageState)
+ {
+ switch (uPageState)
+ {
+ case PAGE_STATE_READ_WRITE:
+ printf("%s RW 0x%p - ", pModule->szModule, pBaseAddress);
+ break;
+ case PAGE_STATE_SHARED:
+ printf("%s SHARED 0x%p - ", pModule->szModule, pBaseAddress);
+ break;
+ case PAGE_STATE_READ_ONLY:
+ printf("%s PRIV 0x%p - ", pModule->szModule, pBaseAddress);
+ break;
+ case PAGE_STATE_NOT_PRESENT:
+ printf("%s NP 0x%p - ", pModule->szModule, pBaseAddress);
+ break;
+ }
+
+ fFirstLine = false;
+ }
+ uLastPageState = uPageState;
+ }
+ else
+ if (!fFirstLine)
+ {
+ printf("0x%p\n", pLastBaseAddress + 0xfff);
+ fFirstLine = true;
+ }
+
+ if (dwModuleSize > PAGE_SIZE)
+ dwModuleSize -= PAGE_SIZE;
+ else
+ dwModuleSize = 0;
+
+ pLastBaseAddress = pBaseAddress;
+ pBaseAddress += PAGE_SIZE;
+ }
+ while (dwModuleSize);
+
+ printf("0x%p\n", pLastBaseAddress + 0xfff);
+ return;
+}
+
+/**
+ * Inspect all loaded modules for the specified process
+ * @param dwProcessId Process id
+ */
+void VBoxServicePageSharingInspectModules(DWORD dwProcessId)
+{
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ {
+ printf("VBoxServicePageSharingInspectModules: CreateToolhelp32Snapshot failed with %d\n", GetLastError());
+ return;
+ }
+
+ printf("VBoxServicePageSharingInspectModules\n");
+
+ MODULEENTRY32 ModuleInfo;
+ BOOL bRet;
+
+ ModuleInfo.dwSize = sizeof(ModuleInfo);
+ bRet = Module32First(hSnapshot, &ModuleInfo);
+ do
+ {
+ /** @todo when changing this make sure VBoxService.exe is excluded! */
+ char *pszDot = strrchr(ModuleInfo.szModule, '.');
+ if ( pszDot
+ && (pszDot[1] == 'e' || pszDot[1] == 'E'))
+ continue; /* ignore executables for now. */
+
+ VBoxServicePageSharingCheckModule(&ModuleInfo);
+ }
+ while (Module32Next(hSnapshot, &ModuleInfo));
+
+ CloseHandle(hSnapshot);
+}
+
+/**
+ * Inspect all running processes for executables and dlls that might be worth sharing
+ * with other VMs.
+ *
+ */
+void VBoxServicePageSharingInspectGuest()
+{
+ VBoxServicePageSharingInspectModules(GetCurrentProcessId());
+
+ printf("\n\nUSER RESULTS\n");
+ printf("cNotPresentPages = %d\n", cNotPresentPages);
+ printf("cWritablePages = %d\n", cWritablePages);
+ printf("cPrivatePages = %d\n", cPrivatePages);
+ printf("cSharedPages = %d\n", cSharedPages);
+
+ cNotPresentPages = 0;
+ cWritablePages = 0;
+ cPrivatePages = 0;
+ cSharedPages = 0;
+
+ /* Check all loaded kernel modules. */
+ if (ZwQuerySystemInformation)
+ {
+ ULONG cbBuffer = 0;
+ PVOID pBuffer = NULL;
+ PRTL_PROCESS_MODULES pSystemModules;
+
+ NTSTATUS ret = ZwQuerySystemInformation(SystemModuleInformation, (PVOID)&cbBuffer, 0, &cbBuffer);
+ if (!cbBuffer)
+ {
+ printf("ZwQuerySystemInformation returned length 0\n");
+ goto skipkernelmodules;
+ }
+
+ pBuffer = RTMemAllocZ(cbBuffer);
+ if (!pBuffer)
+ goto skipkernelmodules;
+
+ ret = ZwQuerySystemInformation(SystemModuleInformation, pBuffer, cbBuffer, &cbBuffer);
+ if (ret != 0)
+ {
+ printf("ZwQuerySystemInformation returned %x (1)\n", ret);
+ goto skipkernelmodules;
+ }
+
+ pSystemModules = (PRTL_PROCESS_MODULES)pBuffer;
+ for (unsigned i = 0; i < pSystemModules->NumberOfModules; i++)
+ {
+ /* User-mode modules seem to have no flags set; skip them as we detected them above. */
+ if (pSystemModules->Modules[i].Flags == 0)
+ continue;
+
+ /* New module; register it. */
+ char szFullFilePath[512];
+ MODULEENTRY32 ModuleInfo;
+
+ strcpy(ModuleInfo.szModule, &pSystemModules->Modules[i].FullPathName[pSystemModules->Modules[i].OffsetToFileName]);
+ GetSystemDirectoryA(szFullFilePath, sizeof(szFullFilePath));
+
+ /* skip \Systemroot\system32 */
+ char *lpPath = strchr(&pSystemModules->Modules[i].FullPathName[1], '\\');
+ if (!lpPath)
+ {
+ printf("Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName);
+ break;
+ }
+
+ lpPath = strchr(lpPath+1, '\\');
+ if (!lpPath)
+ {
+ printf("Unexpected kernel module name %s\n", pSystemModules->Modules[i].FullPathName);
+ break;
+ }
+
+ strcat(szFullFilePath, lpPath);
+ strcpy(ModuleInfo.szExePath, szFullFilePath);
+ ModuleInfo.modBaseAddr = (BYTE *)pSystemModules->Modules[i].ImageBase;
+ ModuleInfo.modBaseSize = pSystemModules->Modules[i].ImageSize;
+
+ VBoxServicePageSharingCheckModule(&ModuleInfo);
+ }
+skipkernelmodules:
+ if (pBuffer)
+ RTMemFree(pBuffer);
+ }
+ printf("\n\nKERNEL RESULTS\n");
+ printf("cNotPresentPages = %d\n", cNotPresentPages);
+ printf("cWritablePages = %d\n", cWritablePages);
+ printf("cPrivatePages = %d\n", cPrivatePages);
+ printf("cSharedPages = %d\n", cSharedPages);
+}
+#else
+void VBoxServicePageSharingInspectGuest()
+{
+ /** @todo other platforms */
+}
+#endif
+
+
+/** @copydoc VBOXSERVICE::pfnInit */
+static DECLCALLBACK(int) VBoxServicePageSharingInit(void)
+{
+ printf("VBoxServicePageSharingInit\n");
+
+#ifdef RT_OS_WINDOWS
+ hNtdll = LoadLibrary("ntdll.dll");
+
+ if (hNtdll)
+ ZwQuerySystemInformation = (PFNZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll, "ZwQuerySystemInformation");
+#endif
+
+ /** @todo report system name and version */
+ /* Never fail here. */
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(void) VBoxServicePageSharingTerm(void)
+{
+ printf("VBoxServicePageSharingTerm\n");
+
+#ifdef RT_OS_WINDOWS
+ if (hNtdll)
+ FreeLibrary(hNtdll);
+#endif
+ return;
+}
+
+int main(int argc, char **argv)
+{
+ /*
+ * Init globals and such.
+ */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /*
+ * Connect to the kernel part before daemonizing so we can fail
+ * and complain if there is some kind of problem. We need to initialize
+ * the guest lib *before* we do the pre-init just in case one of services
+ * needs do to some initial stuff with it.
+ */
+ printf("Calling VbgR3Init()\n");
+ rc = VbglR3Init();
+ if (RT_FAILURE(rc))
+ {
+ printf("VbglR3Init failed with rc=%Rrc.\n", rc);
+ return -1;
+ }
+ VBoxServicePageSharingInit();
+
+ VBoxServicePageSharingInspectGuest();
+
+ VBoxServicePageSharingTerm();
+ return 0;
+}
+
diff --git a/src/VBox/Additions/darwin/Installer/.scm-settings b/src/VBox/Additions/darwin/Installer/.scm-settings
new file mode 100644
index 00000000..d46569be
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/.scm-settings
@@ -0,0 +1,37 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for OS X guest additions installer.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+--filter-out-files /VBoxGuestAdditions_mpkg/Conclusion.rtf
+--filter-out-files /VBoxGuestAdditions_mpkg/Welcome.rtf
+--filter-out-files /VBoxGuestAdditions_mpkg/Localizable.strings
+
+/DiskImage/Uninstall.tool: --treat-as .sh
+/VBoxGuestAdditionsKEXTs/postflight: --treat-as .sh
+/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper: --treat-as .sh
+/VBoxGuestAdditions_mpkg/distribution.dist: --treat-as .xml
+
diff --git a/src/VBox/Additions/darwin/Installer/DiskImage/Uninstall.tool b/src/VBox/Additions/darwin/Installer/DiskImage/Uninstall.tool
new file mode 100755
index 00000000..e057c250
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/DiskImage/Uninstall.tool
@@ -0,0 +1,124 @@
+#!/bin/sh
+# $Id: Uninstall.tool $
+## #file
+# VirtualBox Guest Additions uninstall script.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Override any funny stuff from the user.
+export PATH="/bin:/usr/bin:/sbin:/usr/sbin:$PATH"
+
+#
+# Display a simple welcome message first.
+#
+echo ""
+echo "Welcome to the VirtualBox Guest Additions uninstall script."
+echo ""
+
+# Check if user interraction is required to start uninstall process.
+fUnattended=0
+if test "$#" != "0"; then
+ if test "$#" != "1" -o "$1" != "--unattended"; then
+ echo "Error: Unknown argument(s): $*"
+ echo ""
+ echo "Usage: $0 [--unattended]"
+ echo ""
+ echo "If the '--unattended' option is not given, you will be prompted"
+ echo "for a Yes/No before doing the actual uninstallation."
+ echo ""
+ exit 4;
+ fi
+ fUnattended="Yes"
+fi
+
+if test "$fUnattended" != "Yes"; then
+ echo "Do you wish to continue none the less (Yes/No)?"
+ read fUnattended
+ if test "$fUnattended" != "Yes" -a "$fUnattended" != "YES" -a "$fUnattended" != "yes"; then
+ echo "Aborting uninstall. (answer: '$fUnattended')".
+ exit 2;
+ fi
+ echo ""
+fi
+
+# Stop services
+echo "Checking running services..."
+unload()
+{
+ ITEM_ID=$1
+ ITEM_PATH=$2
+ FORCED_USER=$3
+
+ echo "Unloading $ITEM_ID"
+
+
+ loaded="NO"
+ test -n "$(sudo -u "$FORCED_USER" launchctl list | grep $ITEM_ID)" && loaded="YES"
+ if [ "$loaded" = "YES" ] ; then
+ sudo -p "Please enter $FORCED_USER's password (unloading $ITEM_ID):" sudo -u "$FORCED_USER" launchctl unload -F "$ITEM_PATH/$ITEM_ID.plist"
+ fi
+
+}
+
+unload "org.virtualbox.additions.vboxservice" "/Library/LaunchDaemons" "root"
+unload "org.virtualbox.additions.vboxclient" "/Library/LaunchAgents" `whoami`
+
+# Unload kernel extensions
+echo "Checking running kernel extensions..."
+items="VBoxGuest"
+for item in $items; do
+ kext_item="org.virtualbox.kext.$item"
+ loaded=`kextstat | grep $kext_item`
+ if [ ! -z "$loaded" ] ; then
+ echo "Unloading $item kernel extension"
+ sudo -p "Please enter %u's password (unloading $item):" kextunload -b $kext_item
+ fi
+done
+
+# Remove files and directories
+echo "Checking files and directories..."
+sudo -p "Please enter %u's password (removing files and directories):" rm -rf "/Library/Application Support/VirtualBox Guest Additions"
+sudo -p "Please enter %u's password (removing files and directories):" rm -rf "/Library/Extensions/VBoxGuest.kext"
+sudo -p "Please enter %u's password (removing files and directories):" rm -rf "/Library/LaunchAgents/org.virtualbox.additions.vboxclient.plist"
+sudo -p "Please enter %u's password (removing files and directories):" rm -rf "/Library/LaunchDaemons/org.virtualbox.additions.vboxservice.plist"
+
+# Cleaning up pkgutil database
+echo "Checking package database ..."
+items="kexts tools-and-services"
+for item in $items; do
+ pkg_item="org.virtualbox.pkg.additions.$item"
+ installed=`pkgutil --pkgs="$pkg_item"`
+ if [ ! -z "$installed" ] ; then
+ sudo -p "Please enter %u's password (removing $pkg_item):" pkgutil --forget "$pkg_item"
+ fi
+done
+
+# Remove our kexts from the cache.
+echo "Updating kernel cache."
+sudo -p "Please enter %u's password (refreshing kext cache):" touch "/System/Library/Extensions/"
+sudo -p "Please enter %u's password (refreshing kext cache):" kextcache -update-volume /
+
+echo "Done."
+exit 0;
+
diff --git a/src/VBox/Additions/darwin/Installer/Makefile.kmk b/src/VBox/Additions/darwin/Installer/Makefile.kmk
new file mode 100644
index 00000000..0dae3d97
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/Makefile.kmk
@@ -0,0 +1,414 @@
+# $Id: Makefile.kmk $
+## @file
+# Install misc stuff and create dist packages for Mac OS X Guest Additions.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+#
+# Globals
+#
+VBOX_ADD_PATH_DI_SRC := $(PATH_SUB_CURRENT)
+VBOX_ADD_DI_OUT_DIR := $(PATH_TARGET)/additions/Installer
+BLDDIRS += $(VBOX_ADD_DI_OUT_DIR)
+
+ifdef VBOX_WITH_COMBINED_PACKAGE
+ ifeq ($(KBUILD_TARGET_ARCH),x86)
+ VBOX_PATH_DIST_32 = $(VBOX_PATH_DIST)/additions
+ VBOX_PATH_DIST_64 = $(PATH_OUT_BASE)/darwin.amd64/$(KBUILD_TYPE)/dist/additions
+ else
+ VBOX_PATH_DIST_64 = $(VBOX_PATH_DIST)/additions
+ VBOX_PATH_DIST_32 = $(PATH_OUT_BASE)/darwin.x86/$(KBUILD_TYPE)/dist
+ endif
+ VBOX_DI_FN_DEP_BOTH = $(VBOX_PATH_DIST_32)/$1 $(VBOX_PATH_DIST_64)/$2
+ VBOX_DI_FN_DEP_32 = $(VBOX_PATH_DIST_32)/$1
+ VBOX_DI_FN_DEP_64 = $(VBOX_PATH_DIST_64)/$1
+ VBOX_DI_LIPO = lipo
+else
+ VBOX_DI_FN_DEP_BOTH = $(VBOX_PATH_DIST)/additions/$1
+ ifeq ($(KBUILD_TARGET_ARCH),x86)
+ VBOX_DI_FN_DEP_32 = $(VBOX_PATH_DIST)/additions/$1
+ VBOX_DI_FN_DEP_64 =
+ else
+ VBOX_DI_FN_DEP_64 = $(VBOX_PATH_DIST)/additions/$1
+ VBOX_DI_FN_DEP_32 =
+ endif
+endif
+
+# Unset this to speed up things during makefile hacking.
+VBOX_DARWIN_INST_DEP_ON_MAKEFILE := $(MAKEFILE_CURRENT)
+
+# The location of the pkgbuild program.
+ifndef VBOX_PKGBUILD
+ VBOX_PKGBUILD := pkgbuild
+endif
+
+# The location of the productbuild program.
+ifndef VBOX_PRODUCTBUILD
+ VBOX_PRODUCTBUILD := productbuild
+endif
+
+# Where we do the packing (override this in LocalConfig.kmk if building on smbfs).
+ifndef VBOX_PATH_PACK_TMP
+ VBOX_PATH_PACK_TMP := $(VBOX_ADD_DI_OUT_DIR)
+endif
+
+
+#
+# The packing.
+#
+PACKING += \
+ $(PATH_STAGE)/$(INST_ADDITIONS)VBoxGuestAdditions.pkg \
+ $(PATH_STAGE)/$(INST_ADDITIONS)VBoxDarwinAdditionsUninstall.tool
+
+#OTHER_CLEAN = TODO
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
+#
+# We're running commands as root here, take some care and assertion
+# a sane environment.
+#
+ifeq ($(strip $(VBOX_PATH_DIST)),)
+ $(error VBOX_PATH_DIST=$(VBOX_PATH_DIST))
+endif
+ifeq ($(strip $(VBOX_PATH_DIST)),/)
+ $(error VBOX_PATH_DIST=$(VBOX_PATH_DIST))
+endif
+ifeq ($(strip $(VBOX_PATH_PACK_TMP)),)
+ $(error VBOX_PATH_PACK_TMP=$(VBOX_PATH_PACK_TMP))
+endif
+ifeq ($(strip $(VBOX_PATH_PACK_TMP)),/)
+ $(error VBOX_PATH_PACK_TMP=$(VBOX_PATH_PACK_TMP))
+endif
+
+#
+# The packing targets.
+#
+$(PATH_STAGE)/$(INST_ADDITIONS)VBoxGuestAdditions.pkg: $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditions.pkg
+ $(MKDIR) -p "$(@D)"
+ $(INSTALL) -m 0644 -- "$<" "$@"
+
+$(PATH_STAGE)/$(INST_ADDITIONS)VBoxDarwinAdditionsUninstall.tool: $(VBOX_ADD_PATH_DI_SRC)/DiskImage/Uninstall.tool
+ $(MKDIR) -p "$(@D)"
+ $(INSTALL) -m 0755 -- "$<" "$@"
+
+
+#
+# The meta-package.
+#
+$(VBOX_PATH_PACK_TMP)/VBoxGuestAdditions.pkg: \
+ $(VBOX_PATH_PACK_TMP)/Packages/VBoxGuestAdditionsKEXTs.pkg \
+ $(VBOX_PATH_PACK_TMP)/Packages/VBoxGuestAdditionsToolsAndServices.pkg \
+ $$(wildcard $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/* \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/*.lproj/*) \
+ $(foreach f,$(VBOX_INSTALLER_ADD_LANGUAGES), $(VBOX_BRAND_$(f)_VIRTUALBOX_WELCOME_RTF)) \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/Welcome.rtf \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/Conclusion.rtf \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/distribution-$(KBUILD_TARGET_ARCH).dist \
+ $(VBOX_DARWIN_INST_DEP_ON_MAKEFILE)
+ $(call MSG_TOOL,productbuild,,,$@)
+ @# Cleanup any previously failed attempts.
+ sudo rm -Rf \
+ $@ \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.root \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.desc \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res
+ @# Correct directory permissions are important.
+ $(MKDIR) -p \
+ $(@D) \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.desc \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/English.lproj
+
+ @# Do keyword replacement in the package info and description files.
+ $(SED) \
+ -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \
+ -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \
+ -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \
+ -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+g' \
+ -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \
+ -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \
+ -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \
+ --output $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/English.lproj/Welcome.rtf \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/Welcome.rtf
+ @# Copy the resources.
+ $(INSTALL) -m 0644 $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/Conclusion.rtf $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/English.lproj/Conclusion.rtf
+
+ $(SED) \
+ -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \
+ -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \
+ -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \
+ --output $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/English.lproj/Localizable.strings \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/Localizable.strings
+ $(INSTALL) -m 0644 $(VBOX_BRAND_DARWIN_INSTALLER_BG) $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/background.tif
+
+ $(foreach f,$(VBOX_INSTALLER_ADD_LANGUAGES), \
+ $(MKDIR) -p \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/$(VBOX_INSTALLER_$(f)_DARWIN_TARGET).lproj$(NLTAB) \
+ $(SED) \
+ -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \
+ -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \
+ -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \
+ -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+g' \
+ --output $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/$(VBOX_INSTALLER_$(f)_DARWIN_TARGET).lproj/Welcome.rtf \
+ $(VBOX_BRAND_$(f)_VIRTUALBOX_WELCOME_RTF)$(NLTAB) \
+ $(INSTALL) -m 0644 $(VBOX_BRAND_$(f)_VIRTUALBOX_CONCLUSION_RTF) $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/$(VBOX_INSTALLER_$(f)_DARWIN_TARGET).lproj/Conclusion.rtf$(NLTAB) \
+ $(SED) \
+ -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \
+ -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \
+ -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \
+ --output $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res/$(VBOX_INSTALLER_$(f)_DARWIN_TARGET).lproj/Localizable.strings \
+ $(VBOX_BRAND_$(f)_VIRTUALBOX_LOCALIZABLE_STRINGS)$(NLTAB) \
+ )
+
+ @# Build the package.
+ $(VBOX_PRODUCTBUILD) \
+ --distribution $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditions_mpkg/distribution-$(KBUILD_TARGET_ARCH).dist \
+ --package-path $(VBOX_PATH_PACK_TMP)/Packages \
+ --resources $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res \
+ --identifier org.VirtualBox.mpkg.GuestAdditions \
+ --version $(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD) \
+ $(if-expr defined(VBOX_MACOSX_INSTALLER_SIGN) && $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) == "",--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \
+ $@
+ifdef VBOX_SIGNING_MODE
+ if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING))
+ @# Sign the created pkg.
+ $(call VBOX_SIGN_PKG_FN,$@,org.VirtualBox.mpkg.GuestAdditions)
+ if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING))
+ @# Notarize the signed pkg (includes stapling).
+ $(call VBOX_NOTARIZE_FILE_FN,$@,org.virtualbox.VBoxGuestAdditions.$(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD).$(VBOX_SVN_REV))
+ endif
+ endif
+endif
+
+ @# Cleanup.
+ sudo rm -Rf \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.root \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.desc \
+ $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.dist.res
+
+VBoxDarwinAdditions.pkg:: $(VBOX_PATH_PACK_TMP)/VBoxDarwinAdditions.pkg
+
+#
+# The VirtualBox Kernel extensions.
+#
+VBOX_ADD_DI_KEXTS_UNIVERSAL = VBoxGuest
+VBOX_ADD_DI_KEXTS = $(VBOX_ADD_DI_KEXTS_UNIVERSAL)
+
+$(VBOX_PATH_PACK_TMP)/Packages/VBoxGuestAdditionsKEXTs.pkg: \
+ $(foreach kext,$(VBOX_ADD_DI_KEXTS_UNIVERSAL), $(call VBOX_DI_FN_DEP_BOTH,$(kext).kext/Contents/MacOS/$(kext))) \
+ $(foreach kext,$(VBOX_ADD_DI_KEXTS), $(VBOX_PATH_DIST)/additions/$(kext).kext/Contents/Info.plist) \
+ $$(wildcard $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsKEXTs/* \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsKEXTs/*.lproj/*) \
+ $(foreach f,$(VBOX_INSTALLER_ADD_LANGUAGES), \
+ $(VBOX_BRAND_$(f)_VBOXKEXTS_DESCRIPTION_PLIST) \
+ $(VBOX_BRAND_$(f)_VBOXKEXTS_README_HTML) \
+ $(VBOX_BRAND_$(f)_VBOXKEXTS_INSTALLATIONCHECK_STRINGS)) \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsKEXTs/postflight \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsKEXTs/PkgBuildComponent.plist \
+ $(VBOX_DARWIN_INST_DEP_ON_MAKEFILE)
+ $(call MSG_TOOL,pkgbuild,,,$@)
+ @# Cleanup any previously failed attempts.
+ sudo rm -Rf \
+ $@ \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.desc \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.res
+ @# Correct directory permissions are important.
+ $(MKDIR) -p \
+ $(@D) \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.desc \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.res \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.res/English.lproj
+ $(MKDIR) -p -m 1775 $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library
+ $(MKDIR) -p -m 0755 \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions \
+ $(foreach kext,$(VBOX_ADD_DI_KEXTS), \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext/Contents \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext/Contents/MacOS )
+ @# Copy the common files (Info.plist).
+ $(foreach kext,$(VBOX_ADD_DI_KEXTS), \
+ $(NLTAB)$(INSTALL) -m 0644 $(VBOX_PATH_DIST)/additions/$(kext).kext/Contents/Info.plist $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext/Contents/)
+ @# Copy the binaries and invoking lipo.
+ifdef VBOX_WITH_COMBINED_PACKAGE
+ $(foreach kext,$(VBOX_ADD_DI_KEXTS_UNIVERSAL), \
+ $(NLTAB)$(VBOX_DI_LIPO) -create \
+ $(VBOX_PATH_DIST_32)/additions/$(kext).kext/Contents/MacOS/$(kext) \
+ $(VBOX_PATH_DIST_64)/additions/$(kext).kext/Contents/MacOS/$(kext) \
+ -output $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext/Contents/MacOS/$(kext))
+else
+ $(foreach kext,$(VBOX_ADD_DI_KEXTS), \
+ $(NLTAB)$(INSTALL) -m 0755 $(VBOX_PATH_DIST)/additions/$(kext).kext/Contents/MacOS/$(kext) $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext/Contents/MacOS/)
+endif
+ @# Sign the kext bundles.
+ifdef VBOX_SIGNING_MODE
+ $(foreach kext,$(VBOX_ADD_DI_KEXTS), \
+ $(NLTAB)$(call VBOX_SIGN_BUNDLE_FN,$(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext,) \
+ $(NLTAB)chmod a+r $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/$(kext).kext/Contents/_CodeSignature/* )
+endif
+ @# Set the correct owners.
+ sudo chown root:admin $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library
+ sudo chown -R root:wheel $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions
+
+ # Copy package internal files
+ $(INSTALL) $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsKEXTs/PkgBuildComponent.plist $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.desc/PkgBuildComponent.plist
+
+ # Copy installer scripts
+ $(INSTALL) -m 0755 $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsKEXTs/postflight $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.res
+
+ @# Build the package.
+ $(VBOX_PKGBUILD) \
+ --root $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root/Library/Extensions/ \
+ --component-plist $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.desc/PkgBuildComponent.plist \
+ --script $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.res \
+ --identifier org.virtualbox.pkg.additions.kexts \
+ --version $(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD) \
+ --install-location /Library/Extensions/ \
+ --ownership preserve \
+ $(if-expr defined(VBOX_MACOSX_INSTALLER_SIGN) && $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) == "",--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \
+ $@
+ifdef VBOX_SIGNING_MODE
+ if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING))
+ @# Sign the created pkg.
+ $(call VBOX_SIGN_PKG_FN,$@,org.virtualbox.pkg.vboxguestadditionskexts)
+ endif
+endif
+ @# Cleanup
+ sudo rm -Rf \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.root \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.desc \
+ $(VBOX_PATH_PACK_TMP)/VBoxGuestAdditionsKEXTs.pkg.res
+
+#
+# The VirtualBox Guest Additions Tools & Services.
+#
+
+VBOX_GA_PKG = VBoxGuestAdditionsToolsAndServices.pkg
+VBOX_DI_VB_GA_BINARIES = VBoxClient VBoxControl VBoxService
+$(VBOX_PATH_PACK_TMP)/Packages/$(VBOX_GA_PKG): \
+ $(foreach f, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(call VBOX_DI_FN_DEP_BOTH,$(f)) ) \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxclient.plist \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxservice.plist \
+ $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper \
+ $(VBOX_ADD_PATH_DI_SRC)/DiskImage/Uninstall.tool \
+ $(VBOX_DARWIN_INST_DEP_ON_MAKEFILE)
+ $(call MSG_TOOL,pkgbuild,,,$@)
+ @# Cleanup any previously failed attempts.
+ sudo rm -Rf \
+ $@ \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).desc \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).res
+ @# Correct directory permissions are important.
+ $(MKDIR) -p \
+ $(@D) \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).desc \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).res \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).res/English.lproj
+
+ @# Create directory structure within a package w/ proper permittions
+ $(MKDIR) -p -m 0775 \
+ "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions" \
+ "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/LaunchAgents" \
+ "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/LaunchDaemons"
+
+ @# Install binaries
+ifdef VBOX_WITH_COMBINED_PACKAGE
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(VBOX_DI_LIPO) -create \
+ $(VBOX_PATH_DIST_32)/additions/$(binary) \
+ $(VBOX_PATH_DIST_64)/additions/$(binary) \
+ -output "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)"$(NLTAB))
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES), \
+ $(NLTAB)$(INSTALL) -m 0755 $(VBOX_PATH_DIST_32)/additions/$(binary) "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-x86" \
+ $(NLTAB)$(INSTALL) -m 0755 $(VBOX_PATH_DIST_64)/additions/$(binary) "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-amd64" )
+else
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(INSTALL) -m 0755 $(VBOX_PATH_DIST)/additions/$(binary) "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)"$(NLTAB))
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(INSTALL) -m 0755 $(VBOX_PATH_DIST)/additions/$(binary) "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-$(KBUILD_TARGET_ARCH)"$(NLTAB))
+endif
+ # Add Uninstall.tool
+ $(INSTALL) -m 0755 $(VBOX_ADD_PATH_DI_SRC)/DiskImage/Uninstall.tool "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/"
+ifdef VBOX_SIGNING_MODE
+ $(call VBOX_SIGN_FILE_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/Uninstall.tool,org.virtualbox.app.guestadditions.uninstaller)
+endif
+
+ @# Install launchd stuff
+ $(INSTALL) -m 0755 $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper \
+ "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/"
+ $(INSTALL) -m 644 $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxclient.plist \
+ "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/LaunchAgents/"
+ $(INSTALL) -m 644 $(VBOX_ADD_PATH_DI_SRC)/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxservice.plist \
+ "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/LaunchDaemons/"
+
+ @# Sign the binaries.
+ifdef VBOX_SIGNING_MODE
+ ifdef VBOX_WITH_COMBINED_PACKAGE
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary),org.virtualbox.app.guestadditions.$(notdir $(binary))) )
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-x86,org.virtualbox.app.guestadditions.$(notdir $(binary))-x86) )
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-amd64,org.virtualbox.app.guestadditions.$(notdir $(binary))-amd64) )
+ else
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary),org.virtualbox.app.guestadditions.$(notdir $(binary))) )
+ $(foreach binary, $(VBOX_DI_VB_GA_BINARIES) \
+ ,$(NLTAB)$(call VBOX_SIGN_MACHO_FN,$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/Application Support/VirtualBox Guest Additions/$(binary)-$(KBUILD_TARGET_ARCH),org.virtualbox.app.guestadditions.$(notdir $(binary))-$(KBUILD_TARGET_ARCH)) )
+ endif
+endif
+
+ @# Correct ownership
+ sudo chown -R root:wheel "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/"
+
+ @# Build the package.
+ $(VBOX_PKGBUILD) \
+ --root "$(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root/Library/" \
+ --script $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).res \
+ --identifier org.virtualbox.pkg.additions.tools-and-services \
+ --version $(VBOX_VERSION_MAJOR).$(VBOX_VERSION_MINOR).$(VBOX_VERSION_BUILD) \
+ --install-location "/Library/" \
+ --ownership preserve \
+ $(if-expr defined(VBOX_MACOSX_INSTALLER_SIGN) && $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING)) == "",--sign "$(VBOX_MACOSX_INSTALLER_SIGN)",) \
+ $@
+ifdef VBOX_SIGNING_MODE
+ if $(intersects darwin all 1,$(VBOX_WITH_CORP_CODE_SIGNING))
+ @# Sign the created pkg.
+ $(call VBOX_SIGN_PKG_FN,$@,org.virtualbox.pkg.vboxguestadditions)
+ endif
+endif
+ @# Cleanup
+ sudo rm -Rf \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).root \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).desc \
+ $(VBOX_PATH_PACK_TMP)/$(VBOX_GA_PKG).res
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/PkgBuildComponent.plist b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/PkgBuildComponent.plist
new file mode 100644
index 00000000..56d8701b
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/PkgBuildComponent.plist
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+ <dict>
+ <key>RootRelativeBundlePath</key> <string>VBoxGuest.kext</string>
+ <key>BundleIsRelocatable</key> <false/>
+ <key>BundleIsVersionChecked</key> <false/>
+ <key>BundleHasStrictIdentifier</key> <false/>
+ <key>BundleOverwriteAction</key> <string>upgrade</string>
+ <key>BundlePostInstallScriptPath</key> <string>postflight</string>
+ </dict>
+</array>
+</plist>
+
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/postflight b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/postflight
new file mode 100755
index 00000000..5b0a0b8c
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsKEXTs/postflight
@@ -0,0 +1,104 @@
+#!/bin/sh
+# $Id: postflight $
+## @file
+# Post flight installer script for the VirtualBox OS X kernel extensions.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+set -e
+
+# Setup environment.
+export PATH="/bin:/usr/bin:/sbin:/usr/sbin:$PATH"
+
+unload_service()
+{
+ ITEM_ID=$1
+ ITEM_PATH=$2
+ FORCED_USER=$3
+
+ loaded="NO"
+ test -n "$(sudo -u "$FORCED_USER" launchctl list | grep $ITEM_ID)" && loaded="YES"
+ if [ "$loaded" = "YES" ] ; then
+ echo "Unloading previously installed service: $ITEM_ID"
+ sudo -u "$FORCED_USER" launchctl unload -F "$ITEM_PATH/$ITEM_ID.plist"
+ fi
+}
+
+load_service()
+{
+ ITEM_ID=$1
+ ITEM_PATH=$2
+ FORCED_USER=$3
+
+ echo "Loading newly installed service: $ITEM_ID"
+ sudo -u "$FORCED_USER" launchctl load -F "$ITEM_PATH/$ITEM_ID.plist"
+}
+
+unload_service "org.virtualbox.additions.vboxservice" "/Library/LaunchDaemons" "root"
+
+# Remove the old service for all users
+for user in $(dscl . -list /Users | grep -v -e'^_' -e'root'); do
+ system_user="YES"
+ test -n "$(dscl . -read /Users/$user NFSHomeDirectory | grep '/Users')" && system_user="NO"
+ if [ "$system_user" = "NO" ]; then
+ unload_service "org.virtualbox.additions.vboxclient" "/Library/LaunchAgents" "$user"
+ fi
+done
+
+items="VBoxGuest"
+for item in $items; do
+ kext_item="org.virtualbox.kext.$item"
+
+ loaded="NO"
+ test -n "$(kextstat | grep $kext_item)" && loaded="YES"
+ if [ "$loaded" = "YES" ] ; then
+ echo "Unloading $item kernel extension..."
+ kextunload -b $kext_item
+ fi
+done
+
+MACOS_VERS=$(sw_vers -productVersion)
+
+echo "Updating kernel cache (should trigger loading of new modules)."
+# /System/Library/Extensions is readonly in Catalina and later,
+# so touch returns always false on these systems
+if [[ ${MACOS_VERS} != 11.* ]] && [[ ${MACOS_VERS} != 10.15.* ]]; then
+ touch "/System/Library/Extensions/"
+fi
+kextcache -update-volume / || true
+
+load_service "org.virtualbox.additions.vboxservice" "/Library/LaunchDaemons" "root"
+# Add VBoxClient for all currently defined users
+for user in $(dscl . -list /Users | grep -v -e'^_' -e'root'); do
+ system_user="YES"
+ test -n "$(dscl . -read /Users/$user NFSHomeDirectory | grep '/Users')" && system_user="NO"
+ if [ "$system_user" = "NO" ]; then
+ load_service "org.virtualbox.additions.vboxclient" "/Library/LaunchAgents" "$user"
+ fi
+done
+
+echo "Warning: If VBoxService adjusts the time backwards (because of --biossystemtimeoffset), the installer may hang."
+echo "Done."
+
+exit 0;
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper
new file mode 100755
index 00000000..864fc76b
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/VBoxServiceWrapper
@@ -0,0 +1,63 @@
+#!/bin/sh
+## @file
+# VBoxService wrapper script.
+#
+# Load required kernel extensions before start service (if necessary).
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+export PATH="/bin:/usr/bin:/sbin:/usr/sbin:$PATH"
+
+echo "Check if kernel extensions loaded..."
+items="VBoxGuest"
+for item in $items; do
+ kext_item="org.virtualbox.kext.$item"
+ loaded=`kextstat | grep $kext_item`
+ if [ -z "$loaded" ] ; then
+ echo "Loading $item kernel extension..."
+ XNU_VERSION=`LC_ALL=C uname -r | LC_ALL=C cut -d . -f 1`
+ if [ "$XNU_VERSION" -ge "10" ]; then
+ kextutil /Library/Extensions/$item.kext
+ else
+ kextload /Library/Extensions/$item.kext
+ fi
+ fi
+done
+
+echo "Check if VBoxClient is added for all non-system users"
+for user in $(dscl . -list /Users | grep -v -e'^_' -e'root'); do
+ system_user="YES"
+ test -n "$(dscl . -read /Users/$user NFSHomeDirectory | grep '/Users')" && system_user="NO"
+ if [ "$system_user" = "NO" ]; then
+ loaded="NO"
+ test -n "$(sudo -u "$user" launchctl list | grep 'org.virtualbox.additions.vboxclient')" && loaded="YES"
+ if [ "$loaded" = "NO" ] ; then
+ echo "Loading org.virtualbox.additions.vboxclient for $user"
+ sudo -u "$user" launchctl load -F "/Library/LaunchAgents/org.virtualbox.additions.vboxclient.plist"
+ fi
+ fi
+done
+
+exec "/Library/Application Support/VirtualBox Guest Additions/VBoxService" -f
+
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxclient.plist b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxclient.plist
new file mode 100644
index 00000000..6b8b6b05
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxclient.plist
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Disabled</key> <false/>
+ <key>RunAtLoad</key> <true/>
+ <key>KeepAlive</key> <true/>
+ <key>Label</key> <string>org.virtualbox.additions.vboxclient</string>
+
+ <key>ProgramArguments</key>
+ <array>
+ <string>/Library/Application Support/VirtualBox Guest Additions/VBoxClient</string>
+ <string>-f</string>
+ </array>
+</dict>
+</plist>
+
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxservice.plist b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxservice.plist
new file mode 100644
index 00000000..d462b50d
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditionsToolsAndServices/org.virtualbox.additions.vboxservice.plist
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Disabled</key> <false/>
+ <key>RunAtLoad</key> <true/>
+ <key>KeepAlive</key> <true/>
+ <key>Label</key> <string>org.virtualbox.additions.vboxservice</string>
+
+ <key>ProgramArguments</key>
+ <array>
+ <string>/Library/Application Support/VirtualBox Guest Additions/VBoxServiceWrapper</string>
+ </array>
+</dict>
+</plist>
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Conclusion.rtf b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Conclusion.rtf
new file mode 100644
index 00000000..66a61b5e
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Conclusion.rtf
@@ -0,0 +1,9 @@
+{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470
+{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\vieww10800\viewh8400\viewkind0
+\deftab720
+\pard\pardeftab720\sa240
+
+\f0\fs24 \cf0 VirtualBox Guest Addition successfully installed and ready for use.\
+} \ No newline at end of file
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Localizable.strings b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Localizable.strings
new file mode 100644
index 00000000..3781727e
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Localizable.strings
@@ -0,0 +1,14 @@
+'VirtualBox_title' = '@VBOX_PRODUCT@';
+
+'choiceVBoxKEXTs_title' = 'Kernel Extensions';
+'choiceVBoxKEXTs_msg' = 'Installs the @VBOX_PRODUCT@ Guest Additions Kernel Extensions into /Library/Extensions.';
+
+'choiceVBoxToolsAndServices_title' = 'Tools and Services';
+'choiceVBoxToolsAndServices_msg' = 'Installs the @VBOX_PRODUCT@ Guest Additions Tools and Services to /Library/Application Support/VirtualBox Guest Additions.';
+
+'UNSUPPORTED_AMD64_OS_TLE' = "Incompatible OS version detected!";
+'UNSUPPORTED_AMD64_OS_MSG' = "VirtualBox Guest Additions require Mac OS X 10.7 or later running in 64-bit mode.";
+
+'UNSUPPORTED_X86_OS_TLE' = "Incompatible OS version detected!";
+'UNSUPPORTED_X86_OS_MSG' = "The 32-bit VirtualBox Guest Additions are only for Mac OS X 10.7 and earlier running in 32-bit mode.";
+
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Welcome.rtf b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Welcome.rtf
new file mode 100644
index 00000000..344017e3
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/Welcome.rtf
@@ -0,0 +1,13 @@
+{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470
+{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\vieww10800\viewh8400\viewkind0
+\deftab720
+\pard\pardeftab720\sa280
+
+\f0\b\fs36 \cf0 @VBOX_PRODUCT@ Guest Additions for Mac OS X
+\b0 \
+\pard\pardeftab720\sa240
+
+\fs24 \cf0 Welcome to @VBOX_PRODUCT@ Guest Additions @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ for Mac OS X! This installer will guide you through the installation process.\
+} \ No newline at end of file
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-amd64.dist b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-amd64.dist
new file mode 100644
index 00000000..9e66538a
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-amd64.dist
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2008-2023 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+<installer-gui-script minSpecVersion="1.0">
+ <title>VirtualBox_title</title>
+ <options customize="allow" allow-external-scripts="false" rootVolumeOnly="true" hostArchitectures="i386"/>
+ <domains enable_anywhere="false" enable_currentUserHome="false" enable_localSystem="true"/>
+ <!-- This allowes a better error message compared to allowed-os-versions: -->
+ <installation-check script="checkPrerequisites()"></installation-check>
+ <script>
+ /* js:pkmk:start */
+ function checkPrerequisites()
+ {
+ sArch = system.sysctl('hw.machine');
+ system.log("Detected hw arch: " + sArch);
+ sVer = system.version['ProductVersion'];
+ system.log("Detected OS version: " + sVer);
+ if (sArch == 'x86_64')
+ {
+ if (system.compareVersions(sVer, '10.7') &gt;= 0)
+ {
+ system.log("checkPrerequisites returns true");
+ return true;
+ }
+ }
+ system.log("checkPrerequisites returns false!");
+ my.result.type = 'Fatal';
+ my.result.title = system.localizedString('UNSUPPORTED_AMD64_OS_TLE');
+ my.result.message = system.localizedString('UNSUPPORTED_AMD64_OS_MSG');
+ return false;
+ }
+ /* js:pkmk:end */
+ </script>
+ <background file="background.tif" alignment="topleft" scaling="none"/>
+ <welcome file="Welcome.rtf" mime-type="text/rtf" uti="public.rtf"/>
+ <choices-outline>
+ <line choice="choiceVBoxToolsAndServices"></line>
+ <line choice="choiceVBoxKEXTs"></line>
+ </choices-outline>
+
+ <choice id="choiceVBoxToolsAndServices" title="choiceVBoxToolsAndServices_title" description="choiceVBoxToolsAndServices_msg" start_selected="true" start_enabled="false" start_visible="true">
+ <pkg-ref id="org.virtualbox.pkg.additions.tools-and-services"></pkg-ref>
+ </choice>
+ <choice id="choiceVBoxKEXTs" title="choiceVBoxKEXTs_title" description="choiceVBoxKEXTs_msg" start_selected="true" start_enabled="false" start_visible="true">
+ <pkg-ref id="org.virtualbox.pkg.additions.kexts"></pkg-ref>
+ </choice>
+
+ <pkg-ref id="org.virtualbox.pkg.additions.tools-and-services" auth="Root">file:./Contents/Packages/VBoxGuestAdditionsToolsAndServices.pkg</pkg-ref>
+ <pkg-ref id="org.virtualbox.pkg.additions.kexts" auth="Root">file:./Contents/Packages/VBoxGuestAdditionsKEXTs.pkg</pkg-ref>
+
+</installer-gui-script>
diff --git a/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-x86.dist b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-x86.dist
new file mode 100644
index 00000000..640081b3
--- /dev/null
+++ b/src/VBox/Additions/darwin/Installer/VBoxGuestAdditions_mpkg/distribution-x86.dist
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2008-2023 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ SPDX-License-Identifier: GPL-3.0-only
+-->
+<installer-gui-script minSpecVersion="1.0">
+ <title>VirtualBox_title</title>
+ <options customize="allow" allow-external-scripts="false" rootVolumeOnly="true" hostArchitectures="i386"/>
+ <domains enable_anywhere="false" enable_currentUserHome="false" enable_localSystem="true"/>
+ <!-- This allowes a better error message compared to allowed-os-versions: -->
+ <installation-check script="checkPrerequisites()"></installation-check>
+ <script>
+ /* js:pkmk:start */
+ function checkPrerequisites()
+ {
+ sArch = system.sysctl('hw.machine');
+ system.log("Detected hw arch: " + sArch);
+ sVer = system.version['ProductVersion'];
+ system.log("Detected OS version: " + sVer);
+ if (sArch == 'i386')
+ {
+ if (system.compareVersions(sVer, '10.8') &lt; 0)
+ {
+ system.log("checkPrerequisites returns true");
+ return true;
+ }
+ }
+ system.log("checkPrerequisites returns false!");
+ my.result.type = 'Fatal';
+ my.result.title = system.localizedString('UNSUPPORTED_X86_OS_TLE');
+ my.result.message = system.localizedString('UNSUPPORTED_X86_OS_MSG');
+ return false;
+ }
+ /* js:pkmk:end */
+ </script>
+ <background file="background.tif" alignment="topleft" scaling="none"/>
+ <welcome file="Welcome.rtf" mime-type="text/rtf" uti="public.rtf"/>
+ <choices-outline>
+ <line choice="choiceVBoxToolsAndServices"></line>
+ <line choice="choiceVBoxKEXTs"></line>
+ </choices-outline>
+
+ <choice id="choiceVBoxToolsAndServices" title="choiceVBoxToolsAndServices_title" description="choiceVBoxToolsAndServices_msg" start_selected="true" start_enabled="false" start_visible="true">
+ <pkg-ref id="org.virtualbox.pkg.additions.tools-and-services"></pkg-ref>
+ </choice>
+ <choice id="choiceVBoxKEXTs" title="choiceVBoxKEXTs_title" description="choiceVBoxKEXTs_msg" start_selected="true" start_enabled="false" start_visible="true">
+ <pkg-ref id="org.virtualbox.pkg.additions.kexts"></pkg-ref>
+ </choice>
+
+ <pkg-ref id="org.virtualbox.pkg.additions.tools-and-services" auth="Root">file:./Contents/Packages/VBoxGuestAdditionsToolsAndServices.pkg</pkg-ref>
+ <pkg-ref id="org.virtualbox.pkg.additions.kexts" auth="Root">file:./Contents/Packages/VBoxGuestAdditionsKEXTs.pkg</pkg-ref>
+
+</installer-gui-script>
diff --git a/src/VBox/Additions/darwin/Makefile.kmk b/src/VBox/Additions/darwin/Makefile.kmk
new file mode 100644
index 00000000..43f19541
--- /dev/null
+++ b/src/VBox/Additions/darwin/Makefile.kmk
@@ -0,0 +1,35 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for the Mac OS guest additions base directory.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+include $(PATH_SUB_CURRENT)/VBoxClient/Makefile.kmk
+include $(PATH_SUB_CURRENT)/Installer/Makefile.kmk
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk b/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk
new file mode 100644
index 00000000..ba520a28
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk
@@ -0,0 +1,57 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox Guest Addition Darwin Client.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# VBoxClient - shared clipboard support.
+#
+
+PROGRAMS += VBoxClient
+
+VBoxClient_TEMPLATE = VBoxGuestR3Exe
+VBoxClient_DEFS += VBOX_WITH_HGCM
+
+VBoxClient_SOURCES = \
+ VBoxClient.cpp
+
+ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VBoxClient_DEFS += \
+ VBOX_WITH_SHARED_CLIPBOARD
+ VBoxClient_SOURCES += \
+ VBoxClientClipboard.cpp \
+ VBoxClientClipboardHostToGuest.cpp \
+ VBoxClientClipboardGuestToHost.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp
+endif
+
+VBoxClient_LDFLAGS = -framework IOKit -framework ApplicationServices
+VBoxClient_INST = $(INST_ADDITIONS)
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/darwin/VBoxClient/VBoxClient.cpp b/src/VBox/Additions/darwin/VBoxClient/VBoxClient.cpp
new file mode 100644
index 00000000..c80b1c9b
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxClient/VBoxClient.cpp
@@ -0,0 +1,298 @@
+/** $Id: VBoxClient.cpp $ */
+/** @file
+ * VBoxClient - User specific services, Darwin.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#include <iprt/stream.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/process.h>
+#include <iprt/string.h>
+
+#include "VBoxClientInternal.h"
+
+
+/*********************************************************************************************************************************
+* Glogal Variables *
+*********************************************************************************************************************************/
+
+static int g_cVerbosity = 0;
+static PRTLOGGER g_pLogger = NULL;
+
+static VBOXCLIENTSERVICE g_aServices[] =
+{
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ g_ClipboardService
+#endif
+};
+
+
+/**
+ * Create default logger in order to print output to the specified file.
+ *
+ * @return IPRT status code.
+ */
+static int vbclInitLogger(char *pszLogFileName)
+{
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ int rc = RTLogCreateEx(&g_pLogger, "VBOXCLIENT_RELEASE_LOG", RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG, "all",
+ RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT,
+ NULL /*pfnPhase*/,
+ pszLogFileName ? 10 : 0 /*cHistory*/,
+ pszLogFileName ? 100 * _1M : 0 /*cbHistoryFileMax*/,
+ pszLogFileName ? RT_SEC_1DAY : 0 /*cSecsHistoryTimeSlot*/,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, "%s", pszLogFileName ? pszLogFileName : "");
+ AssertRCReturn(rc, rc);
+
+ /* Register this logger as the release logger */
+ RTLogRelSetDefaultInstance(g_pLogger);
+
+ /* Explicitly flush the log in case of VBOXCLIENT_RELEASE_LOG=buffered. */
+ RTLogFlush(g_pLogger);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Destroy logger.
+ */
+static void vbclTermLogger(char *szLogFileName)
+{
+ // Why SIGBUS here?
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+
+ if (szLogFileName)
+ RTStrFree(szLogFileName);
+}
+
+/**
+ * Displays a verbose message.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+void VBoxClientVerbose(int iLevel, const char *pszFormat, ...)
+{
+ if (iLevel > g_cVerbosity)
+ return;
+
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ LogRel(("%s", psz));
+
+ RTStrFree(psz);
+}
+
+/**
+ * Wait for signals in order to safely terminate process.
+ */
+static void vbclWait(void)
+{
+ sigset_t signalMask;
+ int iSignal;
+
+ /* Register signals that we are waiting for */
+ sigemptyset(&signalMask);
+ sigaddset(&signalMask, SIGHUP);
+ sigaddset(&signalMask, SIGINT);
+ sigaddset(&signalMask, SIGQUIT);
+ sigaddset(&signalMask, SIGABRT);
+ sigaddset(&signalMask, SIGTERM);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+
+ /* Ignoring return status */
+ sigwait(&signalMask, &iSignal);
+}
+
+/**
+ * Start registered services.
+ *
+ * @return IPRT status code.
+ */
+static int vbclStartServices(void)
+{
+ int rc;
+ unsigned int iServiceId = 0;
+
+ VBoxClientVerbose(1, "Starting services...\n");
+ for (iServiceId = 0; iServiceId < RT_ELEMENTS(g_aServices); iServiceId++)
+ {
+ VBoxClientVerbose(1, "Starting service: %s\n", g_aServices[iServiceId].pszName);
+ rc = (g_aServices[iServiceId].pfnStart)();
+ if (RT_FAILURE(rc))
+ {
+ VBoxClientVerbose(1, "unable to start service: %s (%Rrc)\n", g_aServices[iServiceId].pszName, rc);
+ VBoxClientVerbose(1, "Rolling back..\n");
+
+ /* Stop running services */
+ do
+ {
+ VBoxClientVerbose(1, "Stopping service: %s\n", g_aServices[iServiceId].pszName);
+ int rcStop = (g_aServices[iServiceId].pfnStop)();
+ if (RT_FAILURE(rcStop))
+ VBoxClientVerbose(1, "unable to stop service: %s (%Rrc)\n", g_aServices[iServiceId].pszName, rcStop);
+ } while (--iServiceId != 0);
+
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ VBoxClientVerbose(1, "Services start completed.\n");
+
+ return rc;
+}
+
+/**
+ * Stop registered services.
+ *
+ * @return IPRT status code.
+ */
+static void vbclStopServices(void)
+{
+ unsigned int iServiceId = 0;
+
+ VBoxClientVerbose(1, "Stopping services...\n");
+ for (iServiceId = 0; iServiceId < RT_ELEMENTS(g_aServices); iServiceId++)
+ {
+ VBoxClientVerbose(1, "Stopping service: %s\n", g_aServices[iServiceId].pszName);
+ int rc = (g_aServices[iServiceId].pfnStop)();
+ if (RT_FAILURE(rc))
+ VBoxClientVerbose(1, "unable to stop service: %s (%Rrc)\n", g_aServices[iServiceId].pszName, rc);
+ }
+ VBoxClientVerbose(1, "Services stop completed\n");
+}
+
+
+static void usage(char *sProgName)
+{
+ RTPrintf("usage: %s [-fvl]\n", sProgName);
+ RTPrintf(" -f\tRun in foreground (default: no)\n");
+ RTPrintf(" -v\tIncrease verbosity level (default: no verbosity)\n");
+ RTPrintf(" -l\tSpecify log file name (default: no log file)\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ int c;
+
+ bool fDemonize = true;
+ static char *szLogFileName = NULL;
+
+ rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("RTR3InitExe() failed: (%Rrc)\n", rc);
+ return RTMsgInitFailure(rc);
+ }
+
+ /* Parse command line */
+ while((c = getopt(argc, argv, "fvl:")) != -1)
+ {
+ switch(c)
+ {
+ case 'f':
+ fDemonize = false;
+ break;
+ case 'v':
+ g_cVerbosity++;
+ break;
+ case 'l':
+ szLogFileName = RTStrDup(optarg);
+ break;
+
+ default : usage(argv[0]);
+ }
+ }
+
+ /* No more arguments allowed */
+ if ((argc - optind) != 0)
+ usage(argv[0]);
+
+ if (fDemonize)
+ {
+ rc = RTProcDaemonizeUsingFork(true /* fNoChDir */, false /* fNoClose */, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTPrintf("failed to run into background\n");
+ return 1;
+ }
+ }
+
+ rc = VbglR3Init();
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbclInitLogger(szLogFileName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbclStartServices();
+ if (RT_SUCCESS(rc))
+ {
+ vbclWait();
+ vbclStopServices();
+ }
+ else
+ {
+ RTPrintf("failed to start services: (%Rrc)\n", rc);
+ }
+
+ vbclTermLogger(szLogFileName);
+ }
+ else
+ {
+ RTPrintf("failed to start logger: (%Rrc)\n", rc);
+ }
+
+ VbglR3Term();
+ }
+ else
+ {
+ RTPrintf("failed to initialize guest library: (%Rrc)\n", rc);
+ }
+
+ return 0;
+}
diff --git a/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboard.cpp b/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboard.cpp
new file mode 100644
index 00000000..cab193f5
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboard.cpp
@@ -0,0 +1,341 @@
+/** $Id: VBoxClientClipboard.cpp $ */
+/** @file
+ * VBoxClient - Shared Slipboard Dispatcher, Darwin.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Carbon/Carbon.h>
+
+#include <iprt/asm.h>
+#include <iprt/stream.h>
+#include <iprt/thread.h>
+#include <iprt/critsect.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include "VBoxClientInternal.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Host clipboard connection client ID */
+static uint32_t g_u32ClientId;
+/* Guest clipboard reference */
+static PasteboardRef g_PasteboardRef;
+/* Dispatcher tharead handle */
+RTTHREAD g_DispatcherThread;
+/* Pasteboard polling tharead handle */
+RTTHREAD g_GuestPasteboardThread;
+/* Flag that indicates whether or not dispatcher and Pasteboard polling threada should stop */
+static bool volatile g_fShouldStop;
+/* Barrier for Pasteboard */
+static RTCRITSECT g_critsect;
+
+
+/*********************************************************************************************************************************
+* Local Macros *
+*********************************************************************************************************************************/
+
+#define VBOXCLIENT_SERVICE_NAME "clipboard"
+
+
+/*********************************************************************************************************************************
+* Local Function Prototypes *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) vbclClipboardStop(void);
+
+
+/**
+ * Clipboard dispatcher function.
+ *
+ * Forwards cliproard content between host and guest.
+ *
+ * @param ThreadSelf Unused parameter.
+ * @param pvUser Unused parameter.
+ *
+ * @return IPRT status code.
+ */
+static DECLCALLBACK(int) vbclClipboardDispatcher(RTTHREAD ThreadSelf, void *pvUser)
+{
+ bool fQuit = false;
+ NOREF(ThreadSelf);
+ NOREF(pvUser);
+
+ VBoxClientVerbose(2, "starting host clipboard polling thread\n");
+
+ /*
+ * Block all signals for this thread. Only the main thread will handle signals.
+ */
+ sigset_t signalMask;
+ sigfillset(&signalMask);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+
+ while (!fQuit && !ASMAtomicReadBool(&g_fShouldStop))
+ {
+ int rc;
+ uint32_t Msg;
+ uint32_t fFormats;
+
+ VBoxClientVerbose(2, "waiting for new host request\n");
+
+ rc = VbglR3ClipboardGetHostMsgOld(g_u32ClientId, &Msg, &fFormats);
+ if (RT_SUCCESS(rc))
+ {
+ RTCritSectEnter(&g_critsect);
+ switch (Msg)
+ {
+ /* The host is terminating */
+ case VBOX_SHCL_HOST_MSG_QUIT:
+ VBoxClientVerbose(2, "host requested quit\n");
+ fQuit = true;
+ break;
+
+ /* The host needs data in the specified format */
+ case VBOX_SHCL_HOST_MSG_READ_DATA:
+ VBoxClientVerbose(2, "host requested guest's clipboard read\n");
+ rc = vbclClipboardForwardToHost(g_u32ClientId, g_PasteboardRef, fFormats);
+ AssertMsg(RT_SUCCESS(rc), ("paste to host failed\n"));
+ break;
+
+ /* The host has announced available clipboard formats */
+ case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ VBoxClientVerbose(2, "host requested guest's clipboard write\n");
+ rc = vbclClipboardForwardToGuest(g_u32ClientId, g_PasteboardRef, fFormats);
+ AssertMsg(RT_SUCCESS(rc), ("paste to guest failed\n"));
+ break;
+
+ default:
+ VBoxClientVerbose(2, "received unknow command from host service\n");
+ RTThreadSleep(1000);
+ }
+
+ RTCritSectLeave(&g_critsect);
+ }
+ else
+ {
+ RTThreadSleep(1000);
+ }
+ }
+
+ VBoxClientVerbose(2, "host clipboard polling thread stopped\n");
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Clipboard dispatcher function.
+ *
+ * Forwards cliproard content between host and guest.
+ *
+ * @param hThreadSelf Unused parameter.
+ * @param pvUser Unused parameter.
+ *
+ * @return IPRT status code.
+ */
+static DECLCALLBACK(int) vbclGuestPasteboardPoll(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf, pvUser);
+
+ /*
+ * Block all signals for this thread. Only the main thread will handle signals.
+ */
+ sigset_t signalMask;
+ sigfillset(&signalMask);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+
+ VBoxClientVerbose(2, "starting guest clipboard polling thread\n");
+
+ while (!ASMAtomicReadBool(&g_fShouldStop))
+ {
+ PasteboardSyncFlags fSyncFlags;
+ uint32_t fFormats;
+ int rc;
+
+ RTCritSectEnter(&g_critsect);
+
+ fSyncFlags = PasteboardSynchronize(g_PasteboardRef);
+ if (fSyncFlags & kPasteboardModified)
+ {
+ fFormats = vbclClipboardGetAvailableFormats(g_PasteboardRef);
+ rc = VbglR3ClipboardReportFormats(g_u32ClientId, fFormats);
+ if (RT_FAILURE(rc))
+ {
+ VBoxClientVerbose(2, "failed to report pasteboard update (%Rrc)\n", rc);
+ }
+ else
+ {
+ VBoxClientVerbose(2, "guest clipboard update reported: %d\n", (int)fFormats);
+ }
+ }
+
+ RTCritSectLeave(&g_critsect);
+
+ /* Check pasteboard every 200 ms */
+ RTThreadSleep(200);
+ }
+
+ VBoxClientVerbose(2, "guest clipboard polling thread stopped\n");
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize host and guest clipboards, start clipboard dispatcher loop.
+ *
+ * @return IPRT status code.
+ */
+static DECLCALLBACK(int) vbclClipboardStart(void)
+{
+ int rc;
+
+ VBoxClientVerbose(2, "starting clipboard\n");
+
+ rc = RTCritSectInit(&g_critsect);
+ if (RT_FAILURE(rc))
+ return VERR_GENERAL_FAILURE;
+
+ rc = VbglR3ClipboardConnect(&g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ {
+ rc = PasteboardCreate(kPasteboardClipboard, &g_PasteboardRef);
+ if (rc == noErr)
+ {
+ /* Start dispatcher loop */
+ ASMAtomicWriteBool(&g_fShouldStop, false);
+ rc = RTThreadCreate(&g_DispatcherThread,
+ vbclClipboardDispatcher,
+ (void *)NULL,
+ 0,
+ RTTHREADTYPE_DEFAULT,
+ RTTHREADFLAGS_WAITABLE,
+ VBOXCLIENT_SERVICE_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ /* Start dispatcher loop */
+ ASMAtomicWriteBool(&g_fShouldStop, false);
+ rc = RTThreadCreate(&g_GuestPasteboardThread,
+ vbclGuestPasteboardPoll,
+ (void *)NULL,
+ 0,
+ RTTHREADTYPE_DEFAULT,
+ RTTHREADFLAGS_WAITABLE,
+ VBOXCLIENT_SERVICE_NAME);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ /* Stop dispatcher thread */
+ ASMAtomicWriteBool(&g_fShouldStop, true);
+ RTThreadWait(g_DispatcherThread, 10 * 1000 /* Wait 10 seconds */, NULL);
+
+ }
+ VBoxClientVerbose(2, "unable create dispatcher thread\n");
+ CFRelease(g_PasteboardRef);
+ g_PasteboardRef = NULL;
+
+ }
+ else
+ {
+ rc = VERR_GENERAL_FAILURE;
+ VBoxClientVerbose(2, "unable access guest clipboard\n");
+ }
+
+ vbclClipboardStop();
+
+ }
+ else
+ {
+ VBoxClientVerbose(2, "unable to establish connection to clipboard service: %Rrc\n", rc);
+ }
+
+ RTCritSectDelete(&g_critsect);
+
+ return rc;
+}
+
+
+/**
+ * Release host and guest clipboards, stop clipboard dispatcher loop.
+ *
+ * @return IPRT status code.
+ */
+static DECLCALLBACK(int) vbclClipboardStop(void)
+{
+ int rc;
+
+ VBoxClientVerbose(2, "stopping clipboard\n");
+
+ AssertReturn(g_u32ClientId != 0, VERR_GENERAL_FAILURE);
+
+ VbglR3ClipboardReportFormats(g_u32ClientId, 0);
+
+ rc = VbglR3ClipboardDisconnect(g_u32ClientId);
+ if (RT_SUCCESS(rc))
+ g_u32ClientId = 0;
+ else
+ VBoxClientVerbose(2, "unable to close clipboard service connection: %Rrc\n", rc);
+
+ if (g_PasteboardRef)
+ {
+ CFRelease(g_PasteboardRef);
+ g_PasteboardRef = NULL;
+ }
+
+ /* Stop dispatcher thread */
+ ASMAtomicWriteBool(&g_fShouldStop, true);
+ rc = RTThreadWait(g_DispatcherThread, 10 * 1000 /* Wait 10 seconds */, NULL);
+ if (RT_FAILURE(rc))
+ VBoxClientVerbose(2, "failed to stop dispatcher thread");
+
+ /* Stop Pasteboard polling thread */
+ rc = RTThreadWait(g_GuestPasteboardThread, 10 * 1000 /* Wait 10 seconds */, NULL);
+ if (RT_FAILURE(rc))
+ VBoxClientVerbose(2, "failed to stop pasteboard polling thread");
+
+ RTCritSectDelete(&g_critsect);
+
+ return rc;
+}
+
+
+/* Clipboard service struct */
+VBOXCLIENTSERVICE g_ClipboardService =
+{
+ /* pszName */
+ VBOXCLIENT_SERVICE_NAME,
+
+ /* pfnStart */
+ vbclClipboardStart,
+
+ /* pfnStop */
+ vbclClipboardStop,
+};
diff --git a/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardGuestToHost.cpp b/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardGuestToHost.cpp
new file mode 100644
index 00000000..2cef7257
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardGuestToHost.cpp
@@ -0,0 +1,390 @@
+/** $Id: VBoxClientClipboardGuestToHost.cpp $ */
+/** @file
+ * VBoxClient - Shared Clipboard Guest -> Host copying, Darwin.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Carbon/Carbon.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include <iprt/thread.h>
+#include <iprt/mem.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/utf16.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include "VBoxClientInternal.h"
+
+/**
+ * Walk through pasteboard items and report currently available item types.
+ *
+ * @param pPasteboard Reference to guest Pasteboard.
+ * @returns Available formats bit field.
+ */
+uint32_t vbclClipboardGetAvailableFormats(PasteboardRef pPasteboard)
+{
+ uint32_t fFormats = 0;
+ ItemCount cItems = 0;
+ ItemCount iItem;
+ OSStatus rc;
+
+#define VBOXCL_ADD_FORMAT_IF_PRESENT(a_kDarwinFmt, a_fVBoxFmt) \
+ if (PasteboardCopyItemFlavorData(pPasteboard, iItemID, a_kDarwinFmt, &flavorData) == noErr) \
+ { \
+ fFormats |= (uint32_t)a_fVBoxFmt; \
+ CFRelease(flavorData); \
+ }
+
+ rc = PasteboardGetItemCount(pPasteboard, &cItems);
+ AssertReturn((rc == noErr) && (cItems > 0), fFormats);
+
+ for (iItem = 1; iItem <= cItems; iItem++)
+ {
+ PasteboardItemID iItemID;
+ CFDataRef flavorData;
+
+ rc = PasteboardGetItemIdentifier(pPasteboard, iItem, &iItemID);
+ if (rc == noErr)
+ {
+ VBOXCL_ADD_FORMAT_IF_PRESENT(kUTTypeUTF16PlainText, VBOX_SHCL_FMT_UNICODETEXT);
+ VBOXCL_ADD_FORMAT_IF_PRESENT(kUTTypeUTF8PlainText, VBOX_SHCL_FMT_UNICODETEXT);
+ VBOXCL_ADD_FORMAT_IF_PRESENT(kUTTypeBMP, VBOX_SHCL_FMT_BITMAP );
+ VBOXCL_ADD_FORMAT_IF_PRESENT(kUTTypeHTML, VBOX_SHCL_FMT_HTML );
+
+#ifdef CLIPBOARD_DUMP_CONTENT_FORMATS
+ CFArrayRef flavorTypeArray;
+ CFIndex flavorCount;
+ CFStringRef flavorType;
+
+ rc = PasteboardCopyItemFlavors(pPasteboard, iItemID, &flavorTypeArray);
+ if (rc == noErr)
+ {
+ VBoxClientVerbose(3, "SCAN..\n");
+ flavorCount = CFArrayGetCount(flavorTypeArray);
+ VBoxClientVerbose(3, "SCAN (%d)..\n", (int)flavorCount);
+ for(CFIndex flavorIndex = 0; flavorIndex < flavorCount; flavorIndex++)
+ {
+ VBoxClientVerbose(3, "SCAN #%d..\n", (int)flavorIndex);
+ flavorType = (CFStringRef)CFArrayGetValueAtIndex(flavorTypeArray, flavorIndex);
+
+ CFDataRef flavorData1;
+ rc = PasteboardCopyItemFlavorData(pPasteboard, iItemID, flavorType, &flavorData1);
+ if (rc == noErr)
+ {
+ VBoxClientVerbose(3, "Found: %s, size: %d\n", (char *)CFStringGetCStringPtr(flavorType, kCFStringEncodingMacRoman), (int)CFDataGetLength(flavorData1));
+ CFRelease(flavorData1);
+ }
+ }
+ VBoxClientVerbose(3, "SCAN COMPLETE\n");
+ CFRelease(flavorTypeArray);
+ }
+#endif /* CLIPBOARD_DUMP_CONTENT_FORMATS */
+ }
+ }
+
+#undef VBOXCL_ADD_FORMAT_IF_PRESENT
+
+ return fFormats;
+}
+
+
+/**
+ * Search for content of specified type in guest clipboard buffer and put
+ * it into newly allocated buffer.
+ *
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param fFormat Data formats we are looking for.
+ * @param ppvData Where to return pointer to the received data. M
+ * @param pcbData Where to return the size of the data.
+ * @param pcbAlloc Where to return the size of the memory block
+ * *ppvData pointes to. (Usually greater than *cbData
+ * because the allocation is page aligned.)
+ * @returns IPRT status code.
+ */
+static int vbclClipboardReadGuestData(PasteboardRef pPasteboard, CFStringRef sFormat, void **ppvData, uint32_t *pcbData,
+ uint32_t *pcbAlloc)
+{
+ ItemCount cItems, iItem;
+ OSStatus rc;
+
+ void *pvData = NULL;
+ uint32_t cbData = 0;
+ uint32_t cbAlloc = 0;
+
+ AssertPtrReturn(ppvData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbData, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbAlloc, VERR_INVALID_POINTER);
+
+ rc = PasteboardGetItemCount(pPasteboard, &cItems);
+ AssertReturn(rc == noErr, VERR_INVALID_PARAMETER);
+ AssertReturn(cItems > 0, VERR_INVALID_PARAMETER);
+
+ /* Walk through all the items in PasteBoard in order to find
+ that one that correcponds to requested data format. */
+ for (iItem = 1; iItem <= cItems; iItem++)
+ {
+ PasteboardItemID iItemID;
+ CFDataRef flavorData;
+
+ /* Now, get the item's flavors that corresponds to requested type. */
+ rc = PasteboardGetItemIdentifier(pPasteboard, iItem, &iItemID);
+ AssertReturn(rc == noErr, VERR_INVALID_PARAMETER);
+ rc = PasteboardCopyItemFlavorData(pPasteboard, iItemID, sFormat, &flavorData);
+ if (rc == noErr)
+ {
+ void *flavorDataPtr = (void *)CFDataGetBytePtr(flavorData);
+ cbData = CFDataGetLength(flavorData);
+ if (flavorDataPtr && cbData > 0)
+ {
+ cbAlloc = RT_ALIGN_32(cbData, PAGE_SIZE);
+ pvData = RTMemPageAllocZ(cbAlloc);
+ if (pvData)
+ memcpy(pvData, flavorDataPtr, cbData);
+ }
+
+ CFRelease(flavorData);
+
+ /* Found first matching item, no more search. */
+ break;
+ }
+
+ }
+
+ /* Found match */
+ if (pvData)
+ {
+ *ppvData = pvData;
+ *pcbData = cbData;
+ *pcbAlloc = cbAlloc;
+
+ return VINF_SUCCESS;
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
+
+
+/**
+ * Release resources occupied by vbclClipboardReadGuestData().
+ */
+static void vbclClipboardReleaseGuestData(void **ppvData, uint32_t cbAlloc)
+{
+ AssertReturnVoid(ppvData);
+ RTMemPageFree(*ppvData, cbAlloc);
+ *ppvData = NULL;
+}
+
+/**
+ * Pass data to host.
+ */
+static int vbclClipboardHostPasteData(uint32_t u32ClientId, uint32_t u32Format, const void *pvData, uint32_t cbData)
+{
+ /* Allow empty buffers */
+ if (cbData == 0)
+ return VbglR3ClipboardWriteData(u32ClientId, u32Format, NULL, 0);
+
+ AssertReturn(pvData, VERR_INVALID_PARAMETER);
+ return VbglR3ClipboardWriteData(u32ClientId, u32Format, (void *)pvData, cbData); /** @todo r=bird: Why on earth does a write function like VbglR3ClipboardWriteData take a non-const parameter? */
+}
+
+/**
+ * Paste text data into host clipboard.
+ *
+ * @param u32ClientId Host clipboard connection.
+ * @param pwszData UTF-16 encoded string.
+ * @param cbData The length of the string, in bytes, probably
+ * including a terminating zero.
+ */
+static int vbclClipboardHostPasteText(uint32_t u32ClientId, PRTUTF16 pwszData, uint32_t cbData)
+{
+ AssertReturn(cbData > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pwszData, VERR_INVALID_POINTER);
+
+ size_t cwcTmp; /* (includes a schwarzenegger character) */
+ int rc = ShClUtf16LFLenUtf8(pwszData, cbData / sizeof(RTUTF16), &cwcTmp);
+ AssertRCReturn(rc, rc);
+
+ cwcTmp++; /* Add space for terminator. */
+
+ PRTUTF16 pwszTmp = (PRTUTF16)RTMemAlloc(cwcTmp * sizeof(RTUTF16));
+ AssertReturn(pwszTmp, VERR_NO_MEMORY);
+
+ rc = ShClConvUtf16LFToCRLF(pwszData, cbData / sizeof(RTUTF16), pwszTmp, cwcTmp);
+ if (RT_SUCCESS(rc))
+ rc = vbclClipboardHostPasteData(u32ClientId, VBOX_SHCL_FMT_UNICODETEXT,
+ pwszTmp, cwcTmp * sizeof(RTUTF16));
+
+ RTMemFree(pwszTmp);
+
+ return rc;
+}
+
+
+/**
+ * Paste a bitmap onto the host clipboard.
+ *
+ * @param u32ClientId Host clipboard connection.
+ * @param pvData The bitmap data.
+ * @param cbData The size of the bitmap.
+ */
+static int vbclClipboardHostPasteBitmap(uint32_t u32ClientId, void *pvData, uint32_t cbData)
+{
+ const void *pvDib;
+ size_t cbDib;
+ int rc = ShClBmpGetDib(pvData, cbData, &pvDib, &cbDib);
+ AssertRCReturn(rc, rc);
+
+ rc = vbclClipboardHostPasteData(u32ClientId, VBOX_SHCL_FMT_BITMAP, pvDib, cbDib);
+
+ return rc;
+}
+
+
+/**
+ * Read guest's clipboard buffer and forward its content to host.
+ *
+ * @param u32ClientId Host clipboard connection.
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param fFormats List of data formats (bit field) received from host.
+ *
+ * @returns IPRT status code.
+ */
+int vbclClipboardForwardToHost(uint32_t u32ClientId, PasteboardRef pPasteboard, uint32_t fFormats)
+{
+ int rc = VINF_SUCCESS;
+
+ void *pvData = NULL;
+ uint32_t cbData = 0;
+ uint32_t cbAlloc = 0;
+
+ VBoxClientVerbose(3, "vbclClipboardForwardToHost: %d\n", fFormats);
+
+ /* Walk across all item(s) formats */
+ uint32_t fFormatsLeft = fFormats;
+ while (fFormatsLeft)
+ {
+ if (fFormatsLeft & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ VBoxClientVerbose(3, "requested VBOX_SHCL_FMT_UNICODETEXT: %d\n", fFormats);
+
+ RTUTF16 *pUtf16Str = NULL;
+
+ /* First, try to get UTF16 encoded buffer */
+ rc = vbclClipboardReadGuestData(pPasteboard, kUTTypeUTF16PlainText, &pvData, &cbData, &cbAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTUtf16DupEx(&pUtf16Str, (PRTUTF16)pvData, 0);
+ if (RT_FAILURE(rc))
+ pUtf16Str = NULL;
+ }
+ else /* Failed to get UTF16 buffer */
+ {
+ /* Then, try to get UTF8 encoded buffer */
+ rc = vbclClipboardReadGuestData(pPasteboard, kUTTypeUTF8PlainText, &pvData, &cbData, &cbAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrToUtf16((const char *)pvData, &pUtf16Str);
+ if (RT_FAILURE(rc))
+ pUtf16Str = NULL;
+ }
+ }
+
+ /* Finally, we got UTF16 encoded buffer */
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbclClipboardHostPasteText(u32ClientId, (PRTUTF16)pvData, cbData);
+
+ if (pUtf16Str)
+ {
+ RTUtf16Free(pUtf16Str);
+ pUtf16Str = NULL;
+ }
+
+ vbclClipboardReleaseGuestData(&pvData, cbAlloc);
+ }
+ else
+ {
+ /* No data found or error occurred: send empty buffer */
+ rc = vbclClipboardHostPasteData(u32ClientId, VBOX_SHCL_FMT_UNICODETEXT, NULL, 0);
+ }
+
+ fFormatsLeft &= ~(uint32_t)VBOX_SHCL_FMT_UNICODETEXT;
+ }
+
+ else if (fFormatsLeft & VBOX_SHCL_FMT_BITMAP)
+ {
+ VBoxClientVerbose(3, "requested VBOX_SHCL_FMT_BITMAP: %d\n", fFormats);
+
+ rc = vbclClipboardReadGuestData(pPasteboard, kUTTypeBMP, &pvData, &cbData, &cbAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbclClipboardHostPasteBitmap(u32ClientId, pvData, cbData);
+ vbclClipboardReleaseGuestData(&pvData, cbAlloc);
+ }
+ else
+ {
+ /* No data found or error occurred: send empty buffer */
+ rc = vbclClipboardHostPasteData(u32ClientId, VBOX_SHCL_FMT_BITMAP, NULL, 0);
+ }
+
+ fFormatsLeft &= ~(uint32_t)VBOX_SHCL_FMT_BITMAP;
+ }
+
+ else if (fFormatsLeft & VBOX_SHCL_FMT_HTML)
+ {
+ VBoxClientVerbose(3, "requested VBOX_SHCL_FMT_HTML: %d\n", fFormats);
+
+ rc = vbclClipboardReadGuestData(pPasteboard, kUTTypeHTML, &pvData, &cbData, &cbAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbclClipboardHostPasteData(u32ClientId, VBOX_SHCL_FMT_HTML, pvData, cbData);
+ vbclClipboardReleaseGuestData(&pvData, cbAlloc);
+ }
+ else
+ {
+ /* No data found or error occurred: send empty buffer */
+ rc = vbclClipboardHostPasteData(u32ClientId, VBOX_SHCL_FMT_HTML, NULL, 0);
+ }
+
+ fFormatsLeft &= ~(uint32_t)VBOX_SHCL_FMT_HTML;
+ }
+
+ else
+ {
+ VBoxClientVerbose(3, "requested data in unsupported format: %#x\n", fFormatsLeft);
+ break;
+ }
+ }
+
+ return rc; /** @todo r=bird: If there are multiple formats available, which rc is returned here? Does it matter? */
+}
diff --git a/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardHostToGuest.cpp b/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardHostToGuest.cpp
new file mode 100644
index 00000000..1b2d729f
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxClient/VBoxClientClipboardHostToGuest.cpp
@@ -0,0 +1,315 @@
+/** $Id: VBoxClientClipboardHostToGuest.cpp $ */
+/** @file
+ * VBoxClient - Shared Clipboard Host -> Guest copying, Darwin.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Carbon/Carbon.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/thread.h>
+#include <iprt/utf16.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include "VBoxClientInternal.h"
+
+/**
+ * Allocate memory for host buffer and receive it.
+ *
+ * @param u32ClientId Host connection.
+ * @param fFormat Buffer data format.
+ * @param pData Where to store received data.
+ * @param cbDataSize The size of the received data.
+ * @param cbMemSize The actual size of memory occupied by *pData.
+ *
+ * @returns IPRT status code.
+ */
+static int vbclClipboardReadHostData(uint32_t u32ClientId, uint32_t fFormat, void **pData, uint32_t *cbDataSize, uint32_t *cbMemSize)
+{
+ int rc;
+
+ AssertReturn(pData && cbDataSize && cbMemSize, VERR_INVALID_PARAMETER);
+
+ uint32_t cbDataSizeInternal = _4K;
+ uint32_t cbMemSizeInternal = cbDataSizeInternal;
+ void *pDataInternal = RTMemPageAllocZ(cbDataSizeInternal);
+
+ if (!pDataInternal)
+ return VERR_NO_MEMORY;
+
+ rc = VbglR3ClipboardReadData(u32ClientId, fFormat, pDataInternal, cbMemSizeInternal, &cbDataSizeInternal);
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ /* Reallocate bigger buffer and receive all the data */
+ RTMemPageFree(pDataInternal, cbMemSizeInternal);
+ cbDataSizeInternal = cbMemSizeInternal = RT_ALIGN_32(cbDataSizeInternal, PAGE_SIZE);
+ pDataInternal = RTMemPageAllocZ(cbMemSizeInternal);
+ if (!pDataInternal)
+ return VERR_NO_MEMORY;
+
+ rc = VbglR3ClipboardReadData(u32ClientId, fFormat, pDataInternal, cbMemSizeInternal, &cbDataSizeInternal);
+ }
+
+ /* Error occurred of zero-sized buffer */
+ if (RT_FAILURE(rc))
+ {
+ RTMemPageFree(pDataInternal, cbMemSizeInternal);
+ return VERR_NO_MEMORY;
+ }
+
+ *pData = pDataInternal;
+ *cbDataSize = cbDataSizeInternal;
+ *cbMemSize = cbMemSizeInternal;
+
+ return rc;
+}
+
+/**
+ * Release memory occupied by host buffer.
+ *
+ * @param pData Pointer to memory occupied by host buffer.
+ * @param cbMemSize The actual size of memory occupied by *pData.
+ */
+static void vbclClipboardReleaseHostData(void **pData, uint32_t cbMemSize)
+{
+ AssertReturnVoid(pData && cbMemSize > 0);
+ RTMemPageFree(*pData, cbMemSize);
+}
+
+/**
+ * Paste buffer into guest clipboard.
+ *
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param pData Data to be pasted.
+ * @param cbDataSize The size of *pData.
+ * @param fFormat Buffer data format.
+ * @param fClear Whether or not clear guest clipboard before insert data.
+ *
+ * @returns IPRT status code.
+ */
+static int vbclClipboardGuestPasteData(PasteboardRef pPasteboard, UInt8 *pData, CFIndex cbDataSize, CFStringRef sFormat, bool fClear)
+{
+ PasteboardItemID itemId = (PasteboardItemID)1;
+ CFDataRef textData = NULL;
+ OSStatus rc;
+
+ /* Ignoring sunchronization flags here */
+ PasteboardSynchronize(pPasteboard);
+
+ if (fClear)
+ {
+ rc = PasteboardClear(pPasteboard);
+ AssertReturn(rc == noErr, VERR_NOT_SUPPORTED);
+ }
+
+ /* Create a CData object which we could pass to the pasteboard */
+ if ((textData = CFDataCreate(kCFAllocatorDefault, pData, cbDataSize)))
+ {
+ /* Put the Utf-8 version to the pasteboard */
+ rc = PasteboardPutItemFlavor(pPasteboard, itemId, sFormat, textData, 0);
+ CFRelease(textData);
+ if (rc != noErr)
+ {
+ VBoxClientVerbose(3, "unable to put data into guest's clipboard: %d\n", rc);
+ return VERR_GENERAL_FAILURE;
+ }
+ }
+ else
+ return VERR_NO_MEMORY;
+
+ /* Synchronize updated content */
+ PasteboardSynchronize(pPasteboard);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Paste text data into guest clipboard.
+ *
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param pData Data to be pasted.
+ * @param cbDataSize Size of *pData.
+ */
+static int vbclClipboardGuestPasteText(PasteboardRef pPasteboard, void *pData, uint32_t cbDataSize)
+{
+ AssertReturn(pData, VERR_INVALID_PARAMETER);
+
+ /* Skip zero-sized buffer */
+ AssertReturn(cbDataSize > 0, VINF_SUCCESS);
+
+ /* If buffer content is Unicode text, then deliver
+ it in both formats UTF16 (original) and UTF8. */
+
+ /* Convert END-OF-LINE */
+ size_t cwcDst;
+ int rc = ShClUtf16CRLFLenUtf8((RTUTF16 *)pData, cbDataSize / sizeof(RTUTF16), &cwcDst);
+ AssertRCReturn(rc, rc);
+
+ cwcDst++; /* Add space for terminator. */
+
+ PRTUTF16 pwszDst = (RTUTF16 *)RTMemAlloc(cwcDst * sizeof(RTUTF16));
+ AssertPtrReturn(pwszDst, VERR_NO_MEMORY);
+
+ rc = ShClConvUtf16CRLFToLF((RTUTF16 *)pData, cbDataSize / sizeof(RTUTF16), pwszDst, cwcDst);
+ if (RT_SUCCESS(rc))
+ {
+ /* Paste UTF16 */
+ rc = vbclClipboardGuestPasteData(pPasteboard, (UInt8 *)pwszDst, cwcDst * sizeof(RTUTF16), kUTTypeUTF16PlainText, true);
+ if (RT_SUCCESS(rc))
+ {
+ /* Paste UTF8 */
+ char *pszDst;
+ rc = RTUtf16ToUtf8((PRTUTF16)pwszDst, &pszDst);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbclClipboardGuestPasteData(pPasteboard, (UInt8 *)pszDst, strlen(pszDst), kUTTypeUTF8PlainText, false);
+ RTStrFree(pszDst);
+ }
+ }
+
+ }
+
+ RTMemFree(pwszDst);
+
+ return rc;
+}
+
+/**
+ * Paste picture data into guest clipboard.
+ *
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param pData Data to be pasted.
+ * @param cbDataSize The size of *pData.
+ *
+ * @returns IPRT status code.
+ */
+static int vbclClipboardGuestPastePicture(PasteboardRef pPasteboard, void *pData, uint32_t cbDataSize)
+{
+ int rc;
+ void *pBmp;
+ size_t cbBmpSize;
+
+ AssertReturn(pData, VERR_INVALID_PARAMETER);
+ /* Skip zero-sized buffer */
+ AssertReturn(cbDataSize > 0, VINF_SUCCESS);
+
+ rc = ShClDibToBmp(pData, cbDataSize, &pBmp, &cbBmpSize);
+ AssertReturn(RT_SUCCESS(rc), rc);
+
+ rc = vbclClipboardGuestPasteData(pPasteboard, (UInt8 *)pBmp, cbBmpSize, kUTTypeBMP, true);
+ RTMemFree(pBmp);
+
+ return rc;
+}
+
+/**
+ * Read host's clipboard buffer and put its content to guest clipboard.
+ *
+ * @param u32ClientId Host connection.
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param fFormats List of data formats (bit field) received from host.
+ *
+ * @returns IPRT status code.
+ */
+int vbclClipboardForwardToGuest(uint32_t u32ClientId, PasteboardRef pPasteboard, uint32_t fFormats)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ void *pData;
+ uint32_t cbDataSize, cbMemSize;
+ uint32_t fFormatsInternal = fFormats;
+
+ /* Walk across all item(s) formats */
+ while (fFormatsInternal)
+ {
+ if (fFormatsInternal & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ VBoxClientVerbose(3, "found VBOX_SHCL_FMT_UNICODETEXT: %d\n", fFormatsInternal);
+
+ rc = vbclClipboardReadHostData(u32ClientId, VBOX_SHCL_FMT_UNICODETEXT, &pData, &cbDataSize, &cbMemSize);
+ if (RT_SUCCESS(rc))
+ {
+ /* Store data in guest buffer */
+ rc = vbclClipboardGuestPasteText(pPasteboard, pData, cbDataSize);
+
+ /* Release occupied resources */
+ vbclClipboardReleaseHostData(&pData, cbMemSize);
+ }
+
+ fFormatsInternal &= ~((uint32_t)VBOX_SHCL_FMT_UNICODETEXT);
+ }
+
+ else if (fFormatsInternal & VBOX_SHCL_FMT_BITMAP)
+ {
+ VBoxClientVerbose(3, "found VBOX_SHCL_FMT_BITMAP: %d\n", fFormatsInternal);
+
+ rc = vbclClipboardReadHostData(u32ClientId, VBOX_SHCL_FMT_BITMAP, &pData, &cbDataSize, &cbMemSize);
+ if (RT_SUCCESS(rc))
+ {
+ /* Store data in guest buffer */
+ rc = vbclClipboardGuestPastePicture(pPasteboard, pData, cbDataSize);
+
+ /* Release occupied resources */
+ vbclClipboardReleaseHostData(&pData, cbMemSize);
+ }
+
+ fFormatsInternal &= ~((uint32_t)VBOX_SHCL_FMT_BITMAP);
+ }
+
+ else if (fFormatsInternal & VBOX_SHCL_FMT_HTML)
+ {
+ VBoxClientVerbose(3, "found VBOX_SHCL_FMT_HTML: %d\n", fFormatsInternal);
+
+ rc = vbclClipboardReadHostData(u32ClientId, VBOX_SHCL_FMT_HTML, &pData, &cbDataSize, &cbMemSize);
+ if (RT_SUCCESS(rc))
+ {
+ /* Store data in guest buffer */
+ rc = vbclClipboardGuestPasteData(pPasteboard, (UInt8 *)pData, cbDataSize, kUTTypeHTML, true);
+
+ /* Release occupied resources */
+ vbclClipboardReleaseHostData(&pData, cbMemSize);
+ }
+
+ fFormatsInternal &= ~((uint32_t)VBOX_SHCL_FMT_HTML);
+ }
+
+ else
+ {
+ VBoxClientVerbose(3, "received data in unsupported format: %d\n", fFormats);
+ break;
+ }
+ }
+
+ return rc;
+}
diff --git a/src/VBox/Additions/darwin/VBoxClient/VBoxClientInternal.h b/src/VBox/Additions/darwin/VBoxClient/VBoxClientInternal.h
new file mode 100644
index 00000000..73a93b2a
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxClient/VBoxClientInternal.h
@@ -0,0 +1,112 @@
+/** $Id: VBoxClientInternal.h $ */
+/** @file
+ * VBoxClient - common definitions, Darwin.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_darwin_VBoxClient_VBoxClientInternal_h
+#define GA_INCLUDED_SRC_darwin_VBoxClient_VBoxClientInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/VBoxGuestLib.h>
+#include <Carbon/Carbon.h>
+
+/* Service description */
+typedef struct
+{
+ /** The service name. */
+ const char *pszName;
+
+ /**
+ * Start service.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnStart,(void));
+
+ /**
+ * Stop service.
+ * @returns VBox status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnStop,(void));
+
+} VBOXCLIENTSERVICE;
+
+
+/*
+ * Services
+ */
+
+RT_C_DECLS_BEGIN
+
+extern VBOXCLIENTSERVICE g_ClipboardService;
+
+RT_C_DECLS_END
+
+
+/*
+ * Functions
+ */
+
+/**
+ * Displays a verbose message.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+extern void VBoxClientVerbose(int iLevel, const char *pszFormat, ...);
+
+/**
+ * Walk through pasteboard items and report currently available item types.
+ *
+ * @param pPasteboard Reference to guest Pasteboard.
+ # @returns Available formats bit field.
+ */
+extern uint32_t vbclClipboardGetAvailableFormats(PasteboardRef pPasteboard);
+
+/**
+ * Read host's clipboard buffer and put its content to guest clipboard.
+ *
+ * @param u32ClientId Host connection.
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param fFormats List of data formats (bit field) received from host.
+ *
+ * @returns IPRT status code.
+ */
+extern int vbclClipboardForwardToGuest(uint32_t u32ClientId, PasteboardRef pPasteboard, uint32_t fFormats);
+
+/**
+ * Read guest's clipboard buffer and forward its content to host.
+ *
+ * @param u32ClientId Host clipboard connection.
+ * @param pPasteboard Guest PasteBoard reference.
+ * @param fFormats List of data formats (bit field) received from host.
+ *
+ * @returns IPRT status code.
+ */
+extern int vbclClipboardForwardToHost(uint32_t u32ClientId, PasteboardRef pPasteboard, uint32_t fFormats);
+
+#endif /* !GA_INCLUDED_SRC_darwin_VBoxClient_VBoxClientInternal_h */
diff --git a/src/VBox/Additions/darwin/VBoxSF/.scm-settings b/src/VBox/Additions/darwin/VBoxSF/.scm-settings
new file mode 100644
index 00000000..51fd9c2c
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/.scm-settings
@@ -0,0 +1,29 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the OS X shared folders driver.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+--filter-out-files /vboxfs.git.tar.bz2
+
diff --git a/src/VBox/Additions/darwin/VBoxSF/Info.plist b/src/VBox/Additions/darwin/VBoxSF/Info.plist
new file mode 100644
index 00000000..b4be6f70
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/Info.plist
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key> <string>English</string>
+ <key>CFBundleExecutable</key> <string>VBoxSF</string>
+ <key>CFBundleIconFile</key> <string></string>
+ <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxSF</string>
+ <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string>
+ <key>CFBundleName</key> <string>VBoxSF</string>
+ <key>CFBundlePackageType</key> <string>KEXT</string>
+ <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string>
+ <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string>
+ <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string>
+ <key>OSBundleCompatibleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string>
+ <key>IOKitPersonalities</key>
+
+ <dict>
+ <key>VBoxSF</key>
+ <dict>
+ <key>IOMatchCategory</key> <string>org_virtualbox_VBoxSF</string>
+ <key>IOClientClass</key> <string>VBoxSFClient</string>
+ <key>IOClass</key> <string>org_virtualbox_VBoxSF</string>
+ <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxSF</string>
+ <key>IOKitDebug</key> <integer>65535</integer>
+ <key>IOProviderClass</key> <string>IOPCIDevice</string>
+ <key>IONameMatch</key> <string>pci80ee,cafe</string>
+ </dict>
+ </dict>
+
+ <key>NSHumanReadableCopyright</key> <string>Copyright © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string>
+
+ <key>OSBundleLibraries</key>
+ <dict>
+ <key>com.apple.iokit.IOPCIFamily</key> <string>2.5</string>
+ <key>com.apple.kpi.bsd</key> <string>10.6</string>
+ <key>com.apple.kpi.iokit</key> <string>10.6</string>
+ <key>com.apple.kpi.libkern</key> <string>10.6</string>
+ <key>com.apple.kpi.mach</key> <string>10.6</string>
+ <key>com.apple.kpi.unsupported</key> <string>10.6</string>
+ <key>org.virtualbox.kext.VBoxGuest</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string>
+ </dict>
+</dict>
+</plist>
diff --git a/src/VBox/Additions/darwin/VBoxSF/Makefile.kmk b/src/VBox/Additions/darwin/VBoxSF/Makefile.kmk
new file mode 100644
index 00000000..63ce78e6
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/Makefile.kmk
@@ -0,0 +1,80 @@
+# $Id: Makefile.kmk $
+## @file
+# sub-makefile for Darwin Shared Folders.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# VBoxSF - The shared folders kernel extension.
+#
+SYSMODS += VBoxSF
+VBoxSF_TEMPLATE = VBoxGuestR0Drv
+VBoxSF_INST = $(INST_ADDITIONS)VBoxSF.kext/Contents/MacOS/
+VBoxSF_DEFS = VBOX_WITH_HGCM
+VBoxSF_LIBS = $(VBOX_LIB_VBGL_R0)
+VBoxSF_SOURCES = \
+ VBoxSF.cpp \
+ VBoxSF-VfsOps.cpp \
+ VBoxSF-VNodeOps.cpp \
+ VBoxSF-Utils.cpp
+
+
+#
+# Files necessary to make a darwin kernel extension bundle.
+#
+INSTALLS += VBoxSF.kext
+VBoxSF.kext_INST = $(INST_ADDITIONS)VBoxSF.kext/Contents/
+VBoxSF.kext_SOURCES = $(VBoxSF.kext_0_OUTDIR)/Info.plist
+VBoxSF.kext_CLEAN = $(VBoxSF.kext_0_OUTDIR)/Info.plist
+
+$$(VBoxSF.kext_0_OUTDIR)/Info.plist: \
+ $(PATH_SUB_CURRENT)/Info.plist \
+ $(VBOX_VERSION_MK) | $$(dir $$@)
+ $(call MSG_GENERATE,VBoxSF,$@,$<)
+ $(QUIET)$(RM) -f $@
+ $(QUIET)$(SED) \
+ -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \
+ -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \
+ -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \
+ -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+g' \
+ -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \
+ -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \
+ -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \
+ --output $@ \
+ $<
+
+#
+# mount.vboxsf - The Shared Folders mounting tool.
+#
+PROGRAMS += mount.vboxsf
+mount.vboxsf_TEMPLATE = VBoxGuestR3Exe
+mount.vboxsf_SOURCES = mount.vboxsf.cpp
+mount.vboxsf_INST = $(INST_ADDITIONS)VBoxSF.kext/Contents/MacOS/
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp
new file mode 100644
index 00000000..feac592a
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-Utils.cpp
@@ -0,0 +1,608 @@
+/* $Id: VBoxSF-Utils.cpp $ */
+/** @file
+ * VBoxSF - Darwin Shared Folders, Utility Functions.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#include "VBoxSFInternal.h"
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <VBox/log.h>
+
+#if 0
+/**
+ * Helper function to create XNU VFS vnode object.
+ *
+ * @param mp Mount data structure
+ * @param type vnode type (directory, regular file, etc)
+ * @param pParent Parent vnode object (NULL for VBoxVFS root vnode)
+ * @param fIsRoot Flag that indicates if created vnode object is
+ * VBoxVFS root vnode (TRUE for VBoxVFS root vnode, FALSE
+ * for all aother vnodes)
+ * @param Path within Shared Folder
+ * @param ret Returned newly created vnode
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+vboxvfs_create_vnode_internal(struct mount *mp, enum vtype type, vnode_t pParent, int fIsRoot, PSHFLSTRING Path, vnode_t *ret)
+{
+ int rc;
+ vnode_t vnode;
+
+ vboxvfs_vnode_t *pVnodeData;
+ vboxvfs_mount_t *pMount;
+
+ AssertReturn(mp, EINVAL);
+
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp);
+ AssertReturn(pMount, EINVAL);
+ AssertReturn(pMount->pLockGroup, EINVAL);
+
+ AssertReturn(Path, EINVAL);
+
+ pVnodeData = (vboxvfs_vnode_t *)RTMemAllocZ(sizeof(vboxvfs_vnode_t));
+ AssertReturn(pVnodeData, ENOMEM);
+
+ /* Initialize private data */
+ pVnodeData->pHandle = SHFL_HANDLE_NIL;
+ pVnodeData->pPath = Path;
+
+ pVnodeData->pLockAttr = lck_attr_alloc_init();
+ if (pVnodeData->pLockAttr)
+ {
+ pVnodeData->pLock = lck_rw_alloc_init(pMount->pLockGroup, pVnodeData->pLockAttr);
+ if (pVnodeData->pLock)
+ {
+ struct vnode_fsparam vnode_params;
+
+ vnode_params.vnfs_mp = mp;
+ vnode_params.vnfs_vtype = type;
+ vnode_params.vnfs_str = NULL;
+ vnode_params.vnfs_dvp = pParent;
+ vnode_params.vnfs_fsnode = pVnodeData; /** Private data attached per xnu's vnode object */
+ vnode_params.vnfs_vops = g_papfnVBoxVFSVnodeDirOpsVector;
+
+ vnode_params.vnfs_markroot = fIsRoot;
+ vnode_params.vnfs_marksystem = FALSE;
+ vnode_params.vnfs_rdev = 0;
+ vnode_params.vnfs_filesize = 0;
+ vnode_params.vnfs_cnp = NULL;
+
+ vnode_params.vnfs_flags = VNFS_ADDFSREF | VNFS_NOCACHE;
+
+ rc = vnode_create(VNCREATE_FLAVOR, sizeof(vnode_params), &vnode_params, &vnode);
+ if (rc == 0)
+ *ret = vnode;
+
+ return 0;
+ }
+ else
+ {
+ PDEBUG("Unable to allocate lock");
+ rc = ENOMEM;
+ }
+
+ lck_attr_free(pVnodeData->pLockAttr);
+ }
+ else
+ {
+ PDEBUG("Unable to allocate lock attr");
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/**
+ * Convert guest absolute VFS path (starting from VFS root) to a host path
+ * within mounted shared folder (returning it as a char *).
+ *
+ * @param mp Mount data structure
+ * @param pszGuestPath Guest absolute VFS path (starting from VFS root)
+ * @param cbGuestPath Size of pszGuestPath
+ * @param pszHostPath Returned char * wich contains host path
+ * @param cbHostPath Returned pszHostPath size
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+vboxvfs_guest_path_to_char_path_internal(mount_t mp, char *pszGuestPath, int cbGuestPath, char **pszHostPath, int *cbHostPath)
+{
+ vboxvfs_mount_t *pMount;
+
+ /* Guest side: mount point path buffer and its size */
+ char *pszMntPointPath;
+ int cbMntPointPath = MAXPATHLEN;
+
+ /* Host side: path within mounted shared folder and its size */
+ char *pszHostPathInternal;
+ size_t cbHostPathInternal;
+
+ int rc;
+
+ AssertReturn(mp, EINVAL);
+ AssertReturn(pszGuestPath, EINVAL); AssertReturn(cbGuestPath >= 0, EINVAL);
+ AssertReturn(pszHostPath, EINVAL); AssertReturn(cbHostPath, EINVAL);
+
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL); AssertReturn(pMount->pRootVnode, EINVAL);
+
+ /* Get mount point path */
+ pszMntPointPath = (char *)RTMemAllocZ(cbMntPointPath);
+ if (pszMntPointPath)
+ {
+ rc = vn_getpath(pMount->pRootVnode, pszMntPointPath, &cbMntPointPath);
+ if (rc == 0 && cbGuestPath >= cbMntPointPath)
+ {
+ cbHostPathInternal = cbGuestPath - cbMntPointPath + 1;
+ pszHostPathInternal = (char *)RTMemAllocZ(cbHostPathInternal);
+ if (pszHostPathInternal)
+ {
+ memcpy(pszHostPathInternal, pszGuestPath + cbMntPointPath, cbGuestPath - cbMntPointPath);
+ PDEBUG("guest<->host path converion result: '%s' mounted to '%s'", pszHostPathInternal, pszMntPointPath);
+
+ RTMemFree(pszMntPointPath);
+
+ *pszHostPath = pszHostPathInternal;
+ *cbHostPath = cbGuestPath - cbMntPointPath;
+
+ return 0;
+
+ }
+ else
+ {
+ PDEBUG("No memory to allocate buffer for guest<->host path conversion (cbHostPathInternal)");
+ rc = ENOMEM;
+ }
+
+ }
+ else
+ {
+ PDEBUG("Unable to get guest vnode path: %d", rc);
+ }
+
+ RTMemFree(pszMntPointPath);
+ }
+ else
+ {
+ PDEBUG("No memory to allocate buffer for guest<->host path conversion (pszMntPointPath)");
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/**
+ * Convert guest absolute VFS path (starting from VFS root) to a host path
+ * within mounted shared folder.
+ *
+ * @param mp Mount data structure
+ * @param pszGuestPath Guest absolute VFS path (starting from VFS root)
+ * @param cbGuestPath Size of pszGuestPath
+ * @param ppResult Returned PSHFLSTRING object wich contains host path
+ *
+ * @return 0 on success, error code otherwise
+ */
+int
+vboxvfs_guest_path_to_shflstring_path_internal(mount_t mp, char *pszGuestPath, int cbGuestPath, PSHFLSTRING *ppResult)
+{
+ vboxvfs_mount_t *pMount;
+
+ /* Guest side: mount point path buffer and its size */
+ char *pszMntPointPath;
+ int cbMntPointPath = MAXPATHLEN;
+
+ /* Host side: path within mounted shared folder and its size */
+ PSHFLSTRING pSFPath;
+ size_t cbSFPath;
+
+ int rc;
+
+ AssertReturn(mp, EINVAL);
+ AssertReturn(pszGuestPath, EINVAL);
+ AssertReturn(cbGuestPath >= 0, EINVAL);
+
+ char *pszHostPath;
+ int cbHostPath;
+
+ rc = vboxvfs_guest_path_to_char_path_internal(mp, pszGuestPath, cbGuestPath, &pszHostPath, &cbHostPath);
+ if (rc == 0)
+ {
+ cbSFPath = offsetof(SHFLSTRING, String.utf8) + (size_t)cbHostPath + 1;
+ pSFPath = (PSHFLSTRING)RTMemAllocZ(cbSFPath);
+ if (pSFPath)
+ {
+ pSFPath->u16Length = cbHostPath;
+ pSFPath->u16Size = cbHostPath + 1;
+ memcpy(pSFPath->String.utf8, pszHostPath, cbHostPath);
+ vboxvfs_put_path_internal((void **)&pszHostPath);
+
+ *ppResult = pSFPath;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Wrapper function for vboxvfs_guest_path_to_char_path_internal() which
+ * converts guest path to host path using vnode object information.
+ *
+ * @param vnode Guest's VFS object
+ * @param ppHostPath Allocated char * which contain a path
+ * @param pcbPath Size of ppPath
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_guest_vnode_to_char_path_internal(vnode_t vnode, char **ppHostPath, int *pcbHostPath)
+{
+ mount_t mp;
+ int rc;
+
+ char *pszPath;
+ int cbPath = MAXPATHLEN;
+
+ AssertReturn(ppHostPath, EINVAL);
+ AssertReturn(pcbHostPath, EINVAL);
+ AssertReturn(vnode, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+
+ pszPath = (char *)RTMemAllocZ(cbPath);
+ if (pszPath)
+ {
+ rc = vn_getpath(vnode, pszPath, &cbPath);
+ if (rc == 0)
+ {
+ return vboxvfs_guest_path_to_char_path_internal(mp, pszPath, cbPath, ppHostPath, pcbHostPath);
+ }
+ }
+ else
+ {
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/**
+ * Wrapper function for vboxvfs_guest_path_to_shflstring_path_internal() which
+ * converts guest path to host path using vnode object information.
+ *
+ * @param vnode Guest's VFS object
+ * @param ppResult Allocated PSHFLSTRING object which contain a path
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_guest_vnode_to_shflstring_path_internal(vnode_t vnode, PSHFLSTRING *ppResult)
+{
+ mount_t mp;
+ int rc;
+
+ char *pszPath;
+ int cbPath = MAXPATHLEN;
+
+ AssertReturn(ppResult, EINVAL);
+ AssertReturn(vnode, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+
+ pszPath = (char *)RTMemAllocZ(cbPath);
+ if (pszPath)
+ {
+ rc = vn_getpath(vnode, pszPath, &cbPath);
+ if (rc == 0)
+ {
+ return vboxvfs_guest_path_to_shflstring_path_internal(mp, pszPath, cbPath, ppResult);
+ }
+ }
+ else
+ {
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Free resources allocated by vboxvfs_path_internal() and vboxvfs_guest_vnode_to_shflstring_path_internal().
+ *
+ * @param ppHandle Reference to object to be freed.
+ */
+void
+vboxvfs_put_path_internal(void **ppHandle)
+{
+ AssertReturnVoid(ppHandle);
+ AssertReturnVoid(*ppHandle);
+ RTMemFree(*ppHandle);
+}
+
+static void
+vboxvfs_g2h_mode_dump_inernal(uint32_t fHostMode)
+{
+ PDEBUG("Host VFS object flags (0x%X) dump:", (int)fHostMode);
+
+ if (fHostMode & SHFL_CF_ACCESS_READ) PDEBUG("SHFL_CF_ACCESS_READ");
+ if (fHostMode & SHFL_CF_ACCESS_WRITE) PDEBUG("SHFL_CF_ACCESS_WRITE");
+ if (fHostMode & SHFL_CF_ACCESS_APPEND) PDEBUG("SHFL_CF_ACCESS_APPEND");
+
+ if ((fHostMode & (SHFL_CF_ACT_FAIL_IF_EXISTS |
+ SHFL_CF_ACT_REPLACE_IF_EXISTS |
+ SHFL_CF_ACT_OVERWRITE_IF_EXISTS)) == 0)
+ PDEBUG("SHFL_CF_ACT_OPEN_IF_EXISTS");
+
+ if (fHostMode & SHFL_CF_ACT_CREATE_IF_NEW) PDEBUG("SHFL_CF_ACT_CREATE_IF_NEW");
+ if (fHostMode & SHFL_CF_ACT_FAIL_IF_NEW) PDEBUG("SHFL_CF_ACT_FAIL_IF_NEW");
+ if (fHostMode & SHFL_CF_ACT_OVERWRITE_IF_EXISTS) PDEBUG("SHFL_CF_ACT_OVERWRITE_IF_EXISTS");
+ if (fHostMode & SHFL_CF_DIRECTORY) PDEBUG("SHFL_CF_DIRECTORY");
+
+ PDEBUG("Done");
+}
+
+
+/**
+ * Open existing VBoxVFS object and return its handle.
+ *
+ * @param pMount Mount session data.
+ * @param pPath VFS path to the object relative to mount point.
+ * @param fFlags For directory object it should be
+ * SHFL_CF_DIRECTORY and 0 for any other object.
+ * @param pHandle Returned handle.
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_open_internal(vboxvfs_mount_t *pMount, PSHFLSTRING pPath, uint32_t fFlags, SHFLHANDLE *pHandle)
+{
+ SHFLCREATEPARMS parms;
+
+ int rc;
+
+ AssertReturn(pMount, EINVAL);
+ AssertReturn(pPath, EINVAL);
+ AssertReturn(pHandle, EINVAL);
+
+ bzero(&parms, sizeof(parms));
+
+ vboxvfs_g2h_mode_dump_inernal(fFlags);
+
+ parms.Handle = SHFL_HANDLE_NIL;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = fFlags;
+
+ rc = VbglR0SfCreate(&g_SfClientDarwin, &pMount->pMap, pPath, &parms);
+ if (RT_SUCCESS(rc))
+ {
+ *pHandle = parms.Handle;
+ }
+ else
+ {
+ PDEBUG("vboxvfs_open_internal() failed: %d", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Release VBoxVFS object handle openned by vboxvfs_open_internal().
+ *
+ * @param pMount Mount session data.
+ * @param pHandle Handle to close.
+ *
+ * @return 0 on success, IPRT error code otherwise.
+ */
+int
+vboxvfs_close_internal(vboxvfs_mount_t *pMount, SHFLHANDLE pHandle)
+{
+ AssertReturn(pMount, EINVAL);
+ return VbglR0SfClose(&g_SfClientDarwin, &pMount->pMap, pHandle);
+}
+
+/**
+ * Get information about host VFS object.
+ *
+ * @param mp Mount point data
+ * @param pSHFLDPath Path to VFS object within mounted shared folder
+ * @param Info Returned info
+ *
+ * @return 0 on success, error code otherwise.
+ */
+int
+vboxvfs_get_info_internal(mount_t mp, PSHFLSTRING pSHFLDPath, PSHFLFSOBJINFO Info)
+{
+ vboxvfs_mount_t *pMount;
+ SHFLCREATEPARMS parms;
+
+ int rc;
+
+ AssertReturn(mp, EINVAL);
+ AssertReturn(pSHFLDPath, EINVAL);
+ AssertReturn(Info, EINVAL);
+
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+
+ parms.Handle = 0;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ rc = VbglR0SfCreate(&g_SfClientDarwin, &pMount->pMap, pSHFLDPath, &parms);
+ if (rc == 0)
+ *Info = parms.Info;
+
+ return rc;
+}
+
+/**
+ * Check if VFS object exists on a host side.
+ *
+ * @param vnode Guest VFS vnode that corresponds to host VFS object
+ *
+ * @return 1 if exists, 0 otherwise.
+ */
+int
+vboxvfs_exist_internal(vnode_t vnode)
+{
+ int rc;
+
+ PSHFLSTRING pSFPath = NULL;
+ SHFLHANDLE handle;
+ uint32_t fFlags;
+
+ vboxvfs_mount_t *pMount;
+ mount_t mp;
+
+ /* Return FALSE if invalid parameter given */
+ AssertReturn(vnode, 0);
+
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+
+ fFlags = (vnode_isdir(vnode)) ? SHFL_CF_DIRECTORY : 0;
+ fFlags |= SHFL_CF_ACCESS_READ | SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ rc = vboxvfs_guest_vnode_to_shflstring_path_internal(vnode, &pSFPath); AssertReturn(rc == 0, rc);
+ if (rc == 0)
+ {
+ rc = vboxvfs_open_internal(pMount, pSFPath, fFlags, &handle);
+ if (rc == 0)
+ {
+ rc = vboxvfs_close_internal(pMount, handle);
+ if (rc != 0)
+ {
+ PDEBUG("Unable to close() VBoxVFS object handle while checking if object exist on host: %d", rc);
+ }
+ }
+ }
+
+ vboxvfs_put_path_internal((void **)&pSFPath);
+
+ return (rc == 0);
+}
+
+/**
+ * Convert host VFS object mode flags into guest ones.
+ *
+ * @param fHostMode Host flags
+ *
+ * @return Guest flags
+ */
+mode_t
+vboxvfs_h2g_mode_inernal(RTFMODE fHostMode)
+{
+ mode_t fGuestMode = 0;
+
+ fGuestMode = /* Owner */
+ ((fHostMode & RTFS_UNIX_IRUSR) ? S_IRUSR : 0 ) |
+ ((fHostMode & RTFS_UNIX_IWUSR) ? S_IWUSR : 0 ) |
+ ((fHostMode & RTFS_UNIX_IXUSR) ? S_IXUSR : 0 ) |
+ /* Group */
+ ((fHostMode & RTFS_UNIX_IRGRP) ? S_IRGRP : 0 ) |
+ ((fHostMode & RTFS_UNIX_IWGRP) ? S_IWGRP : 0 ) |
+ ((fHostMode & RTFS_UNIX_IXGRP) ? S_IXGRP : 0 ) |
+ /* Other */
+ ((fHostMode & RTFS_UNIX_IROTH) ? S_IROTH : 0 ) |
+ ((fHostMode & RTFS_UNIX_IWOTH) ? S_IWOTH : 0 ) |
+ ((fHostMode & RTFS_UNIX_IXOTH) ? S_IXOTH : 0 ) |
+ /* SUID, SGID, SVTXT */
+ ((fHostMode & RTFS_UNIX_ISUID) ? S_ISUID : 0 ) |
+ ((fHostMode & RTFS_UNIX_ISGID) ? S_ISGID : 0 ) |
+ ((fHostMode & RTFS_UNIX_ISTXT) ? S_ISVTX : 0 ) |
+ /* VFS object types */
+ ((RTFS_IS_FIFO(fHostMode)) ? S_IFIFO : 0 ) |
+ ((RTFS_IS_DEV_CHAR(fHostMode)) ? S_IFCHR : 0 ) |
+ ((RTFS_IS_DIRECTORY(fHostMode)) ? S_IFDIR : 0 ) |
+ ((RTFS_IS_DEV_BLOCK(fHostMode)) ? S_IFBLK : 0 ) |
+ ((RTFS_IS_FILE(fHostMode)) ? S_IFREG : 0 ) |
+ ((RTFS_IS_SYMLINK(fHostMode)) ? S_IFLNK : 0 ) |
+ ((RTFS_IS_SOCKET(fHostMode)) ? S_IFSOCK : 0 );
+
+ return fGuestMode;
+}
+
+/**
+ * Convert guest VFS object mode flags into host ones.
+ *
+ * @param fGuestMode Host flags
+ *
+ * @return Host flags
+ */
+uint32_t
+vboxvfs_g2h_mode_inernal(mode_t fGuestMode)
+{
+ uint32_t fHostMode = 0;
+
+ fHostMode = ((fGuestMode & FREAD) ? SHFL_CF_ACCESS_READ : 0 ) |
+ ((fGuestMode & FWRITE) ? SHFL_CF_ACCESS_WRITE : 0 ) |
+ /* skipped: O_NONBLOCK */
+ ((fGuestMode & O_APPEND) ? SHFL_CF_ACCESS_APPEND : 0 ) |
+ /* skipped: O_SYNC */
+ /* skipped: O_SHLOCK */
+ /* skipped: O_EXLOCK */
+ /* skipped: O_ASYNC */
+ /* skipped: O_FSYNC */
+ /* skipped: O_NOFOLLOW */
+ ((fGuestMode & O_CREAT) ? SHFL_CF_ACT_CREATE_IF_NEW | (!(fGuestMode & O_TRUNC) ? SHFL_CF_ACT_OPEN_IF_EXISTS : 0) : SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACT_FAIL_IF_NEW ) |
+ ((fGuestMode & O_TRUNC) ? SHFL_CF_ACT_OVERWRITE_IF_EXISTS | SHFL_CF_ACCESS_WRITE : 0 );
+ /* skipped: O_EXCL */
+
+ return fHostMode;
+}
+
+/**
+ * Mount helper: Contruct SHFLSTRING which contains VBox share name or path.
+ *
+ * @returns Initialize string buffer on success, NULL if out of memory.
+ * @param pachName The string to pack in a buffer. Does not need to be
+ * zero terminated.
+ * @param cchName The length of pachName to use. RTSTR_MAX for strlen.
+ */
+SHFLSTRING *
+vboxvfs_construct_shflstring(const char *pachName, size_t cchName)
+{
+ AssertReturn(pachName, NULL);
+
+ if (cchName == RTSTR_MAX)
+ cchName = strlen(pachName);
+
+ SHFLSTRING *pSHFLString = (SHFLSTRING *)RTMemAlloc(SHFLSTRING_HEADER_SIZE + cchName + 1);
+ if (pSHFLString)
+ {
+ pSHFLString->u16Length = cchName;
+ pSHFLString->u16Size = cchName + 1;
+ memcpy(pSHFLString->String.utf8, pachName, cchName);
+ pSHFLString->String.utf8[cchName] = '\0';
+
+ return pSHFLString;
+ }
+ return NULL;
+}
+
+#endif
diff --git a/src/VBox/Additions/darwin/VBoxSF/VBoxSF-VNodeOps.cpp b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-VNodeOps.cpp
new file mode 100644
index 00000000..d90e6dbc
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-VNodeOps.cpp
@@ -0,0 +1,843 @@
+/* $Id: VBoxSF-VNodeOps.cpp $ */
+/** @file
+ * VBoxSF - Darwin Shared Folders, VNode Operations.
+ */
+
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#include "VBoxSFInternal.h"
+
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct default_error_args_hack
+{
+ struct default_error_vdesc_hack
+ {
+ int vdesc_offset;
+ const char *vdesc_name;
+ } const *a_desc;
+};
+
+
+
+/**
+ * Default implementation that returns ENOTSUP.
+ */
+static int vboxSfDwnVnDefaultError(struct default_error_args_hack *pArgs)
+{
+ Log(("vboxSfDwnVnDefaultError: %s\n", RT_VALID_PTR(pArgs) && RT_VALID_PTR(pArgs->a_desc) ? pArgs->a_desc->vdesc_name : "??"));
+ RT_NOREF(pArgs);
+ return ENOTSUP;
+}
+
+
+static int vboxFsDwnVnGetAttr(struct vnop_getattr_args *pArgs)
+{
+#if 1
+ RT_NOREF(pArgs);
+ return ENOTSUP;
+#else
+
+ vboxvfs_mount_t *pMount;
+ struct vnode_attr *vnode_args;
+ vboxvfs_vnode_t *pVnodeData;
+
+ struct timespec timespec;
+
+ SHFLFSOBJINFO Info;
+ mount_t mp;
+ vnode_t vnode;
+ int rc;
+
+ PDEBUG("Getting vnode attribute...");
+
+ AssertReturn(pArgs, EINVAL);
+
+ vnode = pArgs->a_vp; AssertReturn(vnode, EINVAL);
+ vnode_args = pArgs->a_vap; AssertReturn(vnode_args, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+ pVnodeData = (vboxvfs_vnode_t *)vnode_fsnode(vnode); AssertReturn(pVnodeData, EINVAL);
+
+ lck_rw_lock_shared(pVnodeData->pLock);
+
+ rc = vboxvfs_get_info_internal(mp, pVnodeData->pPath, &Info);
+ if (rc == 0)
+ {
+ /* Set timestamps */
+ RTTimeSpecGetTimespec(&Info.BirthTime, &timespec); VATTR_RETURN(vnode_args, va_create_time, timespec);
+ RTTimeSpecGetTimespec(&Info.AccessTime, &timespec); VATTR_RETURN(vnode_args, va_access_time, timespec);
+ RTTimeSpecGetTimespec(&Info.ModificationTime, &timespec); VATTR_RETURN(vnode_args, va_modify_time, timespec);
+ RTTimeSpecGetTimespec(&Info.ChangeTime, &timespec); VATTR_RETURN(vnode_args, va_change_time, timespec);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_backup_time);
+
+ /* Set owner info. */
+ VATTR_RETURN(vnode_args, va_uid, pMount->owner);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_gid);
+
+ /* Access mode and flags */
+ VATTR_RETURN(vnode_args, va_mode, vboxvfs_h2g_mode_inernal(Info.Attr.fMode));
+ VATTR_RETURN(vnode_args, va_flags, Info.Attr.u.Unix.fFlags);
+
+ /* The current generation number (0 if this information is not available) */
+ VATTR_RETURN(vnode_args, va_gen, Info.Attr.u.Unix.GenerationId);
+
+ VATTR_RETURN(vnode_args, va_rdev, 0);
+ VATTR_RETURN(vnode_args, va_nlink, 2);
+
+ VATTR_RETURN(vnode_args, va_data_size, sizeof(struct dirent)); /* Size of data returned per each readdir() request */
+
+ /* Hope, when it overflows nothing catastrophical will heppen! If we will not assign
+ * a uniq va_fileid to each vnode, `ls`, 'find' (and simmilar tools that uses fts_read() calls) will think that
+ * each sub-directory is self-cycled. */
+ VATTR_RETURN(vnode_args, va_fileid, (pMount->cFileIdCounter++));
+
+ /* Not supported */
+ VATTR_CLEAR_ACTIVE(vnode_args, va_linkid);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_parentid);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_fsid);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_filerev);
+
+ /* Not present on 10.6 */
+ //VATTR_CLEAR_ACTIVE(vnode_args, va_addedtime);
+
+ /** @todo take care about va_encoding (file name encoding) */
+ VATTR_CLEAR_ACTIVE(vnode_args, va_encoding);
+ /** @todo take care about: va_acl */
+ VATTR_CLEAR_ACTIVE(vnode_args, va_acl);
+
+ VATTR_CLEAR_ACTIVE(vnode_args, va_name);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_uuuid);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_guuid);
+
+ VATTR_CLEAR_ACTIVE(vnode_args, va_total_size);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_total_alloc);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_data_alloc);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_iosize);
+
+ VATTR_CLEAR_ACTIVE(vnode_args, va_nchildren);
+ VATTR_CLEAR_ACTIVE(vnode_args, va_dirlinkcount);
+ }
+ else
+ {
+ PDEBUG("getattr: unable to get VBoxVFS object info");
+ }
+
+ lck_rw_unlock_shared(pVnodeData->pLock);
+
+ return rc;
+#endif
+}
+
+#if 0
+/**
+ * Helper function for vboxvfs_vnode_lookup(): create new vnode.
+ */
+static int
+vboxvfs_vnode_lookup_instantinate_vnode(vnode_t parent_vnode, char *entry_name, vnode_t *result_vnode)
+{
+ /* We need to construct full path to vnode in order to get
+ * vboxvfs_get_info_internal() to understand us! */
+
+ char *pszCurDirPath;
+ int cbCurDirPath = MAXPATHLEN;
+
+ mount_t mp = vnode_mount(parent_vnode); AssertReturn(mp, EINVAL);
+ vnode_t vnode;
+
+ int rc;
+
+ pszCurDirPath = (char *)RTMemAllocZ(cbCurDirPath);
+ if (pszCurDirPath)
+ {
+ rc = vn_getpath(parent_vnode, pszCurDirPath, &cbCurDirPath);
+ if (rc == 0 && cbCurDirPath < MAXPATHLEN)
+ {
+ SHFLFSOBJINFO Info;
+ PSHFLSTRING pSHFLPath;
+
+ /* Add '/' between path parts and truncate name if it is too long */
+ strncat(pszCurDirPath, "/", 1); strncat(pszCurDirPath, entry_name, MAXPATHLEN - cbCurDirPath - 1);
+
+ rc = vboxvfs_guest_path_to_shflstring_path_internal(mp, pszCurDirPath, strlen(pszCurDirPath) + 1, &pSHFLPath);
+ if (rc == 0)
+ {
+ rc = vboxvfs_get_info_internal(mp, pSHFLPath, (PSHFLFSOBJINFO)&Info);
+ if (rc == 0)
+ {
+ enum vtype type;
+
+ if (RTFS_IS_DIRECTORY(Info.Attr.fMode)) type = VDIR;
+ else if (RTFS_IS_FILE (Info.Attr.fMode)) type = VREG;
+ else
+ {
+ PDEBUG("Not supported VFS object (%s) type: mode 0x%X",
+ entry_name,
+ Info.Attr.fMode);
+
+ RTMemFree(pszCurDirPath);
+ vboxvfs_put_path_internal((void **)&pSHFLPath);
+ return ENOENT;
+ }
+ /* Create new vnode */
+ rc = vboxvfs_create_vnode_internal(mp, type, parent_vnode, FALSE, pSHFLPath, &vnode);
+ if (rc == 0)
+ {
+ PDEBUG("new vnode object '%s' has been created", entry_name);
+
+ *result_vnode = vnode;
+ RTMemFree(pszCurDirPath);
+
+ return 0;
+ }
+ else
+ PDEBUG("Unable to create vnode: %d", rc);
+ }
+ else
+ PDEBUG("Unable to get host object info: %d", rc);
+
+ vboxvfs_put_path_internal((void **)&pSHFLPath);
+ }
+ else
+ PDEBUG("Unable to convert guest<->host path");
+ }
+ else
+ PDEBUG("Unable to construct vnode path: %d", rc);
+
+ RTMemFree(pszCurDirPath);
+ }
+ else
+ {
+ PDEBUG("Unable to allocate memory for path buffer");
+ rc = ENOMEM;
+ }
+
+ return rc;
+}
+
+/**
+ * Helper function for vboxvfs_vnode_lookup(): take care
+ * about '.' and '..' directory entries.
+ */
+static int
+vboxvfs_vnode_lookup_dot_handler(struct vnop_lookup_args *pArgs, vnode_t *result_vnode)
+{
+ vnode_t vnode = NULL;
+
+ if (pArgs->a_cnp->cn_flags & ISDOTDOT)
+ {
+ vnode = vnode_getparent(pArgs->a_dvp);
+ if (vnode)
+ {
+ PDEBUG("return parent directory");
+ *result_vnode = vnode;
+ return 0;
+ }
+ else
+ {
+ PDEBUG("return parent directory not found, return current directory");
+ *result_vnode = pArgs->a_dvp;
+ return 0;
+ }
+ }
+ else if ((strncmp(pArgs->a_cnp->cn_nameptr, ".", 1) == 0) &&
+ pArgs->a_cnp->cn_namelen == 1)
+ {
+ PDEBUG("return current directory");
+ *result_vnode = pArgs->a_dvp;
+ return 0;
+ }
+
+ return ENOENT;
+}
+#endif
+
+static int vboxSfDwnVnLookup(struct vnop_lookup_args *pArgs)
+{
+#if 1
+ RT_NOREF(pArgs);
+ return ENOTSUP;
+#else
+ int rc;
+
+ vnode_t vnode;
+ vboxvfs_vnode_t *pVnodeData;
+
+ PDEBUG("Looking up for vnode...");
+
+ AssertReturn(pArgs, EINVAL);
+ AssertReturn(pArgs->a_dvp, EINVAL);
+ AssertReturn(vnode_isdir(pArgs->a_dvp), EINVAL);
+ AssertReturn(pArgs->a_cnp, EINVAL);
+ AssertReturn(pArgs->a_cnp->cn_nameptr, EINVAL);
+ AssertReturn(pArgs->a_vpp, EINVAL);
+
+ pVnodeData = (vboxvfs_vnode_t *)vnode_fsnode(pArgs->a_dvp);
+ AssertReturn(pVnodeData, EINVAL);
+ AssertReturn(pVnodeData->pLock, EINVAL);
+
+ /*
+ todo: take care about pArgs->a_cnp->cn_nameiop
+ */
+
+ if (pArgs->a_cnp->cn_nameiop == LOOKUP) PDEBUG("LOOKUP");
+ else if (pArgs->a_cnp->cn_nameiop == CREATE) PDEBUG("CREATE");
+ else if (pArgs->a_cnp->cn_nameiop == RENAME) PDEBUG("RENAME");
+ else if (pArgs->a_cnp->cn_nameiop == DELETE) PDEBUG("DELETE");
+ else PDEBUG("Unknown cn_nameiop: 0x%X", (int)pArgs->a_cnp->cn_nameiop);
+
+ lck_rw_lock_exclusive(pVnodeData->pLock);
+
+ /* Take care about '.' and '..' entries */
+ if (vboxvfs_vnode_lookup_dot_handler(pArgs, &vnode) == 0)
+ {
+ vnode_get(vnode);
+ *pArgs->a_vpp = vnode;
+
+ lck_rw_unlock_exclusive(pVnodeData->pLock);
+
+ return 0;
+ }
+
+ /* Look into VFS cache and attempt to find previously allocated vnode there. */
+ rc = cache_lookup(pArgs->a_dvp, &vnode, pArgs->a_cnp);
+ if (rc == -1) /* Record found */
+ {
+ PDEBUG("Found record in VFS cache");
+
+ /* Check if VFS object still exist on a host side */
+ if (vboxvfs_exist_internal(vnode))
+ {
+ /* Prepare & return cached vnode */
+ vnode_get(vnode);
+ *pArgs->a_vpp = vnode;
+
+ rc = 0;
+ }
+ else
+ {
+ /* If vnode exist in guets VFS cache, but not exist on a host -- just forget it. */
+ cache_purge(vnode);
+ /** @todo free vnode data here */
+ rc = ENOENT;
+ }
+ }
+ else
+ {
+ PDEBUG("cache_lookup() returned %d, create new VFS vnode", rc);
+
+ rc = vboxvfs_vnode_lookup_instantinate_vnode(pArgs->a_dvp, pArgs->a_cnp->cn_nameptr, &vnode);
+ if (rc == 0)
+ {
+ cache_enter(pArgs->a_dvp, vnode, pArgs->a_cnp);
+ *pArgs->a_vpp = vnode;
+ }
+ else
+ {
+ rc = ENOENT;
+ }
+ }
+
+ lck_rw_unlock_exclusive(pVnodeData->pLock);
+
+ return rc;
+#endif
+}
+
+static int vboxSfDwnVnOpen(struct vnop_open_args *pArgs)
+{
+#if 1
+ RT_NOREF(pArgs);
+ return ENOTSUP;
+#else
+ vnode_t vnode;
+ vboxvfs_vnode_t *pVnodeData;
+ uint32_t fHostFlags;
+ mount_t mp;
+ vboxvfs_mount_t *pMount;
+
+ int rc;
+
+ PDEBUG("Opening vnode...");
+
+ AssertReturn(pArgs, EINVAL);
+
+ vnode = pArgs->a_vp; AssertReturn(vnode, EINVAL);
+ pVnodeData = (vboxvfs_vnode_t *)vnode_fsnode(vnode); AssertReturn(pVnodeData, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+
+ lck_rw_lock_exclusive(pVnodeData->pLock);
+
+ if (vnode_isinuse(vnode, 0))
+ {
+ PDEBUG("vnode '%s' (handle 0x%X) already has VBoxVFS object handle assigned, just return ok",
+ (char *)pVnodeData->pPath->String.utf8,
+ (int)pVnodeData->pHandle);
+
+ lck_rw_unlock_exclusive(pVnodeData->pLock);
+ return 0;
+ }
+
+ /* At this point we must make sure that nobody is using VBoxVFS object handle */
+ //if (pVnodeData->Handle != SHFL_HANDLE_NIL)
+ //{
+ // PDEBUG("vnode has active VBoxVFS object handle set, aborting");
+ // lck_rw_unlock_exclusive(pVnodeData->pLock);
+ // return EINVAL;
+ //}
+
+ fHostFlags = vboxvfs_g2h_mode_inernal(pArgs->a_mode);
+ fHostFlags |= (vnode_isdir(vnode) ? SHFL_CF_DIRECTORY : 0);
+
+ SHFLHANDLE Handle;
+ rc = vboxvfs_open_internal(pMount, pVnodeData->pPath, fHostFlags, &Handle);
+ if (rc == 0)
+ {
+ PDEBUG("Open success: '%s' (handle 0x%X)",
+ (char *)pVnodeData->pPath->String.utf8,
+ (int)Handle);
+
+ pVnodeData->pHandle = Handle;
+ }
+ else
+ {
+ PDEBUG("Unable to open: '%s': %d",
+ (char *)pVnodeData->pPath->String.utf8,
+ rc);
+ }
+
+ lck_rw_unlock_exclusive(pVnodeData->pLock);
+
+ return rc;
+#endif
+}
+
+static int vboxSfDwnVnClose(struct vnop_close_args *pArgs)
+{
+#if 1
+ RT_NOREF(pArgs);
+ return ENOTSUP;
+#else
+
+ vnode_t vnode;
+ mount_t mp;
+ vboxvfs_vnode_t *pVnodeData;
+ vboxvfs_mount_t *pMount;
+
+ int rc;
+
+ PDEBUG("Closing vnode...");
+
+ AssertReturn(pArgs, EINVAL);
+
+ vnode = pArgs->a_vp; AssertReturn(vnode, EINVAL);
+ pVnodeData = (vboxvfs_vnode_t *)vnode_fsnode(vnode); AssertReturn(pVnodeData, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+
+ lck_rw_lock_exclusive(pVnodeData->pLock);
+
+ if (vnode_isinuse(vnode, 0))
+ {
+ PDEBUG("vnode '%s' (handle 0x%X) is still in use, just return ok",
+ (char *)pVnodeData->pPath->String.utf8,
+ (int)pVnodeData->pHandle);
+
+ lck_rw_unlock_exclusive(pVnodeData->pLock);
+ return 0;
+ }
+
+ /* At this point we must make sure that vnode has VBoxVFS object handle assigned */
+ if (pVnodeData->pHandle == SHFL_HANDLE_NIL)
+ {
+ PDEBUG("vnode has no active VBoxVFS object handle set, aborting");
+ lck_rw_unlock_exclusive(pVnodeData->pLock);
+ return EINVAL;
+ }
+
+ rc = vboxvfs_close_internal(pMount, pVnodeData->pHandle);
+ if (rc == 0)
+ {
+ PDEBUG("Close success: '%s' (handle 0x%X)",
+ (char *)pVnodeData->pPath->String.utf8,
+ (int)pVnodeData->pHandle);
+
+ /* Forget about previously assigned VBoxVFS object handle */
+ pVnodeData->pHandle = SHFL_HANDLE_NIL;
+ }
+ else
+ {
+ PDEBUG("Unable to close: '%s' (handle 0x%X): %d",
+ (char *)pVnodeData->pPath->String.utf8,
+ (int)pVnodeData->pHandle, rc);
+ }
+
+ lck_rw_unlock_exclusive(pVnodeData->pLock);
+
+ return rc;
+#endif
+}
+
+#if 0
+/**
+ * Convert SHFLDIRINFO to struct dirent and copy it back to user.
+ */
+static int
+vboxvfs_vnode_readdir_copy_data(ino_t index, SHFLDIRINFO *Info, struct uio *uio, int *numdirent)
+{
+ struct dirent entry;
+
+ int rc;
+
+ entry.d_ino = index;
+ entry.d_reclen = (__uint16_t)sizeof(entry);
+
+ /* Detect dir entry type */
+ if (RTFS_IS_DIRECTORY(Info->Info.Attr.fMode))
+ entry.d_type = DT_DIR;
+ else if (RTFS_IS_FILE(Info->Info.Attr.fMode))
+ entry.d_type = DT_REG;
+ else
+ {
+ PDEBUG("Unknown type of host file: mode 0x%X", (int)Info->Info.Attr.fMode);
+ return ENOTSUP;
+ }
+
+ entry.d_namlen = (__uint8_t)min(sizeof(entry.d_name), Info->name.u16Size);
+ memcpy(entry.d_name, Info->name.String.utf8, entry.d_namlen);
+
+ rc = uiomove((char *)&entry, sizeof(entry), uio);
+ if (rc == 0)
+ {
+ uio_setoffset(uio, index * sizeof(struct dirent));
+ *numdirent = (int)index;
+
+ PDEBUG("discovered entry: '%s' (%d bytes), item #%d", entry.d_name, (int)entry.d_namlen, (int)index);
+ }
+ else
+ {
+ PDEBUG("Failed to return dirent data item #%d (%d)", (int)index, rc);
+ }
+
+ return rc;
+}
+#endif
+
+static int vboxSfDwnVnReadDir(struct vnop_readdir_args *pArgs)
+{
+#if 1
+ RT_NOREF(pArgs);
+ return ENOTSUP;
+#else
+ vboxvfs_mount_t *pMount;
+ vboxvfs_vnode_t *pVnodeData;
+ SHFLDIRINFO *Info;
+ uint32_t cbInfo;
+ mount_t mp;
+ vnode_t vnode;
+ struct uio *uio;
+
+ int rc = 0, rc2;
+
+ PDEBUG("Reading directory...");
+
+ AssertReturn(pArgs, EINVAL);
+ AssertReturn(pArgs->a_eofflag, EINVAL);
+ AssertReturn(pArgs->a_numdirent, EINVAL);
+
+ uio = pArgs->a_uio; AssertReturn(uio, EINVAL);
+ vnode = pArgs->a_vp; AssertReturn(vnode, EINVAL); AssertReturn(vnode_isdir(vnode), EINVAL);
+ pVnodeData = (vboxvfs_vnode_t *)vnode_fsnode(vnode); AssertReturn(pVnodeData, EINVAL);
+ mp = vnode_mount(vnode); AssertReturn(mp, EINVAL);
+ pMount = (vboxvfs_mount_t *)vfs_fsprivate(mp); AssertReturn(pMount, EINVAL);
+
+ lck_rw_lock_shared(pVnodeData->pLock);
+
+ cbInfo = sizeof(Info) + MAXPATHLEN;
+ Info = (SHFLDIRINFO *)RTMemAllocZ(cbInfo);
+ if (!Info)
+ {
+ PDEBUG("No memory to allocate internal data");
+ lck_rw_unlock_shared(pVnodeData->pLock);
+ return ENOMEM;
+ }
+
+ uint32_t index = (uint32_t)uio_offset(uio) / (uint32_t)sizeof(struct dirent);
+ uint32_t cFiles = 0;
+
+ PDEBUG("Exploring VBoxVFS directory (%s), handle (0x%.8X), offset (0x%X), count (%d)", (char *)pVnodeData->pPath->String.utf8, (int)pVnodeData->pHandle, index, uio_iovcnt(uio));
+
+ /* Currently, there is a problem when VbglR0SfDirInfo() is not able to
+ * continue retrieve directory content if the same VBoxVFS handle is used.
+ * This macro forces to use a new handle in readdir() callback. If enabled,
+ * the original handle (obtained in open() callback is ignored). */
+
+ SHFLHANDLE Handle;
+ rc = vboxvfs_open_internal(pMount,
+ pVnodeData->pPath,
+ SHFL_CF_DIRECTORY | SHFL_CF_ACCESS_READ | SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACT_FAIL_IF_NEW,
+ &Handle);
+ if (rc != 0)
+ {
+ PDEBUG("Unable to open dir: %d", rc);
+ RTMemFree(Info);
+ lck_rw_unlock_shared(pVnodeData->pLock);
+ return rc;
+ }
+
+#if 0
+ rc = VbglR0SfDirInfo(&g_vboxSFClient, &pMount->pMap, Handle, 0, 0, index, &cbInfo, (PSHFLDIRINFO)Info, &cFiles);
+#else
+ SHFLSTRING *pMask = vboxvfs_construct_shflstring("*", strlen("*"));
+ if (pMask)
+ {
+ for (uint32_t cSkip = 0; (cSkip < index + 1) && (rc == VINF_SUCCESS); cSkip++)
+ {
+ //rc = VbglR0SfDirInfo(&g_vboxSFClient, &pMount->pMap, Handle, 0 /* pMask */, 0 /* SHFL_LIST_RETURN_ONE */, 0, &cbInfo, (PSHFLDIRINFO)Info, &cFiles);
+
+ uint32_t cbReturned = cbInfo;
+ //rc = VbglR0SfDirInfo(&g_vboxSFClient, &pMount->pMap, Handle, pMask, SHFL_LIST_RETURN_ONE, 0, &cbReturned, (PSHFLDIRINFO)Info, &cFiles);
+ rc = VbglR0SfDirInfo(&g_SfClientDarwin, &pMount->pMap, Handle, 0, SHFL_LIST_RETURN_ONE, 0,
+ &cbReturned, (PSHFLDIRINFO)Info, &cFiles);
+
+ }
+
+ PDEBUG("read %d files", cFiles);
+ RTMemFree(pMask);
+ }
+ else
+ {
+ PDEBUG("Can't alloc mask");
+ rc = ENOMEM;
+ }
+#endif
+ rc2 = vboxvfs_close_internal(pMount, Handle);
+ if (rc2 != 0)
+ {
+ PDEBUG("Unable to close directory: %s: %d",
+ pVnodeData->pPath->String.utf8,
+ rc2);
+ }
+
+ switch (rc)
+ {
+ case VINF_SUCCESS:
+ {
+ rc = vboxvfs_vnode_readdir_copy_data((ino_t)(index + 1), Info, uio, pArgs->a_numdirent);
+ break;
+ }
+
+ case VERR_NO_MORE_FILES:
+ {
+ PDEBUG("No more entries in directory");
+ *(pArgs->a_eofflag) = 1;
+ break;
+ }
+
+ default:
+ {
+ PDEBUG("VbglR0SfDirInfo() for item #%d has failed: %d", (int)index, (int)rc);
+ rc = EINVAL;
+ break;
+ }
+ }
+
+ RTMemFree(Info);
+ lck_rw_unlock_shared(pVnodeData->pLock);
+
+ return rc;
+#endif
+}
+
+
+static int vboxSfDwnVnPathConf(struct vnop_pathconf_args *pArgs)
+{
+ Log(("vboxSfDwnVnPathConf:\n"));
+ RT_NOREF(pArgs);
+ return 0;
+}
+
+
+/**
+ * vnop_reclaim implementation.
+ *
+ * VBoxVFS reclaim callback.
+ * Called when vnode is going to be deallocated. Should release
+ * all the VBoxVFS resources that correspond to current vnode object.
+ *
+ * @param pArgs Operation arguments passed from VFS layer.
+ *
+ * @return 0 on success, BSD error code otherwise.
+ */
+static int vboxSfDwnVnReclaim(struct vnop_reclaim_args *pArgs)
+{
+ AssertReturn(pArgs && pArgs->a_vp, EINVAL);
+
+ /* Check that it's not a root node that's in use. */
+ PVBOXSFMNTDATA pMntData = (PVBOXSFMNTDATA)vfs_fsprivate(vnode_mount(pArgs->a_vp));
+ AssertReturn(!pMntData || pMntData->pVnRoot != pArgs->a_vp, EBUSY);
+
+ /* Get the private data and free it. */
+ PVBOXSFDWNVNDATA pVnData = (PVBOXSFDWNVNDATA)vnode_fsnode(pArgs->a_vp);
+ AssertPtrReturn(pVnData, 0);
+
+ if (pVnData->hHandle != SHFL_HANDLE_NIL)
+ {
+ /** @todo can this happen? */
+ pVnData->hHandle = SHFL_HANDLE_NIL;
+ }
+
+ RTMemFree(pVnData);
+ return 0;
+}
+
+
+/**
+ * Allocates a vnode.
+ *
+ * @returns Pointer to the new VNode, NULL if out of memory.
+ * @param pMount The file system mount structure.
+ * @param enmType The vnode type.
+ * @param pParent The parent vnode, NULL if root.
+ * @param cbFile The file size
+ */
+vnode_t vboxSfDwnVnAlloc(mount_t pMount, enum vtype enmType, vnode_t pParent, uint64_t cbFile)
+{
+ /*
+ * Create our private data.
+ */
+ PVBOXSFDWNVNDATA pVnData = (PVBOXSFDWNVNDATA)RTMemAllocZ(sizeof(*pVnData));
+ if (pVnData)
+ {
+ pVnData->hHandle = SHFL_HANDLE_NIL;
+
+ struct vnode_fsparam VnParms;
+ RT_ZERO(VnParms);
+ VnParms.vnfs_mp = pMount;
+ VnParms.vnfs_vtype = enmType;
+ VnParms.vnfs_str = "vboxsf";
+ VnParms.vnfs_dvp = pParent;
+ VnParms.vnfs_fsnode = pVnData;
+ VnParms.vnfs_vops = g_papfnVBoxSfDwnVnDirOpsVector;
+ VnParms.vnfs_markroot = pParent == NULL;
+ VnParms.vnfs_marksystem = 0;
+ VnParms.vnfs_rdev = 0;
+ VnParms.vnfs_filesize = cbFile;
+ VnParms.vnfs_cnp = 0;
+ VnParms.vnfs_flags = VNFS_NOCACHE;
+
+ vnode_t pVnRet;
+ int rc = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &VnParms, &pVnRet);
+ if (rc == 0)
+ return pVnRet;
+ RTMemFree(pVnData);
+ }
+ printf("vboxSfDwnVnAlloc: out of memory!\n");
+ return NULL;
+}
+
+
+/**
+ * Vnode operations.
+ */
+static struct vnodeopv_entry_desc g_VBoxSfDirOpsDescList[] =
+{
+#define VNODEOPFUNC int(*)(void *)
+ { &vnop_default_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_access_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - probably not needed.
+ //{ &vnop_advlock_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - later.
+ //{ &vnop_allocate_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - maybe, need shfl function
+ { &vnop_blktooff_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_blockmap_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_bwrite_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_close_desc, (VNODEOPFUNC)vboxSfDwnVnClose },
+ //{ &vnop_copyfile_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_create_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_exchange_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_fsync_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_getattr_desc, (VNODEOPFUNC)vboxFsDwnVnGetAttr },
+ //{ &vnop_getnamedstream_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_getxattr_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_inactive_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_ioctl_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_link_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_listxattr_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_lookup_desc, (VNODEOPFUNC)vboxSfDwnVnLookup },
+ { &vnop_mkdir_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_mknod_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_mmap_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_mnomap_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_offtoblk_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_open_desc, (VNODEOPFUNC)vboxSfDwnVnOpen },
+ { &vnop_pagein_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_pageout_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_pathconf_desc, (VNODEOPFUNC)vboxSfDwnVnPathConf },
+ /* { &vnop_print_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, undefined in ML */
+ { &vnop_read_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_readdir_desc, (VNODEOPFUNC)vboxSfDwnVnReadDir },
+ //{ &vnop_readdirattr_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - hfs specific.
+ { &vnop_readlink_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_reclaim_desc, (VNODEOPFUNC)vboxSfDwnVnReclaim },
+ { &vnop_remove_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_removexattr_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_rename_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_revoke_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - not needed
+ { &vnop_rmdir_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_searchfs_desc, (VNODEOPFUNC)err_searchfs },
+ //{ &vnop_select_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - not needed
+ { &vnop_setattr_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { &vnop_setxattr_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ //{ &vnop_strategy_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - not needed
+ { &vnop_symlink_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ /* { &vnop_truncate_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, undefined in ML */
+ //{ &vnop_whiteout_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError }, - not needed/supported
+ { &vnop_write_desc, (VNODEOPFUNC)vboxSfDwnVnDefaultError },
+ { NULL, (VNODEOPFUNC)NULL },
+#undef VNODEOPFUNC
+};
+
+/** ??? */
+int (**g_papfnVBoxSfDwnVnDirOpsVector)(void *);
+
+/**
+ * VNode operation descriptors.
+ */
+struct vnodeopv_desc g_VBoxSfVnodeOpvDesc =
+{
+ &g_papfnVBoxSfDwnVnDirOpsVector,
+ g_VBoxSfDirOpsDescList
+};
+
diff --git a/src/VBox/Additions/darwin/VBoxSF/VBoxSF-VfsOps.cpp b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-VfsOps.cpp
new file mode 100644
index 00000000..e1c9c486
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/VBoxSF-VfsOps.cpp
@@ -0,0 +1,639 @@
+/* $Id: VBoxSF-VfsOps.cpp $ */
+/** @file
+ * VBoxFS - Darwin Shared Folders, Virtual File System Operations.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#include "VBoxSFInternal.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <VBox/log.h>
+
+
+
+/**
+ * vfsops::vfs_getattr implementation.
+ *
+ * @returns 0 on success or errno.h value on failure.
+ * @param pMount The mount data structure.
+ * @param pFsAttr Input & output structure.
+ * @param pContext Unused kAuth parameter.
+ */
+static int vboxSfDwnVfsGetAttr(mount_t pMount, struct vfs_attr *pFsAttr, vfs_context_t pContext)
+{
+ PVBOXSFMNTDATA pThis = (PVBOXSFMNTDATA)vfs_fsprivate(pMount);
+ AssertReturn(pThis, EBADMSG);
+ LogFlow(("vboxSfDwnVfsGetAttr: %s\n", pThis->MntInfo.szFolder));
+ RT_NOREF(pContext);
+
+ /*
+ * Get the file system stats from the host.
+ */
+ int rc;
+ struct MyEmbReq
+ {
+ VBGLIOCIDCHGCMFASTCALL Hdr;
+ VMMDevHGCMCall Call;
+ VBoxSFParmInformation Parms;
+ SHFLVOLINFO VolInfo;
+ } *pReq = (struct MyEmbReq *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq)
+ {
+ RT_ZERO(pReq->VolInfo);
+
+ VBGLIOCIDCHGCMFASTCALL_INIT(&pReq->Hdr, VbglR0PhysHeapGetPhysAddr(pReq), &pReq->Call, g_SfClientDarwin.idClient,
+ SHFL_FN_INFORMATION, SHFL_CPARMS_INFORMATION, sizeof(*pReq));
+ pReq->Parms.id32Root.type = VMMDevHGCMParmType_32bit;
+ pReq->Parms.id32Root.u.value32 = pThis->hHostFolder.root;
+ pReq->Parms.u64Handle.type = VMMDevHGCMParmType_64bit;
+ pReq->Parms.u64Handle.u.value64 = 0;
+ pReq->Parms.f32Flags.type = VMMDevHGCMParmType_32bit;
+ pReq->Parms.f32Flags.u.value32 = SHFL_INFO_VOLUME | SHFL_INFO_GET;
+ pReq->Parms.cb32.type = VMMDevHGCMParmType_32bit;
+ pReq->Parms.cb32.u.value32 = sizeof(pReq->VolInfo);
+ pReq->Parms.pInfo.type = VMMDevHGCMParmType_Embedded;
+ pReq->Parms.pInfo.u.Embedded.cbData = sizeof(pReq->VolInfo);
+ pReq->Parms.pInfo.u.Embedded.offData = RT_UOFFSETOF(struct MyEmbReq, VolInfo) - sizeof(VBGLIOCIDCHGCMFASTCALL);
+ pReq->Parms.pInfo.u.Embedded.fFlags = VBOX_HGCM_F_PARM_DIRECTION_FROM_HOST;
+
+ int vrc = VbglR0HGCMFastCall(g_SfClientDarwin.handle, &pReq->Hdr, sizeof(*pReq));
+ if (RT_SUCCESS(vrc))
+ vrc = pReq->Call.header.result;
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Fill in stuff.
+ */
+ /* Copy over the results we got from the host. */
+ uint32_t cbUnit = pReq->VolInfo.ulBytesPerSector * pReq->VolInfo.ulBytesPerAllocationUnit;
+ VFSATTR_RETURN(pFsAttr, f_bsize, cbUnit);
+ VFSATTR_RETURN(pFsAttr, f_iosize, _64K); /** @todo what's a good block size... */
+ VFSATTR_RETURN(pFsAttr, f_blocks, (uint64_t)pReq->VolInfo.ullTotalAllocationBytes / cbUnit);
+ VFSATTR_RETURN(pFsAttr, f_bavail, (uint64_t)pReq->VolInfo.ullAvailableAllocationBytes / cbUnit);
+ VFSATTR_RETURN(pFsAttr, f_bfree, (uint64_t)pReq->VolInfo.ullAvailableAllocationBytes / cbUnit);
+ VFSATTR_RETURN(pFsAttr, f_bused,
+ ((uint64_t)pReq->VolInfo.ullTotalAllocationBytes - (uint64_t)pReq->VolInfo.ullAvailableAllocationBytes) / cbUnit);
+ fsid_t const fsid = { { vfs_statfs(pMount)->f_fsid.val[0], vfs_typenum(pMount) } };
+ VFSATTR_RETURN(pFsAttr, f_fsid, fsid);
+
+ /* f_owner is handled by caller. */
+ /* f_signature is handled by caller. */
+
+ struct timespec TmpTv = { 1084190406, 0 };
+ VFSATTR_RETURN(pFsAttr, f_create_time, TmpTv);
+
+ /*
+ * Unsupported bits.
+ */
+ /* Dummies for some values we don't support. */
+ VFSATTR_RETURN(pFsAttr, f_objcount, 0);
+ VFSATTR_RETURN(pFsAttr, f_filecount, 0);
+ VFSATTR_RETURN(pFsAttr, f_dircount, 0);
+ VFSATTR_RETURN(pFsAttr, f_maxobjcount, UINT32_MAX);
+ VFSATTR_RETURN(pFsAttr, f_files, UINT32_MAX);
+ VFSATTR_RETURN(pFsAttr, f_ffree, UINT32_MAX);
+ VFSATTR_RETURN(pFsAttr, f_fssubtype, 0);
+ VFSATTR_RETURN(pFsAttr, f_carbon_fsid, 0);
+
+ /* Totally not supported: */
+ VFSATTR_CLEAR_ACTIVE(pFsAttr, f_modify_time);
+ VFSATTR_CLEAR_ACTIVE(pFsAttr, f_access_time);
+ VFSATTR_CLEAR_ACTIVE(pFsAttr, f_backup_time);
+
+ /*
+ * Annoying capability stuff.
+ * The 'valid' bits are only supposed to be set when we know for sure.
+ */
+ if (VFSATTR_IS_ACTIVE(pFsAttr, f_capabilities))
+ {
+ vol_capabilities_attr_t *pCaps = &pFsAttr->f_capabilities;
+
+ pCaps->valid[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_PERSISTENTOBJECTIDS
+ | VOL_CAP_FMT_SYMBOLICLINKS
+ | VOL_CAP_FMT_HARDLINKS
+ | VOL_CAP_FMT_JOURNAL
+ | VOL_CAP_FMT_JOURNAL_ACTIVE
+ | VOL_CAP_FMT_NO_ROOT_TIMES
+ | VOL_CAP_FMT_SPARSE_FILES
+ | VOL_CAP_FMT_ZERO_RUNS
+ | VOL_CAP_FMT_CASE_SENSITIVE
+ | VOL_CAP_FMT_CASE_PRESERVING
+ | VOL_CAP_FMT_FAST_STATFS
+ | VOL_CAP_FMT_2TB_FILESIZE
+ | VOL_CAP_FMT_OPENDENYMODES
+ | VOL_CAP_FMT_HIDDEN_FILES
+ | VOL_CAP_FMT_PATH_FROM_ID
+ | VOL_CAP_FMT_NO_VOLUME_SIZES
+ | VOL_CAP_FMT_DECMPFS_COMPRESSION
+ | VOL_CAP_FMT_64BIT_OBJECT_IDS;
+ pCaps->capabilities[VOL_CAPABILITIES_FORMAT] = VOL_CAP_FMT_2TB_FILESIZE
+ /// @todo | VOL_CAP_FMT_SYMBOLICLINKS - later
+ /// @todo | VOL_CAP_FMT_SPARSE_FILES - probably, needs testing.
+ /*| VOL_CAP_FMT_CASE_SENSITIVE - case-insensitive */
+ | VOL_CAP_FMT_CASE_PRESERVING
+ /// @todo | VOL_CAP_FMT_HIDDEN_FILES - if windows host.
+ /// @todo | VOL_CAP_FMT_OPENDENYMODES - if windows host.
+ ;
+ pCaps->valid[VOL_CAPABILITIES_INTERFACES] = VOL_CAP_INT_SEARCHFS
+ | VOL_CAP_INT_ATTRLIST
+ | VOL_CAP_INT_NFSEXPORT
+ | VOL_CAP_INT_READDIRATTR
+ | VOL_CAP_INT_EXCHANGEDATA
+ | VOL_CAP_INT_COPYFILE
+ | VOL_CAP_INT_ALLOCATE
+ | VOL_CAP_INT_VOL_RENAME
+ | VOL_CAP_INT_ADVLOCK
+ | VOL_CAP_INT_FLOCK
+ | VOL_CAP_INT_EXTENDED_SECURITY
+ | VOL_CAP_INT_USERACCESS
+ | VOL_CAP_INT_MANLOCK
+ | VOL_CAP_INT_NAMEDSTREAMS
+ | VOL_CAP_INT_EXTENDED_ATTR;
+ pCaps->capabilities[VOL_CAPABILITIES_INTERFACES] = 0
+ /// @todo | VOL_CAP_INT_SEARCHFS
+ /// @todo | VOL_CAP_INT_COPYFILE
+ /// @todo | VOL_CAP_INT_READDIRATTR
+ ;
+
+ pCaps->valid[VOL_CAPABILITIES_RESERVED1] = 0;
+ pCaps->capabilities[VOL_CAPABILITIES_RESERVED1] = 0;
+
+ pCaps->valid[VOL_CAPABILITIES_RESERVED2] = 0;
+ pCaps->capabilities[VOL_CAPABILITIES_RESERVED2] = 0;
+
+ VFSATTR_SET_SUPPORTED(pFsAttr, f_capabilities);
+ }
+
+
+ /*
+ * Annoying attribute stuff.
+ * The 'valid' bits are only supposed to be set when we know for sure.
+ */
+ if (VFSATTR_IS_ACTIVE(pFsAttr, f_attributes))
+ {
+ vol_attributes_attr_t *pAt = &pFsAttr->f_attributes;
+
+ pAt->validattr.commonattr = ATTR_CMN_NAME
+ | ATTR_CMN_DEVID
+ | ATTR_CMN_FSID
+ | ATTR_CMN_OBJTYPE
+ | ATTR_CMN_OBJTAG
+ | ATTR_CMN_OBJID
+ | ATTR_CMN_OBJPERMANENTID
+ | ATTR_CMN_PAROBJID
+ | ATTR_CMN_SCRIPT
+ | ATTR_CMN_CRTIME
+ | ATTR_CMN_MODTIME
+ | ATTR_CMN_CHGTIME
+ | ATTR_CMN_ACCTIME
+ | ATTR_CMN_BKUPTIME
+ | ATTR_CMN_FNDRINFO
+ | ATTR_CMN_OWNERID
+ | ATTR_CMN_GRPID
+ | ATTR_CMN_ACCESSMASK
+ | ATTR_CMN_FLAGS
+ | ATTR_CMN_USERACCESS
+ | ATTR_CMN_EXTENDED_SECURITY
+ | ATTR_CMN_UUID
+ | ATTR_CMN_GRPUUID
+ | ATTR_CMN_FILEID
+ | ATTR_CMN_PARENTID
+ | ATTR_CMN_FULLPATH
+ | ATTR_CMN_ADDEDTIME;
+ pAt->nativeattr.commonattr = ATTR_CMN_NAME
+ | ATTR_CMN_DEVID
+ | ATTR_CMN_FSID
+ | ATTR_CMN_OBJTYPE
+ | ATTR_CMN_OBJTAG
+ | ATTR_CMN_OBJID
+ //| ATTR_CMN_OBJPERMANENTID
+ | ATTR_CMN_PAROBJID
+ //| ATTR_CMN_SCRIPT
+ | ATTR_CMN_CRTIME
+ | ATTR_CMN_MODTIME
+ | ATTR_CMN_CHGTIME
+ | ATTR_CMN_ACCTIME
+ //| ATTR_CMN_BKUPTIME
+ //| ATTR_CMN_FNDRINFO
+ //| ATTR_CMN_OWNERID
+ //| ATTR_CMN_GRPID
+ | ATTR_CMN_ACCESSMASK
+ //| ATTR_CMN_FLAGS
+ //| ATTR_CMN_USERACCESS
+ //| ATTR_CMN_EXTENDED_SECURITY
+ //| ATTR_CMN_UUID
+ //| ATTR_CMN_GRPUUID
+ | ATTR_CMN_FILEID
+ | ATTR_CMN_PARENTID
+ | ATTR_CMN_FULLPATH
+ //| ATTR_CMN_ADDEDTIME
+ ;
+ pAt->validattr.volattr = ATTR_VOL_FSTYPE
+ | ATTR_VOL_SIGNATURE
+ | ATTR_VOL_SIZE
+ | ATTR_VOL_SPACEFREE
+ | ATTR_VOL_SPACEAVAIL
+ | ATTR_VOL_MINALLOCATION
+ | ATTR_VOL_ALLOCATIONCLUMP
+ | ATTR_VOL_IOBLOCKSIZE
+ | ATTR_VOL_OBJCOUNT
+ | ATTR_VOL_FILECOUNT
+ | ATTR_VOL_DIRCOUNT
+ | ATTR_VOL_MAXOBJCOUNT
+ | ATTR_VOL_MOUNTPOINT
+ | ATTR_VOL_NAME
+ | ATTR_VOL_MOUNTFLAGS
+ | ATTR_VOL_MOUNTEDDEVICE
+ | ATTR_VOL_ENCODINGSUSED
+ | ATTR_VOL_CAPABILITIES
+ | ATTR_VOL_UUID
+ | ATTR_VOL_ATTRIBUTES
+ | ATTR_VOL_INFO;
+ pAt->nativeattr.volattr = ATTR_VOL_FSTYPE
+ //| ATTR_VOL_SIGNATURE
+ | ATTR_VOL_SIZE
+ | ATTR_VOL_SPACEFREE
+ | ATTR_VOL_SPACEAVAIL
+ | ATTR_VOL_MINALLOCATION
+ | ATTR_VOL_ALLOCATIONCLUMP
+ | ATTR_VOL_IOBLOCKSIZE
+ //| ATTR_VOL_OBJCOUNT
+ //| ATTR_VOL_FILECOUNT
+ //| ATTR_VOL_DIRCOUNT
+ //| ATTR_VOL_MAXOBJCOUNT
+ //| ATTR_VOL_MOUNTPOINT - ??
+ | ATTR_VOL_NAME
+ | ATTR_VOL_MOUNTFLAGS
+ | ATTR_VOL_MOUNTEDDEVICE
+ //| ATTR_VOL_ENCODINGSUSED
+ | ATTR_VOL_CAPABILITIES
+ //| ATTR_VOL_UUID
+ | ATTR_VOL_ATTRIBUTES
+ //| ATTR_VOL_INFO
+ ;
+ pAt->validattr.dirattr = ATTR_DIR_LINKCOUNT
+ | ATTR_DIR_ENTRYCOUNT
+ | ATTR_DIR_MOUNTSTATUS;
+ pAt->nativeattr.dirattr = 0 //ATTR_DIR_LINKCOUNT
+ | ATTR_DIR_ENTRYCOUNT
+ | ATTR_DIR_MOUNTSTATUS
+ ;
+ pAt->validattr.fileattr = ATTR_FILE_LINKCOUNT
+ | ATTR_FILE_TOTALSIZE
+ | ATTR_FILE_ALLOCSIZE
+ | ATTR_FILE_IOBLOCKSIZE
+ | ATTR_FILE_DEVTYPE
+ | ATTR_FILE_FORKCOUNT
+ | ATTR_FILE_FORKLIST
+ | ATTR_FILE_DATALENGTH
+ | ATTR_FILE_DATAALLOCSIZE
+ | ATTR_FILE_RSRCLENGTH
+ | ATTR_FILE_RSRCALLOCSIZE;
+ pAt->nativeattr.fileattr = 0
+ //|ATTR_FILE_LINKCOUNT
+ | ATTR_FILE_TOTALSIZE
+ | ATTR_FILE_ALLOCSIZE
+ //| ATTR_FILE_IOBLOCKSIZE
+ | ATTR_FILE_DEVTYPE
+ //| ATTR_FILE_FORKCOUNT
+ //| ATTR_FILE_FORKLIST
+ | ATTR_FILE_DATALENGTH
+ | ATTR_FILE_DATAALLOCSIZE
+ | ATTR_FILE_RSRCLENGTH
+ | ATTR_FILE_RSRCALLOCSIZE
+ ;
+ pAt->validattr.forkattr = ATTR_FORK_TOTALSIZE
+ | ATTR_FORK_ALLOCSIZE;
+ pAt->nativeattr.forkattr = 0
+ //| ATTR_FORK_TOTALSIZE
+ //| ATTR_FORK_ALLOCSIZE
+ ;
+ VFSATTR_SET_SUPPORTED(pFsAttr, f_attributes);
+ }
+
+ if (VFSATTR_IS_ACTIVE(pFsAttr, f_vol_name))
+ {
+ RTStrCopy(pFsAttr->f_vol_name, MAXPATHLEN, pThis->MntInfo.szFolder);
+ VFSATTR_SET_SUPPORTED(pFsAttr, f_vol_name);
+ }
+
+ rc = 0;
+ }
+ else
+ {
+ Log(("vboxSfOs2QueryFileInfo: VbglR0SfFsInfo failed: %Rrc\n", vrc));
+ rc = RTErrConvertToErrno(vrc);
+ }
+
+ VbglR0PhysHeapFree(pReq);
+ }
+ else
+ rc = ENOMEM;
+ return rc;
+}
+
+
+/**
+ * vfsops::vfs_root implementation.
+ *
+ * @returns 0 on success or errno.h value on failure.
+ * @param pMount The mount data structure.
+ * @param ppVnode Where to return the referenced root node on success.
+ * @param pContext Unused kAuth parameter.
+ */
+static int vboxSfDwnVfsRoot(mount_t pMount, vnode_t *ppVnode, vfs_context_t pContext)
+{
+ PVBOXSFMNTDATA pThis = (PVBOXSFMNTDATA)vfs_fsprivate(pMount);
+ AssertReturn(pThis, EBADMSG);
+ LogFlow(("vboxSfDwnVfsRoot: pThis=%p:{%s}\n", pThis, pThis->MntInfo.szFolder));
+ RT_NOREF(pContext);
+
+ /*
+ * We shouldn't be callable during unmount, should we?
+ */
+ AssertReturn(vfs_isunmount(pMount), EBUSY);
+
+ /*
+ * There should always be a root node around.
+ */
+ if (pThis->pVnRoot)
+ {
+ int rc = vnode_get(pThis->pVnRoot);
+ if (rc == 0)
+ {
+ *ppVnode = pThis->pVnRoot;
+ LogFlow(("vboxSfDwnVfsRoot: return %p\n", *ppVnode));
+ return 0;
+ }
+ Log(("vboxSfDwnVfsRoot: vnode_get failed! %d\n", rc));
+ return rc;
+ }
+
+ LogRel(("vboxSfDwnVfsRoot: pVnRoot is NULL!\n"));
+ return EILSEQ;
+}
+
+
+/**
+ * vfsops::vfs_umount implementation.
+ *
+ * @returns 0 on success or errno.h value on failure.
+ * @param pMount The mount data.
+ * @param fFlags Unmount flags.
+ * @param pContext kAuth context which we don't care much about.
+ *
+ */
+static int vboxSfDwnVfsUnmount(mount_t pMount, int fFlags, vfs_context_t pContext)
+{
+ PVBOXSFMNTDATA pThis = (PVBOXSFMNTDATA)vfs_fsprivate(pMount);
+ AssertReturn(pThis, 0);
+ LogFlowFunc(("pThis=%p:{%s} fFlags=%#x\n", pThis, pThis->MntInfo.szFolder, fFlags));
+ RT_NOREF(pContext);
+
+ /*
+ * Flush vnodes.
+ */
+ int rc = vflush(pMount, pThis->pVnRoot, fFlags & MNT_FORCE ? FORCECLOSE : 0);
+ if (rc == 0)
+ {
+ /*
+ * Is the file system still busy?
+ *
+ * Until we find a way of killing any active host calls, we cannot properly
+ * respect the MNT_FORCE flag here. So, MNT_FORCE is ignored here.
+ */
+ if ( !pThis->pVnRoot
+ || !vnode_isinuse(pThis->pVnRoot, 1))
+ {
+ /*
+ * Release our root vnode reference and do another flush.
+ */
+ if (pThis->pVnRoot)
+ {
+ vnode_put(pThis->pVnRoot);
+ pThis->pVnRoot = NULL;
+ }
+ vflush(pMount, NULLVP, FORCECLOSE);
+
+ /*
+ * Unmap the shared folder and destroy our mount info structure.
+ */
+ vfs_setfsprivate(pMount, NULL);
+
+ rc = VbglR0SfUnmapFolder(&g_SfClientDarwin, &pThis->hHostFolder);
+ AssertRC(rc);
+
+ RT_ZERO(*pThis);
+ RTMemFree(pThis);
+
+ vfs_clearflags(pMount, MNT_LOCAL); /* ?? */
+ rc = 0;
+
+ g_cVBoxSfMounts--;
+ }
+ else
+ {
+ Log(("VBoxSF: umount failed: file system busy! (%s)\n", pThis->MntInfo.szFolder));
+ rc = EBUSY;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * vfsops::vfs_start implementation.
+ */
+static int vboxSfDwnVfsStart(mount_t pMount, int fFlags, vfs_context_t pContext)
+{
+ RT_NOREF(pMount, fFlags, pContext);
+ return 0;
+}
+
+
+/**
+ * vfsops::vfs_mount implementation.
+ *
+ * @returns 0 on success or errno.h value on failure.
+ * @param pMount The mount data structure.
+ * @param pDevVp The device to mount. Not used by us.
+ * @param pUserData User space address of parameters supplied to mount().
+ * We expect a VBOXSFDRWNMOUNTINFO structure.
+ * @param pContext kAuth context needed in order to authentificate mount
+ * operation.
+ */
+static int vboxSfDwnVfsMount(mount_t pMount, vnode_t pDevVp, user_addr_t pUserData, vfs_context_t pContext)
+{
+ RT_NOREF(pDevVp, pContext);
+
+ /*
+ * We don't support mount updating.
+ */
+ if (vfs_isupdate(pMount))
+ {
+ LogRel(("VBoxSF: mount: MNT_UPDATE is not supported.\n"));
+ return ENOTSUP;
+ }
+ if (pUserData == USER_ADDR_NULL)
+ {
+ LogRel(("VBoxSF: mount: pUserData is NULL.\n"));
+ return EINVAL;
+ }
+ struct vfsstatfs *pFsStats = vfs_statfs(pMount);
+ AssertReturn(pFsStats, EINVAL);
+
+ /*
+ * Get the mount information from userland.
+ */
+ PVBOXSFMNTDATA pThis = (PVBOXSFMNTDATA)RTMemAllocZ(sizeof(*pThis));
+ if (!pThis)
+ return ENOMEM;
+ pThis->uidMounter = pFsStats->f_owner;
+
+ int rc = RTR0MemUserCopyFrom(&pThis->MntInfo, (RTR3PTR)pUserData, sizeof(pThis->MntInfo));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("VBoxSF: mount: Failed to copy in mount user data: %Rrc\n", rc));
+ rc = EFAULT;
+ }
+ else if (pThis->MntInfo.u32Magic != VBOXSFDRWNMOUNTINFO_MAGIC)
+ {
+ LogRel(("VBoxSF: mount: Invalid user data magic (%#x)\n", pThis->MntInfo.u32Magic));
+ rc = EINVAL;
+ }
+ else if ( (rc = RTStrValidateEncodingEx(pThis->MntInfo.szFolder, sizeof(pThis->MntInfo.szFolder),
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED)) != VINF_SUCCESS
+ || pThis->MntInfo.szFolder[0] == '\0')
+ {
+ LogRel(("VBoxSF: mount: Invalid or empty share name!\n"));
+ rc = EINVAL;
+ }
+ else
+ {
+ /*
+ * Try map the shared folder.
+ */
+ if (vboxSfDwnConnect())
+ {
+ PSHFLSTRING pName = ShflStringDupUtf8(pThis->MntInfo.szFolder);
+ if (pName)
+ {
+ rc = VbglR0SfMapFolder(&g_SfClientDarwin, pName, &pThis->hHostFolder);
+ RTMemFree(pName);
+ if (RT_SUCCESS(rc))
+ {
+
+ /*
+ * Create a root node, that avoid races later.
+ */
+ pThis->pVnRoot = vboxSfDwnVnAlloc(pMount, VDIR, NULL /*pParent*/, 0);
+ if (pThis->pVnRoot)
+ {
+ /*
+ * Fill file system stats with dummy data.
+ */
+ pFsStats->f_bsize = 512;
+ pFsStats->f_iosize = _64K;
+ pFsStats->f_blocks = _1M;
+ pFsStats->f_bavail = _1M / 4 * 3;
+ pFsStats->f_bused = _1M / 4;
+ pFsStats->f_files = 1024;
+ pFsStats->f_ffree = _64K;
+ vfs_getnewfsid(pMount); /* f_fsid */
+ /* pFsStats->f_fowner - don't touch */
+ /* pFsStats->f_fstypename - don't touch */
+ /* pFsStats->f_mntonname - don't touch */
+ RTStrCopy(pFsStats->f_mntfromname, sizeof(pFsStats->f_mntfromname), pThis->MntInfo.szFolder);
+ /* pFsStats->f_fssubtype - don't touch? */
+ /* pFsStats->f_reserved[0] - don't touch? */
+ /* pFsStats->f_reserved[1] - don't touch? */
+
+ /*
+ * We're good. Set private data and flags.
+ */
+ vfs_setfsprivate(pMount, pThis);
+ vfs_setflags(pMount, MNT_SYNCHRONOUS | MNT_NOSUID | MNT_NODEV);
+ /** @todo Consider flags like MNT_NOEXEC ? */
+
+ /// @todo vfs_setauthopaque(pMount)?
+ /// @todo vfs_clearauthopaqueaccess(pMount)?
+ /// @todo vfs_clearextendedsecurity(pMount)?
+
+ LogRel(("VBoxSF: mount: Successfully mounted '%s' (uidMounter=%u).\n",
+ pThis->MntInfo.szFolder, pThis->uidMounter));
+ return 0;
+ }
+
+ LogRel(("VBoxSF: mount: Failed to allocate root node!\n"));
+ rc = ENOMEM;
+ }
+ else
+ {
+ LogRel(("VBoxSF: mount: VbglR0SfMapFolder failed on '%s': %Rrc\n", pThis->MntInfo.szFolder, rc));
+ rc = ENOENT;
+ }
+ }
+ else
+ rc = ENOMEM;
+ }
+ else
+ {
+ LogRel(("VBoxSF: mount: Not connected to shared folders service!\n"));
+ rc = ENOTCONN;
+ }
+ }
+ RTMemFree(pThis);
+ return rc;
+}
+
+
+/**
+ * VFS operations
+ */
+struct vfsops g_VBoxSfVfsOps =
+{
+ vboxSfDwnVfsMount,
+ vboxSfDwnVfsStart,
+ vboxSfDwnVfsUnmount,
+ vboxSfDwnVfsRoot,
+ NULL, /* Skipped: vfs_quotactl */
+ vboxSfDwnVfsGetAttr,
+ NULL, /* Skipped: vfs_sync */
+ NULL, /* Skipped: vfs_vget */
+ NULL, /* Skipped: vfs_fhtovp */
+ NULL, /* Skipped: vfs_vptofh */
+ NULL, /* Skipped: vfs_init */
+ NULL, /* Skipped: vfs_sysctl */
+ NULL, /* Skipped: vfs_setattr */
+ /* Reserved */
+ { NULL, NULL, NULL, NULL, NULL, NULL, NULL, },
+};
diff --git a/src/VBox/Additions/darwin/VBoxSF/VBoxSF.cpp b/src/VBox/Additions/darwin/VBoxSF/VBoxSF.cpp
new file mode 100644
index 00000000..8d8388a2
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/VBoxSF.cpp
@@ -0,0 +1,261 @@
+/* $Id: VBoxSF.cpp $ */
+/** @file
+ * VBoxSF - Darwin Shared Folders, KEXT entry points.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#include "VBoxSFInternal.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <VBox/version.h>
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static kern_return_t vboxSfDwnModuleLoad(struct kmod_info *pKModInfo, void *pvData);
+static kern_return_t vboxSfDwnModuleUnload(struct kmod_info *pKModInfo, void *pvData);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The VBoxGuest service if we've managed to connect to it already. */
+static IOService *g_pVBoxGuest = NULL;
+/** The shared folder service client structure. */
+VBGLSFCLIENT g_SfClientDarwin = { UINT32_MAX, NULL };
+/** Number of active mounts. Used for unload prevention. */
+uint32_t volatile g_cVBoxSfMounts = 0;
+
+/** VFS table entry for our file system (for vfs_fsremove). */
+static vfstable_t g_pVBoxSfVfsTableEntry;
+/** For vfs_fsentry. */
+static struct vnodeopv_desc *g_apVBoxSfVnodeOpDescList[] =
+{
+ &g_VBoxSfVnodeOpvDesc,
+};
+/** VFS registration structure. */
+static struct vfs_fsentry g_VBoxSfFsEntry =
+{
+ .vfe_vfsops = &g_VBoxSfVfsOps,
+ .vfe_vopcnt = RT_ELEMENTS(g_apVBoxSfVnodeOpDescList),
+ .vfe_opvdescs = g_apVBoxSfVnodeOpDescList,
+ .vfe_fstypenum = -1,
+ .vfe_fsname = VBOXSF_DARWIN_FS_NAME,
+ .vfe_flags = VFS_TBLTHREADSAFE /* Required. */
+ | VFS_TBLFSNODELOCK /* Required. */
+ | VFS_TBLNOTYPENUM /* No historic file system number. */
+ | VFS_TBL64BITREADY, /* Can handle 64-bit processes */
+ /** @todo add VFS_TBLREADDIR_EXTENDED */
+ .vfe_reserv = { NULL, NULL },
+};
+
+
+/**
+ * Declare the module stuff.
+ */
+RT_C_DECLS_BEGIN
+extern kern_return_t _start(struct kmod_info *pKModInfo, void *pvData);
+extern kern_return_t _stop(struct kmod_info *pKModInfo, void *pvData);
+
+KMOD_EXPLICIT_DECL(VBoxSF, VBOX_VERSION_STRING, _start, _stop)
+DECL_HIDDEN_DATA(kmod_start_func_t *) _realmain = vboxSfDwnModuleLoad;
+DECL_HIDDEN_DATA(kmod_stop_func_t *) _antimain = vboxSfDwnModuleUnload;
+DECL_HIDDEN_DATA(int) _kext_apple_cc = __APPLE_CC__;
+RT_C_DECLS_END
+
+
+/**
+ * Connect to VBoxGuest and host shared folders service.
+ *
+ * @returns true if connected, false if not.
+ */
+bool vboxSfDwnConnect(void)
+{
+ /*
+ * Grab VBoxGuest - since it's a dependency of this module, it shouldn't be hard.
+ */
+ if (!g_pVBoxGuest)
+ {
+ OSDictionary *pServiceMatcher = IOService::serviceMatching("org_virtualbox_VBoxGuest");
+ if (pServiceMatcher)
+ {
+ IOService *pVBoxGuest = IOService::waitForMatchingService(pServiceMatcher, 10 * RT_NS_1SEC);
+ if (pVBoxGuest)
+ g_pVBoxGuest = pVBoxGuest;
+ else
+ LogRel(("vboxSfDwnConnect: IOService::waitForMatchingService failed!!\n"));
+ }
+ else
+ LogRel(("vboxSfDwnConnect: serviceMatching failed\n"));
+ }
+
+ if (g_pVBoxGuest)
+ {
+ /*
+ * Get hold of the shared folders service if we haven't already.
+ */
+ if (g_SfClientDarwin.handle != NULL)
+ return true;
+
+ int rc = VbglR0SfConnect(&g_SfClientDarwin);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0SfSetUtf8(&g_SfClientDarwin);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ LogRel(("VBoxSF: VbglR0SfSetUtf8 failed: %Rrc\n", rc));
+
+ VbglR0SfDisconnect(&g_SfClientDarwin);
+ g_SfClientDarwin.handle = NULL;
+ }
+ else
+ LogRel(("VBoxSF: VbglR0SfConnect failed: %Rrc\n", rc));
+ }
+
+ return false;
+}
+
+
+/**
+ * Start the kernel module.
+ */
+static kern_return_t vboxSfDwnModuleLoad(struct kmod_info *pKModInfo, void *pvData)
+{
+ RT_NOREF(pKModInfo, pvData);
+#ifdef DEBUG
+ printf("vboxSfDwnModuleLoad\n");
+ RTLogBackdoorPrintf("vboxSfDwnModuleLoad\n");
+#endif
+
+ /*
+ * Initialize IPRT and the ring-0 guest library.
+ */
+ int rc = RTR0Init(0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0SfInit();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Register the file system.
+ */
+ rc = vfs_fsadd(&g_VBoxSfFsEntry, &g_pVBoxSfVfsTableEntry);
+ if (rc == 0)
+ {
+ /*
+ * Try find VBoxGuest and connect to the shared folders service on the host.
+ */
+ /** @todo should we just ignore the error here and retry at mount time?
+ * Technically, VBoxGuest should be available since it's one of our
+ * dependencies... */
+ vboxSfDwnConnect();
+
+ /*
+ * We're done for now. We'll deal with
+ */
+ LogRel(("VBoxSF: loaded\n"));
+ return KERN_SUCCESS;
+ }
+
+ printf("VBoxSF: vfs_fsadd failed: %d\n", rc);
+ RTLogBackdoorPrintf("VBoxSF: vfs_fsadd failed: %d\n", rc);
+ VbglR0SfTerm();
+ }
+ else
+ {
+ printf("VBoxSF: VbglR0SfInit failed: %d\n", rc);
+ RTLogBackdoorPrintf("VBoxSF: VbglR0SfInit failed: %Rrc\n", rc);
+ }
+ RTR0Term();
+ }
+ else
+ {
+ printf("VBoxSF: RTR0Init failed: %d\n", rc);
+ RTLogBackdoorPrintf("VBoxSF: RTR0Init failed: %Rrc\n", rc);
+ }
+ return KERN_FAILURE;
+}
+
+
+/**
+ * Stop the kernel module.
+ */
+static kern_return_t vboxSfDwnModuleUnload(struct kmod_info *pKModInfo, void *pvData)
+{
+ RT_NOREF(pKModInfo, pvData);
+#ifdef DEBUG
+ printf("vboxSfDwnModuleUnload\n");
+ RTLogBackdoorPrintf("vboxSfDwnModuleUnload\n");
+#endif
+
+
+ /*
+ * Are we busy? If so fail. Otherwise try deregister the file system.
+ */
+ if (g_cVBoxSfMounts > 0)
+ {
+ LogRel(("VBoxSF: Refusing to unload with %u active mounts\n", g_cVBoxSfMounts));
+ return KERN_NO_ACCESS;
+ }
+
+ if (g_pVBoxSfVfsTableEntry)
+ {
+ int rc = vfs_fsremove(g_pVBoxSfVfsTableEntry);
+ if (rc != 0)
+ {
+ LogRel(("VBoxSF: vfs_fsremove failed: %d\n", rc));
+ return KERN_NO_ACCESS;
+ }
+ }
+
+ /*
+ * Disconnect and terminate libraries we're using.
+ */
+ if (g_SfClientDarwin.handle != NULL)
+ {
+ VbglR0SfDisconnect(&g_SfClientDarwin);
+ g_SfClientDarwin.handle = NULL;
+ }
+
+ if (g_pVBoxGuest)
+ {
+ g_pVBoxGuest->release();
+ g_pVBoxGuest = NULL;
+ }
+
+ VbglR0SfTerm();
+ RTR0Term();
+ return KERN_SUCCESS;
+}
+
diff --git a/src/VBox/Additions/darwin/VBoxSF/VBoxSFInternal.h b/src/VBox/Additions/darwin/VBoxSF/VBoxSFInternal.h
new file mode 100644
index 00000000..6d297e82
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/VBoxSFInternal.h
@@ -0,0 +1,117 @@
+/* $Id: VBoxSFInternal.h $ */
+/** @file
+ * VBoxSF - Darwin Shared Folders, internal header.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_darwin_VBoxSF_VBoxSFInternal_h
+#define GA_INCLUDED_SRC_darwin_VBoxSF_VBoxSFInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VBoxSFMount.h"
+
+#include <libkern/libkern.h>
+#include <iprt/types.h>
+#include <IOKit/IOLib.h>
+#include <IOKit/IOService.h>
+#include <mach/mach_port.h>
+#include <mach/kmod.h>
+#include <mach/mach_types.h>
+#include <sys/errno.h>
+#include <sys/dirent.h>
+#include <sys/lock.h>
+#include <sys/fcntl.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <sys/vnode.h>
+#include <vfs/vfs_support.h>
+#undef PVM
+
+#include <iprt/mem.h>
+#include <VBox/VBoxGuest.h>
+#include <VBox/VBoxGuestLibSharedFolders.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Private data we associate with a mount.
+ */
+typedef struct VBOXSFMNTDATA
+{
+ /** The shared folder mapping */
+ VBGLSFMAP hHostFolder;
+ /** The root VNode. */
+ vnode_t pVnRoot;
+ /** User that mounted shared folder (anyone but root?). */
+ uid_t uidMounter;
+ /** The mount info from the mount() call. */
+ VBOXSFDRWNMOUNTINFO MntInfo;
+} VBOXSFMNTDATA;
+/** Pointer to private mount data. */
+typedef VBOXSFMNTDATA *PVBOXSFMNTDATA;
+
+/**
+ * Private data we associate with a VNode.
+ */
+typedef struct VBOXSFDWNVNDATA
+{
+ /** The handle to the host object. */
+ SHFLHANDLE hHandle;
+ ///PSHFLSTRING pPath; /** Path within shared folder */
+ ///lck_attr_t *pLockAttr; /** BSD locking stuff */
+ ///lck_rw_t *pLock; /** BSD locking stuff */
+} VBOXSFDWNVNDATA;
+/** Pointer to private vnode data. */
+typedef VBOXSFDWNVNDATA *PVBOXSFDWNVNDATA;
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+extern VBGLSFCLIENT g_SfClientDarwin;
+extern uint32_t volatile g_cVBoxSfMounts;
+extern struct vfsops g_VBoxSfVfsOps;
+extern struct vnodeopv_desc g_VBoxSfVnodeOpvDesc;
+extern int (**g_papfnVBoxSfDwnVnDirOpsVector)(void *);
+
+
+
+/*********************************************************************************************************************************
+* Functions *
+*********************************************************************************************************************************/
+bool vboxSfDwnConnect(void);
+vnode_t vboxSfDwnVnAlloc(mount_t pMount, enum vtype enmType, vnode_t pParent, uint64_t cbFile);
+
+
+#endif /* !GA_INCLUDED_SRC_darwin_VBoxSF_VBoxSFInternal_h */
+
diff --git a/src/VBox/Additions/darwin/VBoxSF/VBoxSFMount.h b/src/VBox/Additions/darwin/VBoxSF/VBoxSFMount.h
new file mode 100644
index 00000000..318d6e42
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/VBoxSFMount.h
@@ -0,0 +1,54 @@
+/* $Id: VBoxSFMount.h $ */
+/** @file
+ * VBoxSF - Darwin Shared Folders, mount interface.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_darwin_VBoxSF_VBoxSFMount_h
+#define GA_INCLUDED_SRC_darwin_VBoxSF_VBoxSFMount_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/types.h>
+
+/** The shared folders file system name. */
+#define VBOXSF_DARWIN_FS_NAME "vboxsf"
+
+/**
+ * Mount information that gets passed from userland on mount.
+ */
+typedef struct VBOXSFDRWNMOUNTINFO
+{
+ /** Magic value (VBOXSFDRWNMOUNTINFO_MAGIC). */
+ uint32_t u32Magic;
+ /** The shared folder name. */
+ char szFolder[260];
+} VBOXSFDRWNMOUNTINFO;
+typedef VBOXSFDRWNMOUNTINFO *PVBOXSFDRWNMOUNTINFO;
+/** Magic value for VBOXSFDRWNMOUNTINFO::u32Magic. */
+#define VBOXSFDRWNMOUNTINFO_MAGIC UINT32_C(0xc001cafe)
+
+#endif /* !GA_INCLUDED_SRC_darwin_VBoxSF_VBoxSFMount_h */
+
diff --git a/src/VBox/Additions/darwin/VBoxSF/mount.vboxsf.cpp b/src/VBox/Additions/darwin/VBoxSF/mount.vboxsf.cpp
new file mode 100644
index 00000000..57db36bc
--- /dev/null
+++ b/src/VBox/Additions/darwin/VBoxSF/mount.vboxsf.cpp
@@ -0,0 +1,97 @@
+/* $Id: mount.vboxsf.cpp $ */
+/** @file
+ * VBoxSF - Darwin Shared Folders, Mount Utility.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mount.h>
+
+#include "VBoxSFMount.h"
+#include <iprt/string.h>
+
+
+static RTEXITCODE usage(const char *pszArg0)
+{
+ fprintf(stderr, "usage: %s [OPTIONS] <shared folder name> <mount point>\n", pszArg0);
+ return RTEXITCODE_SYNTAX;
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Skip past parameters.
+ */
+ int c;
+ while ((c = getopt(argc, argv, "o:")) != -1)
+ {
+ switch (c)
+ {
+ case 'o':
+ break;
+ default:
+ return usage(argv[0]);
+ }
+ }
+
+ /* Two arguments are rquired: <share name> and <mount point> */
+ if (argc - optind != 2)
+ return usage(argv[0]);
+ const char * const pszFolder = argv[optind++];
+ const char * const pszMountPoint = argv[optind];
+
+ /*
+ * Check that the folder is within bounds and doesn't include any shady characters.
+ */
+ size_t cchFolder = strlen(pszFolder);
+ if ( cchFolder < 1
+ || cchFolder >= RT_SIZEOFMEMB(VBOXSFDRWNMOUNTINFO, szFolder)
+ || strpbrk(pszFolder, "\\/") != NULL)
+ {
+ fprintf(stderr, "Invalid shared folder name '%s'!\n", pszFolder);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Do the mounting.
+ */
+ VBOXSFDRWNMOUNTINFO MntInfo;
+ RT_ZERO(MntInfo);
+ MntInfo.u32Magic = VBOXSFDRWNMOUNTINFO_MAGIC;
+ memcpy(MntInfo.szFolder, pszFolder, cchFolder);
+ int rc = mount(VBOXSF_DARWIN_FS_NAME, pszMountPoint, 0, &MntInfo);
+ if (rc == 0)
+ return 0;
+
+ fprintf(stderr, "error mounting '%s' at '%s': %s (%d)\n", pszFolder, pszMountPoint, strerror(errno), errno);
+ return RTEXITCODE_FAILURE;
+}
diff --git a/src/VBox/Additions/freebsd/.scm-settings b/src/VBox/Additions/freebsd/.scm-settings
new file mode 100644
index 00000000..0d53f266
--- /dev/null
+++ b/src/VBox/Additions/freebsd/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the FreeBSD guest additions.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+--filter-out-files /Installer/pkg-descr
+/drm/vboxvideo_drm.c: --no-convert-tabs
+
diff --git a/src/VBox/Additions/freebsd/Installer/pkg-descr b/src/VBox/Additions/freebsd/Installer/pkg-descr
new file mode 100644
index 00000000..49c9d066
--- /dev/null
+++ b/src/VBox/Additions/freebsd/Installer/pkg-descr
@@ -0,0 +1,3 @@
+VirtualBox guest additions for the FreeBSD operating system
+
+WWW: http://www.virtualbox.org
diff --git a/src/VBox/Additions/freebsd/Installer/vboxguest.sh b/src/VBox/Additions/freebsd/Installer/vboxguest.sh
new file mode 100755
index 00000000..503217f5
--- /dev/null
+++ b/src/VBox/Additions/freebsd/Installer/vboxguest.sh
@@ -0,0 +1,149 @@
+#!/bin/bash
+# $Id: vboxguest.sh $
+## @file
+# VirtualBox Guest Additions kernel module control script for FreeBSD.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+VBOXGUESTFILE=""
+SILENTUNLOAD=""
+
+abort()
+{
+ echo 1>&2 "$1"
+ exit 1
+}
+
+info()
+{
+ echo 1>&2 "$1"
+}
+
+get_module_path()
+{
+ moduledir="/boot/kernel";
+ modulepath=$moduledir/vboxguest.ko
+ if test -f "$modulepath"; then
+ VBOXGUESTFILE="$modulepath"
+ else
+ VBOXGUESTFILE=""
+ fi
+}
+
+check_if_installed()
+{
+ if test "$VBOXGUESTFILE" -a -f "$VBOXGUESTFILE"; then
+ return 0
+ fi
+ abort "VirtualBox kernel module (vboxguest) not installed."
+}
+
+module_loaded()
+{
+ loadentry=`kldstat | grep vboxguest`
+ if test -z "$loadentry"; then
+ return 1
+ fi
+ return 0
+}
+
+check_root()
+{
+ if test `id -u` -ne 0; then
+ abort "This program must be run with administrator privileges. Aborting"
+ fi
+}
+
+start()
+{
+ if module_loaded; then
+ info "vboxguest already loaded..."
+ else
+ /sbin/kldload vboxguest.ko
+ if ! module_loaded; then
+ abort "Failed to load vboxguest."
+ elif test -c "/dev/vboxguest"; then
+ info "Loaded vboxguest."
+ else
+ stop
+ abort "Aborting due to attach failure."
+ fi
+ fi
+}
+
+stop()
+{
+ if module_loaded; then
+ /sbin/kldunload vboxguest.ko
+ info "Unloaded vboxguest."
+ elif test -z "$SILENTUNLOAD"; then
+ info "vboxguest not loaded."
+ fi
+}
+
+restart()
+{
+ stop
+ sync
+ start
+ return 0
+}
+
+status()
+{
+ if module_loaded; then
+ info "vboxguest running."
+ else
+ info "vboxguest stopped."
+ fi
+}
+
+check_root
+get_module_path
+check_if_installed
+
+if test "$2" = "silentunload"; then
+ SILENTUNLOAD="$2"
+fi
+
+case "$1" in
+start)
+ start
+ ;;
+stop)
+ stop
+ ;;
+restart)
+ restart
+ ;;
+status)
+ status
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|status}"
+ exit 1
+esac
+
+exit
+
diff --git a/src/VBox/Additions/freebsd/Makefile b/src/VBox/Additions/freebsd/Makefile
new file mode 100644
index 00000000..6ef49483
--- /dev/null
+++ b/src/VBox/Additions/freebsd/Makefile
@@ -0,0 +1,62 @@
+#
+# Makefile for the VirtualBox FreeBSD Guest Drivers.
+#
+
+#
+# Copyright (C) 2009-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+KBUILD_VERBOSE =
+
+all:
+ @echo "=== Building 'vboxguest' module ==="
+ @$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest
+ @if [ -f vboxguest/vboxguest.ko ]; then \
+ cp vboxguest/vboxguest.ko .; \
+ fi
+ @echo
+ @if [ -d vboxvfs ]; then \
+ echo "=== Building 'vboxvfs' module ==="; \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxvfs; \
+ if [ -f vboxvfs/vboxvfs.ko ]; then \
+ cp vboxvfs/vboxvfs.ko .; \
+ fi; \
+ fi
+
+
+install:
+ @$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest install
+ @if [ -d vboxvfs ]; then \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxvfs install; \
+ fi
+
+clean:
+ @$(MAKE) -C vboxguest clean
+ @if [ -d vboxvfs ]; then \
+ $(MAKE) -C vboxvfs clean; \
+ fi
+ rm -f vboxguest.*o vboxvfs.*o
+
+load:
+ @/sbin/kldunload vboxvfs || true
+ @/sbin/kldunload vboxguest || true
+ @/sbin/kldload ./vboxguest.ko
+ @if [ -f vboxvfs.ko ]; then /sbin/kldload ./vboxvfs.ko; fi
diff --git a/src/VBox/Additions/freebsd/Makefile.kmk b/src/VBox/Additions/freebsd/Makefile.kmk
new file mode 100644
index 00000000..559554ea
--- /dev/null
+++ b/src/VBox/Additions/freebsd/Makefile.kmk
@@ -0,0 +1,195 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the FreeBSD guest additions base directory.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifneq ($(KBUILD_HOST),freebsd)
+ $(error "The FreeBSD guest additions installer can only be built on FreeBSD!")
+endif
+
+# Include sub-makefiles.
+#include $(PATH_SUB_CURRENT)/vboxvfs/Makefile.kmk
+include $(PATH_SUB_CURRENT)/drm/Makefile.kmk
+
+#
+# Globals
+#
+VBOX_FBSD_ADD_INS_OUT_DIR := $(PATH_TARGET)/Additions/Installer/freebsd
+BLDDIRS += \
+ $(VBOX_FBSD_ADD_INS_OUT_DIR) \
+ $(VBOX_FBSD_ADD_INS_OUT_DIR)/module
+VBOX_PATH_FREEBSD_ADDITION_INSTALLER := $(PATH_SUB_CURRENT)/Installer
+VBOX_PATH_X11_ADDITION_INSTALLER := $(PATH_ROOT)/src/VBox/Additions/x11/Installer
+
+
+#
+# Targets
+#
+ifndef VBOX_OSE
+ BLDDIRS += $(VBOX_FBSD_ADD_INS_OUT_DIR) $(VBOX_FBSD_ADD_INS_OUT_DIR)/module
+ PACKING += $(PATH_STAGE_BIN)/additions/VBoxFreeBSDAdditions.tbz
+ OTHER_CLEAN += $(PACKING)
+endif
+
+
+#
+# Files to install
+#
+VBOX_FBSD_ADD_STRIP_BIN = \
+ VBoxService \
+ VBoxClient \
+ VBoxControl \
+ vboxmouse_drv_70.so \
+ vboxmouse_drv_71.so \
+ vboxmouse_drv_14.so \
+ vboxmouse_drv_15.so \
+ vboxmouse_drv_16.so \
+ vboxmouse_drv_17.so \
+ vboxvideo_drv_70.so \
+ vboxvideo_drv_71.so \
+ vboxvideo_drv_13.so \
+ vboxvideo_drv_14.so \
+ vboxvideo_drv_15.so \
+ vboxvideo_drv_16.so \
+ vboxvideo_drv_17.so
+
+VBOX_FBSD_ADD_MODULES = \
+ vboxguest \
+ vboxvideo_drm
+
+#
+# All the bin files that go into the archives.
+#
+VBOX_FBSD_ADD_DBG_SYM_FILES := $(addsuffix .dbgsym,$(VBOX_FBSD_ADD_STRIP_BIN))
+VBOX_FBSD_ADD_INS_FILES := $(addprefix $(VBOX_FBSD_ADD_INS_OUT_DIR)/,$(VBOX_FBSD_ADD_STRIP_BIN) $(VBOX_FBSD_ADD_STRIP_OBJ) $(VBOX_FBSD_ADD_DBG_SYM_FILES))
+VBOX_FBSD_ADD_INS_MODULES := $(addprefix $(VBOX_FBSD_ADD_INS_OUT_DIR)/module/,$(VBOX_FBSD_ADD_MODULES))
+
+# Cleanup of the installer directory files
+OTHER_CLEAN += $(VBOX_FBSD_ADD_INS_FILES)) $(VBOX_FBSD_ADD_INS_MODULES)
+
+# pattern rule for copying the debug info from the VBOX_FBSD_ADD_STRIP_BIN files to the installation directory
+$(addprefix $(VBOX_FBSD_ADD_INS_OUT_DIR)/,$(VBOX_FBSD_ADD_DBG_SYM_FILES)): \
+ $(VBOX_FBSD_ADD_INS_OUT_DIR)/%.dbgsym : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+# pattern rule for stripping and copying the VBOX_FBSD_ADD_STRIP_BIN files to the installation directory
+$(addprefix $(VBOX_FBSD_ADD_INS_OUT_DIR)/,$(VBOX_FBSD_ADD_STRIP_BIN)): \
+ $(VBOX_FBSD_ADD_INS_OUT_DIR)/% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_FBSD_ADD_INS_OUT_DIR)/%.dbgsym \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(addsuffix .dbgsym,$@) $@
+
+# pattern rule for stripping and copying the VBOX_FBSD_ADD_STRIP_OBJ files to the installation directory
+$(addprefix $(VBOX_FBSD_ADD_INS_OUT_DIR)/,$(VBOX_FBSD_ADD_STRIP_OBJ)): \
+ $(VBOX_FBSD_ADD_INS_OUT_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
+
+# pattern rule for copying the VBOX_FBSD_ADD_MODULES files to the installation directory
+$(VBOX_FBSD_ADD_INS_MODULES): \
+ $(VBOX_FBSD_ADD_INS_OUT_DIR)/module/% : $(PATH_STAGE_BIN)/additions/src/% | $(VBOX_FBSD_ADD_INS_OUT_DIR)/module/
+ $(call MSG_INST_FILE,$<,$@)
+# Remove target directories first, otherwise the behaviour of cp will not be
+# what we want if it already exists. See the cp manual page for more details.
+ $(QUIET)$(RM) -Rf $@
+ $(QUIET)cp -af $< $(VBOX_FBSD_ADD_INS_OUT_DIR)/module
+
+
+INSTALLS += $(if $(VBOX_OSE),, fbsd_add_inst-nobin)
+fbsd_add_inst-nobin_INST = obj/Additions/Installer/freebsd
+fbsd_add_inst-nobin_MODE = a+r,u+w
+fbsd_add_inst-nobin_SOURCES = \
+ ../x11/Installer/98vboxadd-xclient \
+ ../x11/Installer/vboxclient.desktop \
+ ../x11/Installer/vboxvideo.ids \
+ ../x11/Installer/x11config.pl \
+ ../x11/Installer/x11config15.pl
+
+
+INSTALLS += GuestDrivers-src
+GuestDrivers-src_INST = bin/additions/src/
+GuestDrivers-src_MODE = a+r,u+w
+GuestDrivers-src_SOURCES = Makefile
+
+# this file needs editing before it can be included in the generic installer.
+$(VBOX_FBSD_ADD_INS_OUT_DIR)/install.sh: \
+ $(VBOX_PATH_FREEBSD_ADDITION_INSTALLER)/install.sh | $$(dir $$@)
+ $(QUIET)$(SED) \
+ -e "s;_VERSION_;$(VBOX_VERSION_STRING);g" \
+ -e "s;_BUILD_;$(shell date);g" \
+ -e "s;_OSE_;$(VBOX_OSE);g" \
+ -e "s;_BUILDTYPE_;$(KBUILD_TYPE);g" \
+ -e "s;_ARCH_;$(KBUILD_TARGET_ARCH);g" \
+ --output $(VBOX_FBSD_ADD_INS_OUT_DIR)/install_.sh \
+ $<
+ $(QUIET)$(INSTALL) -m 0755 $(VBOX_FBSD_ADD_INS_OUT_DIR)/install_.sh $@
+ $(QUIET)$(RM) $(VBOX_FBSD_ADD_INS_OUT_DIR)/install_.sh
+OTHERS_CLEAN += $(VBOX_FBSD_ADD_INS_OUT_DIR)/install.sh
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
+#
+# Build the FreeBSD Guest Additions installer package.
+#
+# Note that $(PATH_SUB_CURRENT) was changed by subfooter.kmk above and
+# any references should be made via variables assigned a know value via := .
+#
+# We need to depend on all source files for the additions and shared
+# folders kernel modules.
+## @todo Replace the wildcard stuff by the correct file lists now that
+# we've got everything included.
+#
+$(PATH_STAGE_BIN)/additions/VBoxFreeBSDAdditions.tbz: \
+ $$(fbsd_add_inst-nobin_2_STAGE_TARGETS) \
+ $$(fbsd_add_inst-bin_2_STAGE_TARGETS) \
+ $(VBOX_FBSD_ADD_INS_FILES) \
+ $(VBOX_FBSD_ADD_INS_MODULES) \
+ $(VBOX_FBSD_ADD_INS_OUT_DIR)/install.sh \
+ $(wildcard $(PATH_STAGE_BIN)/additions/src/*) \
+ $(wildcard $(PATH_STAGE_BIN)/additions/src/*/*) \
+ $(wildcard $(PATH_STAGE_BIN)/additions/src/*/*/*) \
+ $(wildcard $(PATH_STAGE_BIN)/additions/src/*/*/*/*) \
+ $(VBOX_VERSION_STAMP) $(VBOX_SVN_REV_HEADER)
+ pkg_create \
+ -I $(VBOX_PATH_FREEBSD_ADDITION_INSTALLER)/install.sh \
+ -c $(VBOX_PATH_FREEBSD_ADDITION_INSTALLER)/pkg-comment \
+ -d $(VBOX_PATH_FREEBSD_ADDITION_INSTALLER)/pkg-descr \
+ -f $(VBOX_PATH_FREEBSD_ADDITION_INSTALLER)/pkg-plist \
+ $@
+
diff --git a/src/VBox/Additions/freebsd/drm/Makefile b/src/VBox/Additions/freebsd/drm/Makefile
new file mode 100644
index 00000000..c2f9b4fe
--- /dev/null
+++ b/src/VBox/Additions/freebsd/drm/Makefile
@@ -0,0 +1,36 @@
+# $Id: Makefile $
+## @file
+# Makefile for the VirtualBox FreeBSD Host Driver.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+KMOD = vboxvideo
+
+SRCS = \
+ vboxvideo_drm.c
+
+SRCS += device_if.h bus_if.h pci_if.h opt_drm.h
+
+.include <bsd.kmod.mk>
+
diff --git a/src/VBox/Additions/freebsd/drm/Makefile.kmk b/src/VBox/Additions/freebsd/drm/Makefile.kmk
new file mode 100644
index 00000000..62b5e7e6
--- /dev/null
+++ b/src/VBox/Additions/freebsd/drm/Makefile.kmk
@@ -0,0 +1,82 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the vboxvideo DRM module (FreeBSD kernel OpenGL module).
+#
+
+#
+# Copyright (C) 2009-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+INSTALLS += vboxvideo-mod
+
+ifdef VBOX_WITH_ADDITION_DRIVERS
+ SYSMODS += vboxvideo_drm
+endif
+ifneq ($(KBUILD_HOST),freebsd)
+ $(error "The FreeBSD guest additions can only be built on FreeBSD!")
+endif
+
+#
+# Populate FILES_VBOXVIDEO_DRM_NOBIN
+#
+include $(PATH_SUB_CURRENT)/files_vboxvideo_drm
+
+# vboxvideo source
+vboxvideo-mod_INST = $(INST_ADDITIONS)src/vboxvideo_drm/
+vboxvideo-mod_MODE = a+r,u+w
+vboxvideo-mod_SOURCES = $(subst ",,$(FILES_VBOXVIDEO_DRM_NOBIN))
+
+#
+# vboxvideo - The Video DRM (Direct Rendering Module) kernel module
+#
+# Note! Syntax checking only.
+#
+vboxvideo_drm_TEMPLATE = VBoxGuestR0Drv
+vboxvideo_drm_NAME = vboxvideo
+vboxvideo_drm_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV)
+vboxvideo_drm_DEPS += $(VBOX_SVN_REV_KMK)
+vboxvideo_drm_INCS.freebsd = \
+ $(vboxvideo_drm_0_OUTDIR) \
+ $(PATH_STAGE)/gen-sys-hdrs
+vboxvideo_drm_SOURCES = vboxvideo_drm.c
+vboxvideo_drm_LIBS = \
+ $(VBOX_LIB_VBGL_R0) \
+ $(VBOX_LIB_IPRT_GUEST_R0)
+vboxvideo_drm_ORDERDEPS.freebsd = \
+ $(PATH_STAGE)/gen-sys-hdrs/pci_if.h \
+ $(PATH_STAGE)/gen-sys-hdrs/bus_if.h \
+ $(PATH_STAGE)/gen-sys-hdrs/device_if.h \
+ $(vboxvideo_drm_0_OUTDIR)/opt_drm.h
+vboxvideo_drm_CLEAN.freebsd = $(vboxvideo_drm_DEPS)
+
+#
+# Header for DRM not included by us.
+#
+$$(vboxvideo_drm_0_OUTDIR)/opt_drm.h:
+ $(QUIET)$(MKDIR) -p $(vboxvideo_drm_0_OUTDIR)
+ $(QUIET)touch $(vboxvideo_drm_0_OUTDIR)/opt_drm.h
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/freebsd/drm/files_vboxvideo_drm b/src/VBox/Additions/freebsd/drm/files_vboxvideo_drm
new file mode 100755
index 00000000..8addd4b6
--- /dev/null
+++ b/src/VBox/Additions/freebsd/drm/files_vboxvideo_drm
@@ -0,0 +1,35 @@
+#!/bin/sh
+# $Id: files_vboxvideo_drm $
+## @file
+# Shared file between Makefile.kmk and export_modules.sh.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+FILES_VBOXVIDEO_DRM_NOBIN=" \
+ ${PATH_ROOT}/src/VBox/Additions/freebsd/drm/vboxvideo_drm.c=>vboxvideo_drm.c \
+ ${PATH_ROOT}/src/VBox/Additions/freebsd/drm/Makefile=>Makefile \
+"
+
+FILES_VBOXVIDEO_DRM_BIN=" \
+"
diff --git a/src/VBox/Additions/freebsd/drm/vboxvideo_drm.c b/src/VBox/Additions/freebsd/drm/vboxvideo_drm.c
new file mode 100644
index 00000000..de2678fe
--- /dev/null
+++ b/src/VBox/Additions/freebsd/drm/vboxvideo_drm.c
@@ -0,0 +1,173 @@
+/* $Id: vboxvideo_drm.c $ */
+/** @file
+ * VirtualBox Guest Additions - vboxvideo DRM module.
+ * FreeBSD kernel OpenGL module.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ * --------------------------------------------------------------------
+ *
+ * This code is based on:
+ *
+ * tdfx_drv.c -- tdfx driver -*- linux-c -*-
+ * Created: Thu Oct 7 10:38:32 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * 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 (including the next
+ * paragraph) 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
+ * PRECISION INSIGHT 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.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Daryll Strauss <daryll@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "dev/drm/drmP.h"
+#include "dev/drm/drm_pciids.h"
+
+#define DRIVER_AUTHOR "Oracle Corporation"
+#define DRIVER_NAME "vboxvideo"
+#define DRIVER_DESC "VirtualBox DRM"
+#define DRIVER_DATE "20090317"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+
+/** @todo Take PCI IDs from VBox/param.h; VBOX_VESA_VENDORID,
+ * VBOX_VESA_DEVICEID. */
+#define vboxvideo_PCI_IDS { 0x80ee, 0xbeef, 0, "VirtualBox Video" }, \
+ { 0, 0, 0, NULL }
+
+static drm_pci_id_list_t vboxvideo_pciidlist[] = {
+ vboxvideo_PCI_IDS
+};
+
+static void vboxvideo_configure(struct drm_device *dev)
+{
+#if __FreeBSD_version >= 702000
+ dev->driver->buf_priv_size = 1; /* No dev_priv */
+
+ dev->driver->max_ioctl = 0;
+
+ dev->driver->name = DRIVER_NAME;
+ dev->driver->desc = DRIVER_DESC;
+ dev->driver->date = DRIVER_DATE;
+ dev->driver->major = DRIVER_MAJOR;
+ dev->driver->minor = DRIVER_MINOR;
+ dev->driver->patchlevel = DRIVER_PATCHLEVEL;
+#else
+ dev->driver.buf_priv_size = 1; /* No dev_priv */
+
+ dev->driver.max_ioctl = 0;
+
+ dev->driver.name = DRIVER_NAME;
+ dev->driver.desc = DRIVER_DESC;
+ dev->driver.date = DRIVER_DATE;
+ dev->driver.major = DRIVER_MAJOR;
+ dev->driver.minor = DRIVER_MINOR;
+ dev->driver.patchlevel = DRIVER_PATCHLEVEL;
+#endif
+}
+
+static int
+vboxvideo_probe(device_t kdev)
+{
+ return drm_probe(kdev, vboxvideo_pciidlist);
+}
+
+static int
+vboxvideo_attach(device_t kdev)
+{
+ struct drm_device *dev = device_get_softc(kdev);
+
+#if __FreeBSD_version >= 702000
+ dev->driver = malloc(sizeof(struct drm_driver_info), DRM_MEM_DRIVER,
+ M_WAITOK | M_ZERO);
+#else
+ bzero(&dev->driver, sizeof(struct drm_driver_info));
+#endif
+
+ vboxvideo_configure(dev);
+
+ return drm_attach(kdev, vboxvideo_pciidlist);
+}
+
+static int
+vboxvideo_detach(device_t kdev)
+{
+ struct drm_device *dev = device_get_softc(kdev);
+ int ret;
+
+ ret = drm_detach(kdev);
+
+#if __FreeBSD_version >= 702000
+ free(dev->driver, DRM_MEM_DRIVER);
+#endif
+
+ return ret;
+}
+
+static device_method_t vboxvideo_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, vboxvideo_probe),
+ DEVMETHOD(device_attach, vboxvideo_attach),
+ DEVMETHOD(device_detach, vboxvideo_detach),
+
+ { 0, 0 }
+};
+
+static driver_t vboxvideo_driver = {
+ "drm",
+ vboxvideo_methods,
+ sizeof(struct drm_device)
+};
+
+extern devclass_t drm_devclass;
+#if __FreeBSD_version >= 700010
+DRIVER_MODULE(vboxvideo, vgapci, vboxvideo_driver, drm_devclass, 0, 0);
+#else
+DRIVER_MODULE(vboxvideo, pci, vboxvideo_driver, drm_devclass, 0, 0);
+#endif
+MODULE_DEPEND(vboxvideo, drm, 1, 1, 1);
diff --git a/src/VBox/Additions/freebsd/vboxvfs/Makefile.kmk b/src/VBox/Additions/freebsd/vboxvfs/Makefile.kmk
new file mode 100644
index 00000000..ee3619c0
--- /dev/null
+++ b/src/VBox/Additions/freebsd/vboxvfs/Makefile.kmk
@@ -0,0 +1,74 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the FreeBSD Shared folder kernel module.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifneq ($(KBUILD_HOST),freebsd)
+ $(error "The FreeBSD guest additions can only be built on FreeBSD!")
+endif
+
+#
+# vboxvfs - The Shared Folder Driver
+#
+SYSMODS.freebsd += vboxvfs
+vboxvfs_TEMPLATE = VBoxGuestR0Drv
+vboxvfs_DEFS = VBOX_WITH_HGCM
+vboxvfs_INCS = \
+ . \
+ $(vboxvfs_0_OUTDIR)
+vboxvfs_SOURCES = \
+ vboxvfs_vfsops.c \
+ vboxvfs_vnops.c
+vboxvfs_LIBS = \
+ $(VBOX_LIB_VBGL_R0) \
+ $(VBOX_LIB_IPRT_GUEST_R0)
+vboxvfs_DEPS = \
+ $$(vboxvfs_0_OUTDIR)/vnode_if.h \
+ $$(vboxvfs_0_OUTDIR)/vnode_if_newproto.h \
+ $$(vboxvfs_0_OUTDIR)/vnode_if_typedef.h
+vboxvfs_CLEAN += $(vboxvfs_DEPS)
+
+VBOX_AWK := /usr/bin/awk
+
+$$(vboxvfs_0_OUTDIR)/vnode_if.h: $(VBOX_FREEBSD_SRC)/kern/vnode_if.src
+ $(call MSG_TOOL,awk,VBoxGuest,$<,$@)
+ $(QUIET)$(VBOX_AWK) -f $(VBOX_FREEBSD_SRC)/tools/vnode_if.awk $(VBOX_FREEBSD_SRC)/kern/vnode_if.src -h
+ $(QUIET)$(MV) $(vboxvfs_0_OUTDIR)/vnode_if.h $(vboxvfs_0_OUTDIR)/vnode_if.h
+
+$$(vboxvfs_0_OUTDIR)/vnode_if_newproto.h: $(VBOX_FREEBSD_SRC)/kern/vnode_if.src
+ $(call MSG_TOOL,awk,VBoxGuest,$<,$@)
+ $(QUIET)$(VBOX_AWK) -f $(VBOX_FREEBSD_SRC)/tools/vnode_if.awk $(VBOX_FREEBSD_SRC)/kern/vnode_if.src -p
+ $(QUIET)$(MV) $(vboxvfs_0_OUTDIR)/vnode_if_newproto.h $(vboxvfs_0_OUTDIR)/vnode_if_newproto.h
+
+$$(vboxvfs_0_OUTDIR)/vnode_if_typedef.h: $(VBOX_FREEBSD_SRC)/kern/vnode_if.src
+ $(call MSG_TOOL,awk,VBoxGuest,$<,$@)
+ $(QUIET)$(VBOX_AWK) -f $(VBOX_FREEBSD_SRC)/tools/vnode_if.awk $(VBOX_FREEBSD_SRC)/kern/vnode_if.src -q
+ $(QUIET)$(MV) $(vboxvfs_0_OUTDIR)/vnode_if_typedef.h $(vboxvfs_0_OUTDIR)/vnode_if_typedef.h
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/freebsd/vboxvfs/vboxvfs.h b/src/VBox/Additions/freebsd/vboxvfs/vboxvfs.h
new file mode 100644
index 00000000..906e5a67
--- /dev/null
+++ b/src/VBox/Additions/freebsd/vboxvfs/vboxvfs.h
@@ -0,0 +1,105 @@
+/* $Id: vboxvfs.h $ */
+/** @file
+ * Description.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_freebsd_vboxvfs_vboxvfs_h
+#define GA_INCLUDED_SRC_freebsd_vboxvfs_vboxvfs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define VBOXVFS_VFSNAME "vboxvfs"
+#define VBOXVFS_VERSION 1
+
+#define MAX_HOST_NAME 256
+#define MAX_NLS_NAME 32
+
+struct vboxvfs_mount_info {
+ char name[MAX_HOST_NAME];
+ char nls_name[MAX_NLS_NAME];
+ int uid;
+ int gid;
+ int ttl;
+};
+
+#ifdef _KERNEL
+
+#include <VBox/VBoxGuestLibSharedFolders.h>
+#include <sys/mount.h>
+#include <sys/vnode.h>
+
+struct vboxvfsmount {
+ uid_t uid;
+ gid_t gid;
+ mode_t file_mode;
+ mode_t dir_mode;
+ struct mount *mp;
+ struct ucred *owner;
+ u_int flags;
+ long nextino;
+ int caseopt;
+ int didrele;
+};
+
+/* structs - stolen from the linux shared module code */
+struct sf_glob_info {
+ VBGLSFMAP map;
+/* struct nls_table *nls;*/
+ int ttl;
+ int uid;
+ int gid;
+ struct vnode *vnode_root;
+};
+
+struct sf_inode_info {
+ SHFLSTRING *path;
+ int force_restat;
+};
+
+#if 0
+struct sf_dir_info {
+ struct list_head info_list;
+};
+#endif
+
+struct sf_dir_buf {
+ size_t nb_entries;
+ size_t free_bytes;
+ size_t used_bytes;
+ void *buf;
+#if 0
+ struct list_head head;
+#endif
+};
+
+struct sf_reg_info {
+ SHFLHANDLE handle;
+};
+
+#endif /* KERNEL */
+
+#endif /* !GA_INCLUDED_SRC_freebsd_vboxvfs_vboxvfs_h */
+
diff --git a/src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vfsops.c b/src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vfsops.c
new file mode 100644
index 00000000..43b9ec0b
--- /dev/null
+++ b/src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vfsops.c
@@ -0,0 +1,268 @@
+/* $Id: vboxvfs_vfsops.c $ */
+/** @file
+ * Description.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "vboxvfs.h"
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/proc.h>
+#include <sys/bio.h>
+#include <sys/buf.h>
+#include <sys/kernel.h>
+#include <sys/sysctl.h>
+#include <sys/vnode.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+
+#include <iprt/mem.h>
+
+#define VFSMP2SFGLOBINFO(mp) ((struct sf_glob_info *)mp->mnt_data)
+
+static int vboxvfs_version = VBOXVFS_VERSION;
+
+SYSCTL_NODE(_vfs, OID_AUTO, vboxvfs, CTLFLAG_RW, 0, "VirtualBox shared filesystem");
+SYSCTL_INT(_vfs_vboxvfs, OID_AUTO, version, CTLFLAG_RD, &vboxvfs_version, 0, "");
+
+/* global connection to the host service. */
+static VBGLSFCLIENT g_vboxSFClient;
+
+static vfs_init_t vboxvfs_init;
+static vfs_uninit_t vboxvfs_uninit;
+static vfs_cmount_t vboxvfs_cmount;
+static vfs_mount_t vboxvfs_mount;
+static vfs_root_t vboxvfs_root;
+static vfs_quotactl_t vboxvfs_quotactl;
+static vfs_statfs_t vboxvfs_statfs;
+static vfs_unmount_t vboxvfs_unmount;
+
+static struct vfsops vboxvfs_vfsops = {
+ .vfs_init = vboxvfs_init,
+ .vfs_cmount = vboxvfs_cmount,
+ .vfs_mount = vboxvfs_mount,
+ .vfs_quotactl = vboxvfs_quotactl,
+ .vfs_root = vboxvfs_root,
+ .vfs_statfs = vboxvfs_statfs,
+ .vfs_sync = vfs_stdsync,
+ .vfs_uninit = vboxvfs_uninit,
+ .vfs_unmount = vboxvfs_unmount,
+};
+
+
+VFS_SET(vboxvfs_vfsops, vboxvfs, VFCF_NETWORK);
+MODULE_DEPEND(vboxvfs, vboxguest, 1, 1, 1);
+
+static int vboxvfs_cmount(struct mntarg *ma, void * data, int flags, struct thread *td)
+{
+ struct vboxvfs_mount_info args;
+ int rc = 0;
+
+ printf("%s: Enter\n", __FUNCTION__);
+
+ rc = copyin(data, &args, sizeof(struct vboxvfs_mount_info));
+ if (rc)
+ return rc;
+
+ ma = mount_argf(ma, "uid", "%d", args.uid);
+ ma = mount_argf(ma, "gid", "%d", args.gid);
+ ma = mount_arg(ma, "from", args.name, -1);
+
+ rc = kernel_mount(ma, flags);
+
+ printf("%s: Leave rc=%d\n", __FUNCTION__, rc);
+
+ return rc;
+}
+
+static const char *vboxvfs_opts[] = {
+ "uid", "gid", "from", "fstype", "fspath", "errmsg", NULL
+};
+
+static int vboxvfs_mount(struct mount *mp, struct thread *td)
+{
+ int rc;
+ char *pszShare;
+ int cbShare, cbOption;
+ int uid = 0, gid = 0;
+ struct sf_glob_info *pShFlGlobalInfo;
+ SHFLSTRING *pShFlShareName = NULL;
+ int cbShFlShareName;
+
+ printf("%s: Enter\n", __FUNCTION__);
+
+ if (mp->mnt_flag & (MNT_UPDATE | MNT_ROOTFS))
+ return EOPNOTSUPP;
+
+ if (vfs_filteropt(mp->mnt_optnew, vboxvfs_opts))
+ {
+ vfs_mount_error(mp, "%s", "Invalid option");
+ return EINVAL;
+ }
+
+ rc = vfs_getopt(mp->mnt_optnew, "from", (void **)&pszShare, &cbShare);
+ if (rc || pszShare[cbShare-1] != '\0' || cbShare > 0xfffe)
+ return EINVAL;
+
+ rc = vfs_getopt(mp->mnt_optnew, "gid", (void **)&gid, &cbOption);
+ if ((rc != ENOENT) && (rc || cbOption != sizeof(gid)))
+ return EINVAL;
+
+ rc = vfs_getopt(mp->mnt_optnew, "uid", (void **)&uid, &cbOption);
+ if ((rc != ENOENT) && (rc || cbOption != sizeof(uid)))
+ return EINVAL;
+
+ pShFlGlobalInfo = RTMemAllocZ(sizeof(struct sf_glob_info));
+ if (!pShFlGlobalInfo)
+ return ENOMEM;
+
+ cbShFlShareName = offsetof (SHFLSTRING, String.utf8) + cbShare + 1;
+ pShFlShareName = RTMemAllocZ(cbShFlShareName);
+ if (!pShFlShareName)
+ return VERR_NO_MEMORY;
+
+ pShFlShareName->u16Length = cbShare;
+ pShFlShareName->u16Size = cbShare + 1;
+ memcpy (pShFlShareName->String.utf8, pszShare, cbShare + 1);
+
+ rc = VbglR0SfMapFolder (&g_vboxSFClient, pShFlShareName, &pShFlGlobalInfo->map);
+ RTMemFree(pShFlShareName);
+
+ if (RT_FAILURE (rc))
+ {
+ RTMemFree(pShFlGlobalInfo);
+ printf("VbglR0SfMapFolder failed rc=%d\n", rc);
+ return EPROTO;
+ }
+
+ pShFlGlobalInfo->uid = uid;
+ pShFlGlobalInfo->gid = gid;
+
+ mp->mnt_data = pShFlGlobalInfo;
+
+ /** @todo root vnode. */
+
+ vfs_getnewfsid(mp);
+ vfs_mountedfrom(mp, pszShare);
+
+ printf("%s: Leave rc=0\n", __FUNCTION__);
+
+ return 0;
+}
+
+static int vboxvfs_unmount(struct mount *mp, int mntflags, struct thread *td)
+{
+ struct sf_glob_info *pShFlGlobalInfo = VFSMP2SFGLOBINFO(mp);
+ int rc;
+ int flags = 0;
+
+ rc = VbglR0SfUnmapFolder(&g_vboxSFClient, &pShFlGlobalInfo->map);
+ if (RT_FAILURE(rc))
+ printf("Failed to unmap shared folder\n");
+
+ if (mntflags & MNT_FORCE)
+ flags |= FORCECLOSE;
+
+ /* There is 1 extra root vnode reference (vnode_root). */
+ rc = vflush(mp, 1, flags, td);
+ if (rc)
+ return rc;
+
+
+ RTMemFree(pShFlGlobalInfo);
+ mp->mnt_data = NULL;
+
+ return 0;
+}
+
+static int vboxvfs_root(struct mount *mp, int flags, struct vnode **vpp, struct thread *td)
+{
+ int rc = 0;
+ struct sf_glob_info *pShFlGlobalInfo = VFSMP2SFGLOBINFO(mp);
+ struct vnode *vp;
+
+ printf("%s: Enter\n", __FUNCTION__);
+
+ vp = pShFlGlobalInfo->vnode_root;
+ VREF(vp);
+
+ vn_lock(vp, flags | LK_RETRY, td);
+ *vpp = vp;
+
+ printf("%s: Leave\n", __FUNCTION__);
+
+ return rc;
+}
+
+static int vboxvfs_quotactl(struct mount *mp, int cmd, uid_t uid, void *arg, struct thread *td)
+{
+ return EOPNOTSUPP;
+}
+
+int vboxvfs_init(struct vfsconf *vfsp)
+{
+ int rc;
+
+ /* Initialize the R0 guest library. */
+ rc = VbglR0SfInit();
+ if (RT_FAILURE(rc))
+ return ENXIO;
+
+ /* Connect to the host service. */
+ rc = VbglR0SfConnect(&g_vboxSFClient);
+ if (RT_FAILURE(rc))
+ {
+ printf("Failed to get connection to host! rc=%d\n", rc);
+ VbglR0SfTerm();
+ return ENXIO;
+ }
+
+ rc = VbglR0SfSetUtf8(&g_vboxSFClient);
+ if (RT_FAILURE (rc))
+ {
+ printf("VbglR0SfSetUtf8 failed, rc=%d\n", rc);
+ VbglR0SfDisconnect(&g_vboxSFClient);
+ VbglR0SfTerm();
+ return EPROTO;
+ }
+
+ printf("Successfully loaded shared folder module\n");
+
+ return 0;
+}
+
+int vboxvfs_uninit(struct vfsconf *vfsp)
+{
+ VbglR0SfDisconnect(&g_vboxSFClient);
+ VbglR0SfTerm();
+
+ return 0;
+}
+
+int vboxvfs_statfs(struct mount *mp, struct statfs *sbp, struct thread *td)
+{
+ return 0;
+}
diff --git a/src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vnops.c b/src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vnops.c
new file mode 100644
index 00000000..02652896
--- /dev/null
+++ b/src/VBox/Additions/freebsd/vboxvfs/vboxvfs_vnops.c
@@ -0,0 +1,251 @@
+/* $Id: vboxvfs_vnops.c $ */
+/** @file
+ * Description.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "vboxvfs.h"
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/namei.h>
+#include <sys/kernel.h>
+#include <sys/proc.h>
+#include <sys/bio.h>
+#include <sys/buf.h>
+#include <sys/fcntl.h>
+#include <sys/mount.h>
+#include <sys/unistd.h>
+#include <sys/vnode.h>
+#include <sys/limits.h>
+#include <sys/lockf.h>
+#include <sys/stat.h>
+
+#include <vm/vm.h>
+#include <vm/vm_extern.h>
+
+/*
+ * Prototypes for VBOXVFS vnode operations
+ */
+static vop_create_t vboxvfs_create;
+static vop_mknod_t vboxvfs_mknod;
+static vop_open_t vboxvfs_open;
+static vop_close_t vboxvfs_close;
+static vop_access_t vboxvfs_access;
+static vop_getattr_t vboxvfs_getattr;
+static vop_setattr_t vboxvfs_setattr;
+static vop_read_t vboxvfs_read;
+static vop_write_t vboxvfs_write;
+static vop_fsync_t vboxvfs_fsync;
+static vop_remove_t vboxvfs_remove;
+static vop_link_t vboxvfs_link;
+static vop_lookup_t vboxvfs_lookup;
+static vop_rename_t vboxvfs_rename;
+static vop_mkdir_t vboxvfs_mkdir;
+static vop_rmdir_t vboxvfs_rmdir;
+static vop_symlink_t vboxvfs_symlink;
+static vop_readdir_t vboxvfs_readdir;
+static vop_strategy_t vboxvfs_strategy;
+static vop_print_t vboxvfs_print;
+static vop_pathconf_t vboxvfs_pathconf;
+static vop_advlock_t vboxvfs_advlock;
+static vop_getextattr_t vboxvfs_getextattr;
+static vop_ioctl_t vboxvfs_ioctl;
+static vop_getpages_t vboxvfs_getpages;
+static vop_inactive_t vboxvfs_inactive;
+static vop_putpages_t vboxvfs_putpages;
+static vop_reclaim_t vboxvfs_reclaim;
+
+struct vop_vector vboxvfs_vnodeops = {
+ .vop_default = &default_vnodeops,
+
+ .vop_access = vboxvfs_access,
+ .vop_advlock = vboxvfs_advlock,
+ .vop_close = vboxvfs_close,
+ .vop_create = vboxvfs_create,
+ .vop_fsync = vboxvfs_fsync,
+ .vop_getattr = vboxvfs_getattr,
+ .vop_getextattr = vboxvfs_getextattr,
+ .vop_getpages = vboxvfs_getpages,
+ .vop_inactive = vboxvfs_inactive,
+ .vop_ioctl = vboxvfs_ioctl,
+ .vop_link = vboxvfs_link,
+ .vop_lookup = vboxvfs_lookup,
+ .vop_mkdir = vboxvfs_mkdir,
+ .vop_mknod = vboxvfs_mknod,
+ .vop_open = vboxvfs_open,
+ .vop_pathconf = vboxvfs_pathconf,
+ .vop_print = vboxvfs_print,
+ .vop_putpages = vboxvfs_putpages,
+ .vop_read = vboxvfs_read,
+ .vop_readdir = vboxvfs_readdir,
+ .vop_reclaim = vboxvfs_reclaim,
+ .vop_remove = vboxvfs_remove,
+ .vop_rename = vboxvfs_rename,
+ .vop_rmdir = vboxvfs_rmdir,
+ .vop_setattr = vboxvfs_setattr,
+ .vop_strategy = vboxvfs_strategy,
+ .vop_symlink = vboxvfs_symlink,
+ .vop_write = vboxvfs_write,
+};
+
+static int vboxvfs_access(struct vop_access_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_open(struct vop_open_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_close(struct vop_close_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_getattr(struct vop_getattr_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_setattr(struct vop_setattr_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_read(struct vop_read_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_write(struct vop_write_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_create(struct vop_create_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_remove(struct vop_remove_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_rename(struct vop_rename_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_link(struct vop_link_args *ap)
+{
+ return EOPNOTSUPP;
+}
+
+static int vboxvfs_symlink(struct vop_symlink_args *ap)
+{
+ return EOPNOTSUPP;
+}
+
+static int vboxvfs_mknod(struct vop_mknod_args *ap)
+{
+ return EOPNOTSUPP;
+}
+
+static int vboxvfs_mkdir(struct vop_mkdir_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_rmdir(struct vop_rmdir_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_readdir(struct vop_readdir_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_fsync(struct vop_fsync_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_print (struct vop_print_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_pathconf (struct vop_pathconf_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_strategy (struct vop_strategy_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_ioctl(struct vop_ioctl_args *ap)
+{
+ return ENOTTY;
+}
+
+static int vboxvfs_getextattr(struct vop_getextattr_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_advlock(struct vop_advlock_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_lookup(struct vop_lookup_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_inactive(struct vop_inactive_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_reclaim(struct vop_reclaim_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_getpages(struct vop_getpages_args *ap)
+{
+ return 0;
+}
+
+static int vboxvfs_putpages(struct vop_putpages_args *ap)
+{
+ return 0;
+}
+
diff --git a/src/VBox/Additions/haiku/.scm-settings b/src/VBox/Additions/haiku/.scm-settings
new file mode 100644
index 00000000..12be50ac
--- /dev/null
+++ b/src/VBox/Additions/haiku/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for haiku guest additions.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+/VBoxTray/VBoxTray.rdef: --treat-as .rc
+
diff --git a/src/VBox/Additions/haiku/Makefile.kmk b/src/VBox/Additions/haiku/Makefile.kmk
new file mode 100644
index 00000000..77c9dc0f
--- /dev/null
+++ b/src/VBox/Additions/haiku/Makefile.kmk
@@ -0,0 +1,73 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for Haiku Guest Additions.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# This code is based on:
+#
+# VirtualBox Guest Additions for Haiku.
+# Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+# François Revol <revol@free.fr>
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifneq ($(KBUILD_HOST),haiku)
+ $(error "The Haiku guest additions installer can only be built on Haiku!")
+endif
+
+#
+# Include sub-makefiles.
+#
+include $(PATH_SUB_CURRENT)/SharedFolders/Makefile.kmk
+include $(PATH_SUB_CURRENT)/VBoxMouse/Makefile.kmk
+include $(PATH_SUB_CURRENT)/VBoxTray/Makefile.kmk
+include $(PATH_SUB_CURRENT)/VBoxVideo/Makefile.kmk
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/haiku/SharedFolders/Makefile.kmk b/src/VBox/Additions/haiku/SharedFolders/Makefile.kmk
new file mode 100644
index 00000000..daa5d002
--- /dev/null
+++ b/src/VBox/Additions/haiku/SharedFolders/Makefile.kmk
@@ -0,0 +1,89 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for shared folders, Haiku Guest Additions.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# This code is based on:
+#
+# VirtualBox Guest Additions for Haiku.
+# Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+# François Revol <revol@free.fr>
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifdef VBOX_WITH_ADDITION_DRIVERS
+ SYSMODS += vboxsf
+endif
+
+#
+# Populate FILES_VBOXSF_NOBIN
+#
+#include $(PATH_SUB_CURRENT)/files_vboxsf
+
+#
+# The module (for syntax checking).
+# The DEBUG_HASH* stuff is for CONFIG_DYNAMIC_DEBUG-enabled kernels
+#
+vboxsf_TEMPLATE = VBoxGuestR0Drv
+vboxsf_DEFS = \
+ MODULE IN_RT_R0 VBOXGUEST VBOX_WITH_HGCM \
+ KBUILD_MODNAME=KBUILD_STR\(vboxsf\) \
+ KBUILD_BASENAME=KBUILD_STR\(vboxsf\)
+vboxsf_INCS = \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest \
+ $(PATH_ROOT)/src/VBox/Runtime/r0drv/haiku
+vboxsf_SOURCES = \
+ vboxsf.c \
+ vnode_cache.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c
+vboxsf_LIBS = \
+ $(VBOX_LIB_VBGL_R0)
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/haiku/SharedFolders/OpenHashTable.h b/src/VBox/Additions/haiku/SharedFolders/OpenHashTable.h
new file mode 100644
index 00000000..ac01704d
--- /dev/null
+++ b/src/VBox/Additions/haiku/SharedFolders/OpenHashTable.h
@@ -0,0 +1,515 @@
+/* $Id: OpenHashTable.h $ */
+/** @file
+ * OpenHashTable, Haiku Guest Additions.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ *
+ * Copyright 2007, Hugo Santos. All Rights Reserved.
+ * Distributed under the terms of the MIT License.
+ */
+
+#ifndef GA_INCLUDED_SRC_haiku_SharedFolders_OpenHashTable_h
+#define GA_INCLUDED_SRC_haiku_SharedFolders_OpenHashTable_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+#include <OS.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef _KERNEL_MODE
+# include <KernelExport.h>
+# include "kernel_cpp.h"
+#endif
+
+/*!
+ The Definition template must have four methods: `HashKey', `Hash',
+ `Compare' and `GetLink;. It must also define several types as shown in the
+ following example:
+
+ struct Foo {
+ int bar;
+
+ Foo* fNext;
+ };
+
+ struct HashTableDefinition {
+ typedef int KeyType;
+ typedef Foo ValueType;
+
+ size_t HashKey(int key) const
+ {
+ return key >> 1;
+ }
+
+ size_t Hash(Foo* value) const
+ {
+ return HashKey(value->bar);
+ }
+
+ bool Compare(int key, Foo* value) const
+ {
+ return value->bar == key;
+ }
+
+ Foo*& GetLink(Foo* value) const
+ {
+ return value->fNext;
+ }
+ };
+*/
+
+
+struct MallocAllocator
+{
+ void* Allocate(size_t size) const
+ {
+ return malloc(size);
+ }
+
+ void Free(void* memory) const
+ {
+ free(memory);
+ }
+};
+
+
+template<typename Definition, bool AutoExpand = true,
+ bool CheckDuplicates = false, typename Allocator = MallocAllocator>
+class BOpenHashTable
+{
+public:
+ typedef BOpenHashTable<Definition, AutoExpand, CheckDuplicates> HashTable;
+ typedef typename Definition::KeyType KeyType;
+ typedef typename Definition::ValueType ValueType;
+
+ static const size_t kMinimumSize = 8;
+
+ // All allocations are of power of 2 lengths.
+
+ // regrowth factor: 200 / 256 = 78.125%
+ // 50 / 256 = 19.53125%
+
+ BOpenHashTable()
+ :
+ fTableSize(0),
+ fItemCount(0),
+ fTable(NULL)
+ {
+ }
+
+ BOpenHashTable(const Definition& definition)
+ :
+ fDefinition(definition),
+ fTableSize(0),
+ fItemCount(0),
+ fTable(NULL)
+ {
+ }
+
+ BOpenHashTable(const Definition& definition, const Allocator& allocator)
+ :
+ fDefinition(definition),
+ fAllocator(allocator),
+ fTableSize(0),
+ fItemCount(0),
+ fTable(NULL)
+ {
+ }
+
+ ~BOpenHashTable()
+ {
+ fAllocator.Free(fTable);
+ }
+
+ status_t Init(size_t initialSize = kMinimumSize)
+ {
+ if (initialSize > 0 && !_Resize(initialSize))
+ return B_NO_MEMORY;
+ return B_OK;
+ }
+
+ size_t TableSize() const
+ {
+ return fTableSize;
+ }
+
+ size_t CountElements() const
+ {
+ return fItemCount;
+ }
+
+ ValueType* Lookup(const KeyType& key) const
+ {
+ if (fTableSize == 0)
+ return NULL;
+
+ size_t index = fDefinition.HashKey(key) & (fTableSize - 1);
+ ValueType* slot = fTable[index];
+
+ while (slot)
+ {
+ if (fDefinition.Compare(key, slot))
+ break;
+ slot = _Link(slot);
+ }
+
+ return slot;
+ }
+
+ status_t Insert(ValueType* value)
+ {
+ if (fTableSize == 0)
+ {
+ if (!_Resize(kMinimumSize))
+ return B_NO_MEMORY;
+ }
+ else if (AutoExpand && fItemCount >= (fTableSize * 200 / 256))
+ _Resize(fTableSize * 2);
+
+ InsertUnchecked(value);
+ return B_OK;
+ }
+
+ void InsertUnchecked(ValueType* value)
+ {
+ if (CheckDuplicates && _ExhaustiveSearch(value))
+ {
+#ifdef _KERNEL_MODE
+ panic("Hash Table: value already in table.");
+#else
+ debugger("Hash Table: value already in table.");
+#endif
+ }
+
+ _Insert(fTable, fTableSize, value);
+ fItemCount++;
+ }
+
+ // TODO: a ValueType* Remove(const KeyType& key) method is missing
+
+ bool Remove(ValueType* value)
+ {
+ if (!RemoveUnchecked(value))
+ return false;
+
+ if (AutoExpand && fTableSize > kMinimumSize
+ && fItemCount < (fTableSize * 50 / 256))
+ _Resize(fTableSize / 2);
+
+ return true;
+ }
+
+ bool RemoveUnchecked(ValueType* value)
+ {
+ size_t index = fDefinition.Hash(value) & (fTableSize - 1);
+ ValueType* previous = NULL;
+ ValueType* slot = fTable[index];
+
+ while (slot)
+ {
+ ValueType* next = _Link(slot);
+
+ if (value == slot)
+ {
+ if (previous)
+ _Link(previous) = next;
+ else
+ fTable[index] = next;
+ break;
+ }
+
+ previous = slot;
+ slot = next;
+ }
+
+ if (slot == NULL)
+ return false;
+
+ if (CheckDuplicates && _ExhaustiveSearch(value))
+ {
+#ifdef _KERNEL_MODE
+ panic("Hash Table: duplicate detected.");
+#else
+ debugger("Hash Table: duplicate detected.");
+#endif
+ }
+
+ fItemCount--;
+ return true;
+ }
+
+ /*! Removes all elements from the hash table. No resizing happens. The
+ elements are not deleted. If \a returnElements is \c true, the method
+ returns all elements chained via their hash table link.
+ */
+ ValueType* Clear(bool returnElements = false)
+ {
+ if (this->fItemCount == 0)
+ return NULL;
+
+ ValueType* result = NULL;
+
+ if (returnElements)
+ {
+ ValueType** nextPointer = &result;
+
+ // iterate through all buckets
+ for (size_t i = 0; i < fTableSize; i++)
+ {
+ ValueType* element = fTable[i];
+ if (element != NULL)
+ {
+ // add the bucket to the list
+ *nextPointer = element;
+
+ // update nextPointer to point to the fNext of the last
+ // element in the bucket
+ while (element != NULL)
+ {
+ nextPointer = &_Link(element);
+ element = *nextPointer;
+ }
+ }
+ }
+ }
+
+ memset(this->fTable, 0, sizeof(ValueType*) * this->fTableSize);
+
+ return result;
+ }
+
+ /*! If the table needs resizing, the number of bytes for the required
+ allocation is returned. If no resizing is needed, 0 is returned.
+ */
+ size_t ResizeNeeded() const
+ {
+ size_t size = fTableSize;
+ if (size == 0 || fItemCount >= (size * 200 / 256))
+ {
+ // grow table
+ if (size == 0)
+ size = kMinimumSize;
+ while (fItemCount >= size * 200 / 256)
+ size <<= 1;
+ }
+ else if (size > kMinimumSize && fItemCount < size * 50 / 256)
+ {
+ // shrink table
+ while (fItemCount < size * 50 / 256)
+ size >>= 1;
+ if (size < kMinimumSize)
+ size = kMinimumSize;
+ }
+
+ if (size == fTableSize)
+ return 0;
+
+ return size * sizeof(ValueType*);
+ }
+
+ /*! Resizes the table using the given allocation. The allocation must not
+ be \c NULL. It must be of size \a size, which must a value returned
+ earlier by ResizeNeeded(). If the size requirements have changed in the
+ meantime, the method free()s the given allocation and returns \c false,
+ unless \a force is \c true, in which case the supplied allocation is
+ used in any event.
+ Otherwise \c true is returned.
+ If \a oldTable is non-null and resizing is successful, the old table
+ will not be freed, but will be returned via this parameter instead.
+ */
+ bool Resize(void* allocation, size_t size, bool force = false,
+ void** oldTable = NULL)
+ {
+ if (!force && size != ResizeNeeded())
+ {
+ fAllocator.Free(allocation);
+ return false;
+ }
+
+ _Resize((ValueType**)allocation, size / sizeof(ValueType*), oldTable);
+ return true;
+ }
+
+ class Iterator
+ {
+ public:
+ Iterator(const HashTable* table)
+ : fTable(table)
+ {
+ Rewind();
+ }
+
+ Iterator(const HashTable* table, size_t index, ValueType* value)
+ : fTable(table), fIndex(index), fNext(value) {}
+
+ bool HasNext() const { return fNext != NULL; }
+
+ ValueType* Next()
+ {
+ ValueType* current = fNext;
+ _GetNext();
+ return current;
+ }
+
+ void Rewind()
+ {
+ // get the first one
+ fIndex = 0;
+ fNext = NULL;
+ _GetNext();
+ }
+
+ protected:
+ Iterator() {}
+
+ void _GetNext()
+ {
+ if (fNext)
+ fNext = fTable->_Link(fNext);
+
+ while (fNext == NULL && fIndex < fTable->fTableSize)
+ fNext = fTable->fTable[fIndex++];
+ }
+
+ const HashTable* fTable;
+ size_t fIndex;
+ ValueType* fNext;
+ };
+
+ Iterator GetIterator() const
+ {
+ return Iterator(this);
+ }
+
+ Iterator GetIterator(const KeyType& key) const
+ {
+ if (fTableSize == 0)
+ return Iterator(this, fTableSize, NULL);
+
+ size_t index = fDefinition.HashKey(key) & (fTableSize - 1);
+ ValueType* slot = fTable[index];
+
+ while (slot)
+ {
+ if (fDefinition.Compare(key, slot))
+ break;
+ slot = _Link(slot);
+ }
+
+ if (slot == NULL)
+ return Iterator(this, fTableSize, NULL);
+
+ return Iterator(this, index + 1, slot);
+ }
+
+protected:
+ // for g++ 2.95
+ friend class Iterator;
+
+ void _Insert(ValueType** table, size_t tableSize, ValueType* value)
+ {
+ size_t index = fDefinition.Hash(value) & (tableSize - 1);
+
+ _Link(value) = table[index];
+ table[index] = value;
+ }
+
+ bool _Resize(size_t newSize)
+ {
+ ValueType** newTable
+ = (ValueType**)fAllocator.Allocate(sizeof(ValueType*) * newSize);
+ if (newTable == NULL)
+ return false;
+
+ _Resize(newTable, newSize);
+ return true;
+ }
+
+ void _Resize(ValueType** newTable, size_t newSize, void** _oldTable = NULL)
+ {
+ for (size_t i = 0; i < newSize; i++)
+ newTable[i] = NULL;
+
+ if (fTable)
+ {
+ for (size_t i = 0; i < fTableSize; i++)
+ {
+ ValueType* bucket = fTable[i];
+ while (bucket)
+ {
+ ValueType* next = _Link(bucket);
+ _Insert(newTable, newSize, bucket);
+ bucket = next;
+ }
+ }
+
+ if (_oldTable != NULL)
+ *_oldTable = fTable;
+ else
+ fAllocator.Free(fTable);
+ }
+ else if (_oldTable != NULL)
+ *_oldTable = NULL;
+
+ fTableSize = newSize;
+ fTable = newTable;
+ }
+
+ ValueType*& _Link(ValueType* bucket) const
+ {
+ return fDefinition.GetLink(bucket);
+ }
+
+ bool _ExhaustiveSearch(ValueType* value) const
+ {
+ for (size_t i = 0; i < fTableSize; i++)
+ {
+ ValueType* bucket = fTable[i];
+ while (bucket) {
+ if (bucket == value)
+ return true;
+ bucket = _Link(bucket);
+ }
+ }
+
+ return false;
+ }
+
+ Definition fDefinition;
+ Allocator fAllocator;
+ size_t fTableSize;
+ size_t fItemCount;
+ ValueType** fTable;
+};
+
+#endif /* !GA_INCLUDED_SRC_haiku_SharedFolders_OpenHashTable_h */
+
diff --git a/src/VBox/Additions/haiku/SharedFolders/kernel_cpp.h b/src/VBox/Additions/haiku/SharedFolders/kernel_cpp.h
new file mode 100644
index 00000000..7cc3093f
--- /dev/null
+++ b/src/VBox/Additions/haiku/SharedFolders/kernel_cpp.h
@@ -0,0 +1,122 @@
+/* $Id: kernel_cpp.h $ */
+/** @file
+ * Kernel C++, Haiku private.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku. C++ in the kernel.
+ *
+ * Copyright 2002-2009, Axel Dörfler, axeld@pinc-software.de.
+ * Distributed under the terms of the MIT License.
+ */
+
+#ifndef GA_INCLUDED_SRC_haiku_SharedFolders_kernel_cpp_h
+#define GA_INCLUDED_SRC_haiku_SharedFolders_kernel_cpp_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef __cplusplus
+
+#include <new>
+#include <stdlib.h>
+
+#if _KERNEL_MODE || _LOADER_MODE
+
+using namespace std;
+extern const nothrow_t std::nothrow;
+
+// We need new() versions we can use when also linking against libgcc.
+// std::nothrow can't be used since it's defined in both libgcc and
+// kernel_cpp.cpp.
+typedef struct {} mynothrow_t;
+extern const mynothrow_t mynothrow;
+
+
+inline void *
+operator new(size_t size) throw (std::bad_alloc)
+{
+ // we don't actually throw any exceptions, but we have to
+ // keep the prototype as specified in <new>, or else GCC 3
+ // won't like us
+ return malloc(size);
+}
+
+
+inline void *
+operator new[](size_t size) throw (std::bad_alloc)
+{
+ return malloc(size);
+}
+
+
+inline void *
+operator new(size_t size, const std::nothrow_t &) throw ()
+{
+ return malloc(size);
+}
+
+
+inline void *
+operator new[](size_t size, const std::nothrow_t &) throw ()
+{
+ return malloc(size);
+}
+
+
+inline void *
+operator new(size_t size, const mynothrow_t &) throw ()
+{
+ return malloc(size);
+}
+
+
+inline void *
+operator new[](size_t size, const mynothrow_t &) throw ()
+{
+ return malloc(size);
+}
+
+
+inline void
+operator delete(void *ptr) throw ()
+{
+ free(ptr);
+}
+
+
+inline void
+operator delete[](void *ptr) throw ()
+{
+ free(ptr);
+}
+
+#endif // #if _KERNEL_MODE
+
+#endif // __cplusplus
+
+#endif /* !GA_INCLUDED_SRC_haiku_SharedFolders_kernel_cpp_h */
diff --git a/src/VBox/Additions/haiku/SharedFolders/lock.h b/src/VBox/Additions/haiku/SharedFolders/lock.h
new file mode 100644
index 00000000..4bca6a3f
--- /dev/null
+++ b/src/VBox/Additions/haiku/SharedFolders/lock.h
@@ -0,0 +1,315 @@
+/* $Id: lock.h $ */
+/** @file
+ * Lock.h - Haiku, private locking internals.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ *
+ * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
+ * Copyright 2002-2009, Axel Dörfler, axeld@pinc-software.de.
+ * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
+ * Distributed under the terms of the MIT License.
+ */
+
+#ifndef GA_INCLUDED_SRC_haiku_SharedFolders_lock_h
+#define GA_INCLUDED_SRC_haiku_SharedFolders_lock_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <OS.h>
+
+
+struct mutex_waiter;
+
+typedef struct mutex {
+ const char* name;
+ struct mutex_waiter* waiters;
+#if KDEBUG
+ thread_id holder;
+#else
+ int32 count;
+ uint16 ignore_unlock_count;
+#endif
+ uint8 flags;
+} mutex;
+
+#define MUTEX_FLAG_CLONE_NAME 0x1
+
+
+typedef struct recursive_lock {
+ mutex lock;
+#if !KDEBUG
+ thread_id holder;
+#endif
+ int recursion;
+} recursive_lock;
+
+
+struct rw_lock_waiter;
+
+typedef struct rw_lock {
+ const char* name;
+ struct rw_lock_waiter* waiters;
+ thread_id holder;
+ vint32 count;
+ int32 owner_count;
+ int16 active_readers;
+ // Only > 0 while a writer is waiting: number
+ // of active readers when the first waiting
+ // writer started waiting.
+ int16 pending_readers;
+ // Number of readers that have already
+ // incremented "count", but have not yet started
+ // to wait at the time the last writer unlocked.
+ uint32 flags;
+} rw_lock;
+
+#define RW_LOCK_WRITER_COUNT_BASE 0x10000
+
+#define RW_LOCK_FLAG_CLONE_NAME 0x1
+
+
+#if KDEBUG
+# define KDEBUG_RW_LOCK_DEBUG 0
+ // Define to 1 if you want to use ASSERT_READ_LOCKED_RW_LOCK().
+ // The rw_lock will just behave like a recursive locker then.
+# define ASSERT_LOCKED_RECURSIVE(r) \
+ { ASSERT(find_thread(NULL) == (r)->lock.holder); }
+# define ASSERT_LOCKED_MUTEX(m) { ASSERT(find_thread(NULL) == (m)->holder); }
+# define ASSERT_WRITE_LOCKED_RW_LOCK(l) \
+ { ASSERT(find_thread(NULL) == (l)->holder); }
+# if KDEBUG_RW_LOCK_DEBUG
+# define ASSERT_READ_LOCKED_RW_LOCK(l) \
+ { ASSERT(find_thread(NULL) == (l)->holder); }
+# else
+# define ASSERT_READ_LOCKED_RW_LOCK(l) do {} while (false)
+# endif
+#else
+# define ASSERT_LOCKED_RECURSIVE(r) do {} while (false)
+# define ASSERT_LOCKED_MUTEX(m) do {} while (false)
+# define ASSERT_WRITE_LOCKED_RW_LOCK(m) do {} while (false)
+# define ASSERT_READ_LOCKED_RW_LOCK(l) do {} while (false)
+#endif
+
+
+// static initializers
+#if KDEBUG
+# define MUTEX_INITIALIZER(name) { name, NULL, -1, 0 }
+# define RECURSIVE_LOCK_INITIALIZER(name) { MUTEX_INITIALIZER(name), 0 }
+#else
+# define MUTEX_INITIALIZER(name) { name, NULL, 0, 0, 0 }
+# define RECURSIVE_LOCK_INITIALIZER(name) { MUTEX_INITIALIZER(name), -1, 0 }
+#endif
+
+#define RW_LOCK_INITIALIZER(name) { name, NULL, -1, 0, 0, 0 }
+
+
+#if KDEBUG
+# define RECURSIVE_LOCK_HOLDER(recursiveLock) ((recursiveLock)->lock.holder)
+#else
+# define RECURSIVE_LOCK_HOLDER(recursiveLock) ((recursiveLock)->holder)
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void recursive_lock_init(recursive_lock *lock, const char *name);
+ // name is *not* cloned nor freed in recursive_lock_destroy()
+extern void recursive_lock_init_etc(recursive_lock *lock, const char *name,
+ uint32 flags);
+extern void recursive_lock_destroy(recursive_lock *lock);
+extern status_t recursive_lock_lock(recursive_lock *lock);
+extern status_t recursive_lock_trylock(recursive_lock *lock);
+extern void recursive_lock_unlock(recursive_lock *lock);
+extern int32 recursive_lock_get_recursion(recursive_lock *lock);
+
+extern void rw_lock_init(rw_lock* lock, const char* name);
+ // name is *not* cloned nor freed in rw_lock_destroy()
+extern void rw_lock_init_etc(rw_lock* lock, const char* name, uint32 flags);
+extern void rw_lock_destroy(rw_lock* lock);
+extern status_t rw_lock_write_lock(rw_lock* lock);
+
+extern void mutex_init(mutex* lock, const char* name);
+ // name is *not* cloned nor freed in mutex_destroy()
+extern void mutex_init_etc(mutex* lock, const char* name, uint32 flags);
+extern void mutex_destroy(mutex* lock);
+extern status_t mutex_switch_lock(mutex* from, mutex* to);
+ // Unlocks "from" and locks "to" such that unlocking and starting to wait
+ // for the lock is atomically. I.e. if "from" guards the object "to" belongs
+ // to, the operation is safe as long as "from" is held while destroying
+ // "to".
+extern status_t mutex_switch_from_read_lock(rw_lock* from, mutex* to);
+ // Like mutex_switch_lock(), just for a switching from a read-locked
+ // rw_lock.
+
+
+// implementation private:
+
+extern status_t _rw_lock_read_lock(rw_lock* lock);
+extern status_t _rw_lock_read_lock_with_timeout(rw_lock* lock,
+ uint32 timeoutFlags, bigtime_t timeout);
+extern void _rw_lock_read_unlock(rw_lock* lock, bool threadsLocked);
+extern void _rw_lock_write_unlock(rw_lock* lock, bool threadsLocked);
+
+extern status_t _mutex_lock(mutex* lock, bool threadsLocked);
+extern void _mutex_unlock(mutex* lock, bool threadsLocked);
+extern status_t _mutex_trylock(mutex* lock);
+extern status_t _mutex_lock_with_timeout(mutex* lock, uint32 timeoutFlags,
+ bigtime_t timeout);
+
+
+static inline status_t
+rw_lock_read_lock(rw_lock* lock)
+{
+#if KDEBUG_RW_LOCK_DEBUG
+ return rw_lock_write_lock(lock);
+#else
+ int32 oldCount = atomic_add(&lock->count, 1);
+ if (oldCount >= RW_LOCK_WRITER_COUNT_BASE)
+ return _rw_lock_read_lock(lock);
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+rw_lock_read_lock_with_timeout(rw_lock* lock, uint32 timeoutFlags,
+ bigtime_t timeout)
+{
+#if KDEBUG_RW_LOCK_DEBUG
+ return mutex_lock_with_timeout(lock, timeoutFlags, timeout);
+#else
+ int32 oldCount = atomic_add(&lock->count, 1);
+ if (oldCount >= RW_LOCK_WRITER_COUNT_BASE)
+ return _rw_lock_read_lock_with_timeout(lock, timeoutFlags, timeout);
+ return B_OK;
+#endif
+}
+
+
+static inline void
+rw_lock_read_unlock(rw_lock* lock)
+{
+#if KDEBUG_RW_LOCK_DEBUG
+ rw_lock_write_unlock(lock);
+#else
+ int32 oldCount = atomic_add(&lock->count, -1);
+ if (oldCount >= RW_LOCK_WRITER_COUNT_BASE)
+ _rw_lock_read_unlock(lock, false);
+#endif
+}
+
+
+static inline void
+rw_lock_write_unlock(rw_lock* lock)
+{
+ _rw_lock_write_unlock(lock, false);
+}
+
+
+static inline status_t
+mutex_lock(mutex* lock)
+{
+#if KDEBUG
+ return _mutex_lock(lock, false);
+#else
+ if (atomic_add(&lock->count, -1) < 0)
+ return _mutex_lock(lock, false);
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+mutex_lock_threads_locked(mutex* lock)
+{
+#if KDEBUG
+ return _mutex_lock(lock, true);
+#else
+ if (atomic_add(&lock->count, -1) < 0)
+ return _mutex_lock(lock, true);
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+mutex_trylock(mutex* lock)
+{
+#if KDEBUG
+ return _mutex_trylock(lock);
+#else
+ if (atomic_test_and_set(&lock->count, -1, 0) != 0)
+ return B_WOULD_BLOCK;
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+mutex_lock_with_timeout(mutex* lock, uint32 timeoutFlags, bigtime_t timeout)
+{
+#if KDEBUG
+ return _mutex_lock_with_timeout(lock, timeoutFlags, timeout);
+#else
+ if (atomic_add(&lock->count, -1) < 0)
+ return _mutex_lock_with_timeout(lock, timeoutFlags, timeout);
+ return B_OK;
+#endif
+}
+
+
+static inline void
+mutex_unlock(mutex* lock)
+{
+#if !KDEBUG
+ if (atomic_add(&lock->count, 1) < -1)
+#endif
+ _mutex_unlock(lock, false);
+}
+
+
+static inline void
+mutex_transfer_lock(mutex* lock, thread_id thread)
+{
+#if KDEBUG
+ lock->holder = thread;
+#endif
+}
+
+
+extern void lock_debug_init();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_SRC_haiku_SharedFolders_lock_h */
diff --git a/src/VBox/Additions/haiku/SharedFolders/vboxsf.c b/src/VBox/Additions/haiku/SharedFolders/vboxsf.c
new file mode 100644
index 00000000..26182d42
--- /dev/null
+++ b/src/VBox/Additions/haiku/SharedFolders/vboxsf.c
@@ -0,0 +1,1055 @@
+/* $Id: vboxsf.c $ */
+/** @file
+ * Shared folders - Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ *
+ * 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 "vboxsf.h"
+
+#define MODULE_NAME "file_systems/vboxsf"
+#define FS_NAME "vboxsf"
+#define FS_PRETTY_NAME "VirtualBox Shared Folders"
+
+VBGLSFCLIENT g_clientHandle;
+static fs_volume_ops vboxsf_volume_ops;
+static fs_vnode_ops vboxsf_vnode_ops;
+
+status_t init_module(void)
+{
+#if 0
+ /** @todo enable this soon */
+ int rc = get_module(VBOXGUEST_MODULE_NAME, (module_info **)&g_VBoxGuest);
+ if (RT_LIKELY(rc == B_OK)
+ {
+ rc = VbglR0SfInit();
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0SfConnect(&g_clientHandle);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0SfSetUtf8(&g_clientHandle);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0SfSetSymlinks(&g_clientHandle);
+ if (RT_FAILURE(rc))
+ LogRel((FS_NAME ": Warning! VbglR0SfSetSymlinks failed (rc=%d) - symlink will appear as copies.\n", rc));
+
+ mutex_init(&g_vnodeCacheLock, "vboxsf vnode cache lock");
+ Log((FS_NAME ": init_module succeeded.\n");
+ return B_OK;
+ }
+ else
+ LogRel((FS_NAME ": VbglR0SfSetUtf8 failed. rc=%d\n", rc));
+ }
+ else
+ LogRel((FS_NAME ": VbglR0SfConnect failed. rc=%d\n", rc));
+ }
+ else
+ LogRel((FS_NAME ": VbglR0SfInit failed. rc=%d\n", rc));
+ }
+ else
+ LogRel((FS_NAME ": get_module failed for '%s'. rc=%d\n", VBOXGUEST_MODULE_NAME, rc));
+
+ return B_ERROR;
+#endif
+
+ if (get_module(VBOXGUEST_MODULE_NAME, (module_info **)&g_VBoxGuest) != B_OK)
+ {
+ dprintf("get_module(%s) failed\n", VBOXGUEST_MODULE_NAME);
+ return B_ERROR;
+ }
+
+ if (RT_FAILURE(VbglR0SfInit()))
+ {
+ dprintf("VbglR0SfInit failed\n");
+ return B_ERROR;
+ }
+
+ if (RT_FAILURE(VbglR0SfConnect(&g_clientHandle)))
+ {
+ dprintf("VbglR0SfConnect failed\n");
+ return B_ERROR;
+ }
+
+ if (RT_FAILURE(VbglR0SfSetUtf8(&g_clientHandle)))
+ {
+ dprintf("VbglR0SfSetUtf8 failed\n");
+ return B_ERROR;
+ }
+
+ if (RT_FAILURE(VbglR0SfSetSymlinks(&g_clientHandle)))
+ {
+ dprintf("warning: VbglR0SfSetSymlinks failed (old vbox?) - symlinks will appear as copies\n");
+ }
+
+ mutex_init(&g_vnodeCacheLock, "vboxsf vnode cache lock");
+
+ dprintf(FS_NAME ": inited successfully\n");
+ return B_OK;
+}
+
+void uninit_module(void)
+{
+ mutex_destroy(&g_vnodeCacheLock);
+ put_module(VBOXGUEST_MODULE_NAME);
+}
+
+
+PSHFLSTRING make_shflstring(const char* const s)
+{
+ int len = strlen(s);
+ if (len > 0xFFFE)
+ {
+ dprintf(FS_NAME ": make_shflstring: string too long\n");
+ return NULL;
+ }
+
+ PSHFLSTRING rv = malloc(sizeof(SHFLSTRING) + len);
+ if (!rv)
+ return NULL;
+
+ rv->u16Length = len;
+ rv->u16Size = len + 1;
+ strcpy(rv->String.utf8, s);
+ return rv;
+}
+
+
+PSHFLSTRING clone_shflstring(PSHFLSTRING s)
+{
+ PSHFLSTRING rv = malloc(sizeof(SHFLSTRING) + s->u16Length);
+ if (rv)
+ memcpy(rv, s, sizeof(SHFLSTRING) + s->u16Length);
+ return rv;
+}
+
+PSHFLSTRING concat_shflstring_cstr(PSHFLSTRING s1, const char* const s2)
+{
+ size_t s2len = strlen(s2);
+ PSHFLSTRING rv = malloc(sizeof(SHFLSTRING) + s1->u16Length + s2len);
+ if (rv)
+ {
+ memcpy(rv, s1, sizeof(SHFLSTRING) + s1->u16Length);
+ strcat(rv->String.utf8, s2);
+ rv->u16Length += s2len;
+ rv->u16Size += s2len;
+ }
+ return rv;
+}
+
+
+PSHFLSTRING concat_cstr_shflstring(const char* const s1, PSHFLSTRING s2)
+{
+ size_t s1len = strlen(s1);
+ PSHFLSTRING rv = malloc(sizeof(SHFLSTRING) + s1len + s2->u16Length);
+ if (rv)
+ {
+ strcpy(rv->String.utf8, s1);
+ strcat(rv->String.utf8, s2->String.utf8);
+ rv->u16Length = s1len + s2->u16Length;
+ rv->u16Size = rv->u16Length + 1;
+ }
+ return rv;
+}
+
+
+PSHFLSTRING build_path(vboxsf_vnode* dir, const char* const name)
+{
+ dprintf("*** build_path(%p, %p)\n", dir, name);
+ if (!dir || !name)
+ return NULL;
+
+ size_t len = dir->path->u16Length + strlen(name) + 1;
+ PSHFLSTRING rv = malloc(sizeof(SHFLSTRING) + len);
+ if (rv)
+ {
+ strcpy(rv->String.utf8, dir->path->String.utf8);
+ strcat(rv->String.utf8, "/");
+ strcat(rv->String.utf8, name);
+ rv->u16Length = len;
+ rv->u16Size = rv->u16Length + 1;
+ }
+ return rv;
+}
+
+
+status_t mount(fs_volume *volume, const char *device, uint32 flags, const char *args, ino_t *_rootVnodeID)
+{
+ if (device)
+ {
+ dprintf(FS_NAME ": trying to mount a real device as a vbox share is silly\n");
+ return B_BAD_TYPE;
+ }
+
+ dprintf(FS_NAME ": mount(%s)\n", args);
+
+ PSHFLSTRING sharename = make_shflstring(args);
+
+ vboxsf_volume* vbsfvolume = malloc(sizeof(vboxsf_volume));
+ volume->private_volume = vbsfvolume;
+ int rv = VbglR0SfMapFolder(&g_clientHandle, sharename, &(vbsfvolume->map));
+ free(sharename);
+
+ if (rv == 0)
+ {
+ vboxsf_vnode* root_vnode;
+
+ PSHFLSTRING name = make_shflstring("");
+ if (!name)
+ {
+ dprintf(FS_NAME ": make_shflstring() failed\n");
+ return B_NO_MEMORY;
+ }
+
+ status_t rs = vboxsf_new_vnode(&vbsfvolume->map, name, name, &root_vnode);
+ dprintf(FS_NAME ": allocated %p (path=%p name=%p)\n", root_vnode, root_vnode->path, root_vnode->name);
+
+ if (rs != B_OK)
+ {
+ dprintf(FS_NAME ": vboxsf_new_vnode() failed (%d)\n", (int)rs);
+ return rs;
+ }
+
+ rs = publish_vnode(volume, root_vnode->vnode, root_vnode, &vboxsf_vnode_ops, S_IFDIR, 0);
+ dprintf(FS_NAME ": publish_vnode(): %d\n", (int)rs);
+ *_rootVnodeID = root_vnode->vnode;
+ volume->ops = &vboxsf_volume_ops;
+ return B_OK;
+ }
+ else
+ {
+ dprintf(FS_NAME ": VbglR0SfMapFolder failed (%d)\n", rv);
+ free(volume->private_volume);
+ return vbox_err_to_haiku_err(rv);
+ }
+}
+
+
+status_t unmount(fs_volume *volume)
+{
+ dprintf(FS_NAME ": unmount\n");
+ VbglR0SfUnmapFolder(&g_clientHandle, volume->private_volume);
+ return B_OK;
+}
+
+
+status_t vboxsf_read_stat(fs_volume* _volume, fs_vnode* _vnode, struct stat* st)
+{
+ vboxsf_vnode* vnode = _vnode->private_node;
+ vboxsf_volume* volume = _volume->private_volume;
+ SHFLCREATEPARMS params;
+ int rc;
+
+ dprintf("vboxsf_read_stat (_vnode=%p, vnode=%p, path=%p (%s))\n", _vnode, vnode, vnode->path->String.utf8, vnode->path->String.utf8);
+
+ params.Handle = SHFL_HANDLE_NIL;
+ params.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+ dprintf("sf_stat: calling VbglR0SfCreate, file %s, flags %x\n", vnode->path->String.utf8, params.CreateFlags);
+ rc = VbglR0SfCreate(&g_clientHandle, &volume->map, vnode->path, &params);
+ if (rc == VERR_INVALID_NAME)
+ {
+ /* this can happen for names like 'foo*' on a Windows host */
+ return B_ENTRY_NOT_FOUND;
+ }
+ if (RT_FAILURE(rc))
+ {
+ dprintf("VbglR0SfCreate: %d\n", params.Result);
+ return vbox_err_to_haiku_err(params.Result);
+ }
+ if (params.Result != SHFL_FILE_EXISTS)
+ {
+ dprintf("VbglR0SfCreate: %d\n", params.Result);
+ return B_ENTRY_NOT_FOUND;
+ }
+
+ st->st_dev = 0;
+ st->st_ino = vnode->vnode;
+ st->st_mode = mode_from_fmode(params.Info.Attr.fMode);
+ st->st_nlink = 1;
+ st->st_uid = 0;
+ st->st_gid = 0;
+ st->st_rdev = 0;
+ st->st_size = params.Info.cbObject;
+ st->st_blksize = 1;
+ st->st_blocks = params.Info.cbAllocated;
+ st->st_atime = RTTimeSpecGetSeconds(&params.Info.AccessTime);
+ st->st_mtime = RTTimeSpecGetSeconds(&params.Info.ModificationTime);
+ st->st_ctime = RTTimeSpecGetSeconds(&params.Info.BirthTime);
+ return B_OK;
+}
+
+
+status_t vboxsf_open_dir(fs_volume* _volume, fs_vnode* _vnode, void** _cookie)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_vnode* vnode = _vnode->private_node;
+ SHFLCREATEPARMS params;
+
+ RT_ZERO(params);
+ params.Handle = SHFL_HANDLE_NIL;
+ params.CreateFlags = SHFL_CF_DIRECTORY | SHFL_CF_ACT_OPEN_IF_EXISTS | SHFL_CF_ACT_FAIL_IF_NEW | SHFL_CF_ACCESS_READ;
+
+ int rc = VbglR0SfCreate(&g_clientHandle, &volume->map, vnode->path, &params);
+ if (RT_SUCCESS(rc))
+ {
+ if (params.Result == SHFL_FILE_EXISTS && params.Handle != SHFL_HANDLE_NIL)
+ {
+ vboxsf_dir_cookie* cookie = malloc(sizeof(vboxsf_dir_cookie));
+ *_cookie = cookie;
+ cookie->index = 0;
+ cookie->path = build_path(vnode, "*");
+ cookie->handle = params.Handle;
+ cookie->has_more_files = true;
+ cookie->buffer_start = cookie->buffer = NULL;
+ cookie->buffer_length = cookie->num_files = 0;
+ return B_OK;
+ }
+ else
+ return B_ENTRY_NOT_FOUND;
+ }
+ else
+ {
+ dprintf(FS_NAME ": VbglR0SfCreate: %d\n", rc);
+ return vbox_err_to_haiku_err(rc);
+ }
+}
+
+
+/** read a single entry from a dir */
+status_t vboxsf_read_dir_1(vboxsf_volume* volume, vboxsf_vnode* vnode, vboxsf_dir_cookie* cookie,
+ struct dirent* buffer, size_t bufferSize)
+{
+ dprintf("%p, %d, %p\n", cookie, cookie->has_more_files, cookie->buffer);
+ if (!cookie->has_more_files)
+ return B_ENTRY_NOT_FOUND;
+
+ if (!cookie->buffer)
+ {
+ cookie->buffer_length = 16384;
+ cookie->buffer_start = cookie->buffer = malloc(cookie->buffer_length);
+
+ int rc = VbglR0SfDirInfo(&g_clientHandle, &volume->map, cookie->handle, cookie->path, 0, cookie->index,
+ &cookie->buffer_length, cookie->buffer, &cookie->num_files);
+
+ if (rc != 0 && rc != VERR_NO_MORE_FILES)
+ {
+ dprintf(FS_NAME ": VbglR0SfDirInfo failed: %d\n", rc);
+ free(cookie->buffer_start);
+ cookie->buffer_start = NULL;
+ return vbox_err_to_haiku_err(rc);
+ }
+
+ if (rc == VERR_NO_MORE_FILES)
+ {
+ free(cookie->buffer_start);
+ cookie->buffer_start = NULL;
+ cookie->has_more_files = false;
+ return B_ENTRY_NOT_FOUND;
+ }
+ }
+
+ if (bufferSize <= sizeof(struct dirent) + cookie->buffer->name.u16Length)
+ {
+ dprintf("hit end of buffer\n");
+ return B_BUFFER_OVERFLOW;
+ }
+
+ PSHFLSTRING name1 = clone_shflstring(&cookie->buffer->name);
+ if (!name1)
+ {
+ dprintf(FS_NAME ": make_shflstring() failed\n");
+ return B_NO_MEMORY;
+ }
+
+ vboxsf_vnode* new_vnode;
+ int rv = vboxsf_new_vnode(&volume->map, build_path(vnode, name1->String.utf8), name1, &new_vnode);
+ if (rv != B_OK)
+ {
+ dprintf(FS_NAME ": vboxsf_new_vnode() failed\n");
+ return rv;
+ }
+
+ buffer->d_dev = 0;
+ buffer->d_pdev = 0;
+ buffer->d_ino = new_vnode->vnode;
+ buffer->d_pino = vnode->vnode;
+ buffer->d_reclen = sizeof(struct dirent) + cookie->buffer->name.u16Length;
+ strncpy(buffer->d_name, cookie->buffer->name.String.utf8, NAME_MAX);
+
+ size_t size = offsetof(SHFLDIRINFO, name.String) + cookie->buffer->name.u16Size;
+ cookie->buffer = ((void*)cookie->buffer + size);
+ cookie->index++;
+
+ if (cookie->index >= cookie->num_files)
+ {
+ // hit end of this buffer, next call will reallocate a new one
+ free(cookie->buffer_start);
+ cookie->buffer_start = cookie->buffer = NULL;
+ }
+ return B_OK;
+}
+
+
+status_t vboxsf_read_dir(fs_volume* _volume, fs_vnode* _vnode, void* _cookie,
+ struct dirent* buffer, size_t bufferSize, uint32* _num)
+{
+ vboxsf_dir_cookie* cookie = _cookie;
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_vnode* vnode = _vnode->private_node;
+ uint32 num_read = 0;
+ status_t rv = B_OK;
+
+ for (num_read = 0; num_read < *_num && cookie->has_more_files; num_read++)
+ {
+ rv = vboxsf_read_dir_1(volume, vnode, cookie, buffer, bufferSize);
+ if (rv == B_BUFFER_OVERFLOW || rv == B_ENTRY_NOT_FOUND)
+ {
+ // hit end of at least one of the buffers - not really an error
+ rv = B_OK;
+ break;
+ }
+ bufferSize -= buffer->d_reclen;
+ buffer = ((void*)(buffer)) + buffer->d_reclen;
+ }
+
+ *_num = num_read;
+ return rv;
+}
+
+
+status_t vboxsf_free_dir_cookie(fs_volume* _volume, fs_vnode* vnode, void* _cookie)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_dir_cookie* cookie = _cookie;
+
+ VbglR0SfClose(&g_clientHandle, &volume->map, cookie->handle);
+ free(cookie->path);
+ free(cookie);
+
+ return B_OK;
+}
+
+
+status_t vboxsf_read_fs_info(fs_volume* _volume, struct fs_info* info)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+
+ SHFLVOLINFO volume_info;
+ uint32_t bytes = sizeof(SHFLVOLINFO);
+
+ int rc = VbglR0SfFsInfo(&g_clientHandle, &volume->map, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME,
+ &bytes, (PSHFLDIRINFO)&volume_info);
+ if (RT_FAILURE(rc))
+ {
+ dprintf(FS_NAME ": VbglR0SfFsInfo failed (%d)\n", rc);
+ return vbox_err_to_haiku_err(rc);
+ }
+
+ info->flags = B_FS_IS_PERSISTENT;
+ if (volume_info.fsProperties.fReadOnly)
+ info->flags |= B_FS_IS_READONLY;
+
+ info->dev = 0;
+ info->root = 1;
+ info->block_size = volume_info.ulBytesPerAllocationUnit;
+ info->io_size = volume_info.ulBytesPerAllocationUnit;
+ info->total_blocks = volume_info.ullTotalAllocationBytes / info->block_size;
+ info->free_blocks = volume_info.ullAvailableAllocationBytes / info->block_size;
+ info->total_nodes = LONGLONG_MAX;
+ info->free_nodes = LONGLONG_MAX;
+ strcpy(info->volume_name, "VBox share");
+ return B_OK;
+}
+
+
+status_t vboxsf_lookup(fs_volume* _volume, fs_vnode* dir, const char* name, ino_t* _id)
+{
+ dprintf(FS_NAME ": lookup %s\n", name);
+ vboxsf_volume* volume = _volume->private_volume;
+ SHFLCREATEPARMS params;
+
+ RT_ZERO(params);
+ params.Handle = SHFL_HANDLE_NIL;
+ params.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ PSHFLSTRING path = build_path(dir->private_node, name);
+ if (!path)
+ {
+ dprintf(FS_NAME ": make_shflstring() failed\n");
+ return B_NO_MEMORY;
+ }
+
+ int rc = VbglR0SfCreate(&g_clientHandle, &volume->map, path, &params);
+ if (RT_SUCCESS(rc))
+ {
+ if (params.Result == SHFL_FILE_EXISTS)
+ {
+ vboxsf_vnode* vn;
+ status_t rv = vboxsf_new_vnode(&volume->map, path, path, &vn);
+ if (rv == B_OK)
+ {
+ *_id = vn->vnode;
+ rv = publish_vnode(_volume, vn->vnode, vn, &vboxsf_vnode_ops, mode_from_fmode(params.Info.Attr.fMode), 0);
+ }
+ return rv;
+ }
+ else
+ {
+ free(path);
+ return B_ENTRY_NOT_FOUND;
+ }
+ }
+ else
+ {
+ free(path);
+ dprintf(FS_NAME ": VbglR0SfCreate: %d\n", rc);
+ return vbox_err_to_haiku_err(rc);
+ }
+}
+
+
+mode_t mode_from_fmode(RTFMODE fMode)
+{
+ mode_t m = 0;
+
+ if (RTFS_IS_DIRECTORY(fMode))
+ m |= S_IFDIR;
+ else if (RTFS_IS_FILE(fMode))
+ m |= S_IFREG;
+ else if (RTFS_IS_FIFO(fMode))
+ m |= S_IFIFO;
+ else if (RTFS_IS_DEV_CHAR(fMode))
+ m |= S_IFCHR;
+ else if (RTFS_IS_DEV_BLOCK(fMode))
+ m |= S_IFBLK;
+ else if (RTFS_IS_SYMLINK(fMode))
+ m |= S_IFLNK;
+ else if (RTFS_IS_SOCKET(fMode))
+ m |= S_IFSOCK;
+
+ if (fMode & RTFS_UNIX_IRUSR)
+ m |= S_IRUSR;
+ if (fMode & RTFS_UNIX_IWUSR)
+ m |= S_IWUSR;
+ if (fMode & RTFS_UNIX_IXUSR)
+ m |= S_IXUSR;
+ if (fMode & RTFS_UNIX_IRGRP)
+ m |= S_IRGRP;
+ if (fMode & RTFS_UNIX_IWGRP)
+ m |= S_IWGRP;
+ if (fMode & RTFS_UNIX_IXGRP)
+ m |= S_IXGRP;
+ if (fMode & RTFS_UNIX_IROTH)
+ m |= S_IROTH;
+ if (fMode & RTFS_UNIX_IWOTH)
+ m |= S_IWOTH;
+ if (fMode & RTFS_UNIX_IXOTH)
+ m |= S_IXOTH;
+ if (fMode & RTFS_UNIX_ISUID)
+ m |= S_ISUID;
+ if (fMode & RTFS_UNIX_ISGID)
+ m |= S_ISGID;
+ if (fMode & RTFS_UNIX_ISTXT)
+ m |= S_ISVTX;
+
+ return m;
+}
+
+
+status_t vboxsf_open(fs_volume* _volume, fs_vnode* _vnode, int openMode, void** _cookie)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_vnode* vnode = _vnode->private_node;
+
+ dprintf(FS_NAME ": open %s (mode=%x)\n", vnode->path->String.utf8, openMode);
+
+ SHFLCREATEPARMS params;
+
+ RT_ZERO(params);
+ params.Handle = SHFL_HANDLE_NIL;
+
+ if (openMode & O_RDWR)
+ params.CreateFlags |= SHFL_CF_ACCESS_READWRITE;
+ else if (openMode & O_RDONLY)
+ params.CreateFlags |= SHFL_CF_ACCESS_READ;
+ else if (openMode & O_WRONLY)
+ params.CreateFlags |= SHFL_CF_ACCESS_WRITE;
+
+ if (openMode & O_APPEND)
+ params.CreateFlags |= SHFL_CF_ACCESS_APPEND;
+
+ if (openMode & O_CREAT)
+ {
+ params.CreateFlags |= SHFL_CF_ACT_CREATE_IF_NEW;
+ if (openMode & O_EXCL)
+ params.CreateFlags |= SHFL_CF_ACT_FAIL_IF_EXISTS;
+ else if (openMode & O_TRUNC)
+ 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 (openMode & O_TRUNC)
+ params.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
+ else
+ params.CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS;
+ }
+
+ int rc = VbglR0SfCreate(&g_clientHandle, &volume->map, vnode->path, &params);
+ if (!RT_SUCCESS(rc))
+ {
+ dprintf("VbglR0SfCreate returned %d\n", rc);
+ return vbox_err_to_haiku_err(rc);
+ }
+
+ vboxsf_file_cookie* cookie = malloc(sizeof(vboxsf_file_cookie));
+ if (!cookie)
+ {
+ dprintf("couldn't allocate file cookie\n");
+ return B_NO_MEMORY;
+ }
+
+ cookie->handle = params.Handle;
+ cookie->path = vnode->path;
+
+ *_cookie = cookie;
+
+ return B_OK;
+}
+
+
+status_t vboxsf_create(fs_volume* _volume, fs_vnode* _dir, const char *name, int openMode, int perms, void **_cookie, ino_t *_newVnodeID)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+
+ SHFLCREATEPARMS params;
+
+ RT_ZERO(params);
+ params.Handle = SHFL_HANDLE_NIL;
+
+ if (openMode & O_RDWR)
+ params.CreateFlags |= SHFL_CF_ACCESS_READWRITE;
+ else if (openMode & O_RDONLY)
+ params.CreateFlags |= SHFL_CF_ACCESS_READ;
+ else if (openMode & O_WRONLY)
+ params.CreateFlags |= SHFL_CF_ACCESS_WRITE;
+
+ if (openMode & O_APPEND)
+ params.CreateFlags |= SHFL_CF_ACCESS_APPEND;
+
+ if (openMode & O_CREAT)
+ {
+ params.CreateFlags |= SHFL_CF_ACT_CREATE_IF_NEW;
+ if (openMode & O_EXCL)
+ params.CreateFlags |= SHFL_CF_ACT_FAIL_IF_EXISTS;
+ else if (openMode & O_TRUNC)
+ 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 (openMode & O_TRUNC)
+ params.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
+ else
+ params.CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS;
+ }
+
+ PSHFLSTRING path = build_path(_dir->private_node, name);
+ int rc = VbglR0SfCreate(&g_clientHandle, &volume->map, path, &params);
+
+ if (!RT_SUCCESS(rc))
+ {
+ dprintf("VbglR0SfCreate returned %d\n", rc);
+ free(path);
+ return vbox_err_to_haiku_err(rc);
+ }
+
+ vboxsf_file_cookie* cookie = malloc(sizeof(vboxsf_file_cookie));
+ if (!cookie)
+ {
+ dprintf("couldn't allocate file cookie\n");
+ free(path);
+ return B_NO_MEMORY;
+ }
+
+ cookie->handle = params.Handle;
+ cookie->path = path;
+
+ *_cookie = cookie;
+ return vboxsf_lookup(_volume, _dir, name, _newVnodeID);
+}
+
+
+status_t vboxsf_close(fs_volume* _volume, fs_vnode* _vnode, void* _cookie)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_file_cookie* cookie = _cookie;
+
+ int rc = VbglR0SfClose(&g_clientHandle, &volume->map, cookie->handle);
+ dprintf("VbglR0SfClose returned %d\n", rc);
+ return vbox_err_to_haiku_err(rc);
+}
+
+
+status_t vboxsf_rewind_dir(fs_volume* _volume, fs_vnode* _vnode, void* _cookie)
+{
+ vboxsf_dir_cookie* cookie = _cookie;
+ cookie->index = 0;
+ return B_OK;
+}
+
+
+status_t vboxsf_close_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
+{
+ return B_OK;
+}
+
+
+status_t vboxsf_free_cookie(fs_volume *volume, fs_vnode *vnode, void *_cookie)
+{
+ vboxsf_dir_cookie* cookie = _cookie;
+ free(cookie);
+ return B_OK;
+}
+
+status_t vboxsf_read(fs_volume* _volume, fs_vnode* _vnode, void* _cookie, off_t pos, void *buffer, size_t *length)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_vnode* vnode = _vnode->private_node;
+ vboxsf_file_cookie* cookie = _cookie;
+
+ if (*length > 0xFFFFFFFF)
+ *length = 0xFFFFFFFF;
+
+ uint32_t l = *length;
+ void* other_buffer = malloc(l); /** @todo map the user memory into kernel space here for efficiency */
+ int rc = VbglR0SfRead(&g_clientHandle, &volume->map, cookie->handle, pos, &l, other_buffer, false /*fLocked*/);
+ memcpy(buffer, other_buffer, l);
+ free(other_buffer);
+
+ dprintf("VbglR0SfRead returned %d\n", rc);
+ *length = l;
+ return vbox_err_to_haiku_err(rc);
+}
+
+
+status_t vboxsf_write(fs_volume* _volume, fs_vnode* _vnode, void* _cookie, off_t pos, const void *buffer, size_t *length)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_vnode* vnode = _vnode->private_node;
+ vboxsf_file_cookie* cookie = _cookie;
+
+ if (*length > 0xFFFFFFFF)
+ *length = 0xFFFFFFFF;
+
+ uint32_t l = *length;
+ void* other_buffer = malloc(l); /** @todo map the user memory into kernel space here for efficiency */
+ memcpy(other_buffer, buffer, l);
+ int rc = VbglR0SfWrite(&g_clientHandle, &volume->map, cookie->handle, pos, &l, other_buffer, false /*fLocked*/);
+ free(other_buffer);
+
+ *length = l;
+ return vbox_err_to_haiku_err(rc);
+}
+
+
+status_t vboxsf_write_stat(fs_volume *volume, fs_vnode *vnode, const struct stat *stat, uint32 statMask)
+{
+ /* The host handles updating the stat info - in the guest, this is a no-op */
+ return B_OK;
+}
+
+
+status_t vboxsf_create_dir(fs_volume *_volume, fs_vnode *parent, const char *name, int perms)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+
+ SHFLCREATEPARMS params;
+ params.Handle = 0;
+ params.Info.cbObject = 0;
+ params.CreateFlags = SHFL_CF_DIRECTORY | SHFL_CF_ACT_CREATE_IF_NEW |
+ SHFL_CF_ACT_FAIL_IF_EXISTS | SHFL_CF_ACCESS_READ;
+
+ PSHFLSTRING path = build_path(parent->private_node, name);
+ int rc = VbglR0SfCreate(&g_clientHandle, &volume->map, path, &params);
+ free(path);
+ /** @todo r=ramshankar: we should perhaps also check rc here and change
+ * Handle initialization from 0 to SHFL_HANDLE_NIL. */
+ if (params.Handle == SHFL_HANDLE_NIL)
+ return vbox_err_to_haiku_err(rc);
+ VbglR0SfClose(&g_clientHandle, &volume->map, params.Handle);
+ return B_OK;
+}
+
+
+status_t vboxsf_remove_dir(fs_volume *_volume, fs_vnode *parent, const char *name)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+
+ PSHFLSTRING path = build_path(parent->private_node, name);
+ int rc = VbglR0SfRemove(&g_clientHandle, &volume->map, path, SHFL_REMOVE_DIR);
+ free(path);
+
+ return vbox_err_to_haiku_err(rc);
+}
+
+
+status_t vboxsf_unlink(fs_volume *_volume, fs_vnode *parent, const char *name)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+
+ PSHFLSTRING path = build_path(parent->private_node, name);
+ int rc = VbglR0SfRemove(&g_clientHandle, &volume->map, path, SHFL_REMOVE_FILE);
+ free(path);
+
+ return vbox_err_to_haiku_err(rc);
+}
+
+status_t vboxsf_link(fs_volume *volume, fs_vnode *dir, const char *name, fs_vnode *vnode)
+{
+ return B_UNSUPPORTED;
+}
+
+
+status_t vboxsf_rename(fs_volume* _volume, fs_vnode* fromDir, const char* fromName, fs_vnode* toDir, const char* toName)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+
+ PSHFLSTRING oldpath = build_path(fromDir->private_node, fromName);
+ PSHFLSTRING newpath = build_path(toDir->private_node, toName);
+ int rc = VbglR0SfRename(&g_clientHandle, &volume->map, oldpath, newpath, SHFL_RENAME_FILE | SHFL_RENAME_REPLACE_IF_EXISTS);
+ free(oldpath);
+ free(newpath);
+
+ return vbox_err_to_haiku_err(rc);
+}
+
+
+status_t vboxsf_create_symlink(fs_volume* _volume, fs_vnode* dir, const char* name, const char* path, int mode)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+
+ PSHFLSTRING target = make_shflstring(path);
+ PSHFLSTRING linkpath = build_path(dir->private_node, name);
+ SHFLFSOBJINFO stuff;
+ RT_ZERO(stuff);
+
+ int rc = VbglR0SfSymlink(&g_clientHandle, &volume->map, linkpath, target, &stuff);
+
+ free(target);
+ free(linkpath);
+
+ return vbox_err_to_haiku_err(rc);
+}
+
+
+status_t vboxsf_read_symlink(fs_volume* _volume, fs_vnode* link, char* buffer, size_t* _bufferSize)
+{
+ vboxsf_volume* volume = _volume->private_volume;
+ vboxsf_vnode* vnode = link->private_node;
+
+ int rc = VbglR0SfReadLink(&g_clientHandle, &volume->map, vnode->path, *_bufferSize, buffer);
+ *_bufferSize = strlen(buffer);
+
+ return vbox_err_to_haiku_err(rc);
+}
+
+
+/** @todo move this into the runtime */
+status_t vbox_err_to_haiku_err(int rc)
+{
+ switch (rc)
+ {
+ case VINF_SUCCESS: return B_OK;
+ case VERR_INVALID_POINTER: return B_BAD_ADDRESS;
+ case VERR_INVALID_PARAMETER: return B_BAD_VALUE;
+ case VERR_PERMISSION_DENIED: return B_PERMISSION_DENIED;
+ case VERR_NOT_IMPLEMENTED: return B_UNSUPPORTED;
+ case VERR_FILE_NOT_FOUND: return B_ENTRY_NOT_FOUND;
+
+ case SHFL_PATH_NOT_FOUND:
+ case SHFL_FILE_NOT_FOUND: return B_ENTRY_NOT_FOUND;
+ case SHFL_FILE_EXISTS: return B_FILE_EXISTS;
+
+ default:
+ return B_ERROR;
+ }
+}
+
+
+static status_t std_ops(int32 op, ...)
+{
+ switch(op)
+ {
+ case B_MODULE_INIT:
+ dprintf(MODULE_NAME ": B_MODULE_INIT\n");
+ return init_module();
+ case B_MODULE_UNINIT:
+ dprintf(MODULE_NAME ": B_MODULE_UNINIT\n");
+ uninit_module();
+ return B_OK;
+ default:
+ return B_ERROR;
+ }
+}
+
+
+static fs_volume_ops vboxsf_volume_ops =
+{
+ unmount,
+ vboxsf_read_fs_info,
+ NULL, /* write_fs_info */
+ NULL, /* sync */
+ vboxsf_get_vnode,
+ NULL, /* open_index_dir */
+ NULL, /* close_index_dir */
+ NULL, /* free_index_dir_cookie */
+ NULL, /* read_index_dir */
+ NULL, /* rewind_index_dir */
+ NULL, /* create_index */
+ NULL, /* remove_index */
+ NULL, /* read_index_stat */
+ NULL, /* open_query */
+ NULL, /* close_query */
+ NULL, /* free_query_cookie */
+ NULL, /* read_query */
+ NULL, /* rewind_query */
+ NULL, /* all_layers_mounted */
+ NULL, /* create_sub_vnode */
+ NULL, /* delete_sub_vnode */
+};
+
+static fs_vnode_ops vboxsf_vnode_ops =
+{
+ vboxsf_lookup,
+ NULL, /* get_vnode_name */
+ vboxsf_put_vnode,
+ NULL, /* remove_vnode */
+ NULL, /* can_page */
+ NULL, /* read_pages */
+ NULL, /* write_pages */
+ NULL, /* io */
+ NULL, /* cancel_io */
+ NULL, /* get_file_map */
+ NULL, /* ioctl */
+ NULL, /* set_flags */
+ NULL, /* select */
+ NULL, /* deselect */
+ NULL, /* fsync */
+ vboxsf_read_symlink,
+ vboxsf_create_symlink,
+ vboxsf_link,
+ vboxsf_unlink,
+ vboxsf_rename,
+ NULL, /* access */
+ vboxsf_read_stat,
+ vboxsf_write_stat,
+ NULL, /* preallocate */
+ vboxsf_create,
+ vboxsf_open,
+ vboxsf_close,
+ vboxsf_free_cookie,
+ vboxsf_read,
+ vboxsf_write,
+ vboxsf_create_dir,
+ vboxsf_remove_dir,
+ vboxsf_open_dir,
+ vboxsf_close_dir,
+ vboxsf_free_dir_cookie,
+ vboxsf_read_dir,
+ vboxsf_rewind_dir,
+ NULL, /* open_attr_dir */
+ NULL, /* close_attr_dir */
+ NULL, /* free_attr_dir_cookie */
+ NULL, /* read_attr_dir */
+ NULL, /* rewind_attr_dir */
+ NULL, /* create_attr */
+ NULL, /* open_attr */
+ NULL, /* close_attr */
+ NULL, /* free_attr_cookie */
+ NULL, /* read_attr */
+ NULL, /* write_attr */
+ NULL, /* read_attr_stat */
+ NULL, /* write_attr_stat */
+ NULL, /* rename_attr */
+ NULL, /* remove_attr */
+ NULL, /* create_special_node */
+ NULL, /* get_super_vnode */
+};
+
+static file_system_module_info sVBoxSharedFileSystem =
+{
+ {
+ MODULE_NAME B_CURRENT_FS_API_VERSION,
+ 0,
+ std_ops,
+ },
+ FS_NAME,
+ FS_PRETTY_NAME,
+ 0, /* DDM flags */
+ NULL, /* identify_partition */
+ NULL, /* scan_partition */
+ NULL, /* free_identify_partition_cookie */
+ NULL, /* free_partition_content_cookie */
+ mount,
+};
+
+module_info *modules[] =
+{
+ (module_info *)&sVBoxSharedFileSystem,
+ NULL,
+};
+
diff --git a/src/VBox/Additions/haiku/SharedFolders/vboxsf.h b/src/VBox/Additions/haiku/SharedFolders/vboxsf.h
new file mode 100644
index 00000000..d8449987
--- /dev/null
+++ b/src/VBox/Additions/haiku/SharedFolders/vboxsf.h
@@ -0,0 +1,120 @@
+/* $Id: vboxsf.h $ */
+/** @file
+ * Shared folders - Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ *
+ * 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_haiku_SharedFolders_vboxsf_h
+#define GA_INCLUDED_SRC_haiku_SharedFolders_vboxsf_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <malloc.h>
+#include <dirent.h>
+#include <fs_info.h>
+#include <sys/stat.h>
+#include <fs_interface.h>
+#include <KernelExport.h>
+#include <VBoxGuest-haiku.h>
+#include <VBox/VBoxGuestLibSharedFolders.h>
+#include "lock.h"
+
+typedef struct vboxsf_volume
+{
+ VBGLSFMAP map;
+ ino_t rootid;
+} vboxsf_volume;
+
+typedef struct vboxsf_vnode
+{
+ PVBGLSFMAP map;
+ PSHFLSTRING name;
+ PSHFLSTRING path;
+ ino_t vnode;
+ struct vboxsf_vnode* next;
+} vboxsf_vnode;
+
+typedef struct vboxsf_dir_cookie
+{
+ SHFLHANDLE handle;
+ PSHFLSTRING path;
+ uint32_t index;
+ bool has_more_files;
+ PSHFLDIRINFO buffer_start, buffer;
+ uint32_t buffer_length, num_files;
+} vboxsf_dir_cookie;
+
+typedef struct vboxsf_file_cookie
+{
+ SHFLHANDLE handle;
+ PSHFLSTRING path;
+} vboxsf_file_cookie;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+status_t vboxsf_new_vnode(PVBGLSFMAP map, PSHFLSTRING path, PSHFLSTRING name, vboxsf_vnode** p);
+status_t vboxsf_get_vnode(fs_volume* volume, ino_t id, fs_vnode* vnode, int* _type, uint32* _flags, bool reenter);
+status_t vboxsf_put_vnode(fs_volume* volume, fs_vnode* vnode, bool reenter);
+PSHFLSTRING make_shflstring(const char* const s);
+mode_t mode_from_fmode(RTFMODE fMode);
+status_t vbox_err_to_haiku_err(int rc);
+extern mutex g_vnodeCacheLock;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_SRC_haiku_SharedFolders_vboxsf_h */
+
diff --git a/src/VBox/Additions/haiku/SharedFolders/vnode_cache.cpp b/src/VBox/Additions/haiku/SharedFolders/vnode_cache.cpp
new file mode 100644
index 00000000..05221606
--- /dev/null
+++ b/src/VBox/Additions/haiku/SharedFolders/vnode_cache.cpp
@@ -0,0 +1,144 @@
+/* $Id: vnode_cache.cpp $ */
+/** @file
+ * Shared folders - Haiku Guest Additions, vnode cache header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ *
+ * 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 "vboxsf.h"
+#include "OpenHashTable.h"
+
+struct HashTableDefinition
+{
+ typedef uint32 KeyType;
+ typedef vboxsf_vnode ValueType;
+
+ size_t HashKey(uint32 key) const
+ {
+ return key;
+ }
+
+ size_t Hash(vboxsf_vnode* value) const
+ {
+ return HashKey(value->vnode);
+ }
+
+ bool Compare(uint32 key, vboxsf_vnode* value) const
+ {
+ return value->vnode == key;
+ }
+
+ vboxsf_vnode*& GetLink(vboxsf_vnode* value) const
+ {
+ return value->next;
+ }
+};
+
+static BOpenHashTable<HashTableDefinition> g_cache;
+static ino_t g_nextVnid = 1;
+mutex g_vnodeCacheLock;
+
+
+extern "C" status_t vboxsf_new_vnode(PVBSFMAP map, PSHFLSTRING path, PSHFLSTRING name, vboxsf_vnode** p)
+{
+ vboxsf_vnode* vn = (vboxsf_vnode*)malloc(sizeof(vboxsf_vnode));
+ if (vn == NULL)
+ return B_NO_MEMORY;
+
+ dprintf("creating new vnode at %p with path=%p (%s)\n", vn, path->String.utf8, path->String.utf8);
+ vn->map = map;
+ vn->path = path;
+ if (name)
+ vn->name = name;
+ else
+ {
+ const char* cname = strrchr((char*)path->String.utf8, '/');
+ if (!cname)
+ vn->name = path; // no slash, assume this *is* the filename
+ else
+ vn->name = make_shflstring(cname);
+ }
+
+ if (mutex_lock(&g_vnodeCacheLock) < B_OK)
+ {
+ free(vn);
+ return B_ERROR;
+ }
+
+ vn->vnode = g_nextVnid++;
+ *p = vn;
+ dprintf("vboxsf: allocated %p (path=%p name=%p)\n", vn, vn->path, vn->name);
+ status_t rv = g_cache.Insert(vn);
+
+ mutex_unlock(&g_vnodeCacheLock);
+
+ return rv;
+}
+
+
+extern "C" status_t vboxsf_get_vnode(fs_volume* volume, ino_t id, fs_vnode* vnode,
+ int* _type, uint32* _flags, bool reenter)
+{
+ vboxsf_vnode* vn = g_cache.Lookup(id);
+ if (vn)
+ {
+ vnode->private_node = vn;
+ return B_OK;
+ }
+ return B_ERROR;
+}
+
+
+extern "C" status_t vboxsf_put_vnode(fs_volume* volume, fs_vnode* vnode, bool reenter)
+{
+ g_cache.Remove((vboxsf_vnode*)vnode->private_node);
+}
+
diff --git a/src/VBox/Additions/haiku/VBoxMouse/Makefile.kmk b/src/VBox/Additions/haiku/VBoxMouse/Makefile.kmk
new file mode 100644
index 00000000..f8089e11
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxMouse/Makefile.kmk
@@ -0,0 +1,90 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxMouse, Haiku Additions.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# This code is based on:
+#
+# VirtualBox Guest Additions for Haiku.
+# Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+# François Revol <revol@free.fr>
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# @todo split the mouse code to communicate with VBoxMouse/VBoxService
+# to allow building with gcc2.
+# R1 will need gcc2-built input_server add-ons.
+
+PROGRAMS += VBoxMouse
+VBoxMouse_TEMPLATE = VBoxGuestR3Exe
+VBoxMouse_DEFS = VBOX_WITH_HGCM LOG_TO_BACKDOOR
+VBoxMouse_DEFS += LOG_ENABLED
+VBoxMouse_INCS = ../include
+VBoxMouse_SOURCES = \
+ VBoxMouse.cpp
+
+VBoxMouse_LIBS = \
+ be \
+ device \
+ $(VBOX_LIB_IPRT_GUEST_R3) \
+ $(VBOX_LIB_VBGL_R3) \
+ /system/servers/input_server
+
+PROGRAMS += VBoxMouseFilter
+VBoxMouseFilter_TEMPLATE = VBoxGuestR3Exe
+VBoxMouseFilter_DEFS = VBOX_WITH_HGCM LOG_TO_BACKDOOR
+VBoxMouseFilter_DEFS += LOG_ENABLED
+VBoxMouseFilter_INCS = ../include
+VBoxMouseFilter_SOURCES = \
+ VBoxMouseFilter.cpp
+
+VBoxMouseFilter_LIBS = $(VBoxMouse_LIBS)
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.cpp b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.cpp
new file mode 100644
index 00000000..50261a45
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.cpp
@@ -0,0 +1,318 @@
+/* $Id: VBoxMouse.cpp $ */
+/** @file
+ * VBoxMouse; input_server add-on - Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <Clipboard.h>
+#include <Debug.h>
+#include <Message.h>
+#include <String.h>
+
+#include "VBoxMouse.h"
+#include <VBox/VBoxGuest.h> /** @todo use the VbglR3 interface! */
+#include <VBox/VBoxGuestLib.h>
+#include <VBoxGuestInternal.h>
+#include <VBox/VMMDev.h>
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+
+/* Export as global symbol with C linkage, RTDECL is necessary. */
+RTDECL(BInputServerDevice *)
+instantiate_input_device()
+{
+ return new VBoxMouse();
+}
+
+
+static inline int vboxMouseAcquire()
+{
+ uint32_t fFeatures = 0;
+ int rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3SetMouseStatus(fFeatures | VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE | VMMDEV_MOUSE_NEW_PROTOCOL);
+ if (RT_FAILURE(rc))
+ LogRel(("VbglR3SetMouseStatus failed. rc=%d\n", rc));
+ }
+ else
+ LogRel(("VbglR3GetMouseStatus failed. rc=%d\n", rc));
+ return rc;
+}
+
+
+static inline int vboxMouseRelease()
+{
+ uint32_t fFeatures = 0;
+ int rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3SetMouseStatus(fFeatures & ~VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE & ~VMMDEV_MOUSE_NEW_PROTOCOL);
+ if (RT_FAILURE(rc))
+ LogRel(("VbglR3SetMouseStatus failed. rc=%d\n", rc));
+ }
+ else
+ LogRel(("VbglR3GetMouseStatus failed. rc=%d\n", rc));
+ return rc;
+}
+
+
+VBoxMouse::VBoxMouse()
+ : BInputServerDevice(),
+ fDriverFD(-1),
+ fServiceThreadID(-1),
+ fExiting(false)
+{
+}
+
+
+VBoxMouse::~VBoxMouse()
+{
+}
+
+
+status_t VBoxMouse::InitCheck()
+{
+ int rc = VbglR3Init();
+ if (!RT_SUCCESS(rc))
+ return ENXIO;
+
+ input_device_ref device = { (char *)"VBoxMouse", B_POINTING_DEVICE, (void *)this };
+ input_device_ref *deviceList[2] = { &device, NULL };
+ RegisterDevices(deviceList);
+
+ return B_OK;
+}
+
+
+status_t VBoxMouse::SystemShuttingDown()
+{
+ VbglR3Term();
+
+ return B_OK;
+}
+
+
+status_t VBoxMouse::Start(const char *device, void *cookie)
+{
+#if 0
+ status_t err;
+ int rc;
+ uint32_t fFeatures = 0;
+ Log(("VBoxMouse::%s()\n", __FUNCTION__));
+
+ rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ rc = VbglR3SetMouseStatus(fFeatures
+ | VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
+ | VMMDEV_MOUSE_NEW_PROTOCOL);
+ if (!RT_SUCCESS(rc))
+ {
+ LogRel(("VBoxMouse: Error switching guest mouse into absolute mode: %d\n", rc));
+ return B_DEVICE_NOT_FOUND;
+ }
+
+ err = fServiceThreadID = spawn_thread(_ServiceThreadNub,
+ "VBoxMouse", B_NORMAL_PRIORITY, this);
+ if (err >= B_OK)
+ {
+ resume_thread(fServiceThreadID);
+ return B_OK;
+ }
+ else
+ LogRel(("VBoxMouse: Error starting service thread: 0x%08lx\n",
+ err));
+
+ // release the mouse
+ rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ rc = VbglR3SetMouseStatus(fFeatures
+ & ~VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
+ & ~VMMDEV_MOUSE_NEW_PROTOCOL);
+
+ return B_ERROR;
+#endif
+
+ status_t err = B_OK;
+ int rc;
+ uint32_t fFeatures = 0;
+ LogFlowFunc(("device=%s cookie=%p\n", device, cookie));
+
+ rc = vboxMouseAcquire();
+ if (RT_SUCCESS(rc))
+ {
+ err = fServiceThreadID = spawn_thread(_ServiceThreadNub, "VBoxMouse", B_NORMAL_PRIORITY, this);
+ if (err >= B_OK)
+ {
+ resume_thread(fServiceThreadID);
+ return B_OK;
+ }
+ else
+ LogRel(("VBoxMouse::Start Error starting service thread: 0x%08lx\n", err));
+
+ vboxMouseRelease();
+ err = B_ERROR;
+ }
+ else
+ {
+ LogRel(("VBoxMouse::Start vboxMouseAcquire failed. rc=%d\n", rc));
+ err = B_DEVICE_NOT_FOUND;
+ }
+
+ return err;
+}
+
+
+status_t VBoxMouse::Stop(const char *device, void *cookie)
+{
+ status_t status;
+ int rc;
+ uint32_t fFeatures = 0;
+ Log(("VBoxMouse::%s()\n", __FUNCTION__));
+
+ fExiting = true;
+
+ vboxMouseRelease();
+
+ close(fDriverFD);
+ fDriverFD = -1;
+ //XXX WTF ?
+ suspend_thread(fServiceThreadID);
+ resume_thread(fServiceThreadID);
+ wait_for_thread(fServiceThreadID, &status);
+ fServiceThreadID = -1;
+ fExiting = false;
+ return B_OK;
+}
+
+
+status_t VBoxMouse::Control(const char *device, void *cookie, uint32 code, BMessage *message)
+{
+ switch (code)
+ {
+ case B_MOUSE_SPEED_CHANGED:
+ case B_CLICK_SPEED_CHANGED:
+ case B_MOUSE_ACCELERATION_CHANGED:
+ default:
+ return BInputServerDevice::Control(device, cookie, code, message);
+ }
+ return B_OK;
+}
+
+
+status_t VBoxMouse::_ServiceThreadNub(void *_this)
+{
+ VBoxMouse *service = (VBoxMouse *)_this;
+ return service->_ServiceThread();
+}
+
+
+status_t VBoxMouse::_ServiceThread()
+{
+ Log(("VBoxMouse::%s()\n", __FUNCTION__));
+
+ fDriverFD = open(VBOXGUEST_DEVICE_NAME, O_RDWR);
+ if (fDriverFD < 0)
+ return ENXIO;
+
+ /* The thread waits for incoming messages from the host. */
+ while (!fExiting)
+ {
+ uint32_t cx, cy, fFeatures;
+ int rc;
+
+ fd_set readSet, writeSet, errorSet;
+ FD_ZERO(&readSet);
+ FD_ZERO(&writeSet);
+ FD_ZERO(&errorSet);
+ FD_SET(fDriverFD, &readSet);
+ if (fDriverFD < 0)
+ break;
+ rc = select(fDriverFD + 1, &readSet, &writeSet, &errorSet, NULL);
+ if (rc < 0)
+ {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ break;
+ }
+
+ rc = VbglR3GetMouseStatus(&fFeatures, &cx, &cy);
+ if ( RT_SUCCESS(rc)
+ && (fFeatures & VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE))
+ {
+ float x = cx * 1.0 / 65535;
+ float y = cy * 1.0 / 65535;
+
+ _debugPrintf("VBoxMouse: at %d,%d %f,%f\n", cx, cy, x, y);
+
+ /* Send absolute movement */
+ bigtime_t now = system_time();
+ BMessage *event = new BMessage(B_MOUSE_MOVED);
+ event->AddInt64("when", now);
+ event->AddFloat("x", x);
+ event->AddFloat("y", y);
+ event->AddFloat("be:tablet_x", x);
+ event->AddFloat("be:tablet_y", y);
+ //event->PrintToStream();
+ EnqueueMessage(event);
+
+ //LogRelFlow(("processed host event rc = %d\n", rc));
+ }
+ }
+ return 0;
+}
+
diff --git a/src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.h b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.h
new file mode 100644
index 00000000..69a62b8a
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouse.h
@@ -0,0 +1,91 @@
+/* $Id: VBoxMouse.h $ */
+/** @file
+ * VBoxMouse; input_server add-on - Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxMouse_VBoxMouse_h
+#define GA_INCLUDED_SRC_haiku_VBoxMouse_VBoxMouse_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <InputServerDevice.h>
+
+extern "C" _EXPORT BInputServerDevice* instantiate_input_device();
+
+class VBoxMouse : public BInputServerDevice
+{
+ public:
+ VBoxMouse();
+ virtual ~VBoxMouse();
+
+ virtual status_t InitCheck();
+ virtual status_t SystemShuttingDown();
+
+ virtual status_t Start(const char *device, void *cookie);
+ virtual status_t Stop(const char *device, void *cookie);
+ virtual status_t Control(const char *device, void *cookie, uint32 code, BMessage *message);
+
+ private:
+
+ static status_t _ServiceThreadNub(void *_this);
+ status_t _ServiceThread();
+
+ int fDriverFD;
+ thread_id fServiceThreadID;
+ bool fExiting;
+};
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxMouse_VBoxMouse_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.cpp b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.cpp
new file mode 100644
index 00000000..5f0158ba
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.cpp
@@ -0,0 +1,117 @@
+/* $Id: VBoxMouseFilter.cpp $ */
+/** @file
+ * VBoxMouse; input_server filter - Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <Clipboard.h>
+#include <Debug.h>
+#include <Message.h>
+#include <String.h>
+
+#include "VBoxMouseFilter.h"
+#include <VBox/VBoxGuestLib.h>
+#include <VBoxGuestInternal.h>
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+
+/** @todo can this be merged with VBoxMouse? */
+
+RTDECL(BInputServerFilter *)
+instantiate_input_filter()
+{
+ return new VBoxMouseFilter();
+}
+
+VBoxMouseFilter::VBoxMouseFilter()
+ : BInputServerFilter(),
+ fDriverFD(-1),
+ fServiceThreadID(-1),
+ fExiting(false),
+ fCurrentButtons(0)
+{
+}
+
+
+VBoxMouseFilter::~VBoxMouseFilter()
+{
+}
+
+
+filter_result VBoxMouseFilter::Filter(BMessage *message, BList *outList)
+{
+ switch (message->what)
+ {
+ case B_MOUSE_UP:
+ case B_MOUSE_DOWN:
+ {
+ printf("click|release\n");
+ message->FindInt32("buttons", &fCurrentButtons);
+ /** @todo r=ramshankar this looks wrong, no 'break' here? */
+ }
+
+ case B_MOUSE_MOVED:
+ {
+ printf("mouse moved\n");
+ message->ReplaceInt32("buttons", fCurrentButtons);
+ /** @todo r=ramshankar: 'break' or explicit comment please. */
+ }
+ }
+
+ return B_DISPATCH_MESSAGE;
+}
+
diff --git a/src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.h b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.h
new file mode 100644
index 00000000..64996a2f
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxMouse/VBoxMouseFilter.h
@@ -0,0 +1,87 @@
+/* $Id: VBoxMouseFilter.h $ */
+/** @file
+ * VBoxMouse; input_server filter - Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxMouse_VBoxMouseFilter_h
+#define GA_INCLUDED_SRC_haiku_VBoxMouse_VBoxMouseFilter_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <InputServerFilter.h>
+
+extern "C" _EXPORT BInputServerFilter* instantiate_input_filter();
+
+class VBoxMouseFilter : public BInputServerFilter
+{
+ public:
+ VBoxMouseFilter();
+ virtual ~VBoxMouseFilter();
+
+ virtual filter_result Filter(BMessage* message, BList* outList);
+
+ private:
+ static status_t _ServiceThreadNub(void *_this);
+ status_t _ServiceThread();
+
+ int fDriverFD;
+ thread_id fServiceThreadID;
+ bool fExiting;
+ bool fEnabled;
+ int32 fCurrentButtons;
+};
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxMouse_VBoxMouseFilter_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/Makefile.kmk b/src/VBox/Additions/haiku/VBoxTray/Makefile.kmk
new file mode 100644
index 00000000..96d6c52e
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/Makefile.kmk
@@ -0,0 +1,128 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxTray, Haiku Guest Additions.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# This code is based on:
+#
+# VirtualBox Guest Additions for Haiku.
+# Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+# François Revol <revol@free.fr>
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# @todo split the tray code,
+# single bin will cause problems loading gcc4 binary from a gcc2-built Deskbar!
+
+PROGRAMS += VBoxTray
+VBoxTray_TEMPLATE = VBoxGuestR3Exe
+VBoxTray_DEFS = VBOX_WITH_HGCM LOG_TO_BACKDOOR
+VBoxTray_DEFS += LOG_ENABLED
+VBoxTray_INCS = ../include
+VBoxTray_SOURCES = \
+ VBoxClipboard.cpp \
+ VBoxDisplay.cpp \
+ VBoxGuestApplication.cpp \
+ VBoxGuestDeskbarView.cpp
+
+VBoxTray_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp
+
+VBoxTray_LIBS = \
+ be translation \
+ $(VBOX_LIB_VBGL_R3) \
+ $(VBOX_LIB_IPRT_GUEST_R3) \
+ $(VBOX_LIB_VBGL_R3)
+
+VBoxTray_RSRCS += $(VBoxTray_0_OUTDIR)/VBoxTray.rsrc
+VBoxTray_DEPS += $(VBoxTray_0_OUTDIR)/VBoxTray.rsrc
+VBoxTray_CLEAN += $(VBoxTray_0_OUTDIR)/VBoxTray.rsrc
+
+# VBoxGuestApplication.cpp uses VBOX_SVN_REV.
+VBoxGuestApplication.cpp_DEFS += VBOX_SVN_REV=$(VBOX_SVN_REV)
+VBoxGuestApplication.cpp_DEPS = $(VBOX_SVN_REV_KMK)
+VBoxGuestDeskbarView.cpp_DEFS += VBOX_SVN_REV=$(VBOX_SVN_REV)
+VBoxGuestDeskbarView.cpp_DEPS = $(VBOX_SVN_REV_KMK)
+
+## The icon location is configurable.
+VBoxTray.rdef_INCS = $(VBoxTray_0_OUTDIR)
+VBoxTray.rdef_DEFS += VBOX_SVN_REV=$(VBOX_SVN_REV) \
+ VBOX_HAIKU_DESKBAR_ICON_PNG=\"$(VBOX_BRAND_GUI_VBOX_16PX_PNG)\"
+VBoxTray.rdef_DEPS = $(VBOX_SVN_REV_KMK)
+
+VBoxTray.rsrc_DEPS = VBoxTray.rdef
+VBoxTray.rsrc_CLEAN = VBoxTray.rdef
+
+
+
+#XXX: cleanup!
+#XXX: handle deps correctly
+#XXX: -I / is due to a bug in rc with absolute paths
+## Resource file.
+$$(VBoxTray_0_OUTDIR)/VBoxTray.rsrc: $$(VBoxTray_DEFPATH)/VBoxTray.rdef $$(VBoxTray_DEFPATH)/Makefile.kmk | $$(dir $$@)
+ $(call MSG_TOOL,$(VBOX_HAIKU_RCTOOL),HaikuResources,$<,$@)
+ $(QUIET)cat $< | gcc -E -I $(dir $<) -I $(dir $<)/../include $(foreach name, $(INCS), -I $(name)) $(foreach dname, $(VBoxTray.rdef_DEFS), -D$(dname)) - | grep -v '^#' | $(VBOX_HAIKU_RCTOOL) -I / -I $(dir $<) -I $(dir $<)/../include -o "$@" -
+
+
+# rc -I $(VBoxTray_DEFPATH)/../include -o $@ $<
+# $(RM) -f $@
+# $(APPEND) $@ 'IDI_VIRTUALBOX ICON DISCARDABLE "$(subst /,\\,$(VBOX_WINDOWS_ADDITIONS_ICON_FILE))"'
+
+## The icon location is configurable.
+#VBoxTray.rc_INCS = $(VBoxTray_0_OUTDIR)
+#VBoxTray.rc_DEPS = $(VBoxTray_0_OUTDIR)/VBoxTray-icon.rc
+#VBoxTray.rc_CLEAN = $(VBoxTray_0_OUTDIR)/VBoxTray-icon.rc
+
+## Icon include file.
+#$$(VBoxTray_0_OUTDIR)/VBoxTray-icon.rc: $(VBOX_WINDOWS_ADDITIONS_ICON_FILE) $$(VBoxTray_DEFPATH)/Makefile.kmk | $$(dir $$@)
+# $(RM) -f $@
+# $(APPEND) $@ 'IDI_VIRTUALBOX ICON DISCARDABLE "$(subst /,\\,$(VBOX_WINDOWS_ADDITIONS_ICON_FILE))"'
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.cpp b/src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.cpp
new file mode 100644
index 00000000..b500b95d
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.cpp
@@ -0,0 +1,468 @@
+/* $Id: VBoxClipboard.cpp $ */
+/** @file
+ * VBoxClipboard; Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * Fran�ois Revol <revol@free.fr>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <new>
+#include <Bitmap.h>
+#include <BitmapStream.h>
+#include <Clipboard.h>
+#include <DataIO.h>
+#include <Message.h>
+#include <TranslationUtils.h>
+#include <TranslatorFormats.h>
+#include <TranslatorRoster.h>
+#include <String.h>
+
+#include "VBoxGuestApplication.h"
+#include "VBoxClipboard.h"
+#include <VBoxGuestInternal.h>
+
+#include <iprt/mem.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/log.h>
+
+/** @todo r=ramshankar: this hack should go eventually. */
+#ifdef DEBUG_ramshankar
+# undef Log
+# define Log(x) printf x
+# undef LogRel
+# define LogRel(x) printf x
+# undef LogRelFlowFunc
+# define LogRelFlowFunc(x) printf x
+#endif
+
+
+VBoxShClService::VBoxShClService()
+ : BHandler("VBoxShClService"),
+ fClientId(-1),
+ fServiceThreadID(-1),
+ fExiting(false)
+{
+}
+
+
+VBoxShClService::~VBoxShClService()
+{
+}
+
+
+status_t VBoxShClService::Connect()
+{
+ status_t err;
+ LogFlowFunc(("Connect\n"));
+
+ int rc = VbglR3ClipboardConnect(&fClientId);
+ if (RT_SUCCESS(rc))
+ {
+ err = fServiceThreadID = spawn_thread(_ServiceThreadNub, "VBoxShClService", B_NORMAL_PRIORITY, this);
+ if (err >= B_OK)
+ {
+ resume_thread(fServiceThreadID);
+ err = be_clipboard->StartWatching(BMessenger(this));
+ LogFlow(("be_clipboard->StartWatching: %ld\n", err));
+ if (err == B_OK)
+ return B_OK;
+ else
+ LogRel(("VBoxShClService: Error watching the system clipboard: %ld\n", err));
+ }
+ else
+ LogRel(("VBoxShClService: Error starting service thread: %ld\n", err));
+
+ //rc = RTErrConvertFromErrno(err);
+ VbglR3ClipboardDisconnect(fClientId);
+ }
+ else
+ LogRel(("VBoxShClService: Error starting service thread: %d\n", rc));
+ return B_ERROR;
+}
+
+
+status_t VBoxShClService::Disconnect()
+{
+ status_t status;
+
+ be_clipboard->StopWatching(BMessenger(this));
+
+ fExiting = true;
+
+ VbglR3ClipboardDisconnect(fClientId);
+
+ wait_for_thread(fServiceThreadID, &status);
+ return B_OK;
+}
+
+
+void VBoxShClService::MessageReceived(BMessage *message)
+{
+ uint32_t formats = 0;
+ message->PrintToStream();
+ switch (message->what)
+ {
+ case VBOX_GUEST_CLIPBOARD_HOST_MSG_FORMATS:
+ {
+ int rc;
+ uint32_t cb;
+ void *pv;
+ bool commit = false;
+
+ if (message->FindInt32("Formats", (int32 *)&formats) != B_OK)
+ break;
+
+ if (!formats)
+ break;
+
+ if (!be_clipboard->Lock())
+ break;
+
+ be_clipboard->Clear();
+ BMessage *clip = be_clipboard->Data();
+ if (!clip)
+ {
+ be_clipboard->Unlock();
+ break;
+ }
+
+ if (formats & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ pv = _VBoxReadHostClipboard(VBOX_SHCL_FMT_UNICODETEXT, &cb);
+ if (pv)
+ {
+ char *text;
+ rc = RTUtf16ToUtf8((PCRTUTF16)pv, &text);
+ if (RT_SUCCESS(rc))
+ {
+ BString str(text);
+ /** @todo user vboxClipboardUtf16WinToLin() */
+ // convert Windows CRLF to LF
+ str.ReplaceAll("\r\n", "\n");
+ // don't include the \0
+ clip->AddData("text/plain", B_MIME_TYPE, str.String(), str.Length());
+ RTStrFree(text);
+ commit = true;
+ }
+ free(pv);
+ }
+ }
+
+ if (formats & VBOX_SHCL_FMT_BITMAP)
+ {
+ pv = _VBoxReadHostClipboard(VBOX_SHCL_FMT_BITMAP, &cb);
+ if (pv)
+ {
+ void *pBmp = NULL;
+ size_t cbBmp = 0;
+ rc = ShClDibToBmp(pv, cb, &pBmp, &cbBmp);
+ if (RT_SUCCESS(rc))
+ {
+ BMemoryIO mio(pBmp, cbBmp);
+ BBitmap *bitmap = BTranslationUtils::GetBitmap(&mio);
+ if (bitmap)
+ {
+ BMessage bitmapArchive;
+
+ /** @todo r=ramshankar: split this into functions with error checking as
+ * neccessary. */
+ if ( bitmap->IsValid()
+ && bitmap->Archive(&bitmapArchive) == B_OK
+ && clip->AddMessage("image/bitmap", &bitmapArchive) == B_OK)
+ {
+ commit = true;
+ }
+ delete bitmap;
+ }
+ RTMemFree(pBmp);
+ }
+ free(pv);
+ }
+ }
+
+ /*
+ * Make sure we don't bounce this data back to the host, it's impolite. It can also
+ * be used as a hint to applications probably.
+ */
+ clip->AddBool("FromVirtualBoxHost", true);
+ if (commit)
+ be_clipboard->Commit();
+ be_clipboard->Unlock();
+ break;
+ }
+
+ case VBOX_GUEST_CLIPBOARD_HOST_MSG_READ_DATA:
+ {
+ int rc;
+
+ if (message->FindInt32("Formats", (int32 *)&formats) != B_OK)
+ break;
+
+ if (!formats)
+ break;
+ if (!be_clipboard->Lock())
+ break;
+
+ BMessage *clip = be_clipboard->Data();
+ if (!clip)
+ {
+ be_clipboard->Unlock();
+ break;
+ }
+ clip->PrintToStream();
+
+ if (formats & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ const char *text;
+ int32 textLen;
+ if (clip->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &textLen) == B_OK)
+ {
+ // usually doesn't include the \0 so be safe
+ BString str(text, textLen);
+ // convert from LF to Windows CRLF
+ str.ReplaceAll("\n", "\r\n");
+ PRTUTF16 pwsz;
+ rc = RTStrToUtf16(str.String(), &pwsz);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cb = (RTUtf16Len(pwsz) + 1) * sizeof(RTUTF16);
+
+ rc = VbglR3ClipboardWriteData(fClientId, VBOX_SHCL_FMT_UNICODETEXT, pwsz, cb);
+ //printf("VbglR3ClipboardWriteData: %d\n", rc);
+ RTUtf16Free(pwsz);
+ }
+ }
+ }
+ else if (formats & VBOX_SHCL_FMT_BITMAP)
+ {
+ BMessage archivedBitmap;
+ if (clip->FindMessage("image/bitmap", &archivedBitmap) == B_OK ||
+ clip->FindMessage("image/x-be-bitmap", &archivedBitmap) == B_OK)
+ {
+ BBitmap *bitmap = new(std::nothrow) BBitmap(&archivedBitmap);
+ if (bitmap)
+ {
+ // Don't delete bitmap, BBitmapStream will.
+ BBitmapStream stream(bitmap);
+ BTranslatorRoster *roster = BTranslatorRoster::Default();
+ if (roster && bitmap->IsValid())
+ {
+ BMallocIO bmpStream;
+ if (roster->Translate(&stream, NULL, NULL, &bmpStream, B_BMP_FORMAT) == B_OK)
+ {
+ const void *pDib;
+ size_t cbDibSize;
+ /* Strip out the BM header */
+ rc = ShClBmpGetDib(bmpStream.Buffer(), bmpStream.BufferLength(), &pDib, &cbDibSize);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardWriteData(fClientId, VBOX_SHCL_FMT_BITMAP, (void *)pDib,
+ cbDibSize);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ be_clipboard->Unlock();
+ break;
+ }
+
+ case B_CLIPBOARD_CHANGED:
+ {
+ printf("B_CLIPBOARD_CHANGED\n");
+ const void *data;
+ int32 dataLen;
+ if (!be_clipboard->Lock())
+ break;
+
+ BMessage *clip = be_clipboard->Data();
+ if (!clip)
+ {
+ be_clipboard->Unlock();
+ break;
+ }
+
+ bool fromVBox;
+ if (clip->FindBool("FromVirtualBoxHost", &fromVBox) == B_OK && fromVBox)
+ {
+ // It already comes from the host, discard.
+ be_clipboard->Unlock();
+ break;
+ }
+
+ if (clip->FindData("text/plain", B_MIME_TYPE, &data, &dataLen) == B_OK)
+ formats |= VBOX_SHCL_FMT_UNICODETEXT;
+
+ if ( clip->HasMessage("image/bitmap")
+ || clip->HasMessage("image/x-be-bitmap"))
+ {
+ formats |= VBOX_SHCL_FMT_BITMAP;
+ }
+
+ be_clipboard->Unlock();
+
+ VbglR3ClipboardReportFormats(fClientId, formats);
+ break;
+ }
+
+ case B_QUIT_REQUESTED:
+ fExiting = true;
+ break;
+
+ default:
+ BHandler::MessageReceived(message);
+ }
+}
+
+
+status_t VBoxShClService::_ServiceThreadNub(void *_this)
+{
+ VBoxShClService *service = (VBoxShClService *)_this;
+ return service->_ServiceThread();
+}
+
+
+status_t VBoxShClService::_ServiceThread()
+{
+ printf("VBoxShClService::%s()\n", __FUNCTION__);
+
+ /* The thread waits for incoming messages from the host. */
+ for (;;)
+ {
+ uint32_t u32Msg;
+ uint32_t u32Formats;
+ int rc = VbglR3ClipboardGetHostMsgOld(fClientId, &u32Msg, &u32Formats);
+ if (RT_SUCCESS(rc))
+ {
+ switch (u32Msg)
+ {
+ case VBOX_SHCL_HOST_MSG_FORMATS_REPORT:
+ {
+ /*
+ * The host has announced available clipboard formats. Forward
+ * the information to the handler.
+ */
+ LogRelFlowFunc(("VBOX_SHCL_HOST_MSG_REPORT_FORMATS u32Formats=%x\n", u32Formats));
+ BMessage msg(VBOX_GUEST_CLIPBOARD_HOST_MSG_FORMATS);
+ msg.AddInt32("Formats", (uint32)u32Formats);
+ Looper()->PostMessage(&msg, this);
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_READ_DATA:
+ {
+ /* The host needs data in the specified format. */
+ LogRelFlowFunc(("VBOX_SHCL_HOST_MSG_READ_DATA u32Formats=%x\n", u32Formats));
+ BMessage msg(VBOX_GUEST_CLIPBOARD_HOST_MSG_READ_DATA);
+ msg.AddInt32("Formats", (uint32)u32Formats);
+ Looper()->PostMessage(&msg, this);
+ break;
+ }
+
+ case VBOX_SHCL_HOST_MSG_QUIT:
+ {
+ /* The host is terminating. */
+ LogRelFlowFunc(("VBOX_SHCL_HOST_MSG_QUIT\n"));
+ fExiting = true;
+ return VERR_INTERRUPTED;
+ }
+
+ default:
+ Log(("VBoxShClService::%s: Unsupported message from host! Message = %u\n", __FUNCTION__, u32Msg));
+ }
+ }
+ else
+ fExiting = true;
+
+ LogRelFlow(("processed host event rc = %d\n", rc));
+
+ if (fExiting)
+ break;
+ }
+ return 0;
+}
+
+
+void* VBoxShClService::_VBoxReadHostClipboard(uint32_t format, uint32_t *pcb)
+{
+ uint32_t cb = 1024;
+ void *pv;
+ int rc;
+
+ pv = malloc(cb);
+ if (pv == NULL)
+ return NULL;
+
+ rc = VbglR3ClipboardReadData(fClientId, format, pv, cb, pcb);
+ if (RT_SUCCESS(rc) && (rc != VINF_BUFFER_OVERFLOW))
+ return pv;
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ free(pv);
+ cb = *pcb;
+ pv = malloc(cb);
+ if (pv == NULL)
+ return NULL;
+
+ rc = VbglR3ClipboardReadData(fClientId, format, pv, cb, pcb);
+ if (RT_SUCCESS(rc) && (rc != VINF_BUFFER_OVERFLOW))
+ return pv;
+
+ free(pv);
+ }
+ return NULL;
+}
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.h b/src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.h
new file mode 100644
index 00000000..ad4cabbd
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxClipboard.h
@@ -0,0 +1,88 @@
+/* $Id: VBoxClipboard.h $ */
+/** @file
+ * VBoxClipboard, Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxTray_VBoxClipboard_h
+#define GA_INCLUDED_SRC_haiku_VBoxTray_VBoxClipboard_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <Handler.h>
+
+class VBoxShClService : public BHandler
+{
+ public:
+ VBoxShClService();
+ virtual ~VBoxShClService();
+
+ virtual status_t Connect();
+ virtual status_t Disconnect();
+
+ virtual void MessageReceived(BMessage *message);
+
+ private:
+ static status_t _ServiceThreadNub(void *_this);
+ status_t _ServiceThread();
+
+ void* _VBoxReadHostClipboard(uint32_t format, uint32_t *pcb);
+
+ uint32_t fClientId;
+ thread_id fServiceThreadID;
+ bool fExiting;
+};
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxTray_VBoxClipboard_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.cpp b/src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.cpp
new file mode 100644
index 00000000..5eb0a7d5
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.cpp
@@ -0,0 +1,177 @@
+/* $Id: VBoxDisplay.cpp $ */
+/** @file
+ * VBoxDisplayService, Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <new>
+#include <DataIO.h>
+#include <Message.h>
+#include <TranslationUtils.h>
+#include <TranslatorFormats.h>
+#include <TranslatorRoster.h>
+#include <String.h>
+
+#include "VBoxGuestApplication.h"
+#include "VBoxDisplay.h"
+#include <VBoxGuestInternal.h>
+#include "../VBoxVideo/common/VBoxVideo_common.h"
+
+#include <iprt/mem.h>
+#include <VBox/log.h>
+
+#ifdef DEBUG_ramshankar
+# undef Log
+# define Log(x) printf x
+# undef LogRel
+# define LogRel(x) printf x
+# undef LogRelFlowFunc
+# define LogRelFlowFunc(x) printf x
+#endif
+
+VBoxDisplayService::VBoxDisplayService()
+ : BHandler("VBoxDisplayService"),
+ fClientId(-1),
+ fServiceThreadID(-1),
+ fExiting(false),
+ fScreen(B_MAIN_SCREEN_ID)
+{
+}
+
+
+VBoxDisplayService::~VBoxDisplayService()
+{
+}
+
+
+void VBoxDisplayService::Start()
+{
+ status_t err;
+ err = fServiceThreadID = spawn_thread(_ServiceThreadNub, "VBoxDisplayService", B_NORMAL_PRIORITY, this);
+ if (err >= B_OK)
+ resume_thread(fServiceThreadID);
+ else
+ LogRel(("VBoxDisplayService: Error starting service thread: %s\n", strerror(err)));
+}
+
+
+void VBoxDisplayService::MessageReceived(BMessage *message)
+{
+ if (message->what == B_QUIT_REQUESTED)
+ fExiting = true;
+ else
+ BHandler::MessageReceived(message);
+}
+
+
+status_t VBoxDisplayService::_ServiceThreadNub(void *_this)
+{
+ VBoxDisplayService *service = (VBoxDisplayService *)_this;
+ return service->_ServiceThread();
+}
+
+
+status_t VBoxDisplayService::_ServiceThread()
+{
+ LogFlow(("VBoxDisplayService::_ServiceThread"));
+
+ VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
+ VbglR3SetGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0);
+ for (;;)
+ {
+ uint32_t events;
+ int rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 5000, &events);
+ if ( rc == VERR_TIMEOUT
+ || rc == VERR_INTERRUPTED)
+ continue;
+
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cx, cy, cBits, iDisplay;
+ int rc2 = VbglR3GetDisplayChangeRequest(&cx, &cy, &cBits, &iDisplay, NULL, NULL, NULL, NULL, true);
+ LogFlow(("rc2=%d screen %d size changed (%d, %d, %d)\n", rc2, iDisplay, cx, cy, cBits));
+
+ if (RT_SUCCESS(rc2))
+ {
+ display_mode mode;
+ fScreen.GetMode(&mode);
+ if (cBits == 0)
+ cBits = get_depth_for_color_space(mode.space);
+
+ mode.timing.h_display = cx;
+ mode.timing.v_display = cy;
+ mode.space = get_color_space_for_depth(cBits);
+ mode.virtual_width = cx;
+ mode.virtual_height = cy;
+
+ /*= {
+ {0, cx, 0, 0, cBits * cx / 8, cy, 0, 0, cBits * cy / 8, 0},
+ get_color_space_for_depth(cBits),
+ cx, cy, 0, 0, 0
+ };*/
+
+ fScreen.SetMode(&mode, false);
+ }
+ }
+ else
+ fExiting = true;
+
+ LogFlow(("processed host event rc = %d\n", rc));
+ if (fExiting)
+ break;
+ }
+ return 0;
+}
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.h b/src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.h
new file mode 100644
index 00000000..434cca51
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxDisplay.h
@@ -0,0 +1,86 @@
+/* $Id: VBoxDisplay.h $ */
+/** @file
+ * VBoxDisplayService, Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxTray_VBoxDisplay_h
+#define GA_INCLUDED_SRC_haiku_VBoxTray_VBoxDisplay_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <Handler.h>
+#include <Screen.h>
+
+class VBoxDisplayService : public BHandler
+{
+ public:
+ VBoxDisplayService();
+ virtual ~VBoxDisplayService();
+
+ void Start();
+ virtual void MessageReceived(BMessage *message);
+
+ private:
+ static status_t _ServiceThreadNub(void *_this);
+ status_t _ServiceThread();
+
+ uint32_t fClientId;
+ thread_id fServiceThreadID;
+ volatile bool fExiting;
+ BScreen fScreen;
+};
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxTray_VBoxDisplay_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.cpp b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.cpp
new file mode 100644
index 00000000..c2c26762
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.cpp
@@ -0,0 +1,101 @@
+/* $Id: VBoxGuestApplication.cpp $ */
+/** @file
+ * VBoxGuestApplication, Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+
+#include <errno.h>
+#include <Alert.h>
+#include <Debug.h>
+#include <Invoker.h>
+#include <String.h>
+
+#include "VBoxClipboard.h"
+#include "VBoxGuestApplication.h"
+#include "VBoxGuestDeskbarView.h"
+
+VBoxGuestApplication::VBoxGuestApplication()
+ : BApplication(VBOX_GUEST_APP_SIG)
+{
+}
+
+
+VBoxGuestApplication::~VBoxGuestApplication()
+{
+}
+
+
+void VBoxGuestApplication::ReadyToRun()
+{
+ status_t err;
+
+ err = VBoxGuestDeskbarView::AddToDeskbar();
+ LogFlow(("VBoxGuestDeskbarView::ReadyToRun: AddToDeskbar returned 0x%08lx\n", err));
+ exit(0);
+}
+
+
+int main(int argc, const char **argv)
+{
+ new VBoxGuestApplication();
+ be_app->Run();
+ delete be_app;
+ return 0;
+}
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.h b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.h
new file mode 100644
index 00000000..d7996740
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestApplication.h
@@ -0,0 +1,89 @@
+/* $Id: VBoxGuestApplication.h $ */
+/** @file
+ * VBoxGuestApplication, Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxTray_VBoxGuestApplication_h
+#define GA_INCLUDED_SRC_haiku_VBoxTray_VBoxGuestApplication_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** @todo r=ramshankar; why are we including all the headers here!?? Do it in
+ * the .cpp please. */
+#include <Application.h>
+
+#include <iprt/initterm.h>
+#include <iprt/string.h>
+
+#include <VBox/version.h>
+#include <VBox/log.h>
+#include <VBox/VBoxGuest.h> /** @todo use the VbglR3 interface! */
+#include <VBox/VBoxGuestLib.h>
+
+#include <VBoxGuestInternal.h>
+
+class VBoxShClService;
+
+class VBoxGuestApplication : public BApplication
+{
+ public:
+ VBoxGuestApplication();
+ virtual ~VBoxGuestApplication();
+
+ virtual void ReadyToRun();
+};
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxTray_VBoxGuestApplication_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.cpp b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.cpp
new file mode 100644
index 00000000..aaba2392
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.cpp
@@ -0,0 +1,297 @@
+/* $Id: VBoxGuestDeskbarView.cpp $ */
+/** @file
+ * VBoxGuestDeskbarView, Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <errno.h>
+#include <Alert.h>
+#include <Roster.h>
+#include <Debug.h>
+#include <Deskbar.h>
+#include <File.h>
+#include <MenuItem.h>
+#include <Path.h>
+#include <PopUpMenu.h>
+#include <Resources.h>
+#include <String.h>
+#include <TranslationUtils.h>
+
+#include "VBoxGuestDeskbarView.h"
+#include "VBoxGuestApplication.h"
+
+#define VIEWNAME "VBoxGuestDeskbarView"
+
+static status_t
+our_image(image_info& image)
+{
+ /** @todo r=ramshankar: find a way to do this without annoying the compiler, probably uintptr_t? */
+ int32 cookie = 0;
+ while (get_next_image_info(B_CURRENT_TEAM, &cookie, &image) == B_OK)
+ {
+ if ((char *)our_image >= (char *)image.text
+ && (char *)our_image <= (char *)image.text + image.text_size)
+ return B_OK;
+ }
+
+ return B_ERROR;
+}
+
+
+VBoxGuestDeskbarView::VBoxGuestDeskbarView()
+ : BView(BRect(0, 0, 15, 15), VIEWNAME, B_FOLLOW_NONE,
+ B_WILL_DRAW | B_NAVIGABLE),
+ fIcon(NULL), fClipboardService(NULL), fDisplayService(NULL)
+{
+ _Init();
+}
+
+
+VBoxGuestDeskbarView::VBoxGuestDeskbarView(BMessage *archive)
+ : BView(archive),
+ fIcon(NULL)
+{
+ archive->PrintToStream();
+ _Init(archive);
+}
+
+
+VBoxGuestDeskbarView::~VBoxGuestDeskbarView()
+{
+ delete fIcon;
+ if (fClipboardService)
+ {
+ fClipboardService->Disconnect();
+ delete fClipboardService;
+ }
+ VbglR3Term();
+}
+
+
+BArchivable* VBoxGuestDeskbarView::Instantiate(BMessage *data)
+{
+ if (!validate_instantiation(data, VIEWNAME))
+ return NULL;
+
+ return new VBoxGuestDeskbarView(data);
+}
+
+
+status_t VBoxGuestDeskbarView::Archive(BMessage *data, bool deep) const
+{
+ status_t err;
+
+ err = BView::Archive(data, false);
+ if (err < B_OK)
+ {
+ LogRel(("VBoxGuestDeskbarView::Archive failed.rc=%08lx\n", err));
+ return err;
+ }
+ data->AddString("add_on", VBOX_GUEST_APP_SIG);
+ data->AddString("class", "VBoxGuestDeskbarView");
+ return B_OK;
+}
+
+
+void VBoxGuestDeskbarView::Draw(BRect rect)
+{
+ SetDrawingMode(B_OP_ALPHA);
+ DrawBitmap(fIcon);
+}
+
+
+void VBoxGuestDeskbarView::AttachedToWindow()
+{
+ BView::AttachedToWindow();
+ if (Parent())
+ {
+ SetViewColor(Parent()->ViewColor());
+ SetLowColor(Parent()->LowColor());
+ }
+
+ if (fClipboardService) /* Don't repeatedly crash deskbar if vboxdev not loaded */
+ {
+ Looper()->AddHandler(fClipboardService);
+ fClipboardService->Connect();
+ }
+
+ if (fDisplayService)
+ fDisplayService->Start();
+}
+
+
+void VBoxGuestDeskbarView::DetachedFromWindow()
+{
+ BMessage message(B_QUIT_REQUESTED);
+ fClipboardService->MessageReceived(&message);
+ fDisplayService->MessageReceived(&message);
+}
+
+
+void VBoxGuestDeskbarView::MouseDown(BPoint point)
+{
+ int32 buttons = B_PRIMARY_MOUSE_BUTTON;
+ if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
+ Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
+
+ BPoint where = ConvertToScreen(point);
+
+ if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0)
+ {
+ BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
+ menu->SetAsyncAutoDestruct(true);
+ menu->SetFont(be_plain_font);
+
+ menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED)));
+ menu->SetTargetForItems(this);
+
+ menu->Go(where, true, true, true);
+ }
+}
+
+
+void VBoxGuestDeskbarView::MessageReceived(BMessage *message)
+{
+ if (message->what == B_QUIT_REQUESTED)
+ RemoveFromDeskbar();
+ else
+ BHandler::MessageReceived(message);
+}
+
+
+status_t VBoxGuestDeskbarView::AddToDeskbar(bool force)
+{
+ BDeskbar deskbar;
+ status_t err;
+
+ if (force)
+ RemoveFromDeskbar();
+ else if (deskbar.HasItem(VIEWNAME))
+ return B_OK;
+
+ app_info info;
+ err = be_app->GetAppInfo(&info);
+ if (err < B_OK)
+ return err;
+
+ BPath p(&info.ref);
+ return deskbar.AddItem(&info.ref);
+}
+
+
+status_t VBoxGuestDeskbarView::RemoveFromDeskbar()
+{
+ BDeskbar deskbar;
+ return deskbar.RemoveItem(VIEWNAME);
+}
+
+
+status_t VBoxGuestDeskbarView::_Init(BMessage *archive)
+{
+ BString toolTipText;
+ toolTipText << VBOX_PRODUCT << " Guest Additions ";
+ toolTipText << VBOX_VERSION_MAJOR << "." << VBOX_VERSION_MINOR << "." << VBOX_VERSION_BUILD;
+ toolTipText << "r" << VBOX_SVN_REV;
+
+ SetToolTip(toolTipText.String());
+
+ image_info info;
+ if (our_image(info) != B_OK)
+ return B_ERROR;
+
+ BFile file(info.name, B_READ_ONLY);
+ if (file.InitCheck() < B_OK)
+ return B_ERROR;
+
+ BResources resources(&file);
+ if (resources.InitCheck() < B_OK)
+ return B_ERROR;
+
+ const void *data = NULL;
+ size_t size;
+ //data = resources.LoadResource(B_VECTOR_ICON_TYPE,
+ // kNetworkStatusNoDevice + i, &size);
+ data = resources.LoadResource('data', 400, &size);
+ if (data != NULL)
+ {
+ BMemoryIO mem(data, size);
+ fIcon = BTranslationUtils::GetBitmap(&mem);
+ }
+
+ int rc = RTR3InitDll(RTR3INIT_FLAGS_UNOBTRUSIVE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3Init();
+ if (RT_SUCCESS(rc))
+ {
+ fClipboardService = new VBoxShClService();
+ fDisplayService = new VBoxDisplayService();
+ }
+ else
+ LogRel(("VBoxGuestDeskbarView::_init VbglR3Init failed. rc=%d\n", rc));
+ }
+ else
+ LogRel(("VBoxGuestDeskbarView::_init RTR3InitDll failed. rc=%d\n", rc));
+ return RTErrConvertToErrno(rc);
+}
+
+
+RTDECL(BView*) instantiate_deskbar_item()
+{
+ return new VBoxGuestDeskbarView();
+}
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.h b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.h
new file mode 100644
index 00000000..be8cfa54
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxGuestDeskbarView.h
@@ -0,0 +1,109 @@
+/* $Id: VBoxGuestDeskbarView.h $ */
+/** @file
+ * VBoxGuestDeskbarView, Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxTray_VBoxGuestDeskbarView_h
+#define GA_INCLUDED_SRC_haiku_VBoxTray_VBoxGuestDeskbarView_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <Bitmap.h>
+#include <View.h>
+
+#include <iprt/initterm.h>
+#include <iprt/string.h>
+
+#include <VBox/version.h>
+#include <VBox/log.h>
+#include <VBox/VBoxGuest.h> /** @todo use the VbglR3 interface! */
+#include <VBox/VBoxGuestLib.h>
+
+#include <VBoxGuestInternal.h>
+#include "VBoxClipboard.h"
+#include "VBoxDisplay.h"
+
+#define REMOVE_FROM_DESKBAR_MSG 'vbqr'
+
+class VBoxGuestDeskbarView : public BView
+{
+ public:
+ VBoxGuestDeskbarView();
+ VBoxGuestDeskbarView(BMessage *archive);
+ virtual ~VBoxGuestDeskbarView();
+
+ static BArchivable* Instantiate(BMessage *data);
+ virtual status_t Archive(BMessage *data, bool deep = true) const;
+
+ void Draw(BRect rect);
+ void AttachedToWindow();
+ void DetachedFromWindow();
+
+ virtual void MouseDown(BPoint point);
+ virtual void MessageReceived(BMessage *message);
+
+ static status_t AddToDeskbar(bool force = true);
+ static status_t RemoveFromDeskbar();
+
+ private:
+ status_t _Init(BMessage *archive = NULL);
+ BBitmap *fIcon;
+
+ VBoxShClService *fClipboardService;
+ VBoxDisplayService *fDisplayService;
+};
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxTray_VBoxGuestDeskbarView_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxServiceDescriptor.h b/src/VBox/Additions/haiku/VBoxTray/VBoxServiceDescriptor.h
new file mode 100644
index 00000000..0a1a0e6e
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxServiceDescriptor.h
@@ -0,0 +1,78 @@
+/* $Id: VBoxServiceDescriptor.h $ */
+/** @file
+ * VBoxGuestServiceDescriptor, Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxTray_VBoxServiceDescriptor_h
+#define GA_INCLUDED_SRC_haiku_VBoxTray_VBoxServiceDescriptor_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <Handler.h>
+
+class VBoxShClService : public BHandler
+{
+ public:
+ VBoxShClService();
+ virtual ~VBoxShClService();
+};
+
+/* The shared clipboard service prototypes. */
+int VBoxShClInit(const VBOXSERVICEENV *pEnv, void **ppInstance, bool *pfStartThread);
+unsigned __stdcall VBoxShClThread(void *pInstance);
+void VBoxShClDestroy(const VBOXSERVICEENV *pEnv, void *pInstance);
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxTray_VBoxServiceDescriptor_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxTray/VBoxTray.rdef b/src/VBox/Additions/haiku/VBoxTray/VBoxTray.rdef
new file mode 100644
index 00000000..f2bd244c
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxTray/VBoxTray.rdef
@@ -0,0 +1,93 @@
+/* $Id: VBoxTray.rdef $ */
+/** @file
+ * VBoxApp - Resource definition file containing version info and icon, Haiku Guest Additions.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ *-----------------------------------------------------------------------------
+ *
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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 "VBoxGuestInternal.h"
+
+resource(1, "BEOS:APP_SIG") #'MIMS' VBOX_GUEST_APP_SIG;
+
+resource app_flags B_SINGLE_LAUNCH;
+
+/* This is done for all binaries with VBOX_HAIKU_XRES_SETVER_CMDS
+resource app_version {
+ major = VBOX_VERSION_MAJOR,
+ middle = VBOX_VERSION_MINOR,
+ minor = VBOX_VERSION_BUILD,
+
+ variety = B_APPV_DEVELOPMENT,
+ internal = VBOX_SVN_REV,
+
+ short_info = VBOX_PRODUCT,
+ long_info = VBOX_PRODUCT " " VBOX_VERSION_STRING " ©2009-" VBOX_C_YEAR " " VBOX_VENDOR
+};
+*/
+
+/* Maybe one day ? */
+//resource(1, "BEOS:FILE_TYPES") message {
+// "types" = "application/x-vnd.Be.URL.vboxsf"
+//};
+
+resource vector_icon {
+ $"6E636966060500020006023B55F13C5CD2BE755C3D78974A454E48FFCB000EB3"
+ $"B5FF003BFF020006023CD471BA2F923B0FF33DF6F048DA184417BB0000FBFFFF"
+ $"0000FF020006023C318B3A487CBD09B43EEB2D4B485C48176F0000AAFFFF0537"
+ $"C704016D05FF070A062231224E3E5C5248522D37260A042231224E3E5C3E3A0A"
+ $"0422313E3A522D37260A043E5C5248522D3E3A0A043E5E4E5E60485444080835"
+ $"3A393B3C4442384441493A4B444F460A04352E503750533547060A040104000A"
+ $"0001001001178400040A010101000A020102000A030103000A05020506123F45"
+ $"D10000000000003F45D1C70BA246F45D0117820004"
+};
+
+resource(400, "DeskbarShelfIcon.png") #'data' import VBOX_HAIKU_DESKBAR_ICON_PNG;
+
diff --git a/src/VBox/Additions/haiku/VBoxVideo/Makefile.kmk b/src/VBox/Additions/haiku/VBoxVideo/Makefile.kmk
new file mode 100644
index 00000000..43090fec
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxVideo/Makefile.kmk
@@ -0,0 +1,64 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for Haiku VBoxVideo.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# This code is based on:
+#
+# VirtualBox Guest Additions for Haiku.
+# Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+# François Revol <revol@free.fr>
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+include $(PATH_SUB_CURRENT)/accelerant/Makefile.kmk
+include $(PATH_SUB_CURRENT)/driver/Makefile.kmk
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/haiku/VBoxVideo/accelerant/Makefile.kmk b/src/VBox/Additions/haiku/VBoxVideo/accelerant/Makefile.kmk
new file mode 100644
index 00000000..7084e0ed
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxVideo/accelerant/Makefile.kmk
@@ -0,0 +1,75 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxVideo accelerant, Haiku Guest Additions.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# This code is based on:
+#
+# VirtualBox Guest Additions for Haiku.
+# Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+# François Revol <revol@free.fr>
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+PROGRAMS += vboxvideo.accelerant
+vboxvideo.accelerant_TEMPLATE = VBoxGuestR3Exe
+vboxvideo.accelerant_DEFS = VBOX_WITH_HGCM LOG_TO_BACKDOOR
+vboxvideo.accelerant_DEFS += LOG_ENABLED
+vboxvideo.accelerant_INCS = ../include
+vboxvideo.accelerant_SOURCES = \
+ accelerant.cpp
+
+vboxvideo.accelerant_LIBS = \
+ be device \
+ $(VBOX_LIB_IPRT_GUEST_R3_SHARED) \
+ $(VBOX_LIB_VBGL_R3) \
+ /system/servers/app_server
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.cpp b/src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.cpp
new file mode 100644
index 00000000..5352bd82
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.cpp
@@ -0,0 +1,473 @@
+/* $Id: accelerant.cpp $ */
+/** @file
+ * VBoxVideo Accelerant; Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Accelerant.h>
+#include "accelerant.h"
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+AccelerantInfo gInfo;
+static engine_token sEngineToken = { 1, 0 /*B_2D_ACCELERATION*/, NULL };
+
+/** @todo r=ramshankar: get rid of this and replace with IPRT logging. */
+#define TRACE(x...) do { \
+ FILE* logfile = fopen("/var/log/vboxvideo.accelerant.log", "a"); \
+ fprintf(logfile, x); \
+ fflush(logfile); \
+ fsync(fileno(logfile)); \
+ fclose(logfile); \
+ sync(); \
+ } while(0)
+
+class AreaCloner
+{
+ public:
+ AreaCloner()
+ : fArea(-1)
+ {
+ }
+
+ ~AreaCloner()
+ {
+ if (fArea >= B_OK)
+ delete_area(fArea);
+ }
+
+ area_id Clone(const char *name, void **_address, uint32 spec, uint32 protection, area_id sourceArea)
+ {
+ fArea = clone_area(name, _address, spec, protection, sourceArea);
+ return fArea;
+ }
+
+ status_t InitCheck()
+ {
+ return fArea < B_OK ? (status_t)fArea : B_OK;
+ }
+
+ void Keep()
+ {
+ fArea = -1;
+ }
+
+ private:
+ area_id fArea;
+};
+
+extern "C"
+void* get_accelerant_hook(uint32 feature, void *data)
+{
+ TRACE("%s\n", __FUNCTION__);
+ switch (feature)
+ {
+ /* General */
+ case B_INIT_ACCELERANT:
+ return (void *)vboxvideo_init_accelerant;
+ case B_UNINIT_ACCELERANT:
+ return (void *)vboxvideo_uninit_accelerant;
+ case B_CLONE_ACCELERANT:
+ return (void *)vboxvideo_clone_accelerant;
+ case B_ACCELERANT_CLONE_INFO_SIZE:
+ return (void *)vboxvideo_accelerant_clone_info_size;
+ case B_GET_ACCELERANT_CLONE_INFO:
+ return (void *)vboxvideo_get_accelerant_clone_info;
+ case B_GET_ACCELERANT_DEVICE_INFO:
+ return (void *)vboxvideo_get_accelerant_device_info;
+ case B_ACCELERANT_RETRACE_SEMAPHORE:
+ return (void *)vboxvideo_accelerant_retrace_semaphore;
+
+ /* Mode configuration */
+ case B_ACCELERANT_MODE_COUNT:
+ return (void *)vboxvideo_accelerant_mode_count;
+ case B_GET_MODE_LIST:
+ return (void *)vboxvideo_get_mode_list;
+ case B_SET_DISPLAY_MODE:
+ return (void *)vboxvideo_set_display_mode;
+ case B_GET_DISPLAY_MODE:
+ return (void *)vboxvideo_get_display_mode;
+ case B_GET_EDID_INFO:
+ return (void *)vboxvideo_get_edid_info;
+ case B_GET_FRAME_BUFFER_CONFIG:
+ return (void *)vboxvideo_get_frame_buffer_config;
+ case B_GET_PIXEL_CLOCK_LIMITS:
+ return (void *)vboxvideo_get_pixel_clock_limits;
+
+#if 0
+ /* cursor managment */
+ case B_SET_CURSOR_SHAPE:
+ return (void*)vboxvideo_set_cursor_shape;
+ case B_MOVE_CURSOR:
+ return (void*)vboxvideo_move_cursor;
+ case B_SHOW_CURSOR:
+ return (void*)vboxvideo_show_cursor;
+#endif
+
+ /* Engine/synchronization */
+ case B_ACCELERANT_ENGINE_COUNT:
+ return (void *)vboxvideo_accelerant_engine_count;
+ case B_ACQUIRE_ENGINE:
+ return (void *)vboxvideo_acquire_engine;
+ case B_RELEASE_ENGINE:
+ return (void *)vboxvideo_release_engine;
+ case B_WAIT_ENGINE_IDLE:
+ return (void *)vboxvideo_wait_engine_idle;
+ case B_GET_SYNC_TOKEN:
+ return (void *)vboxvideo_get_sync_token;
+ case B_SYNC_TO_TOKEN:
+ return (void *)vboxvideo_sync_to_token;
+ }
+
+ return NULL;
+}
+
+status_t vboxvideo_init_common(int fd, bool cloned)
+{
+ unlink("/var/log/vboxvideo.accelerant.log"); // clear old log - next TRACE() will recreate it
+ TRACE("%s\n", __FUNCTION__);
+
+ gInfo.deviceFD = fd;
+ gInfo.isClone = cloned;
+ gInfo.sharedInfo = NULL;
+ gInfo.sharedInfoArea = -1;
+
+ area_id sharedArea;
+ if (ioctl(gInfo.deviceFD, VBOXVIDEO_GET_PRIVATE_DATA, &sharedArea, sizeof(area_id)) != 0)
+ {
+ TRACE("ioctl failed\n");
+ return B_ERROR;
+ }
+
+ AreaCloner sharedCloner;
+ gInfo.sharedInfoArea = sharedCloner.Clone("vboxvideo shared info", (void **)&gInfo.sharedInfo, B_ANY_ADDRESS,
+ B_READ_AREA | B_WRITE_AREA, sharedArea);
+ status_t status = sharedCloner.InitCheck();
+ if (status < B_OK)
+ {
+ TRACE("InitCheck failed (%s)\n", strerror(status));
+ return status;
+ }
+ sharedCloner.Keep();
+
+ return B_OK;
+}
+
+
+status_t vboxvideo_init_accelerant(int fd)
+{
+ return vboxvideo_init_common(fd, false);
+}
+
+
+ssize_t vboxvideo_accelerant_clone_info_size(void)
+{
+ TRACE("%s\n", __FUNCTION__);
+ return B_PATH_NAME_LENGTH;
+}
+
+
+void vboxvideo_get_accelerant_clone_info(void *data)
+{
+ TRACE("%s\n", __FUNCTION__);
+ ioctl(gInfo.deviceFD, VBOXVIDEO_GET_DEVICE_NAME, data, B_PATH_NAME_LENGTH);
+}
+
+
+status_t vboxvideo_clone_accelerant(void *data)
+{
+ TRACE("%s\n", __FUNCTION__);
+
+ /* Create full device name */
+ char path[MAXPATHLEN];
+ strcpy(path, "/dev/");
+ strcat(path, (const char *)data);
+
+ int fd = open(path, B_READ_WRITE);
+ if (fd < 0)
+ return errno;
+
+ return vboxvideo_init_common(fd, true);
+}
+
+
+void vboxvideo_uninit_accelerant(void)
+{
+ delete_area(gInfo.sharedInfoArea);
+ gInfo.sharedInfo = NULL;
+ gInfo.sharedInfoArea = -1;
+
+ if (gInfo.isClone)
+ close(gInfo.deviceFD);
+
+ TRACE("%s\n", __FUNCTION__);
+}
+
+
+status_t vboxvideo_get_accelerant_device_info(accelerant_device_info *adi)
+{
+ TRACE("%s\n", __FUNCTION__);
+ adi->version = B_ACCELERANT_VERSION;
+ strcpy(adi->name, "Virtual display");
+ strcpy(adi->chipset, "VirtualBox Graphics Adapter");
+ strcpy(adi->serial_no, "9001");
+ return B_OK;
+}
+
+
+sem_id vboxvideo_accelerant_retrace_semaphore(void)
+{
+ TRACE("%s\n", __FUNCTION__);
+ return -1;
+}
+
+
+// modes & constraints
+uint32 vboxvideo_accelerant_mode_count(void)
+{
+ TRACE("%s\n", __FUNCTION__);
+ return 1;
+}
+
+
+status_t vboxvideo_get_mode_list(display_mode *dm)
+{
+ /// @todo return some standard modes here
+ TRACE("%s\n", __FUNCTION__);
+ return vboxvideo_get_display_mode(dm);
+}
+
+
+status_t vboxvideo_set_display_mode(display_mode *modeToSet)
+{
+ TRACE("%s\n", __FUNCTION__);
+ TRACE("trying to set mode %dx%d\n", modeToSet->timing.h_display, modeToSet->timing.v_display);
+ return ioctl(gInfo.deviceFD, VBOXVIDEO_SET_DISPLAY_MODE, modeToSet, sizeof(display_mode));
+}
+
+
+status_t vboxvideo_get_display_mode(display_mode *currentMode)
+{
+ TRACE("%s\n", __FUNCTION__);
+ *currentMode = gInfo.sharedInfo->currentMode;
+ TRACE("current mode is %dx%d\n", currentMode->timing.h_display, currentMode->timing.v_display);
+ return B_OK;
+}
+
+
+status_t vboxvideo_get_edid_info(void *info, size_t size, uint32 *_version)
+{
+ TRACE("%s\n", __FUNCTION__);
+
+ /* Copied from the X11 implementation: */
+ static const uint8 edid_data[128] = {
+ 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). Should we ask the host for this? */
+ 0xEE, /* features (standby, suspend, off, RGB, standard colour space,
+ * preferred timing mode) */
+ 0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
+ /* chromaticity for standard colour space - should we ask the host? */
+ 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* descriptor block 1 goes here */
+ 0x00, 0x00, 0x00, 0xFD, 0x00, /* descriptor block 2, monitor ranges */
+ 0x00, 0xC8, 0x00, 0xC8, 0x64, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, /* 0-200Hz vertical, 0-200KHz horizontal, 1000MHz pixel clock */
+ 0x00, 0x00, 0x00, 0xFC, 0x00, /* descriptor block 3, monitor name */
+ 'V', 'B', 'O', 'X', ' ', 'm', 'o', 'n', 'i', 't', 'o', 'r', '\n',
+ 0x00, 0x00, 0x00, 0x10, 0x00, /* descriptor block 4: dummy data */
+ 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20,
+ 0x00, /* number of extensions */
+ 0x00 /* checksum goes here */
+ };
+
+ if (size < 128)
+ return B_BUFFER_OVERFLOW;
+
+ *_version = 1; /* EDID_VERSION_1 */
+ memcpy(info, edid_data, 128);
+ return B_OK;
+}
+
+
+status_t vboxvideo_get_frame_buffer_config(frame_buffer_config *config)
+{
+ TRACE("%s\n", __FUNCTION__);
+ config->frame_buffer = gInfo.sharedInfo->framebuffer;
+ config->frame_buffer_dma = NULL;
+ config->bytes_per_row = get_depth_for_color_space(gInfo.sharedInfo->currentMode.space)
+ * gInfo.sharedInfo->currentMode.timing.h_display / 8;
+ return B_OK;
+}
+
+
+status_t vboxvideo_get_pixel_clock_limits(display_mode *dm, uint32 *low, uint32 *high)
+{
+ TRACE("%s\n", __FUNCTION__);
+ // irrelevant for virtual monitors
+ *low = 0;
+ *high = 9001;
+ return B_OK;
+}
+
+
+/* Cursor */
+status_t vboxvideo_set_cursor_shape(uint16 width, uint16 height, uint16 hotX, uint16 hotY, uint8 *andMask, uint8 *xorMask)
+{
+ TRACE("%s\n", __FUNCTION__);
+ // VBoxHGSMIUpdatePointerShape
+ return B_UNSUPPORTED;
+}
+
+
+void vboxvideo_move_cursor(uint16 x, uint16 y)
+{
+ TRACE("%s\n", __FUNCTION__);
+}
+
+
+void vboxvideo_show_cursor(bool is_visible)
+{
+ TRACE("%s\n", __FUNCTION__);
+}
+
+
+/* Accelerant engine */
+uint32 vboxvideo_accelerant_engine_count(void)
+{
+ TRACE("%s\n", __FUNCTION__);
+ return 1;
+}
+
+status_t vboxvideo_acquire_engine(uint32 capabilities, uint32 maxWait, sync_token *st, engine_token **et)
+{
+ TRACE("%s\n", __FUNCTION__);
+ *et = &sEngineToken;
+ return B_OK;
+}
+
+
+status_t vboxvideo_release_engine(engine_token *et, sync_token *st)
+{
+ TRACE("%s\n", __FUNCTION__);
+ if (st != NULL)
+ st->engine_id = et->engine_id;
+
+ return B_OK;
+}
+
+
+void vboxvideo_wait_engine_idle(void)
+{
+ TRACE("%s\n", __FUNCTION__);
+}
+
+
+status_t vboxvideo_get_sync_token(engine_token *et, sync_token *st)
+{
+ TRACE("%s\n", __FUNCTION__);
+ return B_OK;
+}
+
+
+status_t vboxvideo_sync_to_token(sync_token *st)
+{
+ TRACE("%s\n", __FUNCTION__);
+ return B_OK;
+}
+
+
+/* 2D acceleration */
+void vboxvideo_screen_to_screen_blit(engine_token *et, blit_params *list, uint32 count)
+{
+ TRACE("%s\n", __FUNCTION__);
+}
+
+
+void vboxvideo_fill_rectangle(engine_token *et, uint32 color, fill_rect_params *list, uint32 count)
+{
+ TRACE("%s\n", __FUNCTION__);
+}
+
+
+void vboxvideo_invert_rectangle(engine_token *et, fill_rect_params *list, uint32 count)
+{
+ TRACE("%s\n", __FUNCTION__);
+}
+
+
+void vboxvideo_fill_span(engine_token *et, uint32 color, uint16 *list, uint32 count)
+{
+ TRACE("%s\n", __FUNCTION__);
+}
diff --git a/src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.h b/src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.h
new file mode 100644
index 00000000..735f4d2b
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxVideo/accelerant/accelerant.h
@@ -0,0 +1,115 @@
+/* $Id: accelerant.h $ */
+/** @file
+ * VBoxVideo Accelerant; Haiku Guest Additions, header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxVideo_accelerant_accelerant_h
+#define GA_INCLUDED_SRC_haiku_VBoxVideo_accelerant_accelerant_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <Accelerant.h>
+#include "../common/VBoxVideo_common.h"
+
+struct AccelerantInfo
+{
+ /** @todo doxygen document these fields */
+ int deviceFD;
+ bool isClone;
+
+ SharedInfo *sharedInfo;
+ area_id sharedInfoArea;
+};
+extern AccelerantInfo gInfo;
+
+/* General */
+status_t vboxvideo_init_accelerant(int fd);
+ssize_t vboxvideo_accelerant_clone_info_size(void);
+void vboxvideo_get_accelerant_clone_info(void *data);
+status_t vboxvideo_clone_accelerant(void *data);
+void vboxvideo_uninit_accelerant(void);
+status_t vboxvideo_get_accelerant_device_info(accelerant_device_info *adi);
+sem_id vboxvideo_accelerant_retrace_semaphore(void);
+
+/* Modes & constraints */
+uint32 vboxvideo_accelerant_mode_count(void);
+status_t vboxvideo_get_mode_list(display_mode *dm);
+status_t vboxvideo_set_display_mode(display_mode *modeToSet);
+status_t vboxvideo_get_display_mode(display_mode *currentMode);
+status_t vboxvideo_get_edid_info(void *info, size_t size, uint32 *_version);
+status_t vboxvideo_get_frame_buffer_config(frame_buffer_config *config);
+status_t vboxvideo_get_pixel_clock_limits(display_mode *dm, uint32 *low, uint32 *high);
+
+/* Cursor */
+status_t vboxvideo_set_cursor_shape(uint16 width, uint16 height, uint16 hotX, uint16 hotY, uint8 *andMask, uint8 *xorMask);
+void vboxvideo_move_cursor(uint16 x, uint16 y);
+void vboxvideo_show_cursor(bool is_visible);
+
+/* Accelerant engine */
+uint32 vboxvideo_accelerant_engine_count(void);
+status_t vboxvideo_acquire_engine(uint32 capabilities, uint32 maxWait, sync_token *st, engine_token **et);
+status_t vboxvideo_release_engine(engine_token *et, sync_token *st);
+void vboxvideo_wait_engine_idle(void);
+status_t vboxvideo_get_sync_token(engine_token *et, sync_token *st);
+status_t vboxvideo_sync_to_token(sync_token *st);
+
+/* 2D acceleration */
+void vboxvideo_screen_to_screen_blit(engine_token *et, blit_params *list, uint32 count);
+void vboxvideo_fill_rectangle(engine_token *et, uint32 color, fill_rect_params *list, uint32 count);
+void vboxvideo_invert_rectangle(engine_token *et, fill_rect_params *list, uint32 count);
+void vboxvideo_fill_span(engine_token *et, uint32 color, uint16 *list, uint32 count);
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxVideo_accelerant_accelerant_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxVideo/common/VBoxVideo_common.h b/src/VBox/Additions/haiku/VBoxVideo/common/VBoxVideo_common.h
new file mode 100644
index 00000000..b00bce3f
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxVideo/common/VBoxVideo_common.h
@@ -0,0 +1,114 @@
+/* $Id: VBoxVideo_common.h $ */
+/** @file
+ * VBoxVideo, Haiku Guest Additions, common header.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_haiku_VBoxVideo_common_VBoxVideo_common_h
+#define GA_INCLUDED_SRC_haiku_VBoxVideo_common_VBoxVideo_common_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <Drivers.h>
+#include <Accelerant.h>
+#include <PCI.h>
+
+struct SharedInfo
+{
+ display_mode currentMode;
+ area_id framebufferArea;
+ void *framebuffer;
+};
+
+enum
+{
+ VBOXVIDEO_GET_PRIVATE_DATA = B_DEVICE_OP_CODES_END + 1,
+ VBOXVIDEO_GET_DEVICE_NAME,
+ VBOXVIDEO_SET_DISPLAY_MODE
+};
+
+static inline uint32 get_color_space_for_depth(uint32 depth)
+{
+ switch (depth)
+ {
+ case 1: return B_GRAY1;
+ case 4: return B_GRAY8;
+ /* The app_server is smart enough to translate this to VGA mode */
+ case 8: return B_CMAP8;
+ case 15: return B_RGB15;
+ case 16: return B_RGB16;
+ case 24: return B_RGB24;
+ case 32: return B_RGB32;
+ }
+
+ return 0;
+}
+
+static inline uint32 get_depth_for_color_space(uint32 depth)
+{
+ switch (depth)
+ {
+ case B_GRAY1: return 1;
+ case B_GRAY8: return 4;
+ case B_CMAP8: return 8;
+ case B_RGB15: return 15;
+ case B_RGB16: return 16;
+ case B_RGB24: return 24;
+ case B_RGB32: return 32;
+ }
+ return 0;
+}
+
+#endif /* !GA_INCLUDED_SRC_haiku_VBoxVideo_common_VBoxVideo_common_h */
+
diff --git a/src/VBox/Additions/haiku/VBoxVideo/driver/Makefile.kmk b/src/VBox/Additions/haiku/VBoxVideo/driver/Makefile.kmk
new file mode 100644
index 00000000..b32a91b3
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxVideo/driver/Makefile.kmk
@@ -0,0 +1,95 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for VBoxVideo driver, Haiku Guest Additions.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+#
+# This code is based on:
+#
+# VirtualBox Guest Additions for Haiku.
+# Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+# François Revol <revol@free.fr>
+#
+# 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.
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifdef VBOX_WITH_ADDITION_DRIVERS
+ SYSMODS += vboxvideo
+endif
+
+#
+# Populate FILES_VBOXSF_NOBIN
+#
+#include $(PATH_SUB_CURRENT)/files_VBoxVideo
+
+#
+# The module (for syntax checking).
+# The DEBUG_HASH* stuff is for CONFIG_DYNAMIC_DEBUG-enabled kernels
+#
+vboxvideo_TEMPLATE = VBoxGuestR0Drv
+vboxvideo_DEFS = \
+ MODULE IN_RT_R0 VBOXGUEST VBOX_WITH_HGCM \
+ KBUILD_MODNAME=KBUILD_STR\(VBoxVideo\) \
+ KBUILD_BASENAME=KBUILD_STR\(VBoxVideo\)
+vboxvideo_INCS = \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuestLib \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest \
+ $(PATH_ROOT)/src/VBox/Runtime/r0drv/haiku \
+ $(VBOX_GRAPHICS_INCS)
+vboxvideo_SOURCES = \
+ driver.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/VBoxGuest-haiku-stubs.c
+vboxvideo_LIBS = \
+ $(VBOX_LIB_VBGL_R0) \
+ $(VBOX_PATH_ADDITIONS_LIB)/HGSMIGuestR0Lib$(VBOX_SUFF_LIB)
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/haiku/VBoxVideo/driver/driver.cpp b/src/VBox/Additions/haiku/VBoxVideo/driver/driver.cpp
new file mode 100644
index 00000000..ce04560a
--- /dev/null
+++ b/src/VBox/Additions/haiku/VBoxVideo/driver/driver.cpp
@@ -0,0 +1,382 @@
+/* $Id: driver.cpp $ */
+/** @file
+ * VBoxVideo driver, Haiku Guest Additions, implementation.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <KernelExport.h>
+#include <PCI.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <string.h>
+#include <graphic_driver.h>
+#include <VBoxGuest-haiku.h>
+#include <VBoxVideoGuest.h>
+#include "../common/VBoxVideo_common.h"
+
+#define VENDOR_ID 0x80ee
+#define DEVICE_ID 0xbeef
+#define DRIVER_NAME "VBoxVideoDriver"
+#define DEVICE_FORMAT "vd_%04X_%04X_%02X%02X%02X"
+
+/** @todo r=ramshankar: pretty sure IPRT has something for page rounding,
+ * replace with IPRT version later. */
+#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))
+
+#define ENABLE_DEBUG_TRACE
+
+#undef TRACE
+#ifdef ENABLE_DEBUG_TRACE
+#define TRACE(x...) dprintf("VBoxVideo: " x)
+#else
+#define TRACE(x...) ;
+#endif
+
+int32 api_version = B_CUR_DRIVER_API_VERSION; // revision of driver API we support
+
+extern "C" status_t vm_set_area_memory_type(area_id id, phys_addr_t physicalBase, uint32 type);
+
+struct Benaphore
+{
+ sem_id sem;
+ int32 count;
+
+ status_t Init(const char *name)
+ {
+ count = 0;
+ sem = create_sem(0, name);
+ return sem < 0 ? sem : B_OK;
+ }
+
+ status_t Acquire()
+ {
+ if (atomic_add(&count, 1) > 0)
+ return acquire_sem(sem);
+ return B_OK;
+ }
+
+ status_t Release()
+ {
+ if (atomic_add(&count, -1) > 1)
+ return release_sem(sem);
+ return B_OK;
+ }
+
+ void Delete()
+ {
+ delete_sem(sem);
+ }
+};
+
+struct DeviceInfo
+{
+ uint32 openCount; /* Count of how many times device has been opened */
+ uint32 flags; /* Device flags */
+ area_id sharedArea; /* Area shared between driver and all accelerants */
+ SharedInfo *sharedInfo; /* Pointer to shared info area memory */
+ pci_info pciInfo; /* Copy of pci info for this device */
+ char name[B_OS_NAME_LENGTH]; /* Name of device */
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+status_t device_open(const char *name, uint32 flags, void **cookie);
+status_t device_close(void *dev);
+status_t device_free(void *dev);
+status_t device_read(void *dev, off_t pos, void *buf, size_t *len);
+status_t device_write(void *dev, off_t pos, const void *buf, size_t *len);
+status_t device_ioctl(void *dev, uint32 msg, void *buf, size_t len);
+static uint32 get_color_space_for_depth(uint32 depth);
+
+
+/*********************************************************************************************************************************
+* Globals *
+*********************************************************************************************************************************/
+/* At most one virtual video card ever appears, no reason for this to be an array */
+static DeviceInfo gDeviceInfo;
+static char *gDeviceNames[2] = { gDeviceInfo.name, NULL };
+static bool gCanHasDevice = false; /* is the device present? */
+static Benaphore gLock;
+static pci_module_info *gPCI;
+
+static device_hooks gDeviceHooks =
+{
+ device_open,
+ device_close,
+ device_free,
+ device_ioctl,
+ device_read,
+ device_write,
+ NULL, /* select */
+ NULL, /* deselect */
+ NULL, /* read_pages */
+ NULL /* write_pages */
+};
+
+
+status_t init_hardware()
+{
+ LogFlowFunc(("init_hardware\n"));
+
+ status_t err = get_module(VBOXGUEST_MODULE_NAME, (module_info **)&g_VBoxGuest);
+ if (err == B_OK)
+ {
+ err = get_module(B_PCI_MODULE_NAME, (module_info **)&gPCI);
+ if (err == B_OK)
+ return B_OK;
+
+ LogRel((DRIVER_NAME ":_init_hardware() get_module(%s) failed. err=%08lx\n", B_PCI_MODULE_NAME));
+ }
+ else
+ LogRel((DRIVER_NAME ":_init_hardware() get_module(%s) failed. err=%08lx\n", VBOXGUEST_MODULE_NAME, err));
+ return B_ERROR;
+}
+
+
+status_t init_driver()
+{
+ LogFlowFunc(("init_driver\n"));
+
+ gLock.Init("VBoxVideo driver lock");
+
+ uint32 pciIndex = 0;
+
+ while (gPCI->get_nth_pci_info(pciIndex, &gDeviceInfo.pciInfo) == B_OK)
+ {
+ if (gDeviceInfo.pciInfo.vendor_id == VENDOR_ID && gDeviceInfo.pciInfo.device_id == DEVICE_ID)
+ {
+ sprintf(gDeviceInfo.name, "graphics/" DEVICE_FORMAT,
+ gDeviceInfo.pciInfo.vendor_id, gDeviceInfo.pciInfo.device_id,
+ gDeviceInfo.pciInfo.bus, gDeviceInfo.pciInfo.device, gDeviceInfo.pciInfo.function);
+ TRACE("found device %s\n", gDeviceInfo.name);
+
+ gCanHasDevice = true;
+ gDeviceInfo.openCount = 0;
+
+ size_t sharedSize = (sizeof(SharedInfo) + 7) & ~7;
+ gDeviceInfo.sharedArea = create_area("vboxvideo shared info",
+ (void **)&gDeviceInfo.sharedInfo, B_ANY_KERNEL_ADDRESS,
+ ROUND_TO_PAGE_SIZE(sharedSize), B_FULL_LOCK,
+ B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_USER_CLONEABLE_AREA);
+
+ uint16_t width, height, vwidth, bpp, flags;
+ VBoxVideoGetModeRegisters(&width, &height, &vwidth, &bpp, &flags);
+
+ gDeviceInfo.sharedInfo->currentMode.space = get_color_space_for_depth(bpp);
+ gDeviceInfo.sharedInfo->currentMode.virtual_width = width;
+ gDeviceInfo.sharedInfo->currentMode.virtual_height = height;
+ gDeviceInfo.sharedInfo->currentMode.h_display_start = 0;
+ gDeviceInfo.sharedInfo->currentMode.v_display_start = 0;
+ gDeviceInfo.sharedInfo->currentMode.flags = 0;
+ gDeviceInfo.sharedInfo->currentMode.timing.h_display = width;
+ gDeviceInfo.sharedInfo->currentMode.timing.v_display = height;
+ /* Not used, but this makes a reasonable-sounding refresh rate show in screen prefs: */
+ gDeviceInfo.sharedInfo->currentMode.timing.h_total = 1000;
+ gDeviceInfo.sharedInfo->currentMode.timing.v_total = 1;
+ gDeviceInfo.sharedInfo->currentMode.timing.pixel_clock = 850;
+
+ /* Map the PCI memory space */
+ uint32 command_reg = gPCI->read_pci_config(gDeviceInfo.pciInfo.bus,
+ gDeviceInfo.pciInfo.device, gDeviceInfo.pciInfo.function, PCI_command, 2);
+ command_reg |= PCI_command_io | PCI_command_memory | PCI_command_master;
+ gPCI->write_pci_config(gDeviceInfo.pciInfo.bus, gDeviceInfo.pciInfo.device,
+ gDeviceInfo.pciInfo.function, PCI_command, 2, command_reg);
+
+ gDeviceInfo.sharedInfo->framebufferArea = map_physical_memory("vboxvideo framebuffer",
+ (phys_addr_t)gDeviceInfo.pciInfo.u.h0.base_registers[0],
+ gDeviceInfo.pciInfo.u.h0.base_register_sizes[0], B_ANY_KERNEL_BLOCK_ADDRESS,
+ B_READ_AREA | B_WRITE_AREA, &(gDeviceInfo.sharedInfo->framebuffer));
+ vm_set_area_memory_type(gDeviceInfo.sharedInfo->framebufferArea,
+ (phys_addr_t)gDeviceInfo.pciInfo.u.h0.base_registers[0], B_MTR_WC);
+ break;
+ }
+
+ pciIndex++;
+ }
+
+ return B_OK;
+}
+
+
+const char** publish_devices()
+{
+ LogFlowFunc(("publish_devices\n"));
+ if (gCanHasDevice)
+ return (const char **)gDeviceNames;
+ return NULL;
+}
+
+
+device_hooks* find_device(const char *name)
+{
+ LogFlowFunc(("find_device\n"));
+ if (gCanHasDevice && strcmp(name, gDeviceInfo.name) == 0)
+ return &gDeviceHooks;
+
+ return NULL;
+}
+
+
+void uninit_driver()
+{
+ LogFlowFunc(("uninit_driver\n"));
+ gLock.Delete();
+ put_module(VBOXGUEST_MODULE_NAME);
+}
+
+status_t device_open(const char *name, uint32 flags, void **cookie)
+{
+ LogFlowFunc(("device_open\n"));
+
+ if (!gCanHasDevice || strcmp(name, gDeviceInfo.name) != 0)
+ return B_BAD_VALUE;
+
+ /** @todo init device! */
+
+ *cookie = (void *)&gDeviceInfo;
+ return B_OK;
+}
+
+
+status_t device_close(void *dev)
+{
+ LogFlowFunc(("device_close\n"));
+ return B_ERROR;
+}
+
+
+status_t device_free(void *dev)
+{
+ LogFlowFunc(("device_free\n"));
+
+ DeviceInfo& di = *(DeviceInfo *)dev;
+ gLock.Acquire();
+
+ if (di.openCount <= 1)
+ {
+ /// @todo deinit device!
+ delete_area(di.sharedArea);
+ di.sharedArea = -1;
+ di.sharedInfo = NULL;
+ }
+
+ if (di.openCount > 0)
+ di.openCount--;
+
+ gLock.Release();
+
+ return B_OK;
+}
+
+
+status_t device_read(void *dev, off_t pos, void *buf, size_t *len)
+{
+ LogFlowFunc(("device_read\n"));
+ return B_NOT_ALLOWED;
+}
+
+
+status_t device_write(void *dev, off_t pos, const void *buf, size_t *len)
+{
+ LogFlowFunc(("device_write\n"));
+ return B_NOT_ALLOWED;
+}
+
+
+status_t device_ioctl(void *cookie, uint32 msg, void *buf, size_t len)
+{
+ LogFlowFunc(("device_ioctl\n"));
+
+ DeviceInfo *dev = (DeviceInfo *)cookie;
+
+ switch (msg)
+ {
+ case B_GET_ACCELERANT_SIGNATURE:
+ {
+ strcpy((char *)buf, "vboxvideo.accelerant");
+ return B_OK;
+ }
+
+ case VBOXVIDEO_GET_PRIVATE_DATA:
+ {
+ /** @todo r=ramshankar: implement RTR0MemUserCopyFrom for haiku. */
+ return user_memcpy(buf, &dev->sharedArea, sizeof(area_id));
+ }
+
+ case VBOXVIDEO_GET_DEVICE_NAME:
+ {
+ /** @todo r=ramshankar: implement RTR0MemUserCopyFrom for haiku. */
+ if (user_strlcpy((char *)buf, gDeviceInfo.name, len) < B_OK)
+ return B_BAD_ADDRESS;
+ return B_OK;
+ }
+
+ case VBOXVIDEO_SET_DISPLAY_MODE:
+ {
+ display_mode *mode = (display_mode *)buf;
+ VBoxVideoSetModeRegisters(mode->timing.h_display, mode->timing.v_display,
+ mode->timing.h_display, get_depth_for_color_space(mode->space), 0, 0, 0);
+ gDeviceInfo.sharedInfo->currentMode = *mode;
+ return B_OK;
+ }
+ default:
+ return B_BAD_VALUE;
+ }
+}
+
diff --git a/src/VBox/Additions/haiku/include/VBoxGuestInternal.h b/src/VBox/Additions/haiku/include/VBoxGuestInternal.h
new file mode 100644
index 00000000..87adbb92
--- /dev/null
+++ b/src/VBox/Additions/haiku/include/VBoxGuestInternal.h
@@ -0,0 +1,74 @@
+/* $$ */
+/** @file
+ * VBoxGuestInternal - Private Haiku additions declarations.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ * Copyright (c) 2011 Mike Smith <mike@scgtrp.net>
+ * François Revol <revol@free.fr>
+ *
+ * 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_HAIKU_VBoxGuestInternal_h
+#define GA_INCLUDED_HAIKU_VBoxGuestInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** The MIME signature of the VBoxGuest application. */
+#define VBOX_GUEST_APP_SIG "application/x-vnd.Oracle-VBoxGuest"
+
+/** The code used for messages sent by and to the system tray. */
+#define VBOX_GUEST_CLIPBOARD_HOST_MSG_READ_DATA 'vbC2'
+#define VBOX_GUEST_CLIPBOARD_HOST_MSG_FORMATS 'vbC3'
+
+/** The code used for messages sent by and to the system tray. */
+#define VBOX_GUEST_APP_ACTION 'vbox'
+
+#endif /* !GA_INCLUDED_HAIKU_VBoxGuestInternal_h */
+
diff --git a/src/VBox/Additions/haiku/include/lock.h b/src/VBox/Additions/haiku/include/lock.h
new file mode 100644
index 00000000..13d2f94c
--- /dev/null
+++ b/src/VBox/Additions/haiku/include/lock.h
@@ -0,0 +1,318 @@
+/* $Id: lock.h $ */
+/** @file
+ * Lock.h - Haiku, private locking internals.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This code is based on:
+ *
+ * VirtualBox Guest Additions for Haiku.
+ *
+ * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
+ * Copyright 2002-2009, Axel Dörfler, axeld@pinc-software.de.
+ * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
+ * Distributed under the terms of the MIT License.
+ */
+
+/** @todo r=ramshankar: Eventually this file should be shipped by Haiku and
+ * should be removed from the VBox tree. */
+
+#ifndef GA_INCLUDED_HAIKU_lock_h
+#define GA_INCLUDED_HAIKU_lock_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <OS.h>
+
+
+struct mutex_waiter;
+
+typedef struct mutex {
+ const char* name;
+ struct mutex_waiter* waiters;
+#if KDEBUG
+ thread_id holder;
+#else
+ int32 count;
+ uint16 ignore_unlock_count;
+#endif
+ uint8 flags;
+} mutex;
+
+#define MUTEX_FLAG_CLONE_NAME 0x1
+
+
+typedef struct recursive_lock {
+ mutex lock;
+#if !KDEBUG
+ thread_id holder;
+#endif
+ int recursion;
+} recursive_lock;
+
+
+struct rw_lock_waiter;
+
+typedef struct rw_lock {
+ const char* name;
+ struct rw_lock_waiter* waiters;
+ thread_id holder;
+ vint32 count;
+ int32 owner_count;
+ int16 active_readers;
+ // Only > 0 while a writer is waiting: number
+ // of active readers when the first waiting
+ // writer started waiting.
+ int16 pending_readers;
+ // Number of readers that have already
+ // incremented "count", but have not yet started
+ // to wait at the time the last writer unlocked.
+ uint32 flags;
+} rw_lock;
+
+#define RW_LOCK_WRITER_COUNT_BASE 0x10000
+
+#define RW_LOCK_FLAG_CLONE_NAME 0x1
+
+
+#if KDEBUG
+# define KDEBUG_RW_LOCK_DEBUG 0
+ // Define to 1 if you want to use ASSERT_READ_LOCKED_RW_LOCK().
+ // The rw_lock will just behave like a recursive locker then.
+# define ASSERT_LOCKED_RECURSIVE(r) \
+ { ASSERT(find_thread(NULL) == (r)->lock.holder); }
+# define ASSERT_LOCKED_MUTEX(m) { ASSERT(find_thread(NULL) == (m)->holder); }
+# define ASSERT_WRITE_LOCKED_RW_LOCK(l) \
+ { ASSERT(find_thread(NULL) == (l)->holder); }
+# if KDEBUG_RW_LOCK_DEBUG
+# define ASSERT_READ_LOCKED_RW_LOCK(l) \
+ { ASSERT(find_thread(NULL) == (l)->holder); }
+# else
+# define ASSERT_READ_LOCKED_RW_LOCK(l) do {} while (false)
+# endif
+#else
+# define ASSERT_LOCKED_RECURSIVE(r) do {} while (false)
+# define ASSERT_LOCKED_MUTEX(m) do {} while (false)
+# define ASSERT_WRITE_LOCKED_RW_LOCK(m) do {} while (false)
+# define ASSERT_READ_LOCKED_RW_LOCK(l) do {} while (false)
+#endif
+
+
+// static initializers
+#if KDEBUG
+# define MUTEX_INITIALIZER(name) { name, NULL, -1, 0 }
+# define RECURSIVE_LOCK_INITIALIZER(name) { MUTEX_INITIALIZER(name), 0 }
+#else
+# define MUTEX_INITIALIZER(name) { name, NULL, 0, 0, 0 }
+# define RECURSIVE_LOCK_INITIALIZER(name) { MUTEX_INITIALIZER(name), -1, 0 }
+#endif
+
+#define RW_LOCK_INITIALIZER(name) { name, NULL, -1, 0, 0, 0 }
+
+
+#if KDEBUG
+# define RECURSIVE_LOCK_HOLDER(recursiveLock) ((recursiveLock)->lock.holder)
+#else
+# define RECURSIVE_LOCK_HOLDER(recursiveLock) ((recursiveLock)->holder)
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void recursive_lock_init(recursive_lock *lock, const char *name);
+ // name is *not* cloned nor freed in recursive_lock_destroy()
+extern void recursive_lock_init_etc(recursive_lock *lock, const char *name,
+ uint32 flags);
+extern void recursive_lock_destroy(recursive_lock *lock);
+extern status_t recursive_lock_lock(recursive_lock *lock);
+extern status_t recursive_lock_trylock(recursive_lock *lock);
+extern void recursive_lock_unlock(recursive_lock *lock);
+extern int32 recursive_lock_get_recursion(recursive_lock *lock);
+
+extern void rw_lock_init(rw_lock* lock, const char* name);
+ // name is *not* cloned nor freed in rw_lock_destroy()
+extern void rw_lock_init_etc(rw_lock* lock, const char* name, uint32 flags);
+extern void rw_lock_destroy(rw_lock* lock);
+extern status_t rw_lock_write_lock(rw_lock* lock);
+
+extern void mutex_init(mutex* lock, const char* name);
+ // name is *not* cloned nor freed in mutex_destroy()
+extern void mutex_init_etc(mutex* lock, const char* name, uint32 flags);
+extern void mutex_destroy(mutex* lock);
+extern status_t mutex_switch_lock(mutex* from, mutex* to);
+ // Unlocks "from" and locks "to" such that unlocking and starting to wait
+ // for the lock is atomically. I.e. if "from" guards the object "to" belongs
+ // to, the operation is safe as long as "from" is held while destroying
+ // "to".
+extern status_t mutex_switch_from_read_lock(rw_lock* from, mutex* to);
+ // Like mutex_switch_lock(), just for a switching from a read-locked
+ // rw_lock.
+
+
+// implementation private:
+
+extern status_t _rw_lock_read_lock(rw_lock* lock);
+extern status_t _rw_lock_read_lock_with_timeout(rw_lock* lock,
+ uint32 timeoutFlags, bigtime_t timeout);
+extern void _rw_lock_read_unlock(rw_lock* lock, bool threadsLocked);
+extern void _rw_lock_write_unlock(rw_lock* lock, bool threadsLocked);
+
+extern status_t _mutex_lock(mutex* lock, bool threadsLocked);
+extern void _mutex_unlock(mutex* lock, bool threadsLocked);
+extern status_t _mutex_trylock(mutex* lock);
+extern status_t _mutex_lock_with_timeout(mutex* lock, uint32 timeoutFlags,
+ bigtime_t timeout);
+
+
+static inline status_t
+rw_lock_read_lock(rw_lock* lock)
+{
+#if KDEBUG_RW_LOCK_DEBUG
+ return rw_lock_write_lock(lock);
+#else
+ int32 oldCount = atomic_add(&lock->count, 1);
+ if (oldCount >= RW_LOCK_WRITER_COUNT_BASE)
+ return _rw_lock_read_lock(lock);
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+rw_lock_read_lock_with_timeout(rw_lock* lock, uint32 timeoutFlags,
+ bigtime_t timeout)
+{
+#if KDEBUG_RW_LOCK_DEBUG
+ return mutex_lock_with_timeout(lock, timeoutFlags, timeout);
+#else
+ int32 oldCount = atomic_add(&lock->count, 1);
+ if (oldCount >= RW_LOCK_WRITER_COUNT_BASE)
+ return _rw_lock_read_lock_with_timeout(lock, timeoutFlags, timeout);
+ return B_OK;
+#endif
+}
+
+
+static inline void
+rw_lock_read_unlock(rw_lock* lock)
+{
+#if KDEBUG_RW_LOCK_DEBUG
+ rw_lock_write_unlock(lock);
+#else
+ int32 oldCount = atomic_add(&lock->count, -1);
+ if (oldCount >= RW_LOCK_WRITER_COUNT_BASE)
+ _rw_lock_read_unlock(lock, false);
+#endif
+}
+
+
+static inline void
+rw_lock_write_unlock(rw_lock* lock)
+{
+ _rw_lock_write_unlock(lock, false);
+}
+
+
+static inline status_t
+mutex_lock(mutex* lock)
+{
+#if KDEBUG
+ return _mutex_lock(lock, false);
+#else
+ if (atomic_add(&lock->count, -1) < 0)
+ return _mutex_lock(lock, false);
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+mutex_lock_threads_locked(mutex* lock)
+{
+#if KDEBUG
+ return _mutex_lock(lock, true);
+#else
+ if (atomic_add(&lock->count, -1) < 0)
+ return _mutex_lock(lock, true);
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+mutex_trylock(mutex* lock)
+{
+#if KDEBUG
+ return _mutex_trylock(lock);
+#else
+ if (atomic_test_and_set(&lock->count, -1, 0) != 0)
+ return B_WOULD_BLOCK;
+ return B_OK;
+#endif
+}
+
+
+static inline status_t
+mutex_lock_with_timeout(mutex* lock, uint32 timeoutFlags, bigtime_t timeout)
+{
+#if KDEBUG
+ return _mutex_lock_with_timeout(lock, timeoutFlags, timeout);
+#else
+ if (atomic_add(&lock->count, -1) < 0)
+ return _mutex_lock_with_timeout(lock, timeoutFlags, timeout);
+ return B_OK;
+#endif
+}
+
+
+static inline void
+mutex_unlock(mutex* lock)
+{
+#if !KDEBUG
+ if (atomic_add(&lock->count, 1) < -1)
+#endif
+ _mutex_unlock(lock, false);
+}
+
+
+static inline void
+mutex_transfer_lock(mutex* lock, thread_id thread)
+{
+#if KDEBUG
+ lock->holder = thread;
+#endif
+}
+
+
+extern void lock_debug_init();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_HAIKU_lock_h */
diff --git a/src/VBox/Additions/haiku/load.sh b/src/VBox/Additions/haiku/load.sh
new file mode 100755
index 00000000..209a55ac
--- /dev/null
+++ b/src/VBox/Additions/haiku/load.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+# $Id: load.sh $
+## @file
+# Driver load script.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+outdir=out/haiku.x86/debug/bin/additions
+instdir=/boot/apps/VBoxAdditions
+
+
+# vboxguest
+mkdir -p ~/config/add-ons/kernel/generic/
+cp $outdir/vboxguest ~/config/add-ons/kernel/generic/
+
+# vboxdev
+mkdir -p ~/config/add-ons/kernel/drivers/dev/misc/
+cp $outdir/vboxdev ~/config/add-ons/kernel/drivers/bin/
+ln -sf ../../bin/vboxdev ~/config/add-ons/kernel/drivers/dev/misc
+
+# VBoxMouse
+cp $outdir/VBoxMouse ~/config/add-ons/input_server/devices/
+cp $outdir/VBoxMouseFilter ~/config/add-ons/input_server/filters/
+
+# Services
+mkdir -p $instdir
+cp $outdir/VBoxService $instdir/
+cp $outdir/VBoxTray $instdir/
+cp $outdir/VBoxControl $instdir/
+ln -sf $instdir/VBoxService ~/config/boot/launch
+
diff --git a/src/VBox/Additions/haiku/unload.sh b/src/VBox/Additions/haiku/unload.sh
new file mode 100755
index 00000000..7ed68eb8
--- /dev/null
+++ b/src/VBox/Additions/haiku/unload.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+# $Id: unload.sh $
+## @file
+# Driver unload script.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+basedir=/boot/home/config/add-ons/
+rm -f $basedir/input_server/devices/VBoxMouse
+rm -f $basedir/kernel/drivers/bin/vboxdev
+rm -f $basedir/kernel/drivers/dev/misc/vboxdev
+rm -f $basedir/kernel/file_systems/vboxsf
+rm -f $basedir/kernel/generic/vboxguest
+rm -rf /boot/apps/VBoxAdditions
+
diff --git a/src/VBox/Additions/linux/Makefile b/src/VBox/Additions/linux/Makefile
new file mode 100644
index 00000000..b28fe98c
--- /dev/null
+++ b/src/VBox/Additions/linux/Makefile
@@ -0,0 +1,136 @@
+#
+# Makefile for the VirtualBox Linux Guest Drivers.
+#
+
+#
+# Copyright (C) 2009-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+ifneq ($(KERNELRELEASE),)
+
+# Building from kBuild (make -C <kernel_directory> M=`pwd`)
+# or inside a kernel source tree.
+
+obj-m = vboxguest/ vboxsf/ vboxvideo/
+
+else # ! KERNELRELEASE
+
+KBUILD_VERBOSE =
+ ifeq ($(KBUILD_VERBOSE),)
+VBOX_QUIET := @
+VBOX_QUIET_SH := @
+ else
+VBOX_QUIET :=
+VBOX_QUIET_SH := set -x;
+ endif
+
+all: vboxguest vboxsf vboxvideo
+
+vboxguest:
+ @echo "=== Building 'vboxguest' module ==="
+ + $(VBOX_QUIET)$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest
+ $(VBOX_QUIET_SH)if [ -f vboxguest/vboxguest.ko ]; then \
+ cp vboxguest/vboxguest.ko .; \
+ else \
+ cp vboxguest/vboxguest.o .; \
+ fi
+ @echo
+
+vboxsf: vboxguest
+ + $(VBOX_QUIET_SH)if [ -d vboxsf ]; then \
+ if [ -f vboxguest/Module.symvers ]; then \
+ cp vboxguest/Module.symvers vboxsf; \
+ fi; \
+ echo "=== Building 'vboxsf' module ==="; \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) KBUILD_EXTRA_SYMBOLS=$(abspath vboxsf/Module.symvers) -C vboxsf || exit 1; \
+ if [ -f vboxsf/vboxsf.ko ]; then \
+ cp vboxsf/vboxsf.ko .; \
+ else \
+ cp vboxsf/vboxsf.o .; \
+ fi; \
+ echo; \
+ fi
+
+vboxvideo:
+ + $(VBOX_QUIET_SH)if [ -d vboxvideo ]; then \
+ echo "=== Building 'vboxvideo' module ==="; \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxvideo || exit 1; \
+ if [ -f vboxvideo/vboxvideo.ko ]; then \
+ cp vboxvideo/vboxvideo.ko .; \
+ elif [ -f vboxvideo/vboxvideo.o ]; then \
+ cp vboxvideo/vboxvideo.o .; \
+ fi; \
+ echo; \
+ fi
+
+install-vboxguest:
+ + $(VBOX_QUIET)$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest install
+
+install-vboxsf:
+ + $(VBOX_QUIET_SH)if [ -d vboxsf ]; then \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxsf install; \
+ fi
+
+install-vboxvideo:
+ + $(VBOX_QUIET_SH)if [ -d vboxvideo ]; then \
+ $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxvideo install; \
+ fi
+
+install: install-vboxguest install-vboxsf install-vboxvideo
+
+clean-vboxguest:
+ + $(VBOX_QUIET)$(MAKE) -C vboxguest clean
+ rm -f vboxguest.*o
+
+clean-vboxsf:
+ + $(VBOX_QUIET_SH)if [ -d vboxsf ]; then \
+ $(MAKE) -C vboxsf clean; \
+ fi
+ rm -f vboxsf.*o
+
+clean-vboxvideo:
+ + $(VBOX_QUIET_SH)if [ -d vboxvideo ]; then \
+ $(MAKE) -C vboxvideo clean; \
+ fi
+ rm -f vboxvideo.*o
+
+clean: clean-vboxguest clean-vboxsf clean-vboxvideo
+
+check:
+ $(VBOX_QUIET)$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxguest check
+
+unload:
+ $(VBOX_QUIET)/sbin/rmmod vboxvideo || true
+ $(VBOX_QUIET)/sbin/rmmod vboxvfs || true
+ $(VBOX_QUIET)/sbin/rmmod vboxsf || true
+ $(VBOX_QUIET)/sbin/rmmod vboxguest || true
+
+load: unload
+ $(VBOX_QUIET)/sbin/insmod vboxguest.ko
+ $(VBOX_QUIET)if [ -f vboxsf.ko ]; then /sbin/insmod vboxsf.ko; fi
+ $(VBOX_QUIET)if [ -f vboxvideo.ko ]; then /sbin/insmod vboxvideo.ko; fi
+
+.PHONY: all install clean check unload load \
+ vboxguest vboxsf vboxvideo \
+ install-vboxguest install-vboxsf install-vboxvideo \
+ clean-vboxguest clean-vboxsf clean-vboxvideo
+
+endif # ! KERNELRELEASE
diff --git a/src/VBox/Additions/linux/Makefile.kmk b/src/VBox/Additions/linux/Makefile.kmk
new file mode 100644
index 00000000..014775ca
--- /dev/null
+++ b/src/VBox/Additions/linux/Makefile.kmk
@@ -0,0 +1,449 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for the linux guest additions base directory.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Include sub-makefiles.
+#
+include $(PATH_SUB_CURRENT)/sharedfolders/Makefile.kmk
+include $(PATH_SUB_CURRENT)/drm/Makefile.kmk
+ifdef VBOX_WITH_LIGHTDM_GREETER
+ include $(PATH_SUB_CURRENT)/lightdm-greeter/Makefile.kmk
+endif
+
+
+#
+# Globals
+#
+
+# Basic path components
+VBOX_LNX_ADD_PACKAGE_NAME := VBoxGuestAdditions
+VBOX_LNX_ADD_INST_OUT_DIR := $(PATH_TARGET)/Additions/Installer/linux/
+VBOX_LNX_ADD_INST_DBG_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)debug/
+VBOX_LNX_ADD_INST_STAGE_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)install/
+
+# Installation paths for binaries and files
+VBOX_LNX_ADD_INST_BIN_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)bin/
+VBOX_LNX_ADD_INST_SBIN_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)sbin/
+VBOX_LNX_ADD_INST_LIB_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)lib/
+VBOX_LNX_ADD_INST_OTHER_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)other/
+VBOX_LNX_ADD_INST_KMOD_DIR_BASE := $(VBOX_LNX_ADD_INST_OUT_DIR)src/
+# This is a symbolic link, so no trailing slash.
+VBOX_LNX_ADD_INST_KMOD_PATH := $(VBOX_LNX_ADD_INST_KMOD_DIR_BASE)vboxguest-$(VBOX_VERSION_STRING)
+VBOX_LNX_ADD_INST_INIT_DIR := $(VBOX_LNX_ADD_INST_OUT_DIR)init/
+
+VBOX_LNX_ADD_ARCH_INST_DIRS := \
+ $(VBOX_LNX_ADD_INST_OUT_DIR) \
+ $(VBOX_LNX_ADD_INST_BIN_DIR) \
+ $(VBOX_LNX_ADD_INST_SBIN_DIR) \
+ $(VBOX_LNX_ADD_INST_INIT_DIR)
+
+# Installation paths for debug symbols
+VBOX_LNX_ADD_DBG_BIN_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)bin/
+VBOX_LNX_ADD_DBG_SBIN_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)sbin/
+VBOX_LNX_ADD_DBG_LIB_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)lib/
+VBOX_LNX_ADD_DBG_OTHER_DIR := $(VBOX_LNX_ADD_INST_DBG_DIR)other/
+
+VBOX_LNX_ADD_DBG_DIRS := \
+ $(VBOX_LNX_ADD_DBG_BIN_DIR) \
+ $(VBOX_LNX_ADD_DBG_SBIN_DIR) \
+ $(VBOX_LNX_ADD_DBG_OTHER_DIR)
+
+# Script source directories
+VBOX_PATH_LNX_ADD_INST := $(PATH_SUB_CURRENT)/installer/
+VBOX_REL_LNX_ADD_INST := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_LNX_ADD_INST))
+VBOX_PATH_X11_ADD_INST := $(PATH_ROOT)/src/VBox/Additions/x11/Installer/
+VBOX_REL_X11_ADD_INST := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_X11_ADD_INST))
+VBOX_PATH_LNX_INST_SRC := $(PATH_ROOT)/src/VBox/Installer/linux/
+VBOX_REL_LNX_INST_SRC := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_LNX_INST_SRC))
+VBOX_PATH_LNX_HOST_DRV := $(PATH_ROOT)/src/VBox/HostDrivers/linux/
+VBOX_REL_LNX_HOST_DRV := $(subst $(PATH_ROOT)/src/VBox, ../..,$(VBOX_PATH_LNX_HOST_DRV))
+
+# Unset this to speed up things during makefile hacking.
+VBOX_LNX_ADD_INST_DEP_ON_MAKEFILE := $(MAKEFILE_CURRENT)
+
+
+#
+# Targets
+#
+VBOX_SELINUX_CMPLD := $(PATH_SUB_CURRENT)/selinux-fedora/vbox_x11.pp
+VBOX_LNX_ADD_ARCHIVE.x86 := $(PATH_OUT_BASE)/linux.x86/$(KBUILD_TYPE)/bin/additions/VBoxGuestAdditions-x86.tar.bz2
+VBOX_LNX_ADD_ARCHIVE.amd64 := $(PATH_OUT_BASE)/linux.amd64/$(KBUILD_TYPE)/bin/additions/VBoxGuestAdditions-amd64.tar.bz2
+ifndef VBOX_WITH_COMBINED_LINUX_GUEST_PACKAGE
+ VBOX_LNX_ADD_ARCHIVES := $(PATH_STAGE_BIN)/additions/VBoxGuestAdditions-$(KBUILD_TARGET_ARCH).tar.bz2
+else
+ VBOX_LNX_ADD_ARCHIVES := \
+ $(VBOX_LNX_ADD_ARCHIVE.x86) \
+ $(VBOX_LNX_ADD_ARCHIVE.amd64)
+endif
+BLDDIRS += \
+ $(VBOX_LNX_ADD_ARCH_INST_DIRS) \
+ $(VBOX_LNX_ADD_DBG_DIRS) \
+ $(VBOX_LNX_ADD_INST_STAGE_DIR)
+
+# Use VBOX_WITHOUT_LINUX_GUEST_PACKAGE to skip building the .run installer.
+# This will only take effect if you also use VBOX_WITHOUT_ADDITIONS_ISO.
+PACKING += \
+ $(if-expr !defined(VBOX_WITHOUT_LINUX_GUEST_PACKAGE), $(PATH_STAGE_BIN)/additions/VBoxLinuxAdditions.run,) \
+ $(VBOX_LNX_ADD_ARCHIVES) \
+ $(PATH_STAGE_BIN)/additions/VBoxGuestAdditions-dbg.tar.bz2
+OTHER_CLEAN += \
+ $(PACKING) \
+ $(VBOX_LNX_ADD_INST_KMOD_PATH) \
+ $(foreach file, $(VBOX_LNX_ADD_ARCHIVES), $(VBOX_LNX_ADD_INST_STAGE_DIR)$(subst -r$(VBOX_SVN_REV),,$(notdir $(file))))
+
+
+#
+# Files to install
+#
+VBOX_LNX_ADD_STRIP_BIN += \
+ VBoxControl \
+ VBoxDRMClient \
+ VBoxClient
+ifdef VBOX_WITH_ADDITIONS_SHIPPING_AUDIO_TEST
+ VBOX_LNX_ADD_STRIP_BIN += \
+ VBoxAudioTest
+endif
+
+VBOX_LNX_ADD_STRIP_SBIN += \
+ VBoxService \
+ $(if $(VBOX_WITH_LIGHTDM_GREETER),vbox-greeter)
+
+VBOX_LNX_ADD_STRIP_MOD = \
+ vboxmouse_drv_70.so \
+ vboxmouse_drv_71.so \
+ vboxmouse_drv_13.so \
+ vboxmouse_drv_14.so \
+ vboxmouse_drv_15.so \
+ vboxmouse_drv_16.so \
+ $(addsuffix .so,$(filter-out %_32,$(filter vboxvideo_drv_%,$(DLLS)))) \
+ $(if $(VBOX_WITH_PAM),pam_vbox.so,) \
+ mount.vboxsf
+
+VBOX_LNX_ADD_MOD = \
+ 98vboxadd-xclient \
+ x11config.sh
+
+VBOX_LNX_ADD_STRIP_OBJ = \
+ vboxmouse_drv.o \
+ vboxvideo_drv.o
+
+VBOX_LNX_ADD_INIT = \
+ vboxadd \
+ vboxadd-service \
+ vboxadd-x11
+
+#
+# All the files that go into the archive
+#
+VBOX_LNX_ADD_INST_FILES := \
+ $(addprefix $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_BIN)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR),$(VBOX_LNX_ADD_STRIP_SBIN)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_MOD)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_OBJ)) \
+ $(addprefix $(VBOX_LNX_ADD_INST_INIT_DIR),$(VBOX_LNX_ADD_INIT))
+
+VBOX_LNX_ADD_DBG_FILES := \
+ $(addprefix $(VBOX_LNX_ADD_DBG_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)) \
+ $(addprefix $(VBOX_LNX_ADD_DBG_SBIN_DIR),$(VBOX_LNX_ADD_STRIP_SBIN)) \
+ $(addprefix $(VBOX_LNX_ADD_DBG_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)) \
+ $(addprefix $(VBOX_LNX_ADD_DBG_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD))
+
+ifdef VBOX_WITH_LIGHTDM_GREETER_PACKING
+ VBOX_LNX_ADD_INST_FILES += \
+ $(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR),vbox-greeter)
+endif
+
+# Cleanup of the installer directory files
+OTHER_CLEAN += $(VBOX_LNX_ADD_INST_FILES) $(VBOX_LNX_ADD_DBG_FILES)
+
+# pattern rules for copying the debug info from the VBOX_LNX_ADD_STRIP_* files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_DBG_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)): \
+ $(VBOX_LNX_ADD_DBG_BIN_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+$(addprefix $(VBOX_LNX_ADD_DBG_SBIN_DIR),$(VBOX_LNX_ADD_STRIP_SBIN)): \
+ $(VBOX_LNX_ADD_DBG_SBIN_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+$(addprefix $(VBOX_LNX_ADD_DBG_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)): \
+ $(VBOX_LNX_ADD_DBG_LIB_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+$(addprefix $(VBOX_LNX_ADD_DBG_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD)): \
+ $(VBOX_LNX_ADD_DBG_OTHER_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_TOOL,copydbg,$<,$@)
+ $(QUIET)objcopy --only-keep-debug $< $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_BIN files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_STRIP_BIN)): \
+ $(VBOX_LNX_ADD_INST_BIN_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_BIN_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_BIN_DIR),$(VBOX_LNX_ADD_DBG_BIN_DIR),$@) $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_SBIN files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR), \
+ $(filter-out vbox-greeter,$(VBOX_LNX_ADD_STRIP_SBIN))): \
+ $(VBOX_LNX_ADD_INST_SBIN_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_SBIN_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_SBIN_DIR),$(VBOX_LNX_ADD_DBG_SBIN_DIR),$@) $@
+
+# pattern rule for stripping and copying vbox-greeter to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_SBIN_DIR),vbox-greeter): \
+ $(VBOX_LNX_ADD_INST_SBIN_DIR)% : $(subst linux.amd64/release,linux.amd64/release/greeter,$(subst linux.x86/release,linux.x86/release/greeter,$(PATH_STAGE_BIN)))/additions/% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $< $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_LIB files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_LIB_DIR),$(VBOX_LNX_ADD_STRIP_LIB)): \
+ $(VBOX_LNX_ADD_INST_LIB_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_LIB_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_LIB_DIR),$(VBOX_LNX_ADD_DBG_LIB_DIR),$@) $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_MOD files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_MOD)): \
+ $(VBOX_LNX_ADD_INST_OTHER_DIR)% : $(PATH_STAGE_BIN)/additions/% \
+ $(VBOX_LNX_ADD_DBG_OTHER_DIR)% \
+ | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ $(QUIET)$(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+ $(QUIET)objcopy --add-gnu-debuglink=$(subst $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_DBG_OTHER_DIR),$@) $@
+
+# pattern rule for stripping and copying the VBOX_LNX_ADD_STRIP_OBJ files to the installation directory
+$(addprefix $(VBOX_LNX_ADD_INST_OTHER_DIR),$(VBOX_LNX_ADD_STRIP_OBJ)): \
+ $(VBOX_LNX_ADD_INST_OTHER_DIR)% : $(PATH_STAGE_BIN)/additions/% | $$(dir $$@)
+ $(call MSG_INST_FILE,$<,$@)
+ifeq ($(VBOX_DO_STRIP),)
+ $(QUIET)$(INSTALL) -m 0644 $< $@
+else # strip to temp file because of umask.
+ $(QUIET)objcopy --strip-unneeded -R .comment $< $@.tmp
+ $(QUIET)$(INSTALL) -m 0644 $@.tmp $@
+ $(QUIET)$(RM) -f -- $@.tmp
+endif
+
+include $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/linux/files_vboxguest
+VBOX_LNX_ADD_INST_FILES_VBOXGUEST = $(patsubst =>%,$(PATH_STAGE_BIN)/additions/src/vboxguest/%,$(filter =>%,$(subst =>, =>,$(subst $(DQUOTE),,$(FILES_VBOXGUEST_NOBIN)))))
+include $(PATH_ROOT)/src/VBox/Additions/linux/sharedfolders/files_vboxsf
+VBOX_LNX_ADD_INST_FILES_VBOXSF = $(patsubst =>%,$(PATH_STAGE_BIN)/additions/src/vboxsf/%,$(filter =>%,$(subst =>, =>,$(subst $(DQUOTE),,$(FILES_VBOXSF_NOBIN)))))
+include $(PATH_ROOT)/src/VBox/Additions/linux/drm/files_vboxvideo_drv
+VBOX_LNX_ADD_INST_FILES_VBOXVIDEO = $(patsubst =>%,$(PATH_STAGE_BIN)/additions/src/vboxvideo/%,$(filter =>%,$(subst =>, =>,$(subst $(DQUOTE),,$(FILES_VBOXVIDEO_DRM_NOBIN)))))
+
+# special rule for the kernel modules
+$(VBOX_LNX_ADD_INST_KMOD_DIR_BASE): \
+ $(PATH_STAGE_BIN)/additions/src/ \
+ $(PATH_ROOT)/Version.kmk \
+ | $(dir $@)
+ $(call MSG_INST_SYM,$<,$@)
+ $(QUIET)$(RM) -Rf $@
+ $(QUIET)$(MKDIR) $@
+ $(QUIET)$(LN_SYMLINK) $< $(VBOX_LNX_ADD_INST_KMOD_PATH)
+
+INSTALLS += GuestDrivers-src
+GuestDrivers-src_INST = $(INST_ADDITIONS)src/
+GuestDrivers-src_MODE = a+r,u+w
+GuestDrivers-src_SOURCES = Makefile
+
+
+INSTALLS += lnx_add_inst-exec
+lnx_add_inst-exec_INST = $(VBOX_LNX_ADD_INST_OTHER_DIR)
+lnx_add_inst-exec_INSTTYPE = stage
+lnx_add_inst-exec_EXEC_SOURCES = \
+ $(VBOX_REL_X11_ADD_INST)98vboxadd-xclient \
+ $(VBOX_REL_X11_ADD_INST)x11config.sh \
+ $(VBOX_REL_LNX_INST_SRC)check_module_dependencies.sh
+
+
+INSTALLS += lnx_add_inst-noexec
+lnx_add_inst-noexec_INST = $(VBOX_LNX_ADD_INST_OTHER_DIR)
+lnx_add_inst-noexec_INSTTYPE = stage
+lnx_add_inst-noexec_SOURCES = \
+ $(VBOX_REL_X11_ADD_INST)vboxclient.desktop \
+ $(VBOX_REL_X11_ADD_INST)vboxvideo.ids \
+ $(if $(VBOX_WITH_LIGHTDM_GREETER_PACKING),lightdm-greeter/vbox-greeter.desktop,) \
+ selinux-fedora/vbox_x11.pp \
+ selinux-fedora/vbox_accel.pp
+
+INSTALLS += lnx_add_inst-license
+lnx_add_inst-license_INST = $(VBOX_LNX_ADD_INST_OUT_DIR)
+lnx_add_inst-license_INSTTYPE = stage
+lnx_add_inst-license_SOURCES = \
+ $(VBOX_BRAND_LICENSE_TXT)=>LICENSE
+
+
+#
+# We need our routines.sh and the uninstallation scripts in the staging
+# directory too
+#
+INSTALLS += LnxAdd-scripts
+LnxAdd-scripts_INST = $(VBOX_LNX_ADD_INST_STAGE_DIR)
+LnxAdd-scripts_INSTTYPE = stage
+LnxAdd-scripts_SOURCES = \
+ $(VBOX_REL_LNX_ADD_INST)deffiles
+LnxAdd-scripts_EXEC_SOURCES = \
+ $(VBOX_REL_LNX_INST_SRC)routines.sh
+
+ifdef VBOX_WITH_LIGHTDM_GREETER_PACKING
+ LnxAdd-scripts_EXEC_SOURCES += \
+ $(VBOX_REL_LNX_ADD_INST)module-autologon.sh=>installer/module-autologon
+endif
+
+
+#
+# And the init scripts
+#
+INSTALLS += LnxAdd-init-scripts
+LnxAdd-init-scripts_INST = $(VBOX_LNX_ADD_INST_INIT_DIR)
+LnxAdd-init-scripts_INSTTYPE = stage
+LnxAdd-init-scripts_EXEC_SOURCES = \
+ $(foreach i,$(VBOX_LNX_ADD_INIT), installer/$(i).sh=>$(i))
+
+# this file needs editing before it can be included in the generic installer.
+$(VBOX_LNX_ADD_INST_STAGE_DIR)install.sh: \
+ $(VBOX_PATH_LNX_ADD_INST)install.sh.in \
+ $(VBOX_VERSION_STAMP) | $$(dir $$@)
+ $(RM) -f -- $@
+ $(QUIET)$(SED) \
+ -e "s;_VERSION_;$(VBOX_VERSION_STRING);g" \
+ -e "s;_BUILDTYPE_;$(KBUILD_TYPE);g" \
+ -e "s;_USERNAME_;$(USERNAME);g" \
+ --output $@ \
+ $<
+ $(QUIET)$(CHMOD) 0755 $@
+OTHER_CLEAN += \
+ $(VBOX_LNX_ADD_INST_OUT_DIR)install.sh \
+ $(VBOX_LNX_ADD_INST_STAGE_DIR)install.sh
+
+
+#
+# Build test for the Guest Additions kernel modules (kmk check).
+#
+$(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,vboxvideo-src,,)
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
+# All the files that go into our archive
+VBOX_LNX_ADD_ARCH_FILES = \
+ $(lnx_add_inst-noexec_2_STAGE_TARGETS) \
+ $(lnx_add_inst-exec_2_STAGE_TARGETS) \
+ $(lnx_add_inst-license_2_STAGE_TARGETS) \
+ $(VBOX_LNX_ADD_INST_FILES) \
+ $(VBOX_LNX_ADD_INST_KMOD_DIR_BASE)
+
+VBOX_LNX_ADD_INST_ARCH_DEPS := \
+ $(VBOX_LNX_ADD_ARCH_FILES) \
+ $(VBOX_LNX_ADD_INST_DEP_ON_MAKEFILE) \
+ $(VBOX_VERSION_STAMP) \
+ $(VBOX_LNX_ADD_INST_FILES_VBOXGUEST) \
+ $(VBOX_LNX_ADD_INST_FILES_VBOXSF) \
+ $(VBOX_LNX_ADD_INST_FILES_VBOXVIDEO)
+
+#
+# .tar.bz2 for converting into .run
+#
+$(VBOX_LNX_ADD_ARCHIVE.$(KBUILD_TARGET_ARCH)): \
+ $(VBOX_LNX_ADD_INST_ARCH_DEPS)
+ $(call MSG_L1,Packing $@)
+ $(QUIET)$(RM) -f -- $(wildcard $(dir $@)VBoxGuestAdditions-*r*.tar.bz2)
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(CHMOD) 0755 $(VBOX_LNX_ADD_ARCH_INST_DIRS)
+ifdef VBOX_USE_PBZIP2
+ $(QUIET)tar --dereference --owner 0 --group 0 -cRf $(patsubst %.bz2,%,$@) \
+ -C $(VBOX_LNX_ADD_INST_OUT_DIR) \
+ LICENSE bin init lib other sbin src \
+ $(if $(filter $(KBUILD_TYPE),debug),debug)
+ $(QUIET)pbzip2 $(patsubst %.bz2,%,$@)
+else
+ $(QUIET)tar --dereference --owner 0 --group 0 --ignore-failed-read -cjRf $@ \
+ -C $(VBOX_LNX_ADD_INST_OUT_DIR) \
+ LICENSE bin init lib other sbin src \
+ $(if $(filter $(KBUILD_TYPE),debug),debug)
+endif
+ $(QUIET)$(CHMOD) 0644 $@
+
+
+#
+# .tar.bz2 containing the debug information
+#
+$(PATH_STAGE_BIN)/additions/VBoxGuestAdditions-dbg.tar.bz2: \
+ $(VBOX_LNX_ADD_DBG_FILES) \
+ $(VBOX_LNX_ADD_INST_DEP_ON_MAKEFILE)
+ $(call MSG_L1,Packing $@)
+ $(QUIET)$(RM) -f -- $@ $(patsubst %.bz2,%,$@)
+ $(QUIET)$(MKDIR) -p $(@D)
+ $(QUIET)$(CHMOD) 0755 $(VBOX_LNX_ADD_DBG_DIRS)
+ifdef VBOX_USE_PBZIP2
+ $(QUIET)tar --dereference --owner 0 --group 0 -cRf $(patsubst %.bz2,%,$@) \
+ -C $(VBOX_LNX_ADD_INST_DBG_DIR) \
+ bin lib sbin
+ $(QUIET)pbzip2 $(patsubst %.bz2,%,$@)
+else
+ $(QUIET)tar --dereference --owner 0 --group 0 --ignore-failed-read -cjRf $@ \
+ -C $(VBOX_LNX_ADD_INST_DBG_DIR) \
+ bin lib sbin
+endif
+ $(QUIET)$(CHMOD) 0644 $@
+
+
+#
+# Build the Linux Guest Additions self extracting installer.
+#
+# Note that $(PATH_SUB_CURRENT) was changed by subfooter.kmk above and
+# any references should be made via variables assigned a know value via := .
+#
+$(PATH_STAGE_BIN)/additions/VBoxLinuxAdditions.run: \
+ $(VBOX_LNX_ADD_ARCHIVES) \
+ $(VBOX_LNX_ADD_INST_STAGE_DIR)install.sh \
+ $$(LnxAdd-scripts_2_STAGE_TARGETS) \
+ $(VBOX_VERSION_STAMP)
+# Remove any archives left over from previous builds so that they don't
+# end up in our installer as well.
+ $(QUIET)$(RM) -f $(foreach file, $(wildcard $(VBOX_LNX_ADD_INST_STAGE_DIR)$(VBOX_LNX_ADD_PACKAGE_NAME)-*.tar.bz2), $(file))
+ $(QUIET)$(foreach file, $(VBOX_LNX_ADD_ARCHIVES), \
+ $(CP) -f $(file) $(VBOX_LNX_ADD_INST_STAGE_DIR)$(subst -r$(VBOX_SVN_REV),,$(notdir $(file)))$(NLTAB) )
+ $(QUIET)$(VBOX_MAKESELF) --nocomp $(if $(VBOX_OSE),,--tar-no-format) $(VBOX_LNX_ADD_INST_STAGE_DIR) $@ \
+ "VirtualBox $(VBOX_VERSION_STRING) Guest Additions for Linux" \
+ /bin/sh ./install.sh "\$$0"
diff --git a/src/VBox/Additions/linux/drm/.scm-settings b/src/VBox/Additions/linux/drm/.scm-settings
new file mode 100644
index 00000000..8e7c51aa
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/.scm-settings
@@ -0,0 +1,35 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for linux drm driver.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+#Sources are MIT licensed for simplier upstreaming, several files are external.
+/*.c|/*.h: --license-based-on-mit --no-convert-tabs
+/vbox_hgsmi.c: --license-mit
+/Makefile.module.kms: --treat-as Makefile
+
+--filter-out-files /README.testing
+
diff --git a/src/VBox/Additions/linux/drm/Makefile.kmk b/src/VBox/Additions/linux/drm/Makefile.kmk
new file mode 100644
index 00000000..82e9e297
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/Makefile.kmk
@@ -0,0 +1,52 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the vboxvideo DRM module (linux kernel OpenGL module).
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Populate FILES_VBOXVIDEO_DRM_NOBIN
+#
+INSTALLS += vboxvideo-src
+include $(PATH_SUB_CURRENT)/files_vboxvideo_drv
+vboxvideo-src_DEPS = \
+ $(PATH_ROOT)/src/VBox/Additions/linux/drm/files_vboxvideo_drv \
+ $(PATH_ROOT)/src/VBox/Additions/linux/drm/indent.sed
+vboxvideo-src_INST = $(INST_ADDITIONS)src/vboxvideo/
+vboxvideo-src_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXVIDEO_DRM_NOBIN))
+vboxvideo-src_EXEC_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXVIDEO_DRM_BIN))
+vboxvideo-src_INSTALLER = $(RM_EXT) -f -- "$2" && \
+ $(if $(filter %.c %.h,$2),$(SED) -f $(PATH_ROOT)/src/VBox/Additions/linux/drm/indent.sed \
+ --output "$2" "$1",$(CP_EXT) "$1" "$2") && \
+ $(CHMOD_EXT) "$(if $(mode),$(mode),0644)" "$2"
+
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/linux/drm/Makefile.module.kms b/src/VBox/Additions/linux/drm/Makefile.module.kms
new file mode 100644
index 00000000..99f4dab3
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/Makefile.module.kms
@@ -0,0 +1,72 @@
+# $Id: Makefile.module.kms $
+## @file
+# VirtualBox Guest Additions Module Makefile.
+#
+# (For 2.6.x this file must be 'Makefile'!)
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Linux kbuild sets this to our source directory if we are called from there
+obj ?= $(CURDIR)
+include $(obj)/Makefile-header.gmk
+VBOXDRM_DIR = $(VBOX_MODULE_SRC_DIR)
+
+# We want to build on Linux 3.11 and later and on all EL 7 kernels.
+VBOX_BUILD =
+ifneq ($(filter-out 1.% 2.% 3.0 3.0.% 3.1 3.1.% 3.2 3.2.% 3.3 3.3.% 3.4 3.4.% 3.5 3.5.% 3.6 3.6.% 3.7 3.7.% 3.8 3.8.% 3.9 3.9.% 3.10 3.10.%,$(KERN_VER)),)
+ VBOX_BUILD = 1
+endif
+ifeq ($(filter-out %.el7.x86_64,$(KERN_VER)),)
+ VBOX_BUILD = 1
+endif
+
+ifneq ($(VBOX_BUILD),)
+
+VBOXMOD_NAME = vboxvideo
+VBOXMOD_OBJS = \
+ hgsmi_base.o \
+ modesetting.o \
+ vbox_drv.o \
+ vbox_fb.o \
+ vbox_irq.o \
+ vbox_main.o \
+ vbox_mode.o \
+ vbox_ttm.o \
+ vbva_base.o \
+ vbox_prime.o \
+ vbox_hgsmi.o
+VBOXMOD_INCL = \
+ $(VBOXDRM_DIR) \
+ $(KERN_INCL)/drm
+
+include $(obj)/Makefile-footer.gmk
+
+else # !VBOX_BUILD
+
+ all:
+ install:
+ clean:
+
+endif # !VBOX_BUILD
+
diff --git a/src/VBox/Additions/linux/drm/README.testing b/src/VBox/Additions/linux/drm/README.testing
new file mode 100644
index 00000000..72cd96c3
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/README.testing
@@ -0,0 +1,13 @@
+This document lists things which have been known to fail in the past with the
+drm video driver and which should be tested regularly, e.g. when making code
+changes or before releases.
+
+ * Test that "auto-resize" is enabled in the GUI if user space supports it.
+ * Test that old versions of Plymouth which do not report rectangles
+ (pre-0.9.0/2014-05-20) update the screen correctly.
+ * Having valid, non-overlapping offset hints on all screens has caused
+ mouse integration and/or screen updates to break for fbdev, X.Org or
+ GNOME Shell/Wayland.
+ * Note that if a multi-screen VM is booted with only one screen enabled
+ the fbdev console will be disabled on the others until reboot. Test this
+ configuration.
diff --git a/src/VBox/Additions/linux/drm/files_vboxvideo_drv b/src/VBox/Additions/linux/drm/files_vboxvideo_drv
new file mode 100755
index 00000000..04092484
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/files_vboxvideo_drv
@@ -0,0 +1,59 @@
+#!/bin/sh
+# $Id: files_vboxvideo_drv $
+## @file
+# Shared file between Makefile.kmk and export_modules.sh.
+#
+
+#
+# Copyright (C) 2011-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+FILES_VBOXVIDEO_DRM_NOBIN=" \
+ ${PATH_OUT}/version-generated.h=>version-generated.h \
+ ${PATH_OUT}/revision-generated.h=>revision-generated.h \
+ ${PATH_OUT}/product-generated.h=>product-generated.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideo.h=>vboxvideo.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideoGuest.h=>vboxvideo_guest.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIChannels.h=>hgsmi_channels.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIChSetup.h=>hgsmi_ch_setup.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIContext.h=>hgsmi_context.h \
+ ${PATH_ROOT}/include/VBox/Graphics/HGSMIDefs.h=>hgsmi_defs.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideoErr.h=>vbox_err.h \
+ ${PATH_ROOT}/include/VBox/Graphics/VBoxVideoVBE.h=>vboxvideo_vbe.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp=>hgsmi_base.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp=>modesetting.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp=>vbva_base.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_drv.c=>vbox_drv.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_drv.h=>vbox_drv.h \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_fb.c=>vbox_fb.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_irq.c=>vbox_irq.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_main.c=>vbox_main.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_mode.c=>vbox_mode.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_prime.c=>vbox_prime.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_ttm.c=>vbox_ttm.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/vbox_hgsmi.c=>vbox_hgsmi.c \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \
+ ${PATH_ROOT}/src/VBox/Additions/linux/drm/Makefile.module.kms=>Makefile \
+"
+
+FILES_VBOXVIDEO_DRM_BIN=" \
+"
diff --git a/src/VBox/Additions/linux/drm/indent.sed b/src/VBox/Additions/linux/drm/indent.sed
new file mode 100644
index 00000000..4401ca44
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/indent.sed
@@ -0,0 +1,285 @@
+# Oracle VM VirtualBox
+# VirtualBox to Linux kernel coding style conversion script.
+
+#
+# Copyright (C) 2017-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# This script is for converting code inside the vboxvideo module to Linux
+# kernel coding style. It assumes correct VirtualBox coding style, will break
+# break if the coding style is wrong (e.g. tab instead of spaces) and is not
+# indended to be a generic solution: for example, identifiers will be
+# translated case by case, not algorithmically. It also assumes that any
+# flexibility in either coding style will be used where possible to make the
+# code conform to both at once.
+
+# Replace up to six leading groups of four spaces with tabs.
+s/^ */\t\t\t\t\t\t/g
+s/^ /\t\t\t\t\t/g
+s/^ /\t\t\t\t/g
+s/^ /\t\t\t/g
+s/^ /\t\t/g
+s/^ /\t/g
+
+# Change various symbols and file names to fit kernel conventions.
+
+# Miscellaneous:
+# Remove @file headers.
+\|/\*\* @file| {
+:start
+ N
+ s|\*/|\*/|g
+ T start
+ N
+ d
+}
+/^\/\* \$Id:.*\*\/$/d
+/^typedef .* HGSMIOFFSET;/d
+/^typedef .* HGSMISIZE;/d
+s/^#\( *\)include <\([^/]*\)>/#\1include "\2"/g
+
+# File names:
+s/\bHGSMIBase\.h\b/vbox_drv.h/g
+s/\bHGSMIChannels\.h\b/hgsmi_channels.h/g
+s/\bHGSMIChSetup\.h\b/hgsmi_ch_setup.h/g
+s/\bHGSMIContext\.h\b/hgsmi_context.h/g
+s/\bHGSMIDefs\.h\b/hgsmi_defs.h/g
+s/\bVBoxVideoGuest\.h\b/vboxvideo_guest.h/g
+s/\bVBoxVideo\.h\b/vboxvideo.h/g
+s/\bVBoxVideoIPRT\.h\b/vbox_err.h/g
+s/\bVBoxVideoVBE\.h\b/vboxvideo_vbe.h/g
+
+# Function names:
+s/\btestQueryConf\b/hgsmi_test_query_conf/g
+s/\bVBoxHGSMIBufferAlloc\b/hgsmi_buffer_alloc/g
+s/\bVBoxHGSMIBufferFree\b/hgsmi_buffer_free/g
+s/\bVBoxHGSMIBufferSubmit\b/hgsmi_buffer_submit/g
+s/\bVBoxHGSMICursorPosition\b/hgsmi_cursor_position/g
+s/\bVBoxHGSMIGetModeHints\b/hgsmi_get_mode_hints/g
+s/\bVBoxHGSMIProcessDisplayInfo\b/hgsmi_process_display_info/g
+s/\bVBoxHGSMIReportFlagsLocation\b/hgsmi_report_flags_location/g
+s/\bVBoxHGSMISendCapsInfo\b/hgsmi_send_caps_info/g
+s/\bVBoxHGSMIUpdateInputMapping\b/hgsmi_update_input_mapping/g
+s/\bVBoxHGSMIUpdatePointerShape\b/hgsmi_update_pointer_shape/g
+s/\bvboxHwBufferAvail\b/vbva_buffer_available/g
+s/\bvboxHwBufferEndUpdate\b/vbva_buffer_end_update/g
+s/\bvboxHwBufferFlush\b/vbva_buffer_flush/g
+s/\bvboxHwBufferPlaceDataAt\b/vbva_buffer_place_data_at/g
+s/\bvboxHwBufferWrite\b/vbva_write/g
+s/\bVBoxQueryConfHGSMI\b/hgsmi_query_conf/g
+s/\bVBoxVBVABufferBeginUpdate\b/vbva_buffer_begin_update/g
+s/\bVBoxVBVABufferEndUpdate\b/vbva_buffer_end_update/g
+s/\bVBoxVBVADisable\b/vbva_disable/g
+s/\bVBoxVBVAEnable\b/vbva_enable/g
+s/\bvboxVBVAInformHost\b/vbva_inform_host/g
+s/\bvboxVBVASetupBufferContext\b/vbva_setup_buffer_context/g
+s/\bVBVO_PORT_READ_U8\b/inb/g
+s/\bVBVO_PORT_READ_U16\b/inw/g
+s/\bVBVO_PORT_READ_U32\b/inl/g
+s/\bVBVO_PORT_WRITE_U8\b *( *\(\b[^(),]*\b\) *, *\(\b[^(),]*\b\) *)/outb(\2, \1)/g
+s/\bVBVO_PORT_WRITE_U16\b *( *\(\b[^(),]*\b\) *, *\(\b[^(),]*\b\) *)/outw(\2, \1)/g
+s/\bVBVO_PORT_WRITE_U32\b *( *\(\b[^(),]*\b\) *, *\(\b[^(),]*\b\) *)/outl(\2, \1)/g
+s/\bVBVO_PORT_WRITE_U[0-9]*\b/VBVO_PORT_WRITE_statement_should_be_on_one_line/g
+
+# Macros:
+s/\b_1K\b/1024/g
+s/\b_4M\b/4*1024*1024/g
+s/\bAssert\b\([^;]*\);/WARN_ON_ONCE(!(\1));/g
+s/\bAssertCompile\b/assert_compile/g
+s/\bAssertCompileSize\b/assert_compile_size/g
+s/\bAssertPtr\b\([^;]*\);/WARN_ON_ONCE(!(\1));/g
+s/\bAssertPtrReturn\b/assert_ptr_return/g
+/AssertPtrNullReturnVoid/d
+s/\bAssertRC\b\([^;]*\);/WARN_ON_ONCE(RT_FAILURE\1);/g
+s/\bAssertRC\b/Assert_RC_statement_should_be_on_one_line/g
+s/\bDECLCALLBACK\b(\([^)]*\))/\1/g
+ s/\bDECLCALLBACKTYPE\b(\([^,)]*\), *\([^,)]*\), *(\([^;)]*\) *) *)/\1 \2(\3)/g
+s/\bDECLCALLBACKMEMBER\b(\([^,)]*\), *\([^,)]*\), *(\([^;)]*\) *) *)/\1 (*\2)(\3)/g
+s/^\bDECLHIDDEN\b(\([^)]*\))/\1/g
+s/\bDECLINLINE\b(\([^)]*\))/static inline \1/g
+s/\bRT_BIT\b/BIT/g
+s/\bRT_BOOL\b(\([^)]*\))/(!!(\1))/g
+/RT_C_DECLS/d
+s/\bUINT16_MAX\b/U16_MAX/g
+s/\bUINT32_MAX\b/U32_MAX/g
+s/\bUINT32_C\b(\(.*\))/\1u/g
+s/!RT_VALID_PTR(/WARN_ON(!/g
+s/\bRT_UNTRUSTED_VOLATILE_HOST\b//g
+s/\bRT_UNTRUSTED_VOLATILE_GUEST\b//g
+s/\bRT_UNTRUSTED_VOLATILE_HSTGST\b//g
+
+# Type names:
+s/\bint32_t\b/s32/g
+s/\buint8_t\b/u8/g
+s/\buint16_t\b/u16/g
+s/\buint32_t\b/u32/g
+s/(HGSMIBUFFERLOCATION \*)//g # Remove C++ casts from void.
+s/typedef struct HGSMIBUFFERLOCATION/struct hgsmi_buffer_location/g
+s/struct HGSMIBUFFERLOCATION/struct hgsmi_buffer_location/g
+s/} HGSMIBUFFERLOCATION/}/g
+s/\bHGSMIBUFFERLOCATION\b/struct hgsmi_buffer_location/g
+s/\([^*] *\)\bPHGSMIGUESTCOMMANDCONTEXT\b/\1struct gen_pool */g
+s/(HGSMIHOSTFLAGS \*)//g # Remove C++ casts from void.
+s/typedef struct HGSMIHOSTFLAGS/struct hgsmi_host_flags/g
+s/struct HGSMIHOSTFLAGS/struct hgsmi_host_flags/g
+s/} HGSMIHOSTFLAGS/}/g
+s/\bHGSMIHOSTFLAGS\b/struct hgsmi_host_flags/g
+s/\bHGSMIOFFSET\b/u32/g
+s/\bHGSMISIZE\b/u32/g
+s/\bRTRECT\b/void/g
+s/(VBVABUFFERCONTEXT \*)//g # Remove C++ casts from void.
+s/struct VBVABUFFERCONTEXT/struct vbva_buf_context/g
+s/} VBVABUFFERCONTEXT/} vbva_buf_context/g
+s/\bVBVABUFFERCONTEXT\b/struct vbva_buf_context/g
+s/\([^*] *\)\bPVBVABUFFERCONTEXT\b/\1struct vbva_buf_context */g
+s/(VBVACAPS \*)//g # Remove C++ casts from void.
+s/struct VBVACAPS/struct vbva_caps/g
+s/} VBVACAPS/} vbva_caps/g
+s/\bVBVACAPS\b/struct vbva_caps/g
+s/(VBVACONF32 \*)//g # Remove C++ casts from void.
+s/struct VBVACONF32/struct vbva_conf32/g
+s/} VBVACONF32/} vbva_conf32/g
+s/\bVBVACONF32\b/struct vbva_conf32/g
+s/(VBVACURSORPOSITION \*)//g # Remove C++ casts from void.
+s/struct VBVACURSORPOSITION/struct vbva_cursor_position/g
+s/} VBVACURSORPOSITION/} vbva_cursor_position/g
+s/\bVBVACURSORPOSITION\b/struct vbva_cursor_position/g
+s/(VBVAENABLE_EX \*)//g # Remove C++ casts from void.
+s/struct VBVAENABLE_EX/struct vbva_enable_ex/g
+s/} VBVAENABLE_EX/} vbva_enable_ex/g
+s/\bVBVAENABLE_EX\b/struct vbva_enable_ex/g
+s/(VBVAMOUSEPOINTERSHAPE \*)//g # Remove C++ casts from void.
+s/struct VBVAMOUSEPOINTERSHAPE/struct vbva_mouse_pointer_shape/g
+s/} VBVAMOUSEPOINTERSHAPE/} vbva_mouse_pointer_shape/g
+s/\bVBVAMOUSEPOINTERSHAPE\b/struct vbva_mouse_pointer_shape/g
+s/(VBVAMODEHINT \*)//g # Remove C++ casts from void.
+s/struct VBVAMODEHINT/struct vbva_modehint/g
+s/} VBVAMODEHINT/} vbva_modehint/g
+s/\bVBVAMODEHINT\b/struct vbva_modehint/g
+s/(VBVAQUERYMODEHINTS \*)//g # Remove C++ casts from void.
+s/struct VBVAQUERYMODEHINTS/struct vbva_query_mode_hints/g
+s/} VBVAQUERYMODEHINTS/} vbva_query_mode_hints/g
+s/\bVBVAQUERYMODEHINTS\b/struct vbva_query_mode_hints/g
+s/(VBVAREPORTINPUTMAPPING \*)//g # Remove C++ casts from void.
+s/struct VBVAREPORTINPUTMAPPING/struct vbva_report_input_mapping/g
+s/} VBVAREPORTINPUTMAPPING/} vbva_report_input_mapping/g
+s/\bVBVAREPORTINPUTMAPPING\b/struct vbva_report_input_mapping/g
+
+# Variable and parameter names:
+s/\baRecords\b/records/g
+s/\bau8Data\b/data/g
+s/\bau32Reserved\b/reserved/g
+s/\bBase\b/base/g
+s/\bbEnable\b/enable/g
+s/\bbRc\b/ret/g
+s/\bcb\b/len/g
+s/\bcbBuffer\b/buffer_length/g
+s/\bcbChunk\b/chunk/g
+s/\bcbData\b/data_len/g
+s/\bcbHintsStructureGuest\b/hints_structure_guest_size/g
+s/\bcbHwBufferAvail\b/available/g
+s/\bcbLength\b/len/g
+s/\bcbLocation\b/buf_len/g
+s/\bcbPartialWriteThreshold\b/partial_write_tresh/g ## @todo fix this?
+s/\bcbPitch\b/pitch/g
+s/\bcbPixels\b/pixel_len/g
+s/\bcBPP\b/bpp/g
+s/\bcbRecord\b/len_and_flags/g ## @todo fix this?
+s/\bcDisplay\b/display/g
+s/\bcHeight\b/height/g
+s/\bcHintsQueried\b/hints_queried_count/g
+s/\bcHotX\b/hot_x/g
+s/\bcHotY\b/hot_y/g
+s/\bcOriginX\b/origin_x/g
+s/\bcOriginY\b/origin_y/g
+s/\bcScreen\b/screen/g
+s/\bcScreens\b/screens/g
+s/\bcWidth\b/width/g
+s/\bfCaps\b/caps/g
+s/\bfFlags\b/flags/g
+s/\bfHwBufferOverflow\b/buffer_overflow/g
+s/\bfReportPosition\b/report_position/g
+s/\bfu32Flags\b/flags/g
+s/\bhostFlags\b/host_flags/g
+s/\bi32Diff\b/diff/g
+s/\bi32OriginX\b/origin_x/g
+s/\bi32OriginY\b/origin_y/g
+s/\bi32Result\b/result/g
+s/\bindexRecordFirst\b/first_record_index/g
+s/\bindexRecordFree\b/free_record_index/g
+s/\bindexRecordNext\b/next/g
+s/\boff32Data\b/data_offset/g
+s/\boff32Free\b/free_offset/g
+s/\boffLocation\b/location/g
+s/\boffStart\b/start_offset/g
+s/\boffVRAMBuffer\b/buffer_offset/g
+s/\bpaHints\b/hints/g
+s/\bpCtx\b/ctx/g
+s/\bpPixels\b/pixels/g
+s/\bpRecord\b/record/g
+s/\bpulValue\b/value_ret/g
+s/\bpVBVA\b/vbva/g
+s/\bpxHost\b/x_host/g
+s/\bpyHost\b/y_host/g
+s/\bu16BitsPerPixel\b/bits_per_pixel/g
+s/\bu16Flags\b/flags/g
+s/\bu32BytesTillBoundary\b/bytes_till_boundary/g
+s/\bu32Flags\b/flags/g
+s/\bu32Height\b/height/g
+s/\bu32HostEvents\b/host_events/g
+s/\bu32HostFlags\b/host_flags/g
+s/\bu32HotX\b/hot_x/g
+s/\bu32HotY\b/hot_y/g
+s/\bu32Index\b/index/g
+s/\bu32LineSize\b/line_size/g
+s/\bu32Offset\b/offset/g
+s/\bu32Reserved\b/reserved/g
+s/\bu32ScreenId\b/screen_id/g
+s/\bu32StartOffset\b/start_offset/g
+s/\bu32SupportedOrders\b/supported_orders/g
+s/\bu32Value\b/value/g
+s/\bu32ViewIndex\b/view_index/g
+s/\bu32Width\b/width/g
+s/\bulValue\b/value/g
+
+# Header file guard:
+s/__HGSMIChannels_h__/__HGSMI_CHANNELS_H__/g
+s/VBOX_INCLUDED_Graphics_HGSMIChSetup_h/__HGSMI_CH_SETUP_H__/g
+
+# And move braces. This must be the last expression as it jumps to the next
+# line.
+/..*$/ {
+ N
+ s/^\([\t ][\t ]*\)} *\n[\t ]*else/\1} else/g
+ t continue_else
+ b try_brace
+:continue_else
+ N
+:try_brace
+ s/^\([\t ].*\)\n[\t ][\t ]*{/\1 {/g
+ s/^\([^#()]*\)\n[\t ]*{/\1 {/g
+ t done_brace
+ P
+ D
+:done_brace
+ p
+ d
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_drv.c b/src/VBox/Additions/linux/drm/vbox_drv.c
new file mode 100644
index 00000000..3e4cbc60
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_drv.c
@@ -0,0 +1,455 @@
+/* $Id: vbox_drv.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ * This file is based on ast_drv.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include <linux/module.h>
+#include <linux/console.h>
+#include <linux/vt_kern.h>
+
+#include "vbox_drv.h"
+
+#include <drm/drm_crtc_helper.h>
+#if RTLNX_VER_MIN(5,1,0) || RTLNX_RHEL_MAJ_PREREQ(8,1)
+# include <drm/drm_probe_helper.h>
+#endif
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# include <drm/drm_aperture.h>
+#endif
+
+#include "version-generated.h"
+#include "revision-generated.h"
+
+/** Detect whether kernel mode setting is OFF. */
+#if defined(CONFIG_VGA_CONSOLE)
+# if RTLNX_VER_MIN(5,17,0) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MIN(9,1) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+# define VBOX_VIDEO_NOMODESET() drm_firmware_drivers_only() && vbox_modeset == -1
+# elif RTLNX_VER_MIN(4,7,0)
+# define VBOX_VIDEO_NOMODESET() vgacon_text_force() && vbox_modeset == -1
+# else /* < 4.7.0 */
+# define VBOX_VIDEO_NOMODESET() 0
+# endif /* < 4.7.0 */
+#else /* !CONFIG_VGA_CONSOLE */
+# define VBOX_VIDEO_NOMODESET() 0
+#endif /* !CONFIG_VGA_CONSOLE */
+
+static int vbox_modeset = -1;
+
+MODULE_PARM_DESC(modeset, "Disable/Enable modesetting");
+module_param_named(modeset, vbox_modeset, int, 0400);
+
+static struct drm_driver driver;
+
+static const struct pci_device_id pciidlist[] = {
+ { 0x80ee, 0xbeef, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { 0, 0, 0},
+};
+MODULE_DEVICE_TABLE(pci, pciidlist);
+
+static int vbox_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MIN(8,3)
+ struct drm_device *dev = NULL;
+ int ret = 0;
+
+# if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MIN(9,1) || RTLNX_SUSE_MAJ_PREREQ(15,4)
+ ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, &driver);
+# else
+ ret = drm_aperture_remove_conflicting_pci_framebuffers(pdev, "vboxvideofb");
+# endif
+ if (ret)
+ {
+ printk("unable to remove conflicting framebuffer devices\n");
+ return ret;
+ }
+# endif /* >= 5.14. */
+
+ dev = drm_dev_alloc(&driver, &pdev->dev);
+ if (IS_ERR(dev)) {
+ ret = PTR_ERR(dev);
+ goto err_drv_alloc;
+ }
+# if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ dev->pdev = pdev;
+# endif
+ pci_set_drvdata(pdev, dev);
+
+ ret = vbox_driver_load(dev);
+ if (ret)
+ goto err_vbox_driver_load;
+
+ ret = drm_dev_register(dev, 0);
+ if (ret)
+ goto err_drv_dev_register;
+ return ret;
+
+err_drv_dev_register:
+ vbox_driver_unload(dev);
+err_vbox_driver_load:
+ drm_dev_put(dev);
+err_drv_alloc:
+ return ret;
+#else /* < 4.19.0 || RHEL < 8.3 */
+ return drm_get_pci_dev(pdev, ent, &driver);
+#endif
+}
+
+static void vbox_pci_remove(struct pci_dev *pdev)
+{
+ struct drm_device *dev = pci_get_drvdata(pdev);
+
+#if RTLNX_VER_MAX(4,19,0)
+ drm_put_dev(dev);
+#else
+ drm_dev_unregister(dev);
+ vbox_driver_unload(dev);
+ drm_dev_put(dev);
+#endif
+}
+
+#if RTLNX_VER_MAX(4,9,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+static void drm_fb_helper_set_suspend_unlocked(struct drm_fb_helper *fb_helper,
+ bool suspend)
+{
+ if (!fb_helper || !fb_helper->fbdev)
+ return;
+
+ console_lock();
+ fb_set_suspend(fb_helper->fbdev, suspend);
+ console_unlock();
+}
+#endif
+
+static int vbox_drm_freeze(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ drm_kms_helper_poll_disable(dev);
+
+ pci_save_state(VBOX_DRM_TO_PCI_DEV(dev));
+
+ drm_fb_helper_set_suspend_unlocked(&vbox->fbdev->helper, true);
+
+ return 0;
+}
+
+static int vbox_drm_thaw(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ drm_mode_config_reset(dev);
+ drm_helper_resume_force_mode(dev);
+ drm_fb_helper_set_suspend_unlocked(&vbox->fbdev->helper, false);
+
+ return 0;
+}
+
+static int vbox_drm_resume(struct drm_device *dev)
+{
+ int ret;
+
+ if (pci_enable_device(VBOX_DRM_TO_PCI_DEV(dev)))
+ return -EIO;
+
+ ret = vbox_drm_thaw(dev);
+ if (ret)
+ return ret;
+
+ drm_kms_helper_poll_enable(dev);
+
+ return 0;
+}
+
+static int vbox_pm_suspend(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct drm_device *ddev = pci_get_drvdata(pdev);
+ int error;
+
+ error = vbox_drm_freeze(ddev);
+ if (error)
+ return error;
+
+ pci_disable_device(pdev);
+ pci_set_power_state(pdev, PCI_D3hot);
+
+ return 0;
+}
+
+static int vbox_pm_resume(struct device *dev)
+{
+ struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+
+ return vbox_drm_resume(ddev);
+}
+
+static int vbox_pm_freeze(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct drm_device *ddev = pci_get_drvdata(pdev);
+
+ if (!ddev || !ddev->dev_private)
+ return -ENODEV;
+
+ return vbox_drm_freeze(ddev);
+}
+
+static int vbox_pm_thaw(struct device *dev)
+{
+ struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+
+ return vbox_drm_thaw(ddev);
+}
+
+static int vbox_pm_poweroff(struct device *dev)
+{
+ struct drm_device *ddev = pci_get_drvdata(to_pci_dev(dev));
+
+ return vbox_drm_freeze(ddev);
+}
+
+static const struct dev_pm_ops vbox_pm_ops = {
+ .suspend = vbox_pm_suspend,
+ .resume = vbox_pm_resume,
+ .freeze = vbox_pm_freeze,
+ .thaw = vbox_pm_thaw,
+ .poweroff = vbox_pm_poweroff,
+ .restore = vbox_pm_resume,
+};
+
+static struct pci_driver vbox_pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ .probe = vbox_pci_probe,
+ .remove = vbox_pci_remove,
+ .driver.pm = &vbox_pm_ops,
+};
+
+#if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+/* This works around a bug in X servers prior to 1.18.4, which sometimes
+ * submit more dirty rectangles than the kernel is willing to handle and
+ * then disable dirty rectangle handling altogether when they see the
+ * EINVAL error. I do not want the code to hang around forever, which is
+ * why I am limiting it to certain kernel versions. We can increase the
+ * limit if some distributions uses old X servers with new kernels. */
+long vbox_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ long rc = drm_ioctl(filp, cmd, arg);
+
+ if (cmd == DRM_IOCTL_MODE_DIRTYFB && rc == -EINVAL)
+ return -EOVERFLOW;
+
+ return rc;
+}
+#endif /* RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4) */
+
+static const struct file_operations vbox_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+#if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+ .unlocked_ioctl = vbox_ioctl,
+#else
+ .unlocked_ioctl = drm_ioctl,
+#endif
+ .mmap = vbox_mmap,
+ .poll = drm_poll,
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,0)
+ .fasync = drm_fasync,
+#endif
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = drm_compat_ioctl,
+#endif
+ .read = drm_read,
+};
+
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+static void
+#else
+static int
+#endif
+vbox_master_set(struct drm_device *dev,
+ struct drm_file *file_priv, bool from_open)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ /*
+ * We do not yet know whether the new owner can handle hotplug, so we
+ * do not advertise dynamic modes on the first query and send a
+ * tentative hotplug notification after that to see if they query again.
+ */
+ vbox->initial_mode_queried = false;
+
+ mutex_lock(&vbox->hw_mutex);
+ /* Start the refresh timer in case the user does not provide dirty
+ * rectangles. */
+ vbox->need_refresh_timer = true;
+ schedule_delayed_work(&vbox->refresh_work, VBOX_REFRESH_PERIOD);
+ mutex_unlock(&vbox->hw_mutex);
+
+#if RTLNX_VER_MAX(5,9,0) && !RTLNX_RHEL_MAJ_PREREQ(8,4) && !RTLNX_SUSE_MAJ_PREREQ(15,3)
+ return 0;
+#endif
+}
+
+#if RTLNX_VER_MAX(4,8,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+static void vbox_master_drop(struct drm_device *dev,
+ struct drm_file *file_priv, bool from_release)
+#else
+static void vbox_master_drop(struct drm_device *dev, struct drm_file *file_priv)
+#endif
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ /* See vbox_master_set() */
+ vbox->initial_mode_queried = false;
+ vbox_report_caps(vbox);
+
+ mutex_lock(&vbox->hw_mutex);
+ vbox->need_refresh_timer = false;
+ mutex_unlock(&vbox->hw_mutex);
+}
+
+static struct drm_driver driver = {
+#if RTLNX_VER_MAX(5,4,0) && !RTLNX_RHEL_MAJ_PREREQ(8,3) && !RTLNX_SUSE_MAJ_PREREQ(15,3)
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ |
+# if RTLNX_VER_MAX(5,1,0) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ DRIVER_IRQ_SHARED |
+# endif
+ DRIVER_PRIME,
+#else /* >= 5.4.0 && RHEL >= 8.3 && SLES >= 15-SP3 */
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_HAVE_IRQ,
+#endif /* < 5.4.0 */
+
+#if RTLNX_VER_MAX(4,19,0) && !RTLNX_RHEL_MAJ_PREREQ(8,3)
+ /* Legacy hooks, but still supported. */
+ .load = vbox_driver_load,
+ .unload = vbox_driver_unload,
+#endif
+ .lastclose = vbox_driver_lastclose,
+ .master_set = vbox_master_set,
+ .master_drop = vbox_master_drop,
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+# if RTLNX_VER_MAX(4,14,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ .set_busid = drm_pci_set_busid,
+# endif
+#endif
+
+ .fops = &vbox_fops,
+#if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_RANGE(8,7, 8,99) && !RTLNX_RHEL_MAJ_PREREQ(9,1) && !RTLNX_SUSE_MAJ_PREREQ(15,5)
+ .irq_handler = vbox_irq_handler,
+#endif
+ .name = DRIVER_NAME,
+ .desc = DRIVER_DESC,
+ .date = DRIVER_DATE,
+ .major = DRIVER_MAJOR,
+ .minor = DRIVER_MINOR,
+ .patchlevel = DRIVER_PATCHLEVEL,
+
+#if RTLNX_VER_MAX(4,7,0)
+ .gem_free_object = vbox_gem_free_object,
+#endif
+ .dumb_create = vbox_dumb_create,
+ .dumb_map_offset = vbox_dumb_mmap_offset,
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+ .dumb_destroy = vbox_dumb_destroy,
+#elif RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ .dumb_destroy = drm_gem_dumb_destroy,
+#endif
+#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99)
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+#endif
+ .gem_prime_import = drm_gem_prime_import,
+ .gem_prime_import_sg_table = vbox_gem_prime_import_sg_table,
+#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99)
+ .gem_prime_mmap = vbox_gem_prime_mmap,
+#endif
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ .dev_priv_size = 0,
+# if RTLNX_VER_MIN(4,7,0)
+ .gem_free_object_unlocked = vbox_gem_free_object,
+# endif
+ .gem_prime_export = drm_gem_prime_export,
+ .gem_prime_pin = vbox_gem_prime_pin,
+ .gem_prime_unpin = vbox_gem_prime_unpin,
+ .gem_prime_get_sg_table = vbox_gem_prime_get_sg_table,
+ .gem_prime_vmap = vbox_gem_prime_vmap,
+ .gem_prime_vunmap = vbox_gem_prime_vunmap,
+#endif
+};
+
+static int __init vbox_init(void)
+{
+ printk("vboxvideo: loading version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n");
+ if (VBOX_VIDEO_NOMODESET())
+ {
+ printk("vboxvideo: kernel is running with *nomodeset* parameter,\n");
+ printk("vboxvideo: please consider either to remove it or load driver\n");
+ printk("vboxvideo: with parameter modeset=1, unloading\n");
+ return -EINVAL;
+ }
+
+ if (vbox_modeset == 0)
+ {
+ printk("vboxvideo: driver loaded with modeset=0 parameter, unloading\n");
+ return -EINVAL;
+ }
+
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ return pci_register_driver(&vbox_pci_driver);
+#else
+ return drm_pci_init(&driver, &vbox_pci_driver);
+#endif
+}
+
+static void __exit vbox_exit(void)
+{
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ pci_unregister_driver(&vbox_pci_driver);
+#else
+ drm_pci_exit(&driver, &vbox_pci_driver);
+#endif
+}
+
+module_init(vbox_init);
+module_exit(vbox_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL and additional rights");
+#ifdef MODULE_VERSION
+MODULE_VERSION(VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV));
+#endif
diff --git a/src/VBox/Additions/linux/drm/vbox_drv.h b/src/VBox/Additions/linux/drm/vbox_drv.h
new file mode 100644
index 00000000..8eb9f83e
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_drv.h
@@ -0,0 +1,557 @@
+/* $Id: vbox_drv.h $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ * This file is based on ast_drv.h
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+
+#ifndef GA_INCLUDED_SRC_linux_drm_vbox_drv_h
+#define GA_INCLUDED_SRC_linux_drm_vbox_drv_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <linux/version.h>
+
+/* iprt/linux/version.h copy - start */
+/** @def RTLNX_VER_MIN
+ * Evaluates to true if the linux kernel version is equal or higher to the
+ * one specfied. */
+#define RTLNX_VER_MIN(a_Major, a_Minor, a_Patch) \
+ (LINUX_VERSION_CODE >= KERNEL_VERSION(a_Major, a_Minor, a_Patch))
+
+/** @def RTLNX_VER_MAX
+ * Evaluates to true if the linux kernel version is less to the one specfied
+ * (exclusive). */
+#define RTLNX_VER_MAX(a_Major, a_Minor, a_Patch) \
+ (LINUX_VERSION_CODE < KERNEL_VERSION(a_Major, a_Minor, a_Patch))
+
+/** @def RTLNX_VER_RANGE
+ * Evaluates to true if the linux kernel version is equal or higher to the given
+ * minimum version and less (but not equal) to the maximum version (exclusive). */
+#define RTLNX_VER_RANGE(a_MajorMin, a_MinorMin, a_PatchMin, a_MajorMax, a_MinorMax, a_PatchMax) \
+ ( LINUX_VERSION_CODE >= KERNEL_VERSION(a_MajorMin, a_MinorMin, a_PatchMin) \
+ && LINUX_VERSION_CODE < KERNEL_VERSION(a_MajorMax, a_MinorMax, a_PatchMax) )
+
+
+/** @def RTLNX_RHEL_MIN
+ * Require a minium RedHat release.
+ * @param a_iMajor The major release number (RHEL_MAJOR).
+ * @param a_iMinor The minor release number (RHEL_MINOR).
+ * @sa RTLNX_RHEL_MAX, RTLNX_RHEL_RANGE, RTLNX_RHEL_MAJ_PREREQ
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_MIN(a_iMajor, a_iMinor) \
+ ((RHEL_MAJOR) > (a_iMajor) || ((RHEL_MAJOR) == (a_iMajor) && (RHEL_MINOR) >= (a_iMinor)))
+#else
+# define RTLNX_RHEL_MIN(a_iMajor, a_iMinor) (0)
+#endif
+
+/** @def RTLNX_RHEL_MAX
+ * Require a maximum RedHat release, true for all RHEL versions below it.
+ * @param a_iMajor The major release number (RHEL_MAJOR).
+ * @param a_iMinor The minor release number (RHEL_MINOR).
+ * @sa RTLNX_RHEL_MIN, RTLNX_RHEL_RANGE, RTLNX_RHEL_MAJ_PREREQ
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_MAX(a_iMajor, a_iMinor) \
+ ((RHEL_MAJOR) < (a_iMajor) || ((RHEL_MAJOR) == (a_iMajor) && (RHEL_MINOR) < (a_iMinor)))
+#else
+# define RTLNX_RHEL_MAX(a_iMajor, a_iMinor) (0)
+#endif
+
+/** @def RTLNX_RHEL_RANGE
+ * Check that it's a RedHat kernel in the given version range.
+ * The max version is exclusive, the minimum inclusive.
+ * @sa RTLNX_RHEL_MIN, RTLNX_RHEL_MAX, RTLNX_RHEL_MAJ_PREREQ
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_RANGE(a_iMajorMin, a_iMinorMin, a_iMajorMax, a_iMinorMax) \
+ (RTLNX_RHEL_MIN(a_iMajorMin, a_iMinorMin) && RTLNX_RHEL_MAX(a_iMajorMax, a_iMinorMax))
+#else
+# define RTLNX_RHEL_RANGE(a_iMajorMin, a_iMinorMin, a_iMajorMax, a_iMinorMax) (0)
+#endif
+
+/** @def RTLNX_RHEL_MAJ_PREREQ
+ * Require a minimum minor release number for the given RedHat release.
+ * @param a_iMajor RHEL_MAJOR must _equal_ this.
+ * @param a_iMinor RHEL_MINOR must be greater or equal to this.
+ * @sa RTLNX_RHEL_MIN, RTLNX_RHEL_MAX
+ */
+#if defined(RHEL_MAJOR) && defined(RHEL_MINOR)
+# define RTLNX_RHEL_MAJ_PREREQ(a_iMajor, a_iMinor) ((RHEL_MAJOR) == (a_iMajor) && (RHEL_MINOR) >= (a_iMinor))
+#else
+# define RTLNX_RHEL_MAJ_PREREQ(a_iMajor, a_iMinor) (0)
+#endif
+
+
+/** @def RTLNX_SUSE_MAJ_PREREQ
+ * Require a minimum minor release number for the given SUSE release.
+ * @param a_iMajor CONFIG_SUSE_VERSION must _equal_ this.
+ * @param a_iMinor CONFIG_SUSE_PATCHLEVEL must be greater or equal to this.
+ */
+#if defined(CONFIG_SUSE_VERSION) && defined(CONFIG_SUSE_PATCHLEVEL)
+# define RTLNX_SUSE_MAJ_PREREQ(a_iMajor, a_iMinor) ((CONFIG_SUSE_VERSION) == (a_iMajor) && (CONFIG_SUSE_PATCHLEVEL) >= (a_iMinor))
+#else
+# define RTLNX_SUSE_MAJ_PREREQ(a_iMajor, a_iMinor) (0)
+#endif
+/* iprt/linux/version.h copy - end */
+
+#if RTLNX_VER_MAX(4,5,0)
+# include <linux/types.h>
+# include <linux/spinlock_types.h>
+#endif
+
+#include <linux/genalloc.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/pci.h>
+
+
+#if RTLNX_VER_MAX(3,14,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+#define U8_MAX ((u8)~0U)
+#define S8_MAX ((s8)(U8_MAX>>1))
+#define S8_MIN ((s8)(-S8_MAX - 1))
+#define U16_MAX ((u16)~0U)
+#define S16_MAX ((s16)(U16_MAX>>1))
+#define S16_MIN ((s16)(-S16_MAX - 1))
+#define U32_MAX ((u32)~0U)
+#define S32_MAX ((s32)(U32_MAX>>1))
+#define S32_MIN ((s32)(-S32_MAX - 1))
+#define U64_MAX ((u64)~0ULL)
+#define S64_MAX ((s64)(U64_MAX>>1))
+#define S64_MIN ((s64)(-S64_MAX - 1))
+#endif
+
+#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+# include <drm/drm_file.h>
+# include <drm/drm_drv.h>
+# include <drm/drm_device.h>
+# include <drm/drm_ioctl.h>
+# include <drm/drm_fourcc.h>
+# if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_RANGE(8,7, 8,99) && !RTLNX_RHEL_MAJ_PREREQ(9,1) && !RTLNX_SUSE_MAJ_PREREQ(15,5)
+# include <drm/drm_irq.h>
+# endif
+# include <drm/drm_vblank.h>
+#else /* < 5.5.0 || RHEL < 8.3 || SLES < 15-SP3 */
+# include <drm/drmP.h>
+#endif
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+# include <drm/drm_encoder.h>
+#endif
+#include <drm/drm_fb_helper.h>
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+# include <drm/drm_gem.h>
+#endif
+
+#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+# include <drm/ttm/ttm_bo.h>
+#else
+# include <drm/ttm/ttm_bo_api.h>
+# include <drm/ttm/ttm_bo_driver.h>
+#endif
+#include <drm/ttm/ttm_placement.h>
+#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+# include <drm/ttm/ttm_memory.h>
+#endif
+#if RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+# include <drm/ttm/ttm_module.h>
+#endif
+#if RTLNX_VER_MIN(5,10,0)
+# include <drm/ttm/ttm_resource.h>
+#endif
+
+#if RTLNX_VER_MIN(6,0,0) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,2) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+# include <drm/drm_framebuffer.h>
+#endif
+
+#include "vboxvideo_guest.h"
+#include "vboxvideo_vbe.h"
+#include "hgsmi_ch_setup.h"
+
+#include "product-generated.h"
+
+#if RTLNX_VER_MAX(4,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+static inline void drm_gem_object_put_unlocked(struct drm_gem_object *obj)
+{
+ drm_gem_object_unreference_unlocked(obj);
+}
+#endif
+
+#if RTLNX_VER_MAX(4,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+static inline void drm_gem_object_put(struct drm_gem_object *obj)
+{
+ drm_gem_object_unreference(obj);
+}
+#endif
+
+#define DRIVER_AUTHOR VBOX_VENDOR
+
+#define DRIVER_NAME "vboxvideo"
+#define DRIVER_DESC VBOX_PRODUCT " Graphics Card"
+#define DRIVER_DATE "20130823"
+
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+
+#define VBOX_MAX_CURSOR_WIDTH 64
+#define VBOX_MAX_CURSOR_HEIGHT 64
+#define CURSOR_PIXEL_COUNT (VBOX_MAX_CURSOR_WIDTH * VBOX_MAX_CURSOR_HEIGHT)
+#define CURSOR_DATA_SIZE (CURSOR_PIXEL_COUNT * 4 + CURSOR_PIXEL_COUNT / 8)
+
+#define VBOX_MAX_SCREENS 32
+
+#define GUEST_HEAP_OFFSET(vbox) ((vbox)->full_vram_size - \
+ VBVA_ADAPTER_INFORMATION_SIZE)
+#define GUEST_HEAP_SIZE VBVA_ADAPTER_INFORMATION_SIZE
+#define GUEST_HEAP_USABLE_SIZE (VBVA_ADAPTER_INFORMATION_SIZE - \
+ sizeof(HGSMIHOSTFLAGS))
+#define HOST_FLAGS_OFFSET GUEST_HEAP_USABLE_SIZE
+
+/** Field "pdev" of struct drm_device was removed in 5.14. This macro
+ * transparently handles this change. Input argument is a pointer
+ * to struct drm_device. */
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# define VBOX_DRM_TO_PCI_DEV(_dev) to_pci_dev(_dev->dev)
+#else
+# define VBOX_DRM_TO_PCI_DEV(_dev) _dev->pdev
+#endif
+
+/** Field "num_pages" of struct ttm_resource was renamed to "size" in 6.2 and
+ * now represents number of bytes. This macro handles this change. Input
+ * argument is a pointer to struct ttm_resource. */
+#if RTLNX_VER_MIN(6,2,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+# define VBOX_BO_RESOURCE_NUM_PAGES(_resource) PFN_UP(_resource->size)
+#else
+# define VBOX_BO_RESOURCE_NUM_PAGES(_resource) _resource->num_pages
+#endif
+
+/** How frequently we refresh if the guest is not providing dirty rectangles. */
+#define VBOX_REFRESH_PERIOD (HZ / 2)
+
+#if RTLNX_VER_MAX(3,13,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+static inline void *devm_kcalloc(struct device *dev, size_t n, size_t size,
+ gfp_t flags)
+{
+ return devm_kzalloc(dev, n * size, flags);
+}
+#endif
+
+struct vbox_fbdev;
+
+struct vbox_private {
+ struct drm_device *dev;
+
+ u8 __iomem *guest_heap;
+ u8 __iomem *vbva_buffers;
+ struct gen_pool *guest_pool;
+ struct VBVABUFFERCONTEXT *vbva_info;
+ bool any_pitch;
+ u32 num_crtcs;
+ /** Amount of available VRAM, including space used for buffers. */
+ u32 full_vram_size;
+ /** Amount of available VRAM, not including space used for buffers. */
+ u32 available_vram_size;
+ /** Array of structures for receiving mode hints. */
+ VBVAMODEHINT *last_mode_hints;
+
+ struct vbox_fbdev *fbdev;
+
+ int fb_mtrr;
+
+ struct {
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ struct drm_global_reference mem_global_ref;
+ struct ttm_bo_global_ref bo_global_ref;
+#endif
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ struct ttm_device bdev;
+#else
+ struct ttm_bo_device bdev;
+#endif
+ bool mm_initialised;
+ } ttm;
+
+ struct mutex hw_mutex; /* protects modeset and accel/vbva accesses */
+ /**
+ * We decide whether or not user-space supports display hot-plug
+ * depending on whether they react to a hot-plug event after the initial
+ * mode query.
+ */
+ bool initial_mode_queried;
+ /**
+ * Do we know that the current user can send us dirty rectangle information?
+ * If not, do periodic refreshes until we do know.
+ */
+ bool need_refresh_timer;
+ /**
+ * As long as the user is not sending us dirty rectangle information,
+ * refresh the whole screen at regular intervals.
+ */
+ struct delayed_work refresh_work;
+ struct work_struct hotplug_work;
+ u32 input_mapping_width;
+ u32 input_mapping_height;
+ /**
+ * Is user-space using an X.Org-style layout of one large frame-buffer
+ * encompassing all screen ones or is the fbdev console active?
+ */
+ bool single_framebuffer;
+ u32 cursor_width;
+ u32 cursor_height;
+ u32 cursor_hot_x;
+ u32 cursor_hot_y;
+ size_t cursor_data_size;
+ u8 cursor_data[CURSOR_DATA_SIZE];
+};
+
+#undef CURSOR_PIXEL_COUNT
+#undef CURSOR_DATA_SIZE
+
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MIN(8,3)
+int vbox_driver_load(struct drm_device *dev);
+#else
+int vbox_driver_load(struct drm_device *dev, unsigned long flags);
+#endif
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+void vbox_driver_unload(struct drm_device *dev);
+#else
+int vbox_driver_unload(struct drm_device *dev);
+#endif
+void vbox_driver_lastclose(struct drm_device *dev);
+
+struct vbox_gem_object;
+
+#ifndef VGA_PORT_HGSMI_HOST
+#define VGA_PORT_HGSMI_HOST 0x3b0
+#define VGA_PORT_HGSMI_GUEST 0x3d0
+#endif
+
+struct vbox_connector {
+ struct drm_connector base;
+ char name[32];
+ struct vbox_crtc *vbox_crtc;
+ struct {
+ u32 width;
+ u32 height;
+ bool disconnected;
+ } mode_hint;
+};
+
+struct vbox_crtc {
+ struct drm_crtc base;
+ bool blanked;
+ bool disconnected;
+ unsigned int crtc_id;
+ u32 fb_offset;
+ bool cursor_enabled;
+ u32 x_hint;
+ u32 y_hint;
+};
+
+struct vbox_encoder {
+ struct drm_encoder base;
+};
+
+struct vbox_framebuffer {
+ struct drm_framebuffer base;
+ struct drm_gem_object *obj;
+};
+
+struct vbox_fbdev {
+ struct drm_fb_helper helper;
+ struct vbox_framebuffer afb;
+ int size;
+ struct ttm_bo_kmap_obj mapping;
+ int x1, y1, x2, y2; /* dirty rect */
+ spinlock_t dirty_lock;
+};
+
+#define to_vbox_crtc(x) container_of(x, struct vbox_crtc, base)
+#define to_vbox_connector(x) container_of(x, struct vbox_connector, base)
+#define to_vbox_encoder(x) container_of(x, struct vbox_encoder, base)
+#define to_vbox_framebuffer(x) container_of(x, struct vbox_framebuffer, base)
+
+int vbox_mode_init(struct drm_device *dev);
+void vbox_mode_fini(struct drm_device *dev);
+
+#if RTLNX_VER_MAX(3,3,0)
+# define DRM_MODE_FB_CMD drm_mode_fb_cmd
+#else
+# define DRM_MODE_FB_CMD drm_mode_fb_cmd2
+#endif
+
+#if RTLNX_VER_MAX(3,15,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+# define CRTC_FB(crtc) ((crtc)->fb)
+#else
+# define CRTC_FB(crtc) ((crtc)->primary->fb)
+#endif
+
+void vbox_enable_accel(struct vbox_private *vbox);
+void vbox_disable_accel(struct vbox_private *vbox);
+void vbox_report_caps(struct vbox_private *vbox);
+
+void vbox_framebuffer_dirty_rectangles(struct drm_framebuffer *fb,
+ struct drm_clip_rect *rects,
+ unsigned int num_rects);
+
+int vbox_framebuffer_init(struct drm_device *dev,
+ struct vbox_framebuffer *vbox_fb,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ const struct DRM_MODE_FB_CMD *mode_cmd,
+#else
+ struct DRM_MODE_FB_CMD *mode_cmd,
+#endif
+ struct drm_gem_object *obj);
+
+int vbox_fbdev_init(struct drm_device *dev);
+void vbox_fbdev_fini(struct drm_device *dev);
+void vbox_fbdev_set_base(struct vbox_private *vbox, unsigned long gpu_addr);
+
+struct vbox_bo {
+ struct ttm_buffer_object bo;
+ struct ttm_placement placement;
+ struct ttm_bo_kmap_obj kmap;
+ struct drm_gem_object gem;
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ u32 placements[3];
+#else
+ struct ttm_place placements[3];
+#endif
+ int pin_count;
+};
+
+#define gem_to_vbox_bo(gobj) container_of((gobj), struct vbox_bo, gem)
+
+static inline struct vbox_bo *vbox_bo(struct ttm_buffer_object *bo)
+{
+ return container_of(bo, struct vbox_bo, bo);
+}
+
+#define to_vbox_obj(x) container_of(x, struct vbox_gem_object, base)
+
+int vbox_dumb_create(struct drm_file *file,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args);
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+int vbox_dumb_destroy(struct drm_file *file,
+ struct drm_device *dev, u32 handle);
+#endif
+
+void vbox_gem_free_object(struct drm_gem_object *obj);
+int vbox_dumb_mmap_offset(struct drm_file *file,
+ struct drm_device *dev,
+ u32 handle, u64 *offset);
+
+#define DRM_FILE_PAGE_OFFSET (0x10000000ULL >> PAGE_SHIFT)
+
+int vbox_mm_init(struct vbox_private *vbox);
+void vbox_mm_fini(struct vbox_private *vbox);
+
+int vbox_bo_create(struct drm_device *dev, int size, int align,
+ u32 flags, struct vbox_bo **pvboxbo);
+
+int vbox_gem_create(struct drm_device *dev,
+ u32 size, bool iskernel, struct drm_gem_object **obj);
+
+#define VBOX_MEM_TYPE_VRAM 0x1
+#define VBOX_MEM_TYPE_SYSTEM 0x2
+
+int vbox_bo_pin(struct vbox_bo *bo, u32 mem_type, u64 *gpu_addr);
+int vbox_bo_unpin(struct vbox_bo *bo);
+
+static inline int vbox_bo_reserve(struct vbox_bo *bo, bool no_wait)
+{
+ int ret;
+
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ ret = ttm_bo_reserve(&bo->bo, true, no_wait, NULL);
+#else
+ ret = ttm_bo_reserve(&bo->bo, true, no_wait, false, 0);
+#endif
+ if (ret) {
+ if (ret != -ERESTARTSYS && ret != -EBUSY)
+ DRM_ERROR("reserve failed %p\n", bo);
+ return ret;
+ }
+ return 0;
+}
+
+static inline void vbox_bo_unreserve(struct vbox_bo *bo)
+{
+ ttm_bo_unreserve(&bo->bo);
+}
+
+void vbox_ttm_placement(struct vbox_bo *bo, u32 mem_type);
+int vbox_bo_push_sysram(struct vbox_bo *bo);
+int vbox_mmap(struct file *filp, struct vm_area_struct *vma);
+
+/* vbox_prime.c */
+int vbox_gem_prime_pin(struct drm_gem_object *obj);
+void vbox_gem_prime_unpin(struct drm_gem_object *obj);
+struct sg_table *vbox_gem_prime_get_sg_table(struct drm_gem_object *obj);
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, size_t size, struct sg_table *table);
+#else
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, struct dma_buf_attachment *attach,
+ struct sg_table *table);
+#endif
+void *vbox_gem_prime_vmap(struct drm_gem_object *obj);
+void vbox_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr);
+#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99)
+int vbox_gem_prime_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *area);
+#endif
+
+/* vbox_irq.c */
+int vbox_irq_init(struct vbox_private *vbox);
+void vbox_irq_fini(struct vbox_private *vbox);
+void vbox_report_hotplug(struct vbox_private *vbox);
+#if RTLNX_VER_MAX(5,15,0) && !RTLNX_RHEL_MAJ_PREREQ(9,1) && !RTLNX_SUSE_MAJ_PREREQ(15,5)
+irqreturn_t vbox_irq_handler(int irq, void *arg);
+#endif
+
+/* vbox_hgsmi.c */
+void *hgsmi_buffer_alloc(struct gen_pool *guest_pool, size_t size,
+ u8 channel, u16 channel_info);
+void hgsmi_buffer_free(struct gen_pool *guest_pool, void *buf);
+int hgsmi_buffer_submit(struct gen_pool *guest_pool, void *buf);
+
+static inline void vbox_write_ioport(u16 index, u16 data)
+{
+ outw(index, VBE_DISPI_IOPORT_INDEX);
+ outw(data, VBE_DISPI_IOPORT_DATA);
+}
+
+#endif /* !GA_INCLUDED_SRC_linux_drm_vbox_drv_h */
diff --git a/src/VBox/Additions/linux/drm/vbox_fb.c b/src/VBox/Additions/linux/drm/vbox_fb.c
new file mode 100644
index 00000000..37cf7ef1
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_fb.c
@@ -0,0 +1,541 @@
+/* $Id: vbox_fb.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ * This file is based on ast_fb.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/tty.h>
+#include <linux/sysrq.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+
+#include "vbox_drv.h"
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include <VBoxVideo.h>
+
+#if RTLNX_VER_MIN(6,2,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+# define VBOX_FBDEV_INFO(_helper) _helper.info
+#else
+# define VBOX_FBDEV_INFO(_helper) _helper.fbdev
+#endif
+
+#if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+/**
+ * Tell the host about dirty rectangles to update.
+ */
+static void vbox_dirty_update(struct vbox_fbdev *fbdev,
+ int x, int y, int width, int height)
+{
+ struct drm_gem_object *obj;
+ struct vbox_bo *bo;
+ int ret = -EBUSY;
+ bool store_for_later = false;
+ int x2, y2;
+ unsigned long flags;
+ struct drm_clip_rect rect;
+
+ obj = fbdev->afb.obj;
+ bo = gem_to_vbox_bo(obj);
+
+ /*
+ * try and reserve the BO, if we fail with busy
+ * then the BO is being moved and we should
+ * store up the damage until later.
+ */
+ if (drm_can_sleep())
+ ret = vbox_bo_reserve(bo, true);
+ if (ret) {
+ if (ret != -EBUSY)
+ return;
+
+ store_for_later = true;
+ }
+
+ x2 = x + width - 1;
+ y2 = y + height - 1;
+ spin_lock_irqsave(&fbdev->dirty_lock, flags);
+
+ if (fbdev->y1 < y)
+ y = fbdev->y1;
+ if (fbdev->y2 > y2)
+ y2 = fbdev->y2;
+ if (fbdev->x1 < x)
+ x = fbdev->x1;
+ if (fbdev->x2 > x2)
+ x2 = fbdev->x2;
+
+ if (store_for_later) {
+ fbdev->x1 = x;
+ fbdev->x2 = x2;
+ fbdev->y1 = y;
+ fbdev->y2 = y2;
+ spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+ return;
+ }
+
+ fbdev->x1 = INT_MAX;
+ fbdev->y1 = INT_MAX;
+ fbdev->x2 = 0;
+ fbdev->y2 = 0;
+
+ spin_unlock_irqrestore(&fbdev->dirty_lock, flags);
+
+ /*
+ * Not sure why the original code subtracted 1 here, but I will keep
+ * it that way to avoid unnecessary differences.
+ */
+ rect.x1 = x;
+ rect.x2 = x2 + 1;
+ rect.y1 = y;
+ rect.y2 = y2 + 1;
+ vbox_framebuffer_dirty_rectangles(&fbdev->afb.base, &rect, 1);
+
+ vbox_bo_unreserve(bo);
+}
+#endif /* RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4) */
+
+#ifdef CONFIG_FB_DEFERRED_IO
+# if RTLNX_VER_MAX(4,7,0) && !RTLNX_RHEL_MAJ_PREREQ(7,4)
+static void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagelist)
+{
+ struct vbox_fbdev *fbdev = info->par;
+ unsigned long start, end, min, max;
+ struct page *page;
+ int y1, y2;
+
+ min = ULONG_MAX;
+ max = 0;
+ list_for_each_entry(page, pagelist, lru) {
+ start = page->index << PAGE_SHIFT;
+ end = start + PAGE_SIZE - 1;
+ min = min(min, start);
+ max = max(max, end);
+ }
+
+ if (min < max) {
+ y1 = min / info->fix.line_length;
+ y2 = (max / info->fix.line_length) + 1;
+ DRM_INFO("%s: Calling dirty update: 0, %d, %d, %d\n",
+ __func__, y1, info->var.xres, y2 - y1 - 1);
+ vbox_dirty_update(fbdev, 0, y1, info->var.xres, y2 - y1 - 1);
+ }
+}
+# endif
+
+static struct fb_deferred_io vbox_defio = {
+ .delay = HZ / 30,
+ .deferred_io = drm_fb_helper_deferred_io,
+};
+#endif /* CONFIG_FB_DEFERRED_IO */
+
+#if RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+static void drm_fb_helper_sys_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
+{
+ struct vbox_fbdev *fbdev = info->par;
+
+ sys_fillrect(info, rect);
+ vbox_dirty_update(fbdev, rect->dx, rect->dy, rect->width, rect->height);
+}
+
+static void drm_fb_helper_sys_copyarea(struct fb_info *info, const struct fb_copyarea *area)
+{
+ struct vbox_fbdev *fbdev = info->par;
+
+ sys_copyarea(info, area);
+ vbox_dirty_update(fbdev, area->dx, area->dy, area->width, area->height);
+}
+
+static void drm_fb_helper_sys_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+ struct vbox_fbdev *fbdev = info->par;
+
+ sys_imageblit(info, image);
+ vbox_dirty_update(fbdev, image->dx, image->dy, image->width,
+ image->height);
+}
+#endif /* RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3) */
+
+static struct fb_ops vboxfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = drm_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par,
+#if RTLNX_VER_MIN(6,5,0) || RTLNX_RHEL_RANGE(9,4, 9,99)
+ .fb_read = fb_sys_read,
+ .fb_write = fb_sys_write,
+ .fb_fillrect = sys_fillrect,
+ .fb_copyarea = sys_copyarea,
+ .fb_imageblit = sys_imageblit,
+ .fb_mmap = NULL,
+#else
+ .fb_fillrect = drm_fb_helper_sys_fillrect,
+ .fb_copyarea = drm_fb_helper_sys_copyarea,
+ .fb_imageblit = drm_fb_helper_sys_imageblit,
+#endif
+ .fb_pan_display = drm_fb_helper_pan_display,
+ .fb_blank = drm_fb_helper_blank,
+ .fb_setcmap = drm_fb_helper_setcmap,
+ .fb_debug_enter = drm_fb_helper_debug_enter,
+ .fb_debug_leave = drm_fb_helper_debug_leave,
+};
+
+static int vboxfb_create_object(struct vbox_fbdev *fbdev,
+ struct DRM_MODE_FB_CMD *mode_cmd,
+ struct drm_gem_object **gobj_p)
+{
+ struct drm_device *dev = fbdev->helper.dev;
+ u32 size;
+ struct drm_gem_object *gobj;
+#if RTLNX_VER_MAX(3,3,0)
+ u32 pitch = mode_cmd->pitch;
+#else
+ u32 pitch = mode_cmd->pitches[0];
+#endif
+
+ int ret;
+
+ size = pitch * mode_cmd->height;
+ ret = vbox_gem_create(dev, size, true, &gobj);
+ if (ret)
+ return ret;
+
+ *gobj_p = gobj;
+
+ return 0;
+}
+
+#if RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+static struct fb_info *drm_fb_helper_alloc_fbi(struct drm_fb_helper *helper)
+{
+ struct fb_info *info;
+ struct vbox_fbdev *fbdev =
+ container_of(helper, struct vbox_fbdev, helper);
+ struct drm_device *dev = fbdev->helper.dev;
+ struct device *device = &dev->pdev->dev;
+
+ info = framebuffer_alloc(0, device);
+ if (!info)
+ return ERR_PTR(-ENOMEM);
+ fbdev->helper.fbdev = info;
+
+ if (fb_alloc_cmap(&info->cmap, 256, 0))
+ return ERR_PTR(-ENOMEM);
+
+ info->apertures = alloc_apertures(1);
+ if (!info->apertures)
+ return ERR_PTR(-ENOMEM);
+
+ return info;
+}
+#endif
+
+static int vboxfb_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct vbox_fbdev *fbdev =
+ container_of(helper, struct vbox_fbdev, helper);
+ struct drm_device *dev = fbdev->helper.dev;
+ struct DRM_MODE_FB_CMD mode_cmd;
+ struct drm_framebuffer *fb;
+ struct fb_info *info;
+ struct drm_gem_object *gobj;
+ struct vbox_bo *bo;
+ int size, ret;
+ u32 pitch;
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ pitch = mode_cmd.width * ((sizes->surface_bpp + 7) / 8);
+#if RTLNX_VER_MAX(3,3,0)
+ mode_cmd.bpp = sizes->surface_bpp;
+ mode_cmd.depth = sizes->surface_depth;
+ mode_cmd.pitch = pitch;
+#else
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+ mode_cmd.pitches[0] = pitch;
+#endif
+
+ size = pitch * mode_cmd.height;
+
+ ret = vboxfb_create_object(fbdev, &mode_cmd, &gobj);
+ if (ret) {
+ DRM_ERROR("failed to create fbcon backing object %d\n", ret);
+ return ret;
+ }
+
+ ret = vbox_framebuffer_init(dev, &fbdev->afb, &mode_cmd, gobj);
+ if (ret)
+ return ret;
+
+ bo = gem_to_vbox_bo(gobj);
+
+ ret = vbox_bo_reserve(bo, false);
+ if (ret)
+ return ret;
+
+ ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_VRAM, NULL);
+ if (ret) {
+ vbox_bo_unreserve(bo);
+ return ret;
+ }
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = ttm_bo_kmap(&bo->bo, 0, VBOX_BO_RESOURCE_NUM_PAGES(bo->bo.resource), &bo->kmap);
+#elif RTLNX_VER_MIN(5,12,0) || RTLNX_RHEL_MAJ_PREREQ(8,5)
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.mem.num_pages, &bo->kmap);
+#else
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap);
+#endif
+
+ vbox_bo_unreserve(bo);
+ if (ret) {
+ DRM_ERROR("failed to kmap fbcon\n");
+ return ret;
+ }
+
+#if RTLNX_VER_MIN(6,2,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+ info = drm_fb_helper_alloc_info(helper);
+#else
+ info = drm_fb_helper_alloc_fbi(helper);
+#endif
+ if (IS_ERR(info))
+ return -PTR_ERR(info);
+
+ info->par = fbdev;
+
+ fbdev->size = size;
+
+ fb = &fbdev->afb.base;
+ fbdev->helper.fb = fb;
+
+ strcpy(info->fix.id, "vboxdrmfb");
+
+ /*
+ * The last flag forces a mode set on VT switches even if the kernel
+ * does not think it is needed.
+ */
+#if RTLNX_VER_MIN(6,6,0)
+ info->flags = FBINFO_MISC_ALWAYS_SETPAR;
+#else
+ info->flags = FBINFO_DEFAULT | FBINFO_MISC_ALWAYS_SETPAR;
+#endif
+ info->fbops = &vboxfb_ops;
+
+#if RTLNX_VER_MAX(6,3,0) && !RTLNX_RHEL_RANGE(8,9, 8,99) && !RTLNX_RHEL_RANGE(9,3, 9,99)
+ /*
+ * This seems to be done for safety checking that the framebuffer
+ * is not registered twice by different drivers.
+ */
+ info->apertures->ranges[0].base = pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0);
+ info->apertures->ranges[0].size = pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0);
+#endif
+
+#if RTLNX_VER_MIN(5,2,0) || RTLNX_RHEL_MAJ_PREREQ(8,2)
+ /*
+ * The corresponding 5.2-rc1 Linux DRM kernel changes have been
+ * also backported to older RedHat based 4.18.0 Linux kernels.
+ */
+ drm_fb_helper_fill_info(info, &fbdev->helper, sizes);
+#elif RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7, 5)
+ drm_fb_helper_fill_fix(info, fb->pitches[0], fb->format->depth);
+#else
+ drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
+#endif
+#if RTLNX_VER_MAX(5,2,0) && !RTLNX_RHEL_MAJ_PREREQ(8,2)
+ drm_fb_helper_fill_var(info, &fbdev->helper, sizes->fb_width,
+ sizes->fb_height);
+#endif
+
+#if RTLNX_VER_MIN(6,5,0)
+ info->screen_buffer = (char *)bo->kmap.virtual;
+ info->fix.smem_start = page_to_phys(vmalloc_to_page(bo->kmap.virtual));
+#endif
+ info->screen_base = (char __iomem *)bo->kmap.virtual;
+ info->screen_size = size;
+
+#ifdef CONFIG_FB_DEFERRED_IO
+# if RTLNX_VER_MIN(5,19,0) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+ info->fix.smem_len = info->screen_size;
+# endif
+ info->fbdefio = &vbox_defio;
+# if RTLNX_VER_MIN(5,19,0)
+ ret = fb_deferred_io_init(info);
+ if (ret)
+ {
+ DRM_ERROR("failed to initialize deferred io: %d\n", ret);
+ return ret;
+ }
+# endif
+ fb_deferred_io_init(info);
+#endif
+
+ info->pixmap.flags = FB_PIXMAP_SYSTEM;
+
+ DRM_DEBUG_KMS("allocated %dx%d\n", fb->width, fb->height);
+
+ return 0;
+}
+
+static struct drm_fb_helper_funcs vbox_fb_helper_funcs = {
+ .fb_probe = vboxfb_create,
+};
+
+#if RTLNX_VER_MAX(4,3,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+static void drm_fb_helper_unregister_fbi(struct drm_fb_helper *fb_helper)
+{
+ if (fb_helper && fb_helper->fbdev)
+ unregister_framebuffer(fb_helper->fbdev);
+}
+#endif
+
+void vbox_fbdev_fini(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct vbox_fbdev *fbdev = vbox->fbdev;
+ struct vbox_framebuffer *afb = &fbdev->afb;
+
+#ifdef CONFIG_FB_DEFERRED_IO
+ if (VBOX_FBDEV_INFO(fbdev->helper) && VBOX_FBDEV_INFO(fbdev->helper)->fbdefio)
+ fb_deferred_io_cleanup(VBOX_FBDEV_INFO(fbdev->helper));
+#endif
+
+#if RTLNX_VER_MIN(6,2,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+ drm_fb_helper_unregister_info(&fbdev->helper);
+#else
+ drm_fb_helper_unregister_fbi(&fbdev->helper);
+#endif
+
+ if (afb->obj) {
+ struct vbox_bo *bo = gem_to_vbox_bo(afb->obj);
+
+ if (!vbox_bo_reserve(bo, false)) {
+ if (bo->kmap.virtual)
+ ttm_bo_kunmap(&bo->kmap);
+ /*
+ * QXL does this, but is it really needed before
+ * freeing?
+ */
+ if (bo->pin_count)
+ vbox_bo_unpin(bo);
+ vbox_bo_unreserve(bo);
+ }
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(afb->obj);
+#else
+ drm_gem_object_put_unlocked(afb->obj);
+#endif
+ afb->obj = NULL;
+ }
+ drm_fb_helper_fini(&fbdev->helper);
+
+#if RTLNX_VER_MIN(3,9,0)
+ drm_framebuffer_unregister_private(&afb->base);
+#endif
+ drm_framebuffer_cleanup(&afb->base);
+}
+
+int vbox_fbdev_init(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct vbox_fbdev *fbdev;
+ int ret = 0;
+
+ fbdev = devm_kzalloc(dev->dev, sizeof(*fbdev), GFP_KERNEL);
+ if (!fbdev)
+ return -ENOMEM;
+
+ vbox->fbdev = fbdev;
+ spin_lock_init(&fbdev->dirty_lock);
+
+#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+ drm_fb_helper_prepare(dev, &fbdev->helper, 32, &vbox_fb_helper_funcs);
+#elif RTLNX_VER_MIN(3,17,0) || RTLNX_RHEL_MIN(7,2)
+ drm_fb_helper_prepare(dev, &fbdev->helper, &vbox_fb_helper_funcs);
+#else
+ fbdev->helper.funcs = &vbox_fb_helper_funcs;
+#endif
+
+#if RTLNX_VER_MIN(5,7,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ ret = drm_fb_helper_init(dev, &fbdev->helper);
+#elif RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ ret = drm_fb_helper_init(dev, &fbdev->helper, vbox->num_crtcs);
+#else /* < 4.11.0 */
+ ret =
+ drm_fb_helper_init(dev, &fbdev->helper, vbox->num_crtcs,
+ vbox->num_crtcs);
+#endif
+ if (ret)
+ return ret;
+
+#if RTLNX_VER_MAX(5,7,0) && !RTLNX_RHEL_MAJ_PREREQ(8,4) && !RTLNX_SUSE_MAJ_PREREQ(15,3)
+ ret = drm_fb_helper_single_add_all_connectors(&fbdev->helper);
+ if (ret)
+ goto err_fini;
+#endif
+
+ /* disable all the possible outputs/crtcs before entering KMS mode */
+ drm_helper_disable_unused_functions(dev);
+
+#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+ ret = drm_fb_helper_initial_config(&fbdev->helper);
+#else
+ ret = drm_fb_helper_initial_config(&fbdev->helper, 32);
+#endif
+ if (ret)
+ goto err_fini;
+
+ return 0;
+
+err_fini:
+ drm_fb_helper_fini(&fbdev->helper);
+ return ret;
+}
+
+void vbox_fbdev_set_base(struct vbox_private *vbox, unsigned long gpu_addr)
+{
+ struct fb_info *fbdev = VBOX_FBDEV_INFO(vbox->fbdev->helper);
+
+#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+ fbdev->fix.smem_start = pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->fbdev->helper.dev), 0) + gpu_addr;
+#else
+ fbdev->fix.smem_start = fbdev->apertures->ranges[0].base + gpu_addr;
+#endif
+ fbdev->fix.smem_len = vbox->available_vram_size - gpu_addr;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_hgsmi.c b/src/VBox/Additions/linux/drm/vbox_hgsmi.c
new file mode 100644
index 00000000..de47f89d
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_hgsmi.c
@@ -0,0 +1,130 @@
+/** @file
+ * VirtualBox Additions Linux kernel video driver hgsmi interface code
+ */
+
+/*
+ * Contributed by Hans de Goede <hdegoede@redhat.com>
+ *
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <HGSMIBase.h>
+#include <VBoxVideoVBE.h>
+
+/* One-at-a-Time Hash from http://www.burtleburtle.net/bob/hash/doobs.html */
+static u32 hgsmi_hash_process(u32 hash, const u8 *data, int size)
+{
+ while (size--) {
+ hash += *data++;
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+
+ return hash;
+}
+
+static u32 hgsmi_hash_end(u32 hash)
+{
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+
+ return hash;
+}
+
+/* Not really a checksum but that is the naming used in all vbox code */
+static u32 hgsmi_checksum(u32 offset,
+ const HGSMIBUFFERHEADER *header,
+ const HGSMIBUFFERTAIL *tail)
+{
+ u32 checksum;
+
+ checksum = hgsmi_hash_process(0, (u8 *)&offset, sizeof(offset));
+ checksum = hgsmi_hash_process(checksum, (u8 *)header, sizeof(*header));
+ /* 4 -> Do not checksum the checksum itself */
+ checksum = hgsmi_hash_process(checksum, (u8 *)tail, 4);
+
+ return hgsmi_hash_end(checksum);
+}
+
+#if RTLNX_VER_MAX(3,13,0)
+void *gen_pool_dma_alloc(struct gen_pool *pool, size_t size, dma_addr_t *dma)
+{
+ unsigned long vaddr = gen_pool_alloc(pool, size);
+
+ if (vaddr)
+ *dma = gen_pool_virt_to_phys(pool, vaddr);
+ return (void *)vaddr;
+}
+#endif
+
+void *hgsmi_buffer_alloc(struct gen_pool *guest_pool, size_t size,
+ u8 channel, u16 channel_info)
+{
+ HGSMIBUFFERHEADER *h;
+ HGSMIBUFFERTAIL *t;
+ size_t total_size;
+ dma_addr_t offset;
+
+ total_size = size + sizeof(*h) + sizeof(*t);
+ h = gen_pool_dma_alloc(guest_pool, total_size, &offset);
+ if (!h)
+ return NULL;
+
+ t = (HGSMIBUFFERTAIL *)((u8 *)h + sizeof(*h) + size);
+
+ h->u8Flags = HGSMI_BUFFER_HEADER_F_SEQ_SINGLE;
+ h->u32DataSize = size;
+ h->u8Channel = channel;
+ h->u16ChannelInfo = channel_info;
+ memset(&h->u.au8Union, 0, sizeof(h->u.au8Union));
+
+ t->u32Reserved = 0;
+ t->u32Checksum = hgsmi_checksum(offset, h, t);
+
+ return (u8 *)h + sizeof(*h);
+}
+
+void hgsmi_buffer_free(struct gen_pool *guest_pool, void *buf)
+{
+ HGSMIBUFFERHEADER *h =
+ (HGSMIBUFFERHEADER *)((u8 *)buf - sizeof(*h));
+ size_t total_size = h->u32DataSize + sizeof(*h) +
+ sizeof(HGSMIBUFFERTAIL);
+
+ gen_pool_free(guest_pool, (unsigned long)h, total_size);
+}
+
+int hgsmi_buffer_submit(struct gen_pool *guest_pool, void *buf)
+{
+ phys_addr_t offset;
+
+ offset = gen_pool_virt_to_phys(guest_pool, (unsigned long)buf -
+ sizeof(HGSMIBUFFERHEADER));
+ outl(offset, VGA_PORT_HGSMI_GUEST);
+ /* Make the compiler aware that the host has changed memory. */
+ mb();
+
+ return 0;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_irq.c b/src/VBox/Additions/linux/drm/vbox_irq.c
new file mode 100644
index 00000000..594388f8
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_irq.c
@@ -0,0 +1,225 @@
+/* $Id: vbox_irq.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ * This file is based on qxl_irq.c
+ * Copyright 2013 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Dave Airlie
+ * Alon Levy
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include "vbox_drv.h"
+
+#if RTLNX_VER_MAX(5,1,0)
+# include <drm/drm_crtc_helper.h>
+# if RTLNX_RHEL_MAJ_PREREQ(8,1)
+# include <drm/drm_probe_helper.h>
+# endif
+#else
+# include <drm/drm_probe_helper.h>
+#endif
+#include <VBoxVideo.h>
+
+static void vbox_clear_irq(void)
+{
+ outl((u32)~0, VGA_PORT_HGSMI_HOST);
+}
+
+static u32 vbox_get_flags(struct vbox_private *vbox)
+{
+ return readl(vbox->guest_heap + HOST_FLAGS_OFFSET);
+}
+
+void vbox_report_hotplug(struct vbox_private *vbox)
+{
+ schedule_work(&vbox->hotplug_work);
+}
+
+irqreturn_t vbox_irq_handler(int irq, void *arg)
+{
+ struct drm_device *dev = (struct drm_device *)arg;
+ struct vbox_private *vbox = (struct vbox_private *)dev->dev_private;
+ u32 host_flags = vbox_get_flags(vbox);
+
+ if (!(host_flags & HGSMIHOSTFLAGS_IRQ))
+ return IRQ_NONE;
+
+ /*
+ * Due to a bug in the initial host implementation of hot-plug irqs,
+ * the hot-plug and cursor capability flags were never cleared.
+ * Fortunately we can tell when they would have been set by checking
+ * that the VSYNC flag is not set.
+ */
+ if (host_flags &
+ (HGSMIHOSTFLAGS_HOTPLUG | HGSMIHOSTFLAGS_CURSOR_CAPABILITIES) &&
+ !(host_flags & HGSMIHOSTFLAGS_VSYNC))
+ vbox_report_hotplug(vbox);
+
+ vbox_clear_irq();
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * Check that the position hints provided by the host are suitable for GNOME
+ * shell (i.e. all screens disjoint and hints for all enabled screens) and if
+ * not replace them with default ones. Providing valid hints improves the
+ * chances that we will get a known screen layout for pointer mapping.
+ */
+static void validate_or_set_position_hints(struct vbox_private *vbox)
+{
+ struct VBVAMODEHINT *hintsi, *hintsj;
+ bool valid = true;
+ u16 currentx = 0;
+ int i, j;
+
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ for (j = 0; j < i; ++j) {
+ hintsi = &vbox->last_mode_hints[i];
+ hintsj = &vbox->last_mode_hints[j];
+
+ if (hintsi->fEnabled && hintsj->fEnabled) {
+ if (hintsi->dx >= 0xffff ||
+ hintsi->dy >= 0xffff ||
+ hintsj->dx >= 0xffff ||
+ hintsj->dy >= 0xffff ||
+ (hintsi->dx <
+ hintsj->dx + (hintsj->cx & 0x8fff) &&
+ hintsi->dx + (hintsi->cx & 0x8fff) >
+ hintsj->dx) ||
+ (hintsi->dy <
+ hintsj->dy + (hintsj->cy & 0x8fff) &&
+ hintsi->dy + (hintsi->cy & 0x8fff) >
+ hintsj->dy))
+ valid = false;
+ }
+ }
+ }
+ if (!valid)
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ if (vbox->last_mode_hints[i].fEnabled) {
+ vbox->last_mode_hints[i].dx = currentx;
+ vbox->last_mode_hints[i].dy = 0;
+ currentx +=
+ vbox->last_mode_hints[i].cx & 0x8fff;
+ }
+ }
+}
+
+/**
+ * Query the host for the most recent video mode hints.
+ */
+static void vbox_update_mode_hints(struct vbox_private *vbox)
+{
+ struct drm_device *dev = vbox->dev;
+ struct drm_connector *connector;
+ struct vbox_connector *vbox_conn;
+ struct VBVAMODEHINT *hints;
+ u16 flags;
+ bool disconnected;
+ unsigned int crtc_id;
+ int ret;
+
+ ret = VBoxHGSMIGetModeHints(vbox->guest_pool, vbox->num_crtcs,
+ vbox->last_mode_hints);
+ if (ret) {
+ DRM_ERROR("vboxvideo: hgsmi_get_mode_hints failed: %d\n", ret);
+ return;
+ }
+
+ validate_or_set_position_hints(vbox);
+#if RTLNX_VER_MIN(3,9,0)
+ drm_modeset_lock_all(dev);
+#else
+ mutex_lock(&dev->mode_config.mutex);
+#endif
+ list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+ vbox_conn = to_vbox_connector(connector);
+
+ hints = &vbox->last_mode_hints[vbox_conn->vbox_crtc->crtc_id];
+ if (hints->magic != VBVAMODEHINT_MAGIC)
+ continue;
+
+ disconnected = !(hints->fEnabled);
+ crtc_id = vbox_conn->vbox_crtc->crtc_id;
+ vbox_conn->mode_hint.width = hints->cx;
+ vbox_conn->mode_hint.height = hints->cy;
+ vbox_conn->vbox_crtc->x_hint = hints->dx;
+ vbox_conn->vbox_crtc->y_hint = hints->dy;
+ vbox_conn->mode_hint.disconnected = disconnected;
+
+ if (vbox_conn->vbox_crtc->disconnected == disconnected)
+ continue;
+
+ if (disconnected)
+ flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_DISABLED;
+ else
+ flags = VBVA_SCREEN_F_ACTIVE | VBVA_SCREEN_F_BLANK;
+
+ VBoxHGSMIProcessDisplayInfo(vbox->guest_pool, crtc_id, 0, 0, 0,
+ hints->cx * 4, hints->cx,
+ hints->cy, 0, flags);
+
+ vbox_conn->vbox_crtc->disconnected = disconnected;
+ }
+#if RTLNX_VER_MIN(3,9,0)
+ drm_modeset_unlock_all(dev);
+#else
+ mutex_unlock(&dev->mode_config.mutex);
+#endif
+}
+
+static void vbox_hotplug_worker(struct work_struct *work)
+{
+ struct vbox_private *vbox = container_of(work, struct vbox_private,
+ hotplug_work);
+
+ vbox_update_mode_hints(vbox);
+ drm_kms_helper_hotplug_event(vbox->dev);
+}
+
+int vbox_irq_init(struct vbox_private *vbox)
+{
+ INIT_WORK(&vbox->hotplug_work, vbox_hotplug_worker);
+ vbox_update_mode_hints(vbox);
+#if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,1) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+ return request_irq(VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq, vbox_irq_handler, IRQF_SHARED, vbox->dev->driver->name, vbox->dev);
+#elif RTLNX_VER_MIN(3,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+ return drm_irq_install(vbox->dev, VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq);
+#else
+ return drm_irq_install(vbox->dev);
+#endif
+}
+
+void vbox_irq_fini(struct vbox_private *vbox)
+{
+#if RTLNX_VER_MIN(5,15,0) || RTLNX_RHEL_RANGE(8,7, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,1) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+ free_irq(VBOX_DRM_TO_PCI_DEV(vbox->dev)->irq, vbox->dev);
+#else
+ drm_irq_uninstall(vbox->dev);
+#endif
+ flush_work(&vbox->hotplug_work);
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_main.c b/src/VBox/Additions/linux/drm/vbox_main.c
new file mode 100644
index 00000000..ba6ee3c1
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_main.c
@@ -0,0 +1,704 @@
+/* $Id: vbox_main.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ * This file is based on ast_main.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>,
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include "vbox_drv.h"
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+# include <drm/drm_modeset_helper.h>
+#endif
+
+#include <VBoxVideoGuest.h>
+#include <VBoxVideoVBE.h>
+
+#include "hgsmi_channels.h"
+
+static void vbox_user_framebuffer_destroy(struct drm_framebuffer *fb)
+{
+ struct vbox_framebuffer *vbox_fb = to_vbox_framebuffer(fb);
+
+ if (vbox_fb->obj)
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(vbox_fb->obj);
+#else
+ drm_gem_object_put_unlocked(vbox_fb->obj);
+#endif
+
+ drm_framebuffer_cleanup(fb);
+ kfree(fb);
+}
+
+void vbox_enable_accel(struct vbox_private *vbox)
+{
+ unsigned int i;
+ struct VBVABUFFER *vbva;
+
+ if (!vbox->vbva_info || !vbox->vbva_buffers) {
+ /* Should never happen... */
+ DRM_ERROR("vboxvideo: failed to set up VBVA.\n");
+ return;
+ }
+
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ if (vbox->vbva_info[i].pVBVA)
+ continue;
+
+ vbva = (void __force *)vbox->vbva_buffers +
+ i * VBVA_MIN_BUFFER_SIZE;
+ if (!VBoxVBVAEnable(&vbox->vbva_info[i],
+ vbox->guest_pool, vbva, i)) {
+ /* very old host or driver error. */
+ DRM_ERROR("vboxvideo: vbva_enable failed\n");
+ return;
+ }
+ }
+}
+
+void vbox_disable_accel(struct vbox_private *vbox)
+{
+ unsigned int i;
+
+ for (i = 0; i < vbox->num_crtcs; ++i)
+ VBoxVBVADisable(&vbox->vbva_info[i], vbox->guest_pool, i);
+}
+
+void vbox_report_caps(struct vbox_private *vbox)
+{
+ u32 caps = VBVACAPS_DISABLE_CURSOR_INTEGRATION |
+ VBVACAPS_IRQ | VBVACAPS_USE_VBVA_ONLY;
+
+ if (vbox->initial_mode_queried)
+ caps |= VBVACAPS_VIDEO_MODE_HINTS;
+
+ VBoxHGSMISendCapsInfo(vbox->guest_pool, caps);
+}
+
+/**
+ * Send information about dirty rectangles to VBVA. If necessary we enable
+ * VBVA first, as this is normally disabled after a change of master in case
+ * the new master does not send dirty rectangle information (is this even
+ * allowed?)
+ */
+void vbox_framebuffer_dirty_rectangles(struct drm_framebuffer *fb,
+ struct drm_clip_rect *rects,
+ unsigned int num_rects)
+{
+ struct vbox_private *vbox = fb->dev->dev_private;
+ struct drm_crtc *crtc;
+ unsigned int i;
+
+ /* The user can send rectangles, we do not need the timer. */
+ vbox->need_refresh_timer = false;
+ mutex_lock(&vbox->hw_mutex);
+ list_for_each_entry(crtc, &fb->dev->mode_config.crtc_list, head) {
+ if (CRTC_FB(crtc) != fb)
+ continue;
+
+ for (i = 0; i < num_rects; ++i) {
+ VBVACMDHDR cmd_hdr;
+ unsigned int crtc_id = to_vbox_crtc(crtc)->crtc_id;
+
+ if ((rects[i].x1 > crtc->x + crtc->hwmode.hdisplay) ||
+ (rects[i].y1 > crtc->y + crtc->hwmode.vdisplay) ||
+ (rects[i].x2 < crtc->x) ||
+ (rects[i].y2 < crtc->y))
+ continue;
+
+ cmd_hdr.x = (s16)rects[i].x1;
+ cmd_hdr.y = (s16)rects[i].y1;
+ cmd_hdr.w = (u16)rects[i].x2 - rects[i].x1;
+ cmd_hdr.h = (u16)rects[i].y2 - rects[i].y1;
+
+ if (!VBoxVBVABufferBeginUpdate(&vbox->vbva_info[crtc_id],
+ vbox->guest_pool))
+ continue;
+
+ VBoxVBVAWrite(&vbox->vbva_info[crtc_id], vbox->guest_pool,
+ &cmd_hdr, sizeof(cmd_hdr));
+ VBoxVBVABufferEndUpdate(&vbox->vbva_info[crtc_id]);
+ }
+ }
+ mutex_unlock(&vbox->hw_mutex);
+}
+
+static int vbox_user_framebuffer_dirty(struct drm_framebuffer *fb,
+ struct drm_file *file_priv,
+ unsigned int flags, unsigned int color,
+ struct drm_clip_rect *rects,
+ unsigned int num_rects)
+{
+ vbox_framebuffer_dirty_rectangles(fb, rects, num_rects);
+
+ return 0;
+}
+
+static const struct drm_framebuffer_funcs vbox_fb_funcs = {
+ .destroy = vbox_user_framebuffer_destroy,
+ .dirty = vbox_user_framebuffer_dirty,
+};
+
+int vbox_framebuffer_init(struct drm_device *dev,
+ struct vbox_framebuffer *vbox_fb,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ const struct DRM_MODE_FB_CMD *mode_cmd,
+#else
+ struct DRM_MODE_FB_CMD *mode_cmd,
+#endif
+ struct drm_gem_object *obj)
+{
+ int ret;
+
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ drm_helper_mode_fill_fb_struct(dev, &vbox_fb->base, mode_cmd);
+#else
+ drm_helper_mode_fill_fb_struct(&vbox_fb->base, mode_cmd);
+#endif
+ vbox_fb->obj = obj;
+ ret = drm_framebuffer_init(dev, &vbox_fb->base, &vbox_fb_funcs);
+ if (ret) {
+ DRM_ERROR("framebuffer init failed %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct drm_framebuffer *vbox_user_framebuffer_create(
+ struct drm_device *dev,
+ struct drm_file *filp,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ const struct drm_mode_fb_cmd2 *mode_cmd)
+#else
+ struct drm_mode_fb_cmd2 *mode_cmd)
+#endif
+{
+ struct drm_gem_object *obj;
+ struct vbox_framebuffer *vbox_fb;
+ int ret = -ENOMEM;
+
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ obj = drm_gem_object_lookup(filp, mode_cmd->handles[0]);
+#else
+ obj = drm_gem_object_lookup(dev, filp, mode_cmd->handles[0]);
+#endif
+ if (!obj)
+ return ERR_PTR(-ENOENT);
+
+ vbox_fb = kzalloc(sizeof(*vbox_fb), GFP_KERNEL);
+ if (!vbox_fb)
+ goto err_unref_obj;
+
+ ret = vbox_framebuffer_init(dev, vbox_fb, mode_cmd, obj);
+ if (ret)
+ goto err_free_vbox_fb;
+
+ return &vbox_fb->base;
+
+err_free_vbox_fb:
+ kfree(vbox_fb);
+err_unref_obj:
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(obj);
+#else
+ drm_gem_object_put_unlocked(obj);
+#endif
+ return ERR_PTR(ret);
+}
+
+static const struct drm_mode_config_funcs vbox_mode_funcs = {
+ .fb_create = vbox_user_framebuffer_create,
+};
+
+#if RTLNX_VER_MAX(4,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+# define pci_iomap_range(dev, bar, offset, maxlen) \
+ ioremap(pci_resource_start(dev, bar) + (offset), maxlen)
+#endif
+
+/**
+ * Tell the host about the views. This design originally targeted the
+ * Windows XP driver architecture and assumed that each screen would
+ * have a dedicated frame buffer with the command buffer following it,
+ * the whole being a "view". The host works out which screen a command
+ * buffer belongs to by checking whether it is in the first view, then
+ * whether it is in the second and so on. The first match wins. We
+ * cheat around this by making the first view be the managed memory
+ * plus the first command buffer, the second the same plus the second
+ * buffer and so on.
+ */
+static int vbox_set_views(struct vbox_private *vbox)
+{
+ VBVAINFOVIEW *p;
+ int i;
+
+ p = VBoxHGSMIBufferAlloc(vbox->guest_pool, sizeof(*p),
+ HGSMI_CH_VBVA, VBVA_INFO_VIEW);
+ if (!p)
+ return -ENOMEM;
+
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ p->u32ViewIndex = i;
+ p->u32ViewOffset = 0;
+ p->u32ViewSize = vbox->available_vram_size +
+ i * VBVA_MIN_BUFFER_SIZE;
+ p->u32MaxScreenSize = vbox->available_vram_size;
+
+ VBoxHGSMIBufferSubmit(vbox->guest_pool, p);
+ }
+
+ VBoxHGSMIBufferFree(vbox->guest_pool, p);
+
+ return 0;
+}
+
+static int vbox_accel_init(struct vbox_private *vbox)
+{
+ unsigned int i, ret;
+
+ vbox->vbva_info = devm_kcalloc(vbox->dev->dev, vbox->num_crtcs,
+ sizeof(*vbox->vbva_info), GFP_KERNEL);
+ if (!vbox->vbva_info)
+ return -ENOMEM;
+
+ /* Take a command buffer for each screen from the end of usable VRAM. */
+ vbox->available_vram_size -= vbox->num_crtcs * VBVA_MIN_BUFFER_SIZE;
+
+ vbox->vbva_buffers = pci_iomap_range(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0,
+ vbox->available_vram_size,
+ vbox->num_crtcs *
+ VBVA_MIN_BUFFER_SIZE);
+ if (!vbox->vbva_buffers)
+ return -ENOMEM;
+
+ for (i = 0; i < vbox->num_crtcs; ++i)
+ VBoxVBVASetupBufferContext(&vbox->vbva_info[i],
+ vbox->available_vram_size +
+ i * VBVA_MIN_BUFFER_SIZE,
+ VBVA_MIN_BUFFER_SIZE);
+
+ vbox_enable_accel(vbox);
+ ret = vbox_set_views(vbox);
+ if (ret)
+ goto err_pci_iounmap;
+
+ return 0;
+
+err_pci_iounmap:
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->vbva_buffers);
+ return ret;
+}
+
+static void vbox_accel_fini(struct vbox_private *vbox)
+{
+ vbox_disable_accel(vbox);
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->vbva_buffers);
+}
+
+/** Do we support the 4.3 plus mode hint reporting interface? */
+static bool have_hgsmi_mode_hints(struct vbox_private *vbox)
+{
+ u32 have_hints, have_cursor;
+ int ret;
+
+ ret = VBoxQueryConfHGSMI(vbox->guest_pool,
+ VBOX_VBVA_CONF32_MODE_HINT_REPORTING,
+ &have_hints);
+ if (ret)
+ return false;
+
+ ret = VBoxQueryConfHGSMI(vbox->guest_pool,
+ VBOX_VBVA_CONF32_GUEST_CURSOR_REPORTING,
+ &have_cursor);
+ if (ret)
+ return false;
+
+ return have_hints == VINF_SUCCESS && have_cursor == VINF_SUCCESS;
+}
+
+/**
+ * Our refresh timer call-back. Only used for guests without dirty rectangle
+ * support.
+ */
+static void vbox_refresh_timer(struct work_struct *work)
+{
+ struct vbox_private *vbox = container_of(work, struct vbox_private,
+ refresh_work.work);
+ bool have_unblanked = false;
+ struct drm_crtc *crtci;
+
+ if (!vbox->need_refresh_timer)
+ return;
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list, head) {
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtci);
+ if (crtci->enabled && !vbox_crtc->blanked)
+ have_unblanked = true;
+ }
+ if (!have_unblanked)
+ return;
+ /* This forces a full refresh. */
+ vbox_enable_accel(vbox);
+ /* Schedule the next timer iteration. */
+ schedule_delayed_work(&vbox->refresh_work, VBOX_REFRESH_PERIOD);
+}
+
+static bool vbox_check_supported(u16 id)
+{
+ u16 dispi_id;
+
+ vbox_write_ioport(VBE_DISPI_INDEX_ID, id);
+ dispi_id = inw(VBE_DISPI_IOPORT_DATA);
+
+ return dispi_id == id;
+}
+
+/**
+ * Set up our heaps and data exchange buffers in VRAM before handing the rest
+ * to the memory manager.
+ */
+static int vbox_hw_init(struct vbox_private *vbox)
+{
+ int ret = -ENOMEM;
+
+ vbox->full_vram_size = inl(VBE_DISPI_IOPORT_DATA);
+ vbox->any_pitch = vbox_check_supported(VBE_DISPI_ID_ANYX);
+
+ DRM_INFO("VRAM %08x\n", vbox->full_vram_size);
+
+ /* Map guest-heap at end of vram */
+ vbox->guest_heap =
+ pci_iomap_range(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0, GUEST_HEAP_OFFSET(vbox),
+ GUEST_HEAP_SIZE);
+ if (!vbox->guest_heap)
+ return -ENOMEM;
+
+ /* Create guest-heap mem-pool use 2^4 = 16 byte chunks */
+ vbox->guest_pool = gen_pool_create(4, -1);
+ if (!vbox->guest_pool)
+ goto err_unmap_guest_heap;
+
+ ret = gen_pool_add_virt(vbox->guest_pool,
+ (unsigned long)vbox->guest_heap,
+ GUEST_HEAP_OFFSET(vbox),
+ GUEST_HEAP_USABLE_SIZE, -1);
+ if (ret)
+ goto err_destroy_guest_pool;
+
+ /* Reduce available VRAM size to reflect the guest heap. */
+ vbox->available_vram_size = GUEST_HEAP_OFFSET(vbox);
+ /* Linux drm represents monitors as a 32-bit array. */
+ VBoxQueryConfHGSMI(vbox->guest_pool, VBOX_VBVA_CONF32_MONITOR_COUNT,
+ &vbox->num_crtcs);
+ vbox->num_crtcs = clamp_t(u32, vbox->num_crtcs, 1, VBOX_MAX_SCREENS);
+
+ if (!have_hgsmi_mode_hints(vbox)) {
+ ret = -ENOTSUPP;
+ goto err_destroy_guest_pool;
+ }
+
+ vbox->last_mode_hints = devm_kcalloc(vbox->dev->dev, vbox->num_crtcs,
+ sizeof(VBVAMODEHINT),
+ GFP_KERNEL);
+ if (!vbox->last_mode_hints) {
+ ret = -ENOMEM;
+ goto err_destroy_guest_pool;
+ }
+
+ ret = vbox_accel_init(vbox);
+ if (ret)
+ goto err_destroy_guest_pool;
+
+ /* Set up the refresh timer for users which do not send dirty rectangles. */
+ INIT_DELAYED_WORK(&vbox->refresh_work, vbox_refresh_timer);
+
+ return 0;
+
+err_destroy_guest_pool:
+ gen_pool_destroy(vbox->guest_pool);
+err_unmap_guest_heap:
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->guest_heap);
+ return ret;
+}
+
+static void vbox_hw_fini(struct vbox_private *vbox)
+{
+ vbox->need_refresh_timer = false;
+ cancel_delayed_work(&vbox->refresh_work);
+ vbox_accel_fini(vbox);
+ gen_pool_destroy(vbox->guest_pool);
+ pci_iounmap(VBOX_DRM_TO_PCI_DEV(vbox->dev), vbox->guest_heap);
+}
+
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MIN(8,3)
+int vbox_driver_load(struct drm_device *dev)
+#else
+int vbox_driver_load(struct drm_device *dev, unsigned long flags)
+#endif
+{
+ struct vbox_private *vbox;
+ int ret = 0;
+
+ if (!vbox_check_supported(VBE_DISPI_ID_HGSMI))
+ return -ENODEV;
+
+ vbox = devm_kzalloc(dev->dev, sizeof(*vbox), GFP_KERNEL);
+ if (!vbox)
+ return -ENOMEM;
+
+ dev->dev_private = vbox;
+ vbox->dev = dev;
+
+ mutex_init(&vbox->hw_mutex);
+
+ ret = vbox_hw_init(vbox);
+ if (ret)
+ return ret;
+
+ ret = vbox_mm_init(vbox);
+ if (ret)
+ goto err_hw_fini;
+
+ drm_mode_config_init(dev);
+
+ dev->mode_config.funcs = (void *)&vbox_mode_funcs;
+ dev->mode_config.min_width = 64;
+ dev->mode_config.min_height = 64;
+ dev->mode_config.preferred_depth = 24;
+ dev->mode_config.max_width = VBE_DISPI_MAX_XRES;
+ dev->mode_config.max_height = VBE_DISPI_MAX_YRES;
+
+ ret = vbox_mode_init(dev);
+ if (ret)
+ goto err_drm_mode_cleanup;
+
+ ret = vbox_irq_init(vbox);
+ if (ret)
+ goto err_mode_fini;
+
+ ret = vbox_fbdev_init(dev);
+ if (ret)
+ goto err_irq_fini;
+
+ return 0;
+
+err_irq_fini:
+ vbox_irq_fini(vbox);
+err_mode_fini:
+ vbox_mode_fini(dev);
+err_drm_mode_cleanup:
+ drm_mode_config_cleanup(dev);
+ vbox_mm_fini(vbox);
+err_hw_fini:
+ vbox_hw_fini(vbox);
+ return ret;
+}
+
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+void vbox_driver_unload(struct drm_device *dev)
+#else
+int vbox_driver_unload(struct drm_device *dev)
+#endif
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+ vbox_fbdev_fini(dev);
+ vbox_irq_fini(vbox);
+ vbox_mode_fini(dev);
+ drm_mode_config_cleanup(dev);
+ vbox_mm_fini(vbox);
+ vbox_hw_fini(vbox);
+#if RTLNX_VER_MAX(4,11,0) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+ return 0;
+#endif
+}
+
+/**
+ * @note this is described in the DRM framework documentation. AST does not
+ * have it, but we get an oops on driver unload if it is not present.
+ */
+void vbox_driver_lastclose(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+
+#if RTLNX_VER_MIN(3,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+ if (vbox->fbdev)
+ drm_fb_helper_restore_fbdev_mode_unlocked(&vbox->fbdev->helper);
+#else
+ drm_modeset_lock_all(dev);
+ if (vbox->fbdev)
+ drm_fb_helper_restore_fbdev_mode(&vbox->fbdev->helper);
+ drm_modeset_unlock_all(dev);
+#endif
+}
+
+int vbox_gem_create(struct drm_device *dev,
+ u32 size, bool iskernel, struct drm_gem_object **obj)
+{
+ struct vbox_bo *vboxbo;
+ int ret;
+
+ *obj = NULL;
+
+ size = roundup(size, PAGE_SIZE);
+ if (size == 0)
+ {
+ DRM_ERROR("bad size\n");
+ return -EINVAL;
+ }
+
+ ret = vbox_bo_create(dev, size, 0, 0, &vboxbo);
+ if (ret) {
+ if (ret != -ERESTARTSYS)
+ DRM_ERROR("failed to allocate GEM object\n");
+ DRM_ERROR("failed to allocate GEM (%d)\n", ret);
+ return ret;
+ }
+
+ *obj = &vboxbo->gem;
+
+ return 0;
+}
+
+int vbox_dumb_create(struct drm_file *file,
+ struct drm_device *dev, struct drm_mode_create_dumb *args)
+{
+ int ret;
+ struct drm_gem_object *gobj;
+ u32 handle;
+
+ args->pitch = args->width * ((args->bpp + 7) / 8);
+ args->size = args->pitch * args->height;
+
+ ret = vbox_gem_create(dev, args->size, false, &gobj);
+ if (ret)
+ return ret;
+
+ ret = drm_gem_handle_create(file, gobj, &handle);
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(gobj);
+#else
+ drm_gem_object_put_unlocked(gobj);
+#endif
+ if (ret)
+ return ret;
+
+ args->handle = handle;
+
+ return 0;
+}
+
+#if RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,3)
+int vbox_dumb_destroy(struct drm_file *file,
+ struct drm_device *dev, u32 handle)
+{
+ return drm_gem_handle_delete(file, handle);
+}
+#endif
+
+#if RTLNX_VER_MAX(4,19,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+static void ttm_bo_put(struct ttm_buffer_object *bo)
+{
+ ttm_bo_unref(&bo);
+}
+#endif
+
+void vbox_gem_free_object(struct drm_gem_object *obj)
+{
+ struct vbox_bo *vbox_bo = gem_to_vbox_bo(obj);
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ /* Starting from kernel 5.14, there is a warning appears in dmesg
+ * on attempt to desroy pinned buffer object. Make sure it is unpinned. */
+ while (vbox_bo->bo.pin_count)
+ {
+ int ret;
+ ret = vbox_bo_unpin(vbox_bo);
+ if (ret)
+ {
+ DRM_ERROR("unable to unpin buffer object\n");
+ break;
+ }
+ }
+#endif
+
+ ttm_bo_put(&vbox_bo->bo);
+}
+
+static inline u64 vbox_bo_mmap_offset(struct vbox_bo *bo)
+{
+#if RTLNX_VER_MIN(5,4,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ return drm_vma_node_offset_addr(&bo->bo.base.vma_node);
+#elif RTLNX_VER_MAX(3,12,0) && !RTLNX_RHEL_MAJ_PREREQ(7,0)
+ return bo->bo.addr_space_offset;
+#else
+ return drm_vma_node_offset_addr(&bo->bo.vma_node);
+#endif /* >= 5.4.0 */
+}
+
+int
+vbox_dumb_mmap_offset(struct drm_file *file,
+ struct drm_device *dev,
+ u32 handle, u64 *offset)
+{
+ struct drm_gem_object *obj;
+ int ret = 0;
+ struct vbox_bo *bo;
+
+ mutex_lock(&dev->struct_mutex);
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ obj = drm_gem_object_lookup(file, handle);
+#else
+ obj = drm_gem_object_lookup(dev, file, handle);
+#endif
+ if (!obj) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+
+ bo = gem_to_vbox_bo(obj);
+ *offset = vbox_bo_mmap_offset(bo);
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = drm_vma_node_allow(&bo->bo.base.vma_node, file);
+ if (ret)
+ {
+ DRM_ERROR("unable to grant previladges to user");
+ }
+#endif
+
+ drm_gem_object_put(obj);
+
+out_unlock:
+ mutex_unlock(&dev->struct_mutex);
+ return ret;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_mode.c b/src/VBox/Additions/linux/drm/vbox_mode.c
new file mode 100644
index 00000000..bfe13ced
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_mode.c
@@ -0,0 +1,949 @@
+/* $Id: vbox_mode.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ * This file is based on ast_mode.c
+ * Copyright 2012 Red Hat Inc.
+ * Parts based on xf86-video-ast
+ * Copyright (c) 2005 ASPEED Technology Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ */
+/*
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com,
+ * Hans de Goede <hdegoede@redhat.com>
+ */
+#include "vbox_drv.h"
+#include <linux/export.h>
+#include <drm/drm_crtc_helper.h>
+#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99)
+# include <drm/drm_modeset_helper_vtables.h>
+# include <drm/drm_modeset_helper.h>
+#endif
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+# include <drm/drm_plane_helper.h>
+#endif
+#if RTLNX_VER_MIN(5,1,0) || RTLNX_RHEL_MAJ_PREREQ(8,1)
+# include <drm/drm_probe_helper.h>
+#endif
+
+#if RTLNX_VER_MIN(6,0,0) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,2) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+# include <drm/drm_edid.h>
+#endif
+
+#include "VBoxVideo.h"
+
+static int vbox_cursor_set2(struct drm_crtc *crtc, struct drm_file *file_priv,
+ u32 handle, u32 width, u32 height,
+ s32 hot_x, s32 hot_y);
+static int vbox_cursor_move(struct drm_crtc *crtc, int x, int y);
+
+/**
+ * Set a graphics mode. Poke any required values into registers, do an HGSMI
+ * mode set and tell the host we support advanced graphics functions.
+ */
+static void vbox_do_modeset(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct vbox_private *vbox;
+ int width, height, bpp, pitch;
+ u16 flags;
+ s32 x_offset, y_offset;
+
+ vbox = crtc->dev->dev_private;
+ width = mode->hdisplay ? mode->hdisplay : 640;
+ height = mode->vdisplay ? mode->vdisplay : 480;
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ bpp = crtc->enabled ? CRTC_FB(crtc)->format->cpp[0] * 8 : 32;
+ pitch = crtc->enabled ? CRTC_FB(crtc)->pitches[0] : width * bpp / 8;
+#elif RTLNX_VER_MIN(3,3,0)
+ bpp = crtc->enabled ? CRTC_FB(crtc)->bits_per_pixel : 32;
+ pitch = crtc->enabled ? CRTC_FB(crtc)->pitches[0] : width * bpp / 8;
+#else
+ bpp = crtc->enabled ? CRTC_FB(crtc)->bits_per_pixel : 32;
+ pitch = crtc->enabled ? CRTC_FB(crtc)->pitch : width * bpp / 8;
+#endif
+ x_offset = vbox->single_framebuffer ? crtc->x : vbox_crtc->x_hint;
+ y_offset = vbox->single_framebuffer ? crtc->y : vbox_crtc->y_hint;
+
+ /*
+ * This is the old way of setting graphics modes. It assumed one screen
+ * and a frame-buffer at the start of video RAM. On older versions of
+ * VirtualBox, certain parts of the code still assume that the first
+ * screen is programmed this way, so try to fake it.
+ */
+ if (vbox_crtc->crtc_id == 0 && crtc->enabled &&
+ vbox_crtc->fb_offset / pitch < 0xffff - crtc->y &&
+ vbox_crtc->fb_offset % (bpp / 8) == 0)
+ VBoxVideoSetModeRegisters(
+ width, height, pitch * 8 / bpp,
+#if RTLNX_VER_MIN(4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ CRTC_FB(crtc)->format->cpp[0] * 8,
+#else
+ CRTC_FB(crtc)->bits_per_pixel,
+#endif
+ 0,
+ vbox_crtc->fb_offset % pitch / bpp * 8 + crtc->x,
+ vbox_crtc->fb_offset / pitch + crtc->y);
+
+ flags = VBVA_SCREEN_F_ACTIVE;
+ flags |= (crtc->enabled && !vbox_crtc->blanked) ?
+ 0 : VBVA_SCREEN_F_BLANK;
+ flags |= vbox_crtc->disconnected ? VBVA_SCREEN_F_DISABLED : 0;
+ VBoxHGSMIProcessDisplayInfo(vbox->guest_pool, vbox_crtc->crtc_id,
+ x_offset, y_offset, vbox_crtc->fb_offset +
+ crtc->x * bpp / 8 + crtc->y * pitch,
+ pitch, width, height,
+ vbox_crtc->blanked ? 0 : bpp, flags);
+}
+
+static void vbox_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct vbox_private *vbox = crtc->dev->dev_private;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ vbox_crtc->blanked = false;
+ /* Restart the refresh timer if necessary. */
+ schedule_delayed_work(&vbox->refresh_work, VBOX_REFRESH_PERIOD);
+ break;
+ case DRM_MODE_DPMS_STANDBY:
+ case DRM_MODE_DPMS_SUSPEND:
+ case DRM_MODE_DPMS_OFF:
+ vbox_crtc->blanked = true;
+ break;
+ }
+
+ mutex_lock(&vbox->hw_mutex);
+ vbox_do_modeset(crtc, &crtc->hwmode);
+ mutex_unlock(&vbox->hw_mutex);
+}
+
+static bool vbox_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+/*
+ * Try to map the layout of virtual screens to the range of the input device.
+ * Return true if we need to re-set the crtc modes due to screen offset
+ * changes.
+ */
+static bool vbox_set_up_input_mapping(struct vbox_private *vbox)
+{
+ struct drm_crtc *crtci;
+ struct drm_connector *connectori;
+ struct drm_framebuffer *fb1 = NULL;
+ bool single_framebuffer = true;
+ bool old_single_framebuffer = vbox->single_framebuffer;
+ u16 width = 0, height = 0;
+
+ /*
+ * Are we using an X.Org-style single large frame-buffer for all crtcs?
+ * If so then screen layout can be deduced from the crtc offsets.
+ * Same fall-back if this is the fbdev frame-buffer.
+ */
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list, head) {
+ if (!fb1) {
+ fb1 = CRTC_FB(crtci);
+ if (to_vbox_framebuffer(fb1) == &vbox->fbdev->afb)
+ break;
+ } else if (CRTC_FB(crtci) && fb1 != CRTC_FB(crtci)) {
+ single_framebuffer = false;
+ }
+ }
+ if (single_framebuffer) {
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list,
+ head) {
+ if (to_vbox_crtc(crtci)->crtc_id != 0)
+ continue;
+
+ if (!CRTC_FB(crtci))
+ break;
+ vbox->single_framebuffer = true;
+ vbox->input_mapping_width = CRTC_FB(crtci)->width;
+ vbox->input_mapping_height = CRTC_FB(crtci)->height;
+ return old_single_framebuffer !=
+ vbox->single_framebuffer;
+ }
+ }
+ /* Otherwise calculate the total span of all screens. */
+ list_for_each_entry(connectori, &vbox->dev->mode_config.connector_list,
+ head) {
+ struct vbox_connector *vbox_connector =
+ to_vbox_connector(connectori);
+ struct vbox_crtc *vbox_crtc = vbox_connector->vbox_crtc;
+
+ width = max_t(u16, width, vbox_crtc->x_hint +
+ vbox_connector->mode_hint.width);
+ height = max_t(u16, height, vbox_crtc->y_hint +
+ vbox_connector->mode_hint.height);
+ }
+
+ vbox->single_framebuffer = false;
+ vbox->input_mapping_width = width;
+ vbox->input_mapping_height = height;
+
+ return old_single_framebuffer != vbox->single_framebuffer;
+}
+
+static int vbox_crtc_set_base(struct drm_crtc *crtc,
+ struct drm_framebuffer *old_fb,
+ struct drm_framebuffer *new_fb,
+ int x, int y)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct drm_gem_object *obj;
+ struct vbox_framebuffer *vbox_fb;
+ struct vbox_bo *bo;
+ int ret;
+ u64 gpu_addr;
+
+ vbox_fb = to_vbox_framebuffer(new_fb);
+ obj = vbox_fb->obj;
+ bo = gem_to_vbox_bo(obj);
+
+ ret = vbox_bo_reserve(bo, false);
+ if (ret)
+ return ret;
+
+ ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_VRAM, &gpu_addr);
+ vbox_bo_unreserve(bo);
+ if (ret)
+ return ret;
+
+ /* Unpin the previous fb. Do this after the new one has been pinned rather
+ * than before and re-pinning it on failure in case that fails too. */
+ if (old_fb) {
+ vbox_fb = to_vbox_framebuffer(old_fb);
+ obj = vbox_fb->obj;
+ bo = gem_to_vbox_bo(obj);
+ ret = vbox_bo_reserve(bo, false);
+ /* This should never fail, as no one else should be accessing it and we
+ * should be running under the modeset locks. */
+ if (!ret) {
+ vbox_bo_unpin(bo);
+ vbox_bo_unreserve(bo);
+ }
+ else
+ {
+ DRM_ERROR("unable to lock buffer object: error %d\n", ret);
+ }
+ }
+
+ if (&vbox->fbdev->afb == vbox_fb)
+ vbox_fbdev_set_base(vbox, gpu_addr);
+
+ vbox_crtc->fb_offset = gpu_addr;
+ if (vbox_set_up_input_mapping(vbox)) {
+ struct drm_crtc *crtci;
+
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list,
+ head) {
+ vbox_do_modeset(crtci, &crtci->mode);
+ }
+ }
+
+ return 0;
+}
+
+static int vbox_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y, struct drm_framebuffer *old_fb)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ int ret = vbox_crtc_set_base(crtc, old_fb, CRTC_FB(crtc), x, y);
+ if (ret)
+ return ret;
+ mutex_lock(&vbox->hw_mutex);
+ vbox_do_modeset(crtc, mode);
+ VBoxHGSMIUpdateInputMapping(vbox->guest_pool, 0, 0,
+ vbox->input_mapping_width,
+ vbox->input_mapping_height);
+ mutex_unlock(&vbox->hw_mutex);
+
+ return ret;
+}
+
+static int vbox_crtc_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+#if RTLNX_VER_MIN(4,12,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+ struct drm_pending_vblank_event *event,
+ uint32_t page_flip_flags,
+ struct drm_modeset_acquire_ctx *ctx)
+#elif RTLNX_VER_MIN(3,12,0) || RTLNX_RHEL_MAJ_PREREQ(7,0)
+ struct drm_pending_vblank_event *event,
+ uint32_t page_flip_flags)
+#else
+ struct drm_pending_vblank_event *event)
+#endif
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ struct drm_device *drm = vbox->dev;
+ unsigned long flags;
+ int rc;
+
+ rc = vbox_crtc_set_base(crtc, CRTC_FB(crtc), fb, 0, 0);
+ if (rc)
+ return rc;
+
+ mutex_lock(&vbox->hw_mutex);
+ vbox_do_modeset(crtc, &crtc->mode);
+ mutex_unlock(&vbox->hw_mutex);
+
+ spin_lock_irqsave(&drm->event_lock, flags);
+
+ if (event)
+#if RTLNX_VER_MIN(3,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_crtc_send_vblank_event(crtc, event);
+#else
+ drm_send_vblank_event(drm, -1, event);
+#endif
+
+ spin_unlock_irqrestore(&drm->event_lock, flags);
+
+ return 0;
+}
+
+static void vbox_crtc_disable(struct drm_crtc *crtc)
+{
+}
+
+static void vbox_crtc_prepare(struct drm_crtc *crtc)
+{
+}
+
+static void vbox_crtc_commit(struct drm_crtc *crtc)
+{
+}
+
+static const struct drm_crtc_helper_funcs vbox_crtc_helper_funcs = {
+ .dpms = vbox_crtc_dpms,
+ .mode_fixup = vbox_crtc_mode_fixup,
+ .mode_set = vbox_crtc_mode_set,
+ .disable = vbox_crtc_disable,
+ .prepare = vbox_crtc_prepare,
+ .commit = vbox_crtc_commit,
+};
+
+static void vbox_crtc_reset(struct drm_crtc *crtc)
+{
+}
+
+static void vbox_crtc_destroy(struct drm_crtc *crtc)
+{
+ drm_crtc_cleanup(crtc);
+ kfree(crtc);
+}
+
+static const struct drm_crtc_funcs vbox_crtc_funcs = {
+ .cursor_move = vbox_cursor_move,
+ .cursor_set2 = vbox_cursor_set2,
+ .reset = vbox_crtc_reset,
+ .set_config = drm_crtc_helper_set_config,
+ /* .gamma_set = vbox_crtc_gamma_set, */
+ .page_flip = vbox_crtc_page_flip,
+ .destroy = vbox_crtc_destroy,
+};
+
+static struct vbox_crtc *vbox_crtc_init(struct drm_device *dev, unsigned int i)
+{
+ struct vbox_crtc *vbox_crtc;
+
+ vbox_crtc = kzalloc(sizeof(*vbox_crtc), GFP_KERNEL);
+ if (!vbox_crtc)
+ return NULL;
+
+ vbox_crtc->crtc_id = i;
+
+ drm_crtc_init(dev, &vbox_crtc->base, &vbox_crtc_funcs);
+ drm_mode_crtc_set_gamma_size(&vbox_crtc->base, 256);
+ drm_crtc_helper_add(&vbox_crtc->base, &vbox_crtc_helper_funcs);
+
+ return vbox_crtc;
+}
+
+static void vbox_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+ kfree(encoder);
+}
+
+#if RTLNX_VER_MAX(3,13,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+static struct drm_encoder *drm_encoder_find(struct drm_device *dev, u32 id)
+{
+ struct drm_mode_object *mo;
+
+ mo = drm_mode_object_find(dev, id, DRM_MODE_OBJECT_ENCODER);
+ return mo ? obj_to_encoder(mo) : NULL;
+}
+#endif
+
+static struct drm_encoder *vbox_best_single_encoder(struct drm_connector
+ *connector)
+{
+#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ struct drm_encoder *encoder;
+
+ /* There is only one encoder per connector */
+ drm_connector_for_each_possible_encoder(connector, encoder)
+ return encoder;
+#else /* < 5.5 || RHEL < 8.3 */
+ int enc_id = connector->encoder_ids[0];
+
+ /* pick the encoder ids */
+ if (enc_id)
+# if RTLNX_VER_MIN(4,15,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || (defined(CONFIG_SUSE_VERSION) && RTLNX_VER_MIN(4,12,0))
+ return drm_encoder_find(connector->dev, NULL, enc_id);
+# else
+ return drm_encoder_find(connector->dev, enc_id);
+# endif
+#endif /* < 5.5 || RHEL < 8.3 */
+ return NULL;
+}
+
+static const struct drm_encoder_funcs vbox_enc_funcs = {
+ .destroy = vbox_encoder_destroy,
+};
+
+static void vbox_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool vbox_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void vbox_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static void vbox_encoder_prepare(struct drm_encoder *encoder)
+{
+}
+
+static void vbox_encoder_commit(struct drm_encoder *encoder)
+{
+}
+
+static const struct drm_encoder_helper_funcs vbox_enc_helper_funcs = {
+ .dpms = vbox_encoder_dpms,
+ .mode_fixup = vbox_mode_fixup,
+ .prepare = vbox_encoder_prepare,
+ .commit = vbox_encoder_commit,
+ .mode_set = vbox_encoder_mode_set,
+};
+
+static struct drm_encoder *vbox_encoder_init(struct drm_device *dev,
+ unsigned int i)
+{
+ struct vbox_encoder *vbox_encoder;
+
+ vbox_encoder = kzalloc(sizeof(*vbox_encoder), GFP_KERNEL);
+ if (!vbox_encoder)
+ return NULL;
+
+ drm_encoder_init(dev, &vbox_encoder->base, &vbox_enc_funcs,
+#if RTLNX_VER_MIN(4,5,0) || RTLNX_RHEL_MAJ_PREREQ(7,3)
+ DRM_MODE_ENCODER_DAC, NULL);
+#else
+ DRM_MODE_ENCODER_DAC);
+#endif
+ drm_encoder_helper_add(&vbox_encoder->base, &vbox_enc_helper_funcs);
+
+ vbox_encoder->base.possible_crtcs = 1 << i;
+ return &vbox_encoder->base;
+}
+
+/**
+ * Generate EDID data with a mode-unique serial number for the virtual
+ * monitor to try to persuade Unity that different modes correspond to
+ * different monitors and it should not try to force the same resolution on
+ * them.
+ */
+static void vbox_set_edid(struct drm_connector *connector, int width,
+ int height)
+{
+ enum { EDID_SIZE = 128 };
+ unsigned char edid[EDID_SIZE] = {
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, /* header */
+ 0x58, 0x58, /* manufacturer (VBX) */
+ 0x00, 0x00, /* product code */
+ 0x00, 0x00, 0x00, 0x00, /* serial number goes here */
+ 0x01, /* week of manufacture */
+ 0x00, /* year of manufacture */
+ 0x01, 0x03, /* EDID version */
+ 0x80, /* capabilities - digital */
+ 0x00, /* horiz. res in cm, zero for projectors */
+ 0x00, /* vert. res in cm */
+ 0x78, /* display gamma (120 == 2.2). */
+ 0xEE, /* features (standby, suspend, off, RGB, std */
+ /* colour space, preferred timing mode) */
+ 0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
+ /* chromaticity for standard colour space. */
+ 0x00, 0x00, 0x00, /* no default timings */
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, /* no standard timings */
+ 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x06, 0x00, 0x02, 0x02,
+ 0x02, 0x02,
+ /* descriptor block 1 goes below */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* descriptor block 2, monitor ranges */
+ 0x00, 0x00, 0x00, 0xFD, 0x00,
+ 0x00, 0xC8, 0x00, 0xC8, 0x64, 0x00, 0x0A, 0x20, 0x20, 0x20,
+ 0x20, 0x20,
+ /* 0-200Hz vertical, 0-200KHz horizontal, 1000MHz pixel clock */
+ 0x20,
+ /* descriptor block 3, monitor name */
+ 0x00, 0x00, 0x00, 0xFC, 0x00,
+ 'V', 'B', 'O', 'X', ' ', 'm', 'o', 'n', 'i', 't', 'o', 'r',
+ '\n',
+ /* descriptor block 4: dummy data */
+ 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20,
+ 0x00, /* number of extensions */
+ 0x00 /* checksum goes here */
+ };
+ int clock = (width + 6) * (height + 6) * 60 / 10000;
+ unsigned int i, sum = 0;
+
+ edid[12] = width & 0xff;
+ edid[13] = width >> 8;
+ edid[14] = height & 0xff;
+ edid[15] = height >> 8;
+ edid[54] = clock & 0xff;
+ edid[55] = clock >> 8;
+ edid[56] = width & 0xff;
+ edid[58] = (width >> 4) & 0xf0;
+ edid[59] = height & 0xff;
+ edid[61] = (height >> 4) & 0xf0;
+ for (i = 0; i < EDID_SIZE - 1; ++i)
+ sum += edid[i];
+ edid[EDID_SIZE - 1] = (0x100 - (sum & 0xFF)) & 0xFF;
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,7) || RTLNX_RHEL_MAJ_PREREQ(8,1) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ drm_connector_update_edid_property(connector, (struct edid *)edid);
+#else
+ drm_mode_connector_update_edid_property(connector, (struct edid *)edid);
+#endif
+}
+
+static int vbox_get_modes(struct drm_connector *connector)
+{
+ struct vbox_connector *vbox_connector = NULL;
+ struct drm_display_mode *mode = NULL;
+ struct vbox_private *vbox = NULL;
+ unsigned int num_modes = 0;
+ int preferred_width, preferred_height;
+
+ vbox_connector = to_vbox_connector(connector);
+ vbox = connector->dev->dev_private;
+ /*
+ * Heuristic: we do not want to tell the host that we support dynamic
+ * resizing unless we feel confident that the user space client using
+ * the video driver can handle hot-plug events. So the first time modes
+ * are queried after a "master" switch we tell the host that we do not,
+ * and immediately after we send the client a hot-plug notification as
+ * a test to see if they will respond and query again.
+ * That is also the reason why capabilities are reported to the host at
+ * this place in the code rather than elsewhere.
+ * We need to report the flags location before reporting the IRQ
+ * capability.
+ */
+ VBoxHGSMIReportFlagsLocation(vbox->guest_pool, GUEST_HEAP_OFFSET(vbox) +
+ HOST_FLAGS_OFFSET);
+ if (vbox_connector->vbox_crtc->crtc_id == 0)
+ vbox_report_caps(vbox);
+ if (!vbox->initial_mode_queried) {
+ if (vbox_connector->vbox_crtc->crtc_id == 0) {
+ vbox->initial_mode_queried = true;
+ vbox_report_hotplug(vbox);
+ }
+ return drm_add_modes_noedid(connector, 800, 600);
+ }
+ /* Also assume that a client which supports hot-plugging also knows
+ * how to update the screen in a way we can use, the only known
+ * relevent client which cannot is Plymouth in Ubuntu 14.04. */
+ vbox->need_refresh_timer = false;
+ num_modes = drm_add_modes_noedid(connector, 2560, 1600);
+ preferred_width = vbox_connector->mode_hint.width ?
+ vbox_connector->mode_hint.width : 1024;
+ preferred_height = vbox_connector->mode_hint.height ?
+ vbox_connector->mode_hint.height : 768;
+ mode = drm_cvt_mode(connector->dev, preferred_width, preferred_height,
+ 60, false, false, false);
+ if (mode) {
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ ++num_modes;
+ }
+ vbox_set_edid(connector, preferred_width, preferred_height);
+
+#if RTLNX_VER_MIN(3,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ if (vbox_connector->vbox_crtc->x_hint != -1)
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_x_property,
+ vbox_connector->vbox_crtc->x_hint);
+ else
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_x_property, 0);
+
+ if (vbox_connector->vbox_crtc->y_hint != -1)
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_y_property,
+ vbox_connector->vbox_crtc->y_hint);
+ else
+ drm_object_property_set_value(&connector->base,
+ vbox->dev->mode_config.suggested_y_property, 0);
+#endif
+
+ return num_modes;
+}
+
+#if RTLNX_VER_MAX(3,14,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+static int vbox_mode_valid(struct drm_connector *connector,
+#else
+static enum drm_mode_status vbox_mode_valid(struct drm_connector *connector,
+#endif
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static void vbox_connector_destroy(struct drm_connector *connector)
+{
+#if RTLNX_VER_MAX(3,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_sysfs_connector_remove(connector);
+#else
+ drm_connector_unregister(connector);
+#endif
+ drm_connector_cleanup(connector);
+ kfree(connector);
+}
+
+static enum drm_connector_status
+vbox_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct vbox_connector *vbox_connector;
+
+ vbox_connector = to_vbox_connector(connector);
+
+ return vbox_connector->mode_hint.disconnected ?
+ connector_status_disconnected : connector_status_connected;
+}
+
+static int vbox_fill_modes(struct drm_connector *connector, u32 max_x,
+ u32 max_y)
+{
+ struct vbox_connector *vbox_connector;
+ struct drm_device *dev;
+ struct drm_display_mode *mode, *iterator;
+
+ vbox_connector = to_vbox_connector(connector);
+ dev = vbox_connector->base.dev;
+ list_for_each_entry_safe(mode, iterator, &connector->modes, head) {
+ list_del(&mode->head);
+ drm_mode_destroy(dev, mode);
+ }
+
+ return drm_helper_probe_single_connector_modes(connector, max_x, max_y);
+}
+
+static const struct drm_connector_helper_funcs vbox_connector_helper_funcs = {
+ .mode_valid = vbox_mode_valid,
+ .get_modes = vbox_get_modes,
+ .best_encoder = vbox_best_single_encoder,
+};
+
+static const struct drm_connector_funcs vbox_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = vbox_connector_detect,
+ .fill_modes = vbox_fill_modes,
+ .destroy = vbox_connector_destroy,
+};
+
+static int vbox_connector_init(struct drm_device *dev,
+ struct vbox_crtc *vbox_crtc,
+ struct drm_encoder *encoder)
+{
+ struct vbox_connector *vbox_connector;
+ struct drm_connector *connector;
+
+ vbox_connector = kzalloc(sizeof(*vbox_connector), GFP_KERNEL);
+ if (!vbox_connector)
+ return -ENOMEM;
+
+ connector = &vbox_connector->base;
+ vbox_connector->vbox_crtc = vbox_crtc;
+
+ drm_connector_init(dev, connector, &vbox_connector_funcs,
+ DRM_MODE_CONNECTOR_VGA);
+ drm_connector_helper_add(connector, &vbox_connector_helper_funcs);
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+#if RTLNX_VER_MIN(3,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_mode_create_suggested_offset_properties(dev);
+ drm_object_attach_property(&connector->base,
+ dev->mode_config.suggested_x_property, 0);
+ drm_object_attach_property(&connector->base,
+ dev->mode_config.suggested_y_property, 0);
+#endif
+#if RTLNX_VER_MAX(3,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ drm_sysfs_connector_add(connector);
+#else
+ drm_connector_register(connector);
+#endif
+
+#if RTLNX_VER_MIN(4,19,0) || RTLNX_RHEL_MAJ_PREREQ(7,7) || RTLNX_RHEL_MAJ_PREREQ(8,1) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ drm_connector_attach_encoder(connector, encoder);
+#else
+ drm_mode_connector_attach_encoder(connector, encoder);
+#endif
+
+ return 0;
+}
+
+int vbox_mode_init(struct drm_device *dev)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct drm_encoder *encoder;
+ struct vbox_crtc *vbox_crtc;
+ unsigned int i;
+ int ret;
+
+ /* vbox_cursor_init(dev); */
+ for (i = 0; i < vbox->num_crtcs; ++i) {
+ vbox_crtc = vbox_crtc_init(dev, i);
+ if (!vbox_crtc)
+ return -ENOMEM;
+ encoder = vbox_encoder_init(dev, i);
+ if (!encoder)
+ return -ENOMEM;
+ ret = vbox_connector_init(dev, vbox_crtc, encoder);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+void vbox_mode_fini(struct drm_device *dev)
+{
+ /* vbox_cursor_fini(dev); */
+}
+
+/**
+ * Copy the ARGB image and generate the mask, which is needed in case the host
+ * does not support ARGB cursors. The mask is a 1BPP bitmap with the bit set
+ * if the corresponding alpha value in the ARGB image is greater than 0xF0.
+ */
+static void copy_cursor_image(u8 *src, u8 *dst, u32 width, u32 height,
+ size_t mask_size)
+{
+ size_t line_size = (width + 7) / 8;
+ u32 i, j;
+
+ memcpy(dst + mask_size, src, width * height * 4);
+ for (i = 0; i < height; ++i)
+ for (j = 0; j < width; ++j)
+ if (((u32 *)src)[i * width + j] > 0xf0000000)
+ dst[i * line_size + j / 8] |= (0x80 >> (j % 8));
+}
+
+static int vbox_cursor_set2(struct drm_crtc *crtc, struct drm_file *file_priv,
+ u32 handle, u32 width, u32 height,
+ s32 hot_x, s32 hot_y)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ struct vbox_crtc *vbox_crtc = to_vbox_crtc(crtc);
+ struct ttm_bo_kmap_obj uobj_map;
+ size_t data_size, mask_size;
+ struct drm_gem_object *obj;
+ u32 flags, caps = 0;
+ struct vbox_bo *bo;
+ bool src_isiomem;
+ u8 *dst = NULL;
+ u8 *src;
+ int ret;
+
+ if (!handle) {
+ bool cursor_enabled = false;
+ struct drm_crtc *crtci;
+
+ /* Hide cursor. */
+ vbox_crtc->cursor_enabled = false;
+ list_for_each_entry(crtci, &vbox->dev->mode_config.crtc_list,
+ head) {
+ if (to_vbox_crtc(crtci)->cursor_enabled)
+ cursor_enabled = true;
+ }
+
+ if (!cursor_enabled)
+ VBoxHGSMIUpdatePointerShape(vbox->guest_pool, 0, 0, 0,
+ 0, 0, NULL, 0);
+ return 0;
+ }
+
+ vbox_crtc->cursor_enabled = true;
+
+ if (width > VBOX_MAX_CURSOR_WIDTH || height > VBOX_MAX_CURSOR_HEIGHT ||
+ width == 0 || height == 0)
+ return -EINVAL;
+ ret = VBoxQueryConfHGSMI(vbox->guest_pool,
+ VBOX_VBVA_CONF32_CURSOR_CAPABILITIES, &caps);
+ if (ret)
+ return ret == VERR_NO_MEMORY ? -ENOMEM : -EINVAL;
+
+ if (!(caps & VBOX_VBVA_CURSOR_CAPABILITY_HARDWARE)) {
+ /*
+ * -EINVAL means cursor_set2() not supported, -EAGAIN means
+ * retry at once.
+ */
+ return -EBUSY;
+ }
+
+#if RTLNX_VER_MIN(4,7,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ obj = drm_gem_object_lookup(file_priv, handle);
+#else
+ obj = drm_gem_object_lookup(crtc->dev, file_priv, handle);
+#endif
+ if (!obj) {
+ DRM_ERROR("Cannot find cursor object %x for crtc\n", handle);
+ return -ENOENT;
+ }
+
+ bo = gem_to_vbox_bo(obj);
+ ret = vbox_bo_reserve(bo, false);
+ if (ret)
+ goto out_unref_obj;
+
+ /*
+ * The mask must be calculated based on the alpha
+ * channel, one bit per ARGB word, and must be 32-bit
+ * padded.
+ */
+ mask_size = ((width + 7) / 8 * height + 3) & ~3;
+ data_size = width * height * 4 + mask_size;
+ vbox->cursor_hot_x = hot_x;
+ vbox->cursor_hot_y = hot_y;
+ vbox->cursor_width = width;
+ vbox->cursor_height = height;
+ vbox->cursor_data_size = data_size;
+ dst = vbox->cursor_data;
+
+#if RTLNX_VER_MIN(6,4,0)
+ /* Make sure bo is in SYSTEM (main) memory, so we can access it directly. */
+ ret = vbox_bo_pin(bo, VBOX_MEM_TYPE_SYSTEM, NULL);
+ if (ret)
+ {
+ DRM_ERROR("cannot pin bo to main memory\n");
+ goto out_bo_unpin;
+ }
+#endif
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = ttm_bo_kmap(&bo->bo, 0, VBOX_BO_RESOURCE_NUM_PAGES(bo->bo.resource), &uobj_map);
+#elif RTLNX_VER_MIN(5,12,0) || RTLNX_RHEL_MAJ_PREREQ(8,5)
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.mem.num_pages, &uobj_map);
+#else
+ ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &uobj_map);
+#endif
+ if (ret) {
+ vbox->cursor_data_size = 0;
+ goto out_unreserve_bo;
+ }
+
+ src = ttm_kmap_obj_virtual(&uobj_map, &src_isiomem);
+ if (src_isiomem) {
+ DRM_ERROR("src cursor bo not in main memory\n");
+ ret = -EIO;
+ goto out_unmap_bo;
+ }
+
+ copy_cursor_image(src, dst, width, height, mask_size);
+
+ flags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE |
+ VBOX_MOUSE_POINTER_ALPHA;
+ ret = VBoxHGSMIUpdatePointerShape(vbox->guest_pool, flags,
+ vbox->cursor_hot_x, vbox->cursor_hot_y,
+ width, height, dst, data_size);
+ ret = ret == VINF_SUCCESS ? 0 : ret == VERR_NO_MEMORY ? -ENOMEM :
+ ret == VERR_NOT_SUPPORTED ? -EBUSY : -EINVAL;
+
+out_unmap_bo:
+ ttm_bo_kunmap(&uobj_map);
+#if RTLNX_VER_MIN(6,4,0)
+out_bo_unpin:
+ vbox_bo_unpin(bo);
+#endif
+out_unreserve_bo:
+ vbox_bo_unreserve(bo);
+out_unref_obj:
+#if RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ drm_gem_object_put(obj);
+#else
+ drm_gem_object_put_unlocked(obj);
+#endif
+
+ return ret;
+}
+
+static int vbox_cursor_move(struct drm_crtc *crtc, int x, int y)
+{
+ struct vbox_private *vbox = crtc->dev->dev_private;
+ s32 crtc_x =
+ vbox->single_framebuffer ? crtc->x : to_vbox_crtc(crtc)->x_hint;
+ s32 crtc_y =
+ vbox->single_framebuffer ? crtc->y : to_vbox_crtc(crtc)->y_hint;
+ int ret;
+
+ x += vbox->cursor_hot_x;
+ y += vbox->cursor_hot_y;
+ if (x + crtc_x < 0 || y + crtc_y < 0 ||
+ x + crtc_x >= vbox->input_mapping_width ||
+ y + crtc_y >= vbox->input_mapping_width ||
+ vbox->cursor_data_size == 0)
+ return 0;
+ ret = VBoxHGSMICursorPosition(vbox->guest_pool, true, x + crtc_x,
+ y + crtc_y, NULL, NULL);
+ return ret == VINF_SUCCESS ? 0 : ret == VERR_NO_MEMORY ? -ENOMEM : ret ==
+ VERR_NOT_SUPPORTED ? -EBUSY : -EINVAL;
+}
diff --git a/src/VBox/Additions/linux/drm/vbox_prime.c b/src/VBox/Additions/linux/drm/vbox_prime.c
new file mode 100644
index 00000000..87cf0a99
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_prime.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ * This file is based on qxl_prime.c
+ * Copyright 2017 Canonical
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Andreas Pokorny
+ */
+
+#include "vbox_drv.h"
+
+/*
+ * Based on qxl_prime.c:
+ * Empty Implementations as there should not be any other driver for a virtual
+ * device that might share buffers with vboxvideo
+ */
+
+int vbox_gem_prime_pin(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+ return -ENOSYS;
+}
+
+void vbox_gem_prime_unpin(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+}
+
+struct sg_table *vbox_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+ return ERR_PTR(-ENOSYS);
+}
+
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, size_t size, struct sg_table *table)
+#else
+struct drm_gem_object *vbox_gem_prime_import_sg_table(
+ struct drm_device *dev, struct dma_buf_attachment *attach,
+ struct sg_table *table)
+#endif
+{
+ WARN_ONCE(1, "not implemented");
+ return ERR_PTR(-ENOSYS);
+}
+
+void *vbox_gem_prime_vmap(struct drm_gem_object *obj)
+{
+ WARN_ONCE(1, "not implemented");
+ return ERR_PTR(-ENOSYS);
+}
+
+void vbox_gem_prime_vunmap(struct drm_gem_object *obj, void *vaddr)
+{
+ WARN_ONCE(1, "not implemented");
+}
+
+#if RTLNX_VER_MAX(6,6,0) && !RTLNX_RHEL_RANGE(9,4, 9,99)
+int vbox_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *area)
+{
+ WARN_ONCE(1, "not implemented");
+ return -ENOSYS;
+}
+#endif
diff --git a/src/VBox/Additions/linux/drm/vbox_ttm.c b/src/VBox/Additions/linux/drm/vbox_ttm.c
new file mode 100644
index 00000000..d020aa3f
--- /dev/null
+++ b/src/VBox/Additions/linux/drm/vbox_ttm.c
@@ -0,0 +1,843 @@
+/* $Id: vbox_ttm.c $ */
+/** @file
+ * VirtualBox Additions Linux kernel video driver
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ * This file is based on ast_ttm.c
+ * Copyright 2012 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ *
+ * Authors: Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com>
+ */
+#include "vbox_drv.h"
+
+#if RTLNX_VER_MIN(6,3,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_MAJ_PREREQ(9,3)
+# include <drm/ttm/ttm_tt.h>
+#endif
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_MAJ_PREREQ(8,5)
+# include <drm/drm_gem.h>
+# include <drm/drm_gem_ttm_helper.h>
+# include <drm/drm_gem_vram_helper.h>
+#else
+# include <drm/ttm/ttm_page_alloc.h>
+#endif
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+# include <drm/ttm/ttm_range_manager.h>
+#endif
+
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+#define PLACEMENT_FLAGS(placement) (placement)
+#else
+#define PLACEMENT_FLAGS(placement) ((placement).flags)
+#endif
+
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static inline struct vbox_private *vbox_bdev(struct ttm_device *bd)
+#else
+static inline struct vbox_private *vbox_bdev(struct ttm_bo_device *bd)
+#endif
+{
+ return container_of(bd, struct vbox_private, ttm.bdev);
+}
+
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+static int vbox_ttm_mem_global_init(struct drm_global_reference *ref)
+{
+ return ttm_mem_global_init(ref->object);
+}
+
+static void vbox_ttm_mem_global_release(struct drm_global_reference *ref)
+{
+ ttm_mem_global_release(ref->object);
+}
+
+/**
+ * Adds the vbox memory manager object/structures to the global memory manager.
+ */
+static int vbox_ttm_global_init(struct vbox_private *vbox)
+{
+ struct drm_global_reference *global_ref;
+ int ret;
+
+#if RTLNX_VER_MAX(5,0,0)
+ global_ref = &vbox->ttm.mem_global_ref;
+ global_ref->global_type = DRM_GLOBAL_TTM_MEM;
+ global_ref->size = sizeof(struct ttm_mem_global);
+ global_ref->init = &vbox_ttm_mem_global_init;
+ global_ref->release = &vbox_ttm_mem_global_release;
+ ret = drm_global_item_ref(global_ref);
+ if (ret) {
+ DRM_ERROR("Failed setting up TTM memory subsystem.\n");
+ return ret;
+ }
+
+ vbox->ttm.bo_global_ref.mem_glob = vbox->ttm.mem_global_ref.object;
+#endif
+ global_ref = &vbox->ttm.bo_global_ref.ref;
+ global_ref->global_type = DRM_GLOBAL_TTM_BO;
+ global_ref->size = sizeof(struct ttm_bo_global);
+ global_ref->init = &ttm_bo_global_init;
+ global_ref->release = &ttm_bo_global_release;
+
+ ret = drm_global_item_ref(global_ref);
+ if (ret) {
+ DRM_ERROR("Failed setting up TTM BO subsystem.\n");
+#if RTLNX_VER_MAX(5,0,0)
+ drm_global_item_unref(&vbox->ttm.mem_global_ref);
+#endif
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * Removes the vbox memory manager object from the global memory manager.
+ */
+static void vbox_ttm_global_release(struct vbox_private *vbox)
+{
+ drm_global_item_unref(&vbox->ttm.bo_global_ref.ref);
+ drm_global_item_unref(&vbox->ttm.mem_global_ref);
+}
+#endif
+
+static void vbox_bo_ttm_destroy(struct ttm_buffer_object *tbo)
+{
+ struct vbox_bo *bo;
+
+ bo = container_of(tbo, struct vbox_bo, bo);
+
+ drm_gem_object_release(&bo->gem);
+ kfree(bo);
+}
+
+static bool vbox_ttm_bo_is_vbox_bo(struct ttm_buffer_object *bo)
+{
+ if (bo->destroy == &vbox_bo_ttm_destroy)
+ return true;
+
+ return false;
+}
+
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+static int
+vbox_bo_init_mem_type(struct ttm_bo_device *bdev, u32 type,
+ struct ttm_mem_type_manager *man)
+{
+ switch (type) {
+ case TTM_PL_SYSTEM:
+ man->flags = TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->available_caching = TTM_PL_MASK_CACHING;
+ man->default_caching = TTM_PL_FLAG_CACHED;
+ break;
+ case TTM_PL_VRAM:
+ man->func = &ttm_bo_manager_func;
+ man->flags = TTM_MEMTYPE_FLAG_FIXED | TTM_MEMTYPE_FLAG_MAPPABLE;
+ man->available_caching = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC;
+ man->default_caching = TTM_PL_FLAG_WC;
+ break;
+ default:
+ DRM_ERROR("Unsupported memory type %u\n", (unsigned int)type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#endif
+
+static void
+vbox_bo_evict_flags(struct ttm_buffer_object *bo, struct ttm_placement *pl)
+{
+ struct vbox_bo *vboxbo = vbox_bo(bo);
+
+ if (!vbox_ttm_bo_is_vbox_bo(bo))
+ return;
+
+ vbox_ttm_placement(vboxbo, VBOX_MEM_TYPE_SYSTEM);
+ *pl = vboxbo->placement;
+}
+
+#if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+static int vbox_bo_verify_access(struct ttm_buffer_object *bo,
+ struct file *filp)
+{
+ return 0;
+}
+#endif
+
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_RANGE(8,5, 8,99)
+static int vbox_ttm_io_mem_reserve(struct ttm_bo_device *bdev,
+ struct ttm_mem_reg *mem)
+{
+ struct vbox_private *vbox = vbox_bdev(bdev);
+ struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type];
+
+ mem->bus.addr = NULL;
+ mem->bus.offset = 0;
+ mem->bus.size = mem->num_pages << PAGE_SHIFT;
+ mem->bus.base = 0;
+ mem->bus.is_iomem = false;
+ if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE))
+ return -EINVAL;
+ switch (mem->mem_type) {
+ case TTM_PL_SYSTEM:
+ /* system memory */
+ return 0;
+ case TTM_PL_VRAM:
+ mem->bus.offset = mem->start << PAGE_SHIFT;
+ mem->bus.base = pci_resource_start(vbox->dev->pdev, 0);
+ mem->bus.is_iomem = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+#else
+# if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+static int vbox_ttm_io_mem_reserve(struct ttm_bo_device *bdev,
+ struct ttm_resource *mem)
+# else /* > 5.13.0 */
+static int vbox_ttm_io_mem_reserve(struct ttm_device *bdev,
+ struct ttm_resource *mem)
+# endif /* > 5.13.0 */
+{
+ struct vbox_private *vbox = vbox_bdev(bdev);
+ mem->bus.addr = NULL;
+ mem->bus.offset = 0;
+# if RTLNX_VER_MAX(5,12,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ mem->size = mem->num_pages << PAGE_SHIFT;
+# endif
+ mem->start = 0;
+ mem->bus.is_iomem = false;
+ switch (mem->mem_type) {
+ case TTM_PL_SYSTEM:
+ /* system memory */
+ return 0;
+ case TTM_PL_VRAM:
+# if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ mem->bus.caching = ttm_write_combined;
+# endif
+# if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ mem->bus.offset = (mem->start << PAGE_SHIFT) + pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0);
+# else
+ mem->bus.offset = mem->start << PAGE_SHIFT;
+ mem->start = pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0);
+# endif
+ mem->bus.is_iomem = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+#endif
+
+
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static void vbox_ttm_io_mem_free(struct ttm_device *bdev,
+ struct ttm_resource *mem)
+{
+}
+#elif RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static void vbox_ttm_io_mem_free(struct ttm_bo_device *bdev,
+ struct ttm_resource *mem)
+{
+}
+#else
+static void vbox_ttm_io_mem_free(struct ttm_bo_device *bdev,
+ struct ttm_mem_reg *mem)
+{
+}
+#endif
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static void vbox_ttm_tt_destroy(struct ttm_device *bdev, struct ttm_tt *tt)
+{
+ ttm_tt_fini(tt);
+ kfree(tt);
+}
+#elif RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static void vbox_ttm_tt_destroy(struct ttm_bo_device *bdev, struct ttm_tt *tt)
+{
+ ttm_tt_fini(tt);
+ kfree(tt);
+}
+#else
+static void vbox_ttm_backend_destroy(struct ttm_tt *tt)
+{
+ ttm_tt_fini(tt);
+ kfree(tt);
+}
+
+static struct ttm_backend_func vbox_tt_backend_func = {
+ .destroy = &vbox_ttm_backend_destroy,
+};
+#endif
+
+#if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+static struct ttm_tt *vbox_ttm_tt_create(struct ttm_bo_device *bdev,
+ unsigned long size,
+ u32 page_flags,
+ struct page *dummy_read_page)
+#else
+static struct ttm_tt *vbox_ttm_tt_create(struct ttm_buffer_object *bo,
+ u32 page_flags)
+#endif
+{
+ struct ttm_tt *tt;
+
+ tt = kzalloc(sizeof(*tt), GFP_KERNEL);
+ if (!tt)
+ return NULL;
+
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_RANGE(8,5, 8,99)
+ tt->func = &vbox_tt_backend_func;
+#endif
+#if RTLNX_VER_MIN(5,19,0) || RTLNX_RHEL_RANGE(8,8, 8,99) || RTLNX_RHEL_RANGE(9,2, 9,99) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+ if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined, 0)) {
+#elif RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ if (ttm_tt_init(tt, bo, page_flags, ttm_write_combined)) {
+#elif RTLNX_VER_MIN(4,17,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ if (ttm_tt_init(tt, bo, page_flags)) {
+#else
+ if (ttm_tt_init(tt, bdev, size, page_flags, dummy_read_page)) {
+#endif
+
+ kfree(tt);
+ return NULL;
+ }
+
+ return tt;
+}
+
+#if RTLNX_VER_MAX(4,17,0)
+# if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+static int vbox_ttm_tt_populate(struct ttm_tt *ttm)
+{
+ return ttm_pool_populate(ttm);
+}
+# else
+static int vbox_ttm_tt_populate(struct ttm_tt *ttm,
+ struct ttm_operation_ctx *ctx)
+{
+ return ttm_pool_populate(ttm, ctx);
+}
+# endif
+
+static void vbox_ttm_tt_unpopulate(struct ttm_tt *ttm)
+{
+ ttm_pool_unpopulate(ttm);
+}
+#endif
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static int vbox_bo_move(struct ttm_buffer_object *bo, bool evict,
+ struct ttm_operation_ctx *ctx, struct ttm_resource *new_mem,
+ struct ttm_place *hop)
+{
+# if RTLNX_VER_MIN(6,4,0)
+ if (!bo->resource)
+ {
+ if (new_mem->mem_type != TTM_PL_SYSTEM)
+ {
+ hop->mem_type = TTM_PL_SYSTEM;
+ hop->flags = TTM_PL_FLAG_TEMPORARY;
+ return -EMULTIHOP;
+ }
+ ttm_bo_move_null(bo, new_mem);
+ return 0;
+ }
+# endif
+ return ttm_bo_move_memcpy(bo, ctx, new_mem);
+}
+#endif
+
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+static struct ttm_device_funcs vbox_bo_driver = {
+#else /* < 5.13.0 */
+static struct ttm_bo_driver vbox_bo_driver = {
+#endif /* < 5.13.0 */
+ .ttm_tt_create = vbox_ttm_tt_create,
+#if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ .ttm_tt_destroy = vbox_ttm_tt_destroy,
+#endif
+#if RTLNX_VER_MAX(4,17,0)
+ .ttm_tt_populate = vbox_ttm_tt_populate,
+ .ttm_tt_unpopulate = vbox_ttm_tt_unpopulate,
+#endif
+#if RTLNX_VER_MAX(5,10,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ .init_mem_type = vbox_bo_init_mem_type,
+#endif
+#if RTLNX_VER_MIN(4,10,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)
+ .eviction_valuable = ttm_bo_eviction_valuable,
+#endif
+ .evict_flags = vbox_bo_evict_flags,
+#if RTLNX_VER_MAX(5,14,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ .verify_access = vbox_bo_verify_access,
+#endif
+ .io_mem_reserve = &vbox_ttm_io_mem_reserve,
+ .io_mem_free = &vbox_ttm_io_mem_free,
+#if RTLNX_VER_MIN(4,12,0) || RTLNX_RHEL_MAJ_PREREQ(7,5)
+# if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ .io_mem_pfn = ttm_bo_default_io_mem_pfn,
+# endif
+#endif
+#if (RTLNX_VER_RANGE(4,7,0, 4,11,0) || RTLNX_RHEL_MAJ_PREREQ(7,4)) && !RTLNX_RHEL_MAJ_PREREQ(7,5)
+ .lru_tail = &ttm_bo_default_lru_tail,
+ .swap_lru_tail = &ttm_bo_default_swap_lru_tail,
+#endif
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ .move = &vbox_bo_move,
+#endif
+};
+
+int vbox_mm_init(struct vbox_private *vbox)
+{
+ int ret;
+ struct drm_device *dev = vbox->dev;
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ struct ttm_device *bdev = &vbox->ttm.bdev;
+#else
+ struct ttm_bo_device *bdev = &vbox->ttm.bdev;
+#endif
+
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ ret = vbox_ttm_global_init(vbox);
+ if (ret)
+ return ret;
+#endif
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ret = ttm_device_init(&vbox->ttm.bdev,
+#else
+ ret = ttm_bo_device_init(&vbox->ttm.bdev,
+#endif
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ vbox->ttm.bo_global_ref.ref.object,
+#endif
+ &vbox_bo_driver,
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ dev->dev,
+#endif
+#if RTLNX_VER_MIN(3,15,0) || RTLNX_RHEL_MAJ_PREREQ(7,1)
+ dev->anon_inode->i_mapping,
+#endif
+#if RTLNX_VER_MIN(5,5,0) || RTLNX_RHEL_MIN(8,3) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ dev->vma_offset_manager,
+#elif RTLNX_VER_MAX(5,2,0) && !RTLNX_RHEL_MAJ_PREREQ(8,2)
+ DRM_FILE_PAGE_OFFSET,
+#endif
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ false,
+#endif
+ true);
+ if (ret) {
+ DRM_ERROR("Error initialising bo driver; %d\n", ret);
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ goto err_ttm_global_release;
+#else
+ return ret;
+#endif
+ }
+
+#if RTLNX_VER_MIN(5,10,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ ret = ttm_range_man_init(bdev, TTM_PL_VRAM, false,
+ vbox->available_vram_size >> PAGE_SHIFT);
+#else
+ ret = ttm_bo_init_mm(bdev, TTM_PL_VRAM,
+ vbox->available_vram_size >> PAGE_SHIFT);
+#endif
+ if (ret) {
+ DRM_ERROR("Failed ttm VRAM init: %d\n", ret);
+ goto err_device_release;
+ }
+
+#ifdef DRM_MTRR_WC
+ vbox->fb_mtrr = drm_mtrr_add(pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0),
+ pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0),
+ DRM_MTRR_WC);
+#else
+ vbox->fb_mtrr = arch_phys_wc_add(pci_resource_start(VBOX_DRM_TO_PCI_DEV(dev), 0),
+ pci_resource_len(VBOX_DRM_TO_PCI_DEV(dev), 0));
+#endif
+ return 0;
+
+err_device_release:
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ttm_device_fini(&vbox->ttm.bdev);
+#else
+ ttm_bo_device_release(&vbox->ttm.bdev);
+#endif
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+err_ttm_global_release:
+ vbox_ttm_global_release(vbox);
+#endif
+ return ret;
+}
+
+void vbox_mm_fini(struct vbox_private *vbox)
+{
+#ifdef DRM_MTRR_WC
+ drm_mtrr_del(vbox->fb_mtrr,
+ pci_resource_start(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0),
+ pci_resource_len(VBOX_DRM_TO_PCI_DEV(vbox->dev), 0), DRM_MTRR_WC);
+#else
+ arch_phys_wc_del(vbox->fb_mtrr);
+#endif
+#if RTLNX_VER_MIN(5,13,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ ttm_device_fini(&vbox->ttm.bdev);
+#else
+ ttm_bo_device_release(&vbox->ttm.bdev);
+#endif
+#if RTLNX_VER_MAX(5,0,0) && !RTLNX_RHEL_MAJ_PREREQ(7,7) && !RTLNX_RHEL_MAJ_PREREQ(8,1)
+ vbox_ttm_global_release(vbox);
+#endif
+}
+
+void vbox_ttm_placement(struct vbox_bo *bo, u32 mem_type)
+{
+ u32 c = 0;
+#if RTLNX_VER_MAX(3,18,0) && !RTLNX_RHEL_MAJ_PREREQ(7,2)
+ bo->placement.fpfn = 0;
+ bo->placement.lpfn = 0;
+#else
+ unsigned int i;
+#endif
+
+ bo->placement.placement = bo->placements;
+ bo->placement.busy_placement = bo->placements;
+
+ if (mem_type & VBOX_MEM_TYPE_VRAM) {
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ bo->placements[c].mem_type = TTM_PL_VRAM;
+ PLACEMENT_FLAGS(bo->placements[c++]) = 0;
+#elif RTLNX_VER_MIN(5,10,0)
+ bo->placements[c].mem_type = TTM_PL_VRAM;
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED;
+#else
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_FLAG_WC | TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_VRAM;
+#endif
+ }
+ if (mem_type & VBOX_MEM_TYPE_SYSTEM) {
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) = 0;
+#elif RTLNX_VER_MIN(5,10,0)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING;
+#else
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+#endif
+ }
+ if (!c) {
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) = 0;
+#elif RTLNX_VER_MIN(5,10,0)
+ bo->placements[c].mem_type = TTM_PL_SYSTEM;
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING;
+#else
+ PLACEMENT_FLAGS(bo->placements[c++]) =
+ TTM_PL_MASK_CACHING | TTM_PL_FLAG_SYSTEM;
+#endif
+ }
+
+ bo->placement.num_placement = c;
+ bo->placement.num_busy_placement = c;
+
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ for (i = 0; i < c; ++i) {
+ bo->placements[i].fpfn = 0;
+ bo->placements[i].lpfn = 0;
+ }
+#endif
+}
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+static const struct drm_gem_object_funcs vbox_drm_gem_object_funcs = {
+ .free = vbox_gem_free_object,
+ .print_info = drm_gem_ttm_print_info,
+# if RTLNX_VER_MIN(6,5,0)
+ .vmap = drm_gem_ttm_vmap,
+ .vunmap = drm_gem_ttm_vunmap,
+# endif
+# if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ .mmap = drm_gem_ttm_mmap,
+# endif
+};
+#endif
+
+int vbox_bo_create(struct drm_device *dev, int size, int align,
+ u32 flags, struct vbox_bo **pvboxbo)
+{
+ struct vbox_private *vbox = dev->dev_private;
+ struct vbox_bo *vboxbo;
+#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ size_t acc_size;
+#endif
+ int ret;
+
+ vboxbo = kzalloc(sizeof(*vboxbo), GFP_KERNEL);
+ if (!vboxbo)
+ return -ENOMEM;
+
+ ret = drm_gem_object_init(dev, &vboxbo->gem, size);
+ if (ret)
+ goto err_free_vboxbo;
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ if (!vboxbo->gem.funcs) {
+ vboxbo->gem.funcs = &vbox_drm_gem_object_funcs;
+ }
+#endif
+ vboxbo->bo.bdev = &vbox->ttm.bdev;
+#if RTLNX_VER_MAX(3,15,0) && !RTLNX_RHEL_MAJ_PREREQ(7,1)
+ vboxbo->bo.bdev->dev_mapping = dev->dev_mapping;
+#endif
+
+ vbox_ttm_placement(vboxbo, VBOX_MEM_TYPE_VRAM | VBOX_MEM_TYPE_SYSTEM);
+
+#if RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99)
+ acc_size = ttm_bo_dma_acc_size(&vbox->ttm.bdev, size,
+ sizeof(struct vbox_bo));
+#endif
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ /* Initialization of the following was removed from DRM stack
+ * in 5.14, so we need to do it manually. */
+ vboxbo->bo.base.funcs = &vbox_drm_gem_object_funcs;
+ kref_init(&vboxbo->bo.base.refcount);
+ vboxbo->bo.base.size = size;
+ vboxbo->bo.base.dev = dev;
+ dma_resv_init(&vboxbo->bo.base._resv);
+ drm_vma_node_reset(&vboxbo->bo.base.vma_node);
+#endif
+
+#if RTLNX_VER_MIN(6,1,0) || RTLNX_RHEL_RANGE(8,9, 8,99) || RTLNX_RHEL_RANGE(9,3, 9,99) || RTLNX_SUSE_MAJ_PREREQ(15,5)
+ ret = ttm_bo_init_validate(&vbox->ttm.bdev, &vboxbo->bo,
+#else
+ ret = ttm_bo_init(&vbox->ttm.bdev, &vboxbo->bo, size,
+#endif /* < 6.1.0 */
+ ttm_bo_type_device, &vboxbo->placement,
+#if RTLNX_VER_MAX(4,17,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ align >> PAGE_SHIFT, false, NULL, acc_size,
+#elif RTLNX_VER_MAX(5,13,0) && !RTLNX_RHEL_RANGE(8,6, 8,99) /* < 5.13.0, < RHEL(8.6, 8.99) */
+ align >> PAGE_SHIFT, false, acc_size,
+#else /* > 5.13.0 */
+ align >> PAGE_SHIFT, false,
+#endif /* > 5.13.0 */
+#if RTLNX_VER_MIN(3,18,0) || RTLNX_RHEL_MAJ_PREREQ(7,2)
+ NULL, NULL, vbox_bo_ttm_destroy);
+#else
+ NULL, vbox_bo_ttm_destroy);
+#endif
+ if (ret)
+ {
+ /* In case of failure, ttm_bo_init() supposed to call
+ * vbox_bo_ttm_destroy() which in turn will free @vboxbo. */
+ goto err_exit;
+ }
+
+ *pvboxbo = vboxbo;
+
+ return 0;
+
+err_free_vboxbo:
+ kfree(vboxbo);
+err_exit:
+ return ret;
+}
+
+static inline u64 vbox_bo_gpu_offset(struct vbox_bo *bo)
+{
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ return bo->bo.resource->start << PAGE_SHIFT;
+#elif RTLNX_VER_MIN(5,9,0) || RTLNX_RHEL_MIN(8,4) || RTLNX_SUSE_MAJ_PREREQ(15,3)
+ return bo->bo.mem.start << PAGE_SHIFT;
+#else
+ return bo->bo.offset;
+#endif
+}
+
+int vbox_bo_pin(struct vbox_bo *bo, u32 mem_type, u64 *gpu_addr)
+{
+#if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ struct ttm_operation_ctx ctx = { false, false };
+#endif
+ int ret;
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ int i;
+#endif
+
+ if (bo->pin_count) {
+ bo->pin_count++;
+ if (gpu_addr)
+ *gpu_addr = vbox_bo_gpu_offset(bo);
+
+ return 0;
+ }
+
+ vbox_ttm_placement(bo, mem_type);
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ for (i = 0; i < bo->placement.num_placement; i++)
+ PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT;
+#endif
+
+#if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
+#else
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+#endif
+ if (ret)
+ return ret;
+
+ bo->pin_count = 1;
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ ttm_bo_pin(&bo->bo);
+#endif
+
+ if (gpu_addr)
+ *gpu_addr = vbox_bo_gpu_offset(bo);
+
+ return 0;
+}
+
+int vbox_bo_unpin(struct vbox_bo *bo)
+{
+#if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+# if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ struct ttm_operation_ctx ctx = { false, false };
+# endif
+#endif
+ int ret = 0;
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ int i;
+#endif
+
+ if (!bo->pin_count) {
+ DRM_ERROR("unpin bad %p\n", bo);
+ return 0;
+ }
+ bo->pin_count--;
+ if (bo->pin_count)
+ return 0;
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ for (i = 0; i < bo->placement.num_placement; i++)
+ PLACEMENT_FLAGS(bo->placements[i]) &= ~TTM_PL_FLAG_NO_EVICT;
+#endif
+
+#if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
+#elif RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+#endif
+ if (ret)
+ return ret;
+
+#if RTLNX_VER_MIN(5,11,0) || RTLNX_RHEL_RANGE(8,5, 8,99)
+ ttm_bo_unpin(&bo->bo);
+#endif
+
+ return 0;
+}
+
+#if RTLNX_VER_MAX(5,11,0) && !RTLNX_RHEL_MAJ_PREREQ(8,5)
+/*
+ * Move a vbox-owned buffer object to system memory if no one else has it
+ * pinned. The caller must have pinned it previously, and this call will
+ * release the caller's pin.
+ */
+int vbox_bo_push_sysram(struct vbox_bo *bo)
+{
+# if RTLNX_VER_MIN(4,16,0) || RTLNX_RHEL_MAJ_PREREQ(7,6) || RTLNX_SUSE_MAJ_PREREQ(15,1) || RTLNX_SUSE_MAJ_PREREQ(12,5)
+ struct ttm_operation_ctx ctx = { false, false };
+# endif
+ int i, ret;
+
+ if (!bo->pin_count) {
+ DRM_ERROR("unpin bad %p\n", bo);
+ return 0;
+ }
+ bo->pin_count--;
+ if (bo->pin_count)
+ return 0;
+
+ if (bo->kmap.virtual)
+ ttm_bo_kunmap(&bo->kmap);
+
+ vbox_ttm_placement(bo, VBOX_MEM_TYPE_SYSTEM);
+
+ for (i = 0; i < bo->placement.num_placement; i++)
+ PLACEMENT_FLAGS(bo->placements[i]) |= TTM_PL_FLAG_NO_EVICT;
+
+# if RTLNX_VER_MAX(4,16,0) && !RTLNX_RHEL_MAJ_PREREQ(7,6) && !RTLNX_SUSE_MAJ_PREREQ(15,1) && !RTLNX_SUSE_MAJ_PREREQ(12,5)
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, false, false);
+# else
+ ret = ttm_bo_validate(&bo->bo, &bo->placement, &ctx);
+# endif
+ if (ret) {
+ DRM_ERROR("pushing to VRAM failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+int vbox_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_file *file_priv;
+ struct vbox_private *vbox;
+ int ret = -EINVAL;
+
+ if (unlikely(vma->vm_pgoff < DRM_FILE_PAGE_OFFSET))
+ return -EINVAL;
+
+ file_priv = filp->private_data;
+ vbox = file_priv->minor->dev->dev_private;
+
+#if RTLNX_VER_MIN(5,14,0) || RTLNX_RHEL_RANGE(8,6, 8,99)
+ (void)vbox;
+ if (drm_dev_is_unplugged(file_priv->minor->dev))
+ return -ENODEV;
+ ret = drm_gem_mmap(filp, vma);
+#else
+ ret = ttm_bo_mmap(filp, vma, &vbox->ttm.bdev);
+#endif
+ return ret;
+}
diff --git a/src/VBox/Additions/linux/export_modules.sh b/src/VBox/Additions/linux/export_modules.sh
new file mode 100755
index 00000000..19713671
--- /dev/null
+++ b/src/VBox/Additions/linux/export_modules.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+# $Id$
+## @file
+# Create a tar archive containing the sources of the Linux guest kernel modules.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+export LC_ALL=C
+
+# The below is GNU-specific. See VBox.sh for the longer Solaris/OS X version.
+TARGET=`readlink -e -- "${0}"` || exit 1
+MY_DIR="${TARGET%/[!/]*}"
+
+if [ -z "${1}" ] || { [ "x${1}" = x--folder ] && [ -z "${2}" ]; }; then
+ echo "Usage: $0 <filename.tar.gz>"
+ echo " Export VirtualBox kernel modules to <filename.tar.gz>."
+ echo "Usage: $0 --folder <folder>"
+ echo " Copy VirtualBox kernel module source to <folder>."
+ exit 1
+fi
+
+if test "x${1}" = x--folder; then
+ PATH_OUT="${2}"
+else
+ PATH_OUT="`cd \`dirname $1\`; pwd`/.vbox_modules"
+ FILE_OUT="`cd \`dirname $1\`; pwd`/`basename $1`"
+fi
+PATH_ROOT="`cd ${MY_DIR}/../../../..; pwd`"
+PATH_LOG=/tmp/vbox-export-guest.log
+PATH_LINUX="$PATH_ROOT/src/VBox/Additions/linux"
+PATH_VBOXGUEST="$PATH_ROOT/src/VBox/Additions/common/VBoxGuest"
+PATH_VBOXSF="$PATH_ROOT/src/VBox/Additions/linux/sharedfolders"
+PATH_VBOXVIDEO="$PATH_ROOT/src/VBox/Additions/linux/drm"
+
+VBOX_VERSION_MAJOR=`sed -e "s/^ *VBOX_VERSION_MAJOR *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk`
+VBOX_VERSION_MINOR=`sed -e "s/^ *VBOX_VERSION_MINOR *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk`
+VBOX_VERSION_BUILD=`sed -e "s/^ *VBOX_VERSION_BUILD *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk`
+VBOX_SVN_CONFIG_REV=`sed -e 's/^ *VBOX_SVN_REV_CONFIG_FALLBACK *:= \+\$(patsubst *%:,, *\$Rev: *\([0-9]\+\) *\$ *) */\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_SVN_VERSION_REV=`sed -e 's/^ *VBOX_SVN_REV_VERSION_FALLBACK *:= \+\$(patsubst *%:,, *\$Rev: *\([0-9]\+\) *\$ *) */\1/;t;d' $PATH_ROOT/Version.kmk`
+if [ "$VBOX_SVN_CONFIG_REV" -gt "$VBOX_SVN_VERSION_REV" ]; then
+ VBOX_SVN_REV=$VBOX_SVN_CONFIG_REV
+else
+ VBOX_SVN_REV=$VBOX_SVN_VERSION_REV
+fi
+VBOX_VENDOR=`sed -e 's/^ *VBOX_VENDOR *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_VENDOR_SHORT=`sed -e 's/^ *VBOX_VENDOR_SHORT *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_PRODUCT=`sed -e 's/^ *VBOX_PRODUCT *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk`
+VBOX_C_YEAR=`date +%Y`
+
+. $PATH_VBOXGUEST/linux/files_vboxguest
+. $PATH_VBOXSF/files_vboxsf
+. $PATH_VBOXVIDEO/files_vboxvideo_drv
+
+# Temporary path for creating the modules, will be removed later
+mkdir -p $PATH_OUT || exit 1
+
+# Create auto-generated version file, needed by all modules
+echo "#ifndef ___version_generated_h___" > $PATH_OUT/version-generated.h
+echo "#define ___version_generated_h___" >> $PATH_OUT/version-generated.h
+echo "" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_MAJOR $VBOX_VERSION_MAJOR" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_MINOR $VBOX_VERSION_MINOR" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_BUILD $VBOX_VERSION_BUILD" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_STRING_RAW \"$VBOX_VERSION_MAJOR.$VBOX_VERSION_MINOR.$VBOX_VERSION_BUILD\"" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_VERSION_STRING \"$VBOX_VERSION_MAJOR.$VBOX_VERSION_MINOR.$VBOX_VERSION_BUILD\"" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_API_VERSION_STRING \"${VBOX_VERSION_MAJOR}_${VBOX_VERSION_MINOR}\"" >> $PATH_OUT/version-generated.h
+echo "#define VBOX_PRIVATE_BUILD_DESC \"Private build with export_modules\"" >> $PATH_OUT/version-generated.h
+echo "" >> $PATH_OUT/version-generated.h
+echo "#endif" >> $PATH_OUT/version-generated.h
+
+# Create auto-generated revision file, needed by all modules
+echo "#ifndef __revision_generated_h__" > $PATH_OUT/revision-generated.h
+echo "#define __revision_generated_h__" >> $PATH_OUT/revision-generated.h
+echo "" >> $PATH_OUT/revision-generated.h
+echo "#define VBOX_SVN_REV $VBOX_SVN_REV" >> $PATH_OUT/revision-generated.h
+echo "" >> $PATH_OUT/revision-generated.h
+echo "#endif" >> $PATH_OUT/revision-generated.h
+
+# Create auto-generated product file, needed by all modules
+echo "#ifndef ___product_generated_h___" > $PATH_OUT/product-generated.h
+echo "#define ___product_generated_h___" >> $PATH_OUT/product-generated.h
+echo "" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_VENDOR \"$VBOX_VENDOR\"" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_VENDOR_SHORT \"$VBOX_VENDOR_SHORT\"" >> $PATH_OUT/product-generated.h
+echo "" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_PRODUCT \"$VBOX_PRODUCT\"" >> $PATH_OUT/product-generated.h
+echo "#define VBOX_C_YEAR \"$VBOX_C_YEAR\"" >> $PATH_OUT/product-generated.h
+echo "" >> $PATH_OUT/product-generated.h
+echo "#endif" >> $PATH_OUT/product-generated.h
+
+# vboxguest (VirtualBox guest kernel module)
+mkdir $PATH_OUT/vboxguest || exit 1
+for f in $FILES_VBOXGUEST_NOBIN; do
+ install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxguest/`echo $f|cut -d'>' -f2`"
+done
+for f in $FILES_VBOXGUEST_BIN; do
+ install -D -m 0755 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxguest/`echo $f|cut -d'>' -f2`"
+done
+
+# vboxsf (VirtualBox guest kernel module for shared folders)
+mkdir $PATH_OUT/vboxsf || exit 1
+for f in $FILES_VBOXSF_NOBIN; do
+ install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxsf/`echo $f|cut -d'>' -f2`"
+done
+for f in $FILES_VBOXSF_BIN; do
+ install -D -m 0755 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxsf/`echo $f|cut -d'>' -f2`"
+done
+
+# vboxvideo (VirtualBox guest kernel module for drm support)
+mkdir $PATH_OUT/vboxvideo || exit 1
+for f in $FILES_VBOXVIDEO_DRM_NOBIN; do
+ install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxvideo/`echo $f|cut -d'>' -f2`"
+done
+for f in $FILES_VBOXVIDEO_DRM_BIN; do
+ install -D -m 0755 `echo $f|cut -d'=' -f1` "$PATH_OUT/vboxvideo/`echo $f|cut -d'>' -f2`"
+done
+sed -f $PATH_VBOXVIDEO/indent.sed -i $PATH_OUT/vboxvideo/*.[ch]
+
+# convenience Makefile
+install -D -m 0644 $PATH_LINUX/Makefile "$PATH_OUT/Makefile"
+
+# Only temporary, omit from archive
+rm $PATH_OUT/version-generated.h
+rm $PATH_OUT/revision-generated.h
+rm $PATH_OUT/product-generated.h
+
+# If we are exporting to a folder then stop now.
+test "x${1}" = x--folder && exit 0
+
+# Do a test build
+echo Doing a test build, this may take a while.
+make -C $PATH_OUT > $PATH_LOG 2>&1 &&
+ make -C $PATH_OUT clean >> $PATH_LOG 2>&1 ||
+ echo "Warning: test build failed. Please check $PATH_LOG"
+
+# Create the archive
+tar -czf $FILE_OUT -C $PATH_OUT . || exit 1
+
+# Remove the temporary directory
+rm -r $PATH_OUT
+
diff --git a/src/VBox/Additions/linux/installer/.scm-settings b/src/VBox/Additions/linux/installer/.scm-settings
new file mode 100644
index 00000000..cd5b9eba
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for linux guest additions installer.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+/install.sh.in: --treat-as .sh
+/deffiles: --treat-as files_
diff --git a/src/VBox/Additions/linux/installer/autorun.sh b/src/VBox/Additions/linux/installer/autorun.sh
new file mode 100755
index 00000000..146cb46b
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/autorun.sh
@@ -0,0 +1,204 @@
+#!/bin/sh
+# $Id: autorun.sh $
+## @file
+# VirtualBox Guest Additions installation script for *nix guests
+#
+
+#
+# Copyright (C) 2009-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+
+# Deal with differing "which" semantics
+mywhich() {
+ which "$1" 2>/dev/null | grep -v "no $1"
+}
+
+# Get the name and execute switch for a useful terminal emulator
+#
+# Sets $gxtpath to the emulator path or empty
+# Sets $gxttitle to the "title" switch for that emulator
+# Sets $gxtexec to the "execute" switch for that emulator
+# May clobber $gtx*
+# Calls mywhich
+getxterm() {
+ # gnome-terminal and mate-terminal use -e differently to other emulators
+ for gxti in "konsole --title -e" "gnome-terminal --title -x" "mate-terminal --title -x" "xterm -T -e"; do
+ set $gxti
+ gxtpath="`mywhich $1`"
+ case "$gxtpath" in ?*)
+ gxttitle=$2
+ gxtexec=$3
+ return
+ ;;
+ esac
+ done
+}
+
+# Quotes its argument by inserting '\' in front of every character save
+# for 'A-Za-z0-9/'. Prints the result to stdout.
+quotify() {
+ echo "$1" | sed -e 's/\([^a-zA-Z0-9/]\)/\\\1/g'
+}
+
+ostype=`uname -s`
+if test "$ostype" != "Linux" && test "$ostype" != "SunOS" ; then
+ echo "Linux/Solaris not detected."
+ exit 1
+fi
+
+# The below is GNU-specific. See VBox.sh for the longer Solaris/OS X version.
+TARGET=`readlink -e -- "${0}"` || exit 1
+path="${TARGET%/[!/]*}"
+# 32-bit or 64-bit?
+case `uname -m` in
+ i[3456789]86|x86|i86pc)
+ arch='x86'
+ ;;
+ x86_64|amd64|AMD64)
+ arch='amd64'
+ ;;
+ *)
+ echo "Unknown architecture `uname -m`."
+ exit 1
+ ;;
+esac
+
+# execute the installer
+if test "$ostype" = "Linux"; then
+ for i in "$path/VBoxLinuxAdditions.run" \
+ "$path/VBoxLinuxAdditions-$arch.run"; do
+ if test -f "$i"; then
+ getxterm
+ case "$gxtpath" in ?*)
+ TITLE="VirtualBox Guest Additions installation"
+ BINARY="`quotify "$i"`"
+ exec "$gxtpath" "$gxttitle" "$TITLE" "$gxtexec" /bin/sh "$path/runasroot.sh" --has-terminal "$TITLE" "/bin/sh $BINARY --xwin" "Please try running "\""$i"\"" manually."
+ exit
+ ;;
+ *)
+ echo "Unable to start installation process with elevated privileges automatically. Please try running "\""$i"\"" manually."
+ exit
+ ;;
+ esac
+ fi
+ done
+
+ # else: unknown failure
+ echo "Linux guest additions installer not found -- try to start it manually."
+ exit 1
+
+elif test "$ostype" = "SunOS"; then
+
+ # check for combined package
+ installfile="$path/VBoxSolarisAdditions.pkg"
+ if test -f "$installfile"; then
+
+ # check for pkgadd bin
+ pkgaddbin=pkgadd
+ found=`which pkgadd | grep "no pkgadd"`
+ if test ! -z "$found"; then
+ if test -f "/usr/sbin/pkgadd"; then
+ pkgaddbin=/usr/sbin/pkgadd
+ else
+ echo "Could not find pkgadd."
+ exit 1
+ fi
+ fi
+
+ # check for pfexec
+ pfexecbin=pfexec
+ found=`which pfexec | grep "no pfexec"`
+ if test ! -z "$found"; then
+ # Use su and prompt for password
+ echo "Could not find pfexec."
+ subin=`which su`
+ else
+ idbin=/usr/xpg4/bin/id
+ if test ! -x "$idbin"; then
+ found=`which id 2> /dev/null`
+ if test ! -x "$found"; then
+ echo "Failed to find a suitable user id executable."
+ exit 1
+ else
+ idbin=$found
+ fi
+ fi
+
+ # check if pfexec can get the job done
+ if test "$idbin" = "/usr/xpg4/bin/id"; then
+ userid=`$pfexecbin $idbin -u`
+ else
+ userid=`$pfexecbin $idbin | cut -f1 -d'(' | cut -f2 -d'='`
+ fi
+ if test $userid != "0"; then
+ # pfexec exists but user has no pfexec privileges, switch to using su and prompting password
+ subin=`which su`
+ fi
+ fi
+
+ # create temporary admin file for autoinstall
+ TMPFILE=`mktemp -q /tmp/vbox.XXXXXX`
+ if [ -z $TMPFILE ]; then
+ echo "Unable to create a temporary file"
+ exit 1
+ fi
+ echo "basedir=default
+runlevel=nocheck
+conflict=quit
+setuid=nocheck
+action=nocheck
+partial=quit
+instance=unique
+idepend=quit
+rdepend=quit
+space=quit
+mail=
+" > $TMPFILE
+
+ # check gnome-terminal, use it if it exists.
+ if test -f "/usr/bin/gnome-terminal"; then
+ # use su/pfexec
+ if test -z "$subin"; then
+ /usr/bin/gnome-terminal --title "Installing VirtualBox Additions" --command "/bin/sh -c '$pfexecbin $pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest; /bin/echo press ENTER to close this window; /bin/read'"
+ else
+ /usr/bin/gnome-terminal --title "Installing VirtualBox Additions: Root password required." --command "/bin/sh -c '$subin - root -c \"$pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest\"; /bin/echo press ENTER to close this window; /bin/read'"
+ fi
+ elif test -f "/usr/X11/bin/xterm"; then
+ # use xterm
+ if test -z "$subin"; then
+ /usr/X11/bin/xterm -title "Installing VirtualBox Additions" -e "$pfexecbin $pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest; /bin/echo press ENTER to close this window; /bin/read"
+ else
+ /usr/X11/bin/xterm -title "Installing VirtualBox Additions: Root password required." -e "$subin - root -c \"$pkgaddbin -G -d $installfile -n -a $TMPFILE SUNWvboxguest\"; /bin/echo press ENTER to close this window; /bin/read"
+ fi
+ else
+ echo "No suitable terminal not found. -- install additions using pkgadd -d."
+ fi
+ rm -r $TMPFILE
+
+ exit 0
+ fi
+
+ echo "Solaris guest additions installer not found -- try to start them manually."
+ exit 1
+fi
+
diff --git a/src/VBox/Additions/linux/installer/deffiles b/src/VBox/Additions/linux/installer/deffiles
new file mode 100644
index 00000000..79fa03dd
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/deffiles
@@ -0,0 +1,80 @@
+# $Id: deffiles $
+## @file
+# VirtualBox linux Guest Additions default files list
+#
+
+#
+# Copyright (C) 2009-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# This file contains the names of all files that the Guest Additions for
+# Linux guests have ever installed into the main filesystem tree (/usr and
+# /sbin). No entries should ever be removed from this file, as it is used
+# for uninstalling old installations of VirtualBox which did not keep track
+# of the names of their files, or damaged installations which lost track of
+# them.
+
+DEFAULT_FILE_NAMES=" \
+ /usr/bin/vboxadd-xclient \
+ /usr/bin/VBoxAudioTest \
+ /usr/bin/VBoxClient \
+ /usr/bin/VBoxControl \
+ /usr/sbin/vbox-greeter \
+ /usr/sbin/vboxadd-timesync \
+ /usr/sbin/vboxadd-service \
+ /usr/sbin/VBoxService \
+ /sbin/mount.vboxsf \
+ /usr/share/xgreeters/vbox-greeter.desktop \
+ /etc/X11/Xsession.d/98vboxadd-xclient \
+ /etc/X11/xinit.d/98vboxadd-xclient \
+ /etc/X11/xinit/xinitrc.d/98vboxadd-xclient.sh \
+ /usr/bin/VBoxRandR \
+ /usr/bin/VBoxClient-all \
+ /etc/xdg/autostart/vboxclient.desktop \
+ /usr/share/autostart/vboxclient.desktop \
+ /etc/udev/rules.d/60-vboxadd.rules \
+ /usr/share/xserver-xorg/pci/vboxvideo.ids \
+ /etc/hal/fdi/policy/90-vboxguest.fdi \
+ /etc/udev/rules.d/70-xorg-vboxmouse.rules \
+ /usr/share/hal/fdi/policy/20thirdparty/90-vboxguest.fdi \
+ /usr/lib/X11/xorg.conf.d/50-vboxmouse.conf \
+ /usr/share/X11/xorg.conf.d/50-vboxmouse.conf \
+ /usr/lib/dri/vboxvideo_dri.so \
+ /usr/lib64/dri/vboxvideo_dri.so \
+ /usr/lib64/xorg/modules/drivers/vboxvideo_drv.so \
+ /usr/lib/xorg/modules/drivers/vboxvideo_drv.so \
+ /usr/X11R6/lib64/modules/drivers/vboxvideo_drv.so \
+ /usr/X11R6/lib/modules/drivers/vboxvideo_drv.so \
+ /usr/X11R6/lib/X11/modules/drivers/vboxvideo_drv.so \
+ /usr/lib64/xorg/modules/input/vboxmouse_drv.so \
+ /usr/lib/xorg/modules/input/vboxmouse_drv.so \
+ /usr/X11R6/lib64/modules/input/vboxmouse_drv.so \
+ /usr/X11R6/lib/modules/input/vboxmouse_drv.so \
+ /usr/X11R6/lib/X11/modules/input/vboxmouse_drv.so
+"
+
+DEFAULT_VERSIONED_FILE_NAMES=" \
+ /usr/src/vboxadd \
+ /usr/src/vboxguest \
+ /usr/src/vboxvfs \
+ /usr/src/vboxsf \
+ /usr/src/vboxvideo \
+"
diff --git a/src/VBox/Additions/linux/installer/install.sh.in b/src/VBox/Additions/linux/installer/install.sh.in
new file mode 100755
index 00000000..a3c6329a
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/install.sh.in
@@ -0,0 +1,640 @@
+#!/bin/sh
+#
+# Oracle VM VirtualBox
+# VirtualBox Makeself installation starter script
+# for Linux Guest Additions
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Testing:
+# * After successful installation, 0 is returned if the vboxguest module version
+# built matches the one loaded and 2 is returned otherwise. E.g. VBoxClient
+# running will prevent vboxguest reloading.
+# * If the kernel modules cannot be built (run the installer with KERN_VER=none)
+# or loaded (run with KERN_VER=<installed non-current version>) then 1 is
+# returned.
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+
+# Note: These variable names must *not* clash with variables in $CONFIG_DIR/$CONFIG!
+PACKAGE="VBoxGuestAdditions"
+PACKAGE_NAME="VirtualBox Guest Additions"
+UNINSTALL="uninstall.sh"
+PUBLIC_UNINSTALL_HOOK="/usr/sbin/vbox-uninstall-guest-additions"
+ROUTINES="routines.sh"
+INSTALLATION_VER="_VERSION_"
+BUILD_VBOX_KBUILD_TYPE="_BUILDTYPE_"
+BUILD_USERNAME="_USERNAME_"
+UNINSTALL_SCRIPTS="vboxadd-x11 vboxvfs vboxadd-timesync vboxadd-service vboxadd"
+
+INSTALLATION_DIR="/opt/$PACKAGE-$INSTALLATION_VER"
+CONFIG_DIR="/var/lib/$PACKAGE"
+CONFIG="config"
+CONFIG_FILES="filelist"
+SELF=$1
+LOGFILE="/var/log/vboxadd-install.log"
+
+## Were we able to stop any previously running Additions kernel modules?
+MODULES_STOPPED=1
+
+. "./$ROUTINES"
+
+check_root
+
+check_deps bzip2 tar
+
+create_log "$LOGFILE"
+
+usage()
+{
+ catinfo << EOF
+
+Usage: $SELF install [<installation directory>]
+ [--with-<module>]
+ [--package-base <base> |
+ uninstall
+ [--force] [--no-setup]
+
+Options:
+ --package-base <base> For use when building distribution packages.
+ Installs relative to <base> instead of to "/",
+ does not run setup, installs to "<base>/usr/lib"
+ instead of to "/opt" and does not create uninstall.
+ --force Forces an upgrade. Not recommended.
+
+Example:
+$SELF install
+EOF
+ exit 1
+}
+
+# Create a symlink in the filesystem and add it to the list of package files
+add_symlink()
+{
+ self=add_symlink
+ ## Parameters:
+ # The file the link should point to
+ target="$1"
+ # The name of the actual symlink file. Must be an absolute path to a
+ # non-existing file in an existing directory.
+ link="$2"
+ link_dir="`dirname "$link"`"
+ test -n "$target" ||
+ { echo 1>&2 "$self: no target specified"; return 1; }
+ test -d "$link_dir" ||
+ { echo 1>&2 "$self: link directory $link_dir does not exist"; return 1; }
+ expr "$link" : "/.*" > /dev/null ||
+ { echo 1>&2 "$self: link file name is not absolute"; return 1; }
+ rm -f "$link"
+ ln -s "$target" "$link"
+ echo "$link" >> "$CONFIG_DIR/$CONFIG_FILES"
+}
+
+# Create symbolic links targeting all files in a directory in another
+# directory in the filesystem
+link_into_fs()
+{
+ ## Parameters:
+ # Directory containing the link target files
+ target_branch="$1"
+ # Directory to create the link files in
+ directory="$2"
+ for i in "$INSTALLATION_DIR/$target_branch"/*; do
+ test -e "$i" &&
+ add_symlink "$i" "$directory/`basename $i`"
+ done
+}
+
+# Look for broken installations or installations without a known uninstaller
+# and try to clean them up, asking the user first.
+def_uninstall()
+{
+ ## Parameters:
+ # Whether to force cleanup without asking the user
+ force="$1"
+
+ . ./deffiles
+ found=0
+ for i in "/opt/$PACKAGE-"*; do
+ test -e "$i" && found=1
+ done
+ for i in $DEFAULT_FILE_NAMES; do
+ test "$found" = 0 && test -e "$i" && found=1
+ done
+ test "$found" = 0 &&
+ for i in $DEFAULT_VERSIONED_FILE_NAMES; do
+ for j in $i-*; do
+ test "$found" = 0 && test -e "$j" && found=1
+ done
+ done
+ test "$found" = 0 && return 0
+ if ! test "$1" = "force" ; then
+ # Try to make the promised notification appear on next start.
+ VBoxControl guestproperty delete \
+ /VirtualBox/GuestAdd/HostVerLastChecked 2>&1 > /dev/null
+ cat 1>&2 << EOF
+This system appears to have a version of the VirtualBox Guest Additions
+already installed. If it is part of the operating system and kept up-to-date,
+there is most likely no need to replace it. If it is not up-to-date, you
+should get a notification when you start the system. If you wish to replace
+it with this version, please do not continue with this installation now, but
+instead remove the current version first, following the instructions for the
+operating system.
+
+If your system simply has the remains of a version of the Additions you could
+not remove you should probably continue now, and these will be removed during
+installation.
+
+Do you wish to continue? [yes or no]
+EOF
+ read reply dummy
+ if ! expr "$reply" : [yY] > /dev/null &&
+ ! expr "$reply" : [yY][eE][sS] > /dev/null
+ then
+ info
+ info "Cancelling installation."
+ return 1
+ fi
+ fi
+ # Inhibit rebuilding of any installed kernels.
+ for i in /lib/modules/*; do
+ ver="${i##*/}"
+ test ! -d "$i"/build || touch /var/lib/VBoxGuestAdditions/skip-"$ver"
+ done
+ # Stop what we can in the way of services and remove them from the
+ # system
+ for i in $UNINSTALL_SCRIPTS; do
+ stop_init_script "$i" 2>> "${LOGFILE}"
+ test -z "$NO_CLEANUP" && test -x "./$i" && "./$i" cleanup 1>&2 2>> "$LOGFILE"
+ delrunlevel "$i" 2>> "${LOGFILE}"
+ remove_init_script "$i" 2>> "${LOGFILE}"
+ done
+ for i in "/opt/$PACKAGE-"*/init; do
+ for j in $UNINSTALL_SCRIPTS; do
+ script="${i}/${j}"
+ test -x "${script}" && test -z "$NO_CLEANUP" &&
+ grep -q '^# *cleanup_script *$' "${script}" &&
+ "${script}" cleanup 1>&2 2>> "$LOGFILE"
+ done
+ done
+
+ # Get rid of any remaining files
+ for i in $DEFAULT_FILE_NAMES; do
+ rm -f "$i" 2> /dev/null
+ done
+ for i in $DEFAULT_VERSIONED_FILE_NAMES; do
+ rm -f "$i-"* 2> /dev/null
+ done
+ rm -f "/usr/lib/$PACKAGE" "/usr/lib64/$PACKAGE" "/usr/share/$PACKAGE" \
+ "/usr/lib/i386-linux-gnu/$PACKAGE" "/usr/lib/x86_64-linux-gnu/$PACKAGE"
+
+ # And any packages left under /opt
+ for i in "/opt/$PACKAGE-"*; do
+ test -d "$i" && rm -rf "$i"
+ done
+ return 0
+}
+
+info "$PACKAGE_NAME installer"
+
+# Sensible default actions
+ACTION="install"
+NO_CLEANUP=""
+FORCE_UPGRADE=""
+PACKAGE_BASE=""
+
+while [ $# -ge 2 ];
+do
+ ARG=$2
+ shift
+
+ if [ -z "$MY_END_OF_OPTIONS" ]; then
+ case "$ARG" in
+
+ install)
+ ACTION="install"
+ ;;
+
+ uninstall)
+ ACTION="uninstall"
+ ;;
+
+ package)
+ ACTION="package"
+ INSTALLATION_DIR=/usr/lib
+ PACKAGE_BASE="$2"
+ shift
+ if test ! -d "${PACKAGE_BASE}"; then
+ info "Package base directory not found."
+ usage
+ fi
+ ;;
+
+ ## @todo Add per-module options handling, e.g. --lightdm-greeter-dir
+ # or --lightdm-config
+
+ ## @todo Add listing all available modules (+ their options, e.g.
+ # with callback mod_mymod_show_options?)
+
+ --with-*)
+ MODULE_CUR=`expr "$ARG" : '--with-\(.*\)'`
+ # Check if corresponding module in installer/module-$1 exists.
+ # Note: Module names may not contain spaces or other funny things.
+ if [ ! -f "./installer/module-${MODULE_CUR}" ]; then
+ info "Error: Module \"${MODULE_CUR}\" does not exist."
+ usage
+ fi
+ # Give the module the chance of doing initialization work / checks.
+ . "./installer/module-${MODULE_CUR}"
+ mod_${MODULE_CUR}_init
+ if test $? -ne 0; then
+ echo 1>&2 "Module '${MODULE_CUR}' failed to initialize"
+ if ! test "$FORCE_UPGRADE" = "force"; then
+ return 1
+ fi
+ # Continue initialization.
+ fi
+ # Add module to the list of modules to handle later.
+ if test -z "${INSTALLATION_MODULES_LIST}"; then
+ INSTALLATION_MODULES_LIST="${MODULE_CUR}"
+ else
+ INSTALLATION_MODULES_LIST="${INSTALLATION_MODULES_LIST} ${MODULE_CUR}"
+ fi
+ shift
+ ;;
+
+ --force|force) # Keep "force" for backwards compatibility.
+ FORCE_UPGRADE="force"
+ ;;
+
+ --no-setup|no_setup)
+ # Deprecated; keep this setting for backwards compatibility.
+ ;;
+
+ --no-cleanup|no_cleanup) # Keep "no_cleanup" for backwards compatibility.
+ # Do not do cleanup of old modules when removing them. For
+ # testing purposes only.
+ NO_CLEANUP="no_cleanup"
+ ;;
+
+ --)
+ MY_END_OF_OPTIONS="1"
+ ;;
+
+ *)
+ if [ "`echo $1|cut -c1`" != "/" ]; then
+ info "Please specify an absolute path"
+ usage
+ fi
+ INSTALLATION_DIR="$1"
+ shift
+ ;;
+ esac
+ fi
+done
+
+# Check architecture
+cpu=`uname -m`;
+case "$cpu" in
+ i[3456789]86|x86)
+ cpu="x86"
+ lib_candidates="/usr/lib/i386-linux-gnu /usr/lib /lib"
+ ;;
+ x86_64|amd64)
+ cpu="amd64"
+ lib_candidates="/usr/lib/x86_64-linux-gnu /usr/lib64 /usr/lib /lib64 /lib"
+ ;;
+ *)
+ cpu="unknown"
+esac
+ARCH_PACKAGE="$PACKAGE-$cpu.tar.bz2"
+if [ ! -r "$ARCH_PACKAGE" ]; then
+ info "Detected unsupported $cpu machine type."
+ exit 1
+fi
+# Find the most appropriate libary folder by seeing which of the candidate paths
+# are actually in the shared linker path list and choosing the first. We look
+# for Debian-specific paths first, then LSB ones, then the new RedHat ones.
+libs=`ldconfig -v 2>/dev/null | grep -v ^$'\t'`
+for i in $lib_candidates; do
+ if echo $libs | grep -q $i; then
+ lib_path=$i
+ break
+ fi
+done
+if [ ! -x "$lib_path" ]; then
+ info "Unable to determine correct library path."
+ exit 1
+fi
+
+# uninstall any previous installation
+# If the currently installed Additions have provided an uninstallation hook, try
+# that first.
+if test -x "${PUBLIC_UNINSTALL_HOOK}"; then
+ "${PUBLIC_UNINSTALL_HOOK}" 1>&2 ||
+ abort "Failed to remove existing installation. Aborting..."
+fi
+
+INSTALL_DIR=""
+uninstalled=0
+test -r "$CONFIG_DIR/$CONFIG" && . "$CONFIG_DIR/$CONFIG"
+if test -n "$INSTALL_DIR" && test -n "$UNINSTALLER" &&
+ test -x "$INSTALL_DIR/$UNINSTALLER"; then
+ "$INSTALL_DIR/$UNINSTALLER" $NO_CLEANUP 1>&2 ||
+ abort "Failed to remove existing installation. Aborting..."
+ uninstalled=1
+fi
+test "$uninstalled" = 0 && def_uninstall "$FORCE_UPGRADE" && uninstalled=1
+test "$uninstalled" = 0 && exit 1
+rm -f "$CONFIG_DIR/$CONFIG"
+rm -f "$CONFIG_DIR/$CONFIG_FILES"
+rmdir "$CONFIG_DIR" 2>/dev/null || true
+test "$ACTION" = "install" || exit 0
+
+# Now check whether the kernel modules were stopped.
+lsmod | grep -q vboxguest && MODULES_STOPPED=
+
+# Choose a proper umask
+umask 022
+
+# Set installer modules directory
+INSTALLATION_MODULES_DIR="$INSTALLATION_DIR/installer/"
+
+# install the new version
+mkdir -p -m 755 "$CONFIG_DIR"
+test ! -d "$INSTALLATION_DIR" && REMOVE_INSTALLATION_DIR=1
+mkdir -p -m 755 "$INSTALLATION_DIR"
+
+# install and load installer modules
+if [ -d installer ]; then
+ info "Copying additional installer modules ..."
+ mkdir -p -m 755 "$INSTALLATION_MODULES_DIR"
+ for CUR_FILE in `ls installer/*`; do
+ install -p -m 755 "$CUR_FILE" "$INSTALLATION_MODULES_DIR"
+ if [ $? -ne 0 ]; then
+ info "Error: Failed to copy installer module \"$CUR_FILE\""
+ if ! test "$FORCE_UPGRADE" = "force"; then
+ exit 1
+ fi
+ fi
+ done
+fi
+
+# Create a list of the files in the archive, skipping any directories which
+# already exist in the filesystem.
+bzip2 -d -c "$ARCH_PACKAGE" | tar -tf - |
+ while read name; do
+ fullname="$INSTALLATION_DIR/$name"
+ case "$fullname" in
+ */)
+ test ! -d "$fullname" &&
+ echo "$fullname" >> "$CONFIG_DIR/$CONFIG_FILES"
+ ;;
+ *)
+ echo "$fullname" >> "$CONFIG_DIR/$CONFIG_FILES"
+ ;;
+ esac
+ done
+bzip2 -d -c "$ARCH_PACKAGE" | tar -xf - -C "$INSTALLATION_DIR" || exit 1
+
+# Set symlinks into /usr and /sbin
+link_into_fs "bin" "${PACKAGE_BASE}/usr/bin"
+link_into_fs "sbin" "${PACKAGE_BASE}/usr/sbin"
+link_into_fs "lib" "$lib_path"
+link_into_fs "src" "${PACKAGE_BASE}/usr/src"
+
+if [ -d "$INSTALLATION_MODULES_DIR" ]; then
+ info "Installing additional modules ..."
+ for CUR_MODULE in `find "$INSTALLATION_MODULES_DIR" 2>/dev/null`
+ do
+ echo "$CUR_MODULE" >> "$CONFIG_DIR/$CONFIG_FILES"
+ done
+fi
+
+for CUR_MODULE in ${INSTALLATION_MODULES_LIST}
+do
+ mod_${CUR_MODULE}_install
+ if [ $? -ne 0 ]; then
+ info "Error: Failed to install module \"$CUR_MODULE\""
+ if ! test "$FORCE_UPGRADE" = "force"; then
+ exit 1
+ fi
+ fi
+done
+
+# Remember our installation configuration before we call any init scripts
+cat > "$CONFIG_DIR/$CONFIG" << EOF
+# $PACKAGE installation record.
+# Package installation directory
+INSTALL_DIR='$INSTALLATION_DIR'
+# Additional installation modules
+INSTALL_MODULES_DIR='$INSTALLATION_MODULES_DIR'
+INSTALL_MODULES_LIST='$INSTALLATION_MODULES_LIST'
+# Package uninstaller. If you repackage this software, please make sure
+# that this prints a message and returns an error so that the default
+# uninstaller does not attempt to delete the files installed by your
+# package.
+UNINSTALLER='$UNINSTALL'
+# Package version
+INSTALL_VER='$INSTALLATION_VER'
+# Build type and user name for logging purposes
+BUILD_VBOX_KBUILD_TYPE='$BUILD_BUILDTYPE'
+BUILD_USERNAME='$USERNAME'
+EOF
+
+# Give the modules the chance to write their stuff
+# to the installation config as well.
+if [ -n "${INSTALLATION_MODULES_LIST}" ]; then
+ info "Saving modules configuration ..."
+ for CUR_MODULE in ${INSTALLATION_MODULES_LIST}
+ do
+ echo "`mod_${CUR_MODULE}_config_save`" >> "$CONFIG_DIR/$CONFIG"
+ done
+fi
+
+# Install, set up and start init scripts
+install_init_script "$INSTALLATION_DIR"/init/vboxadd vboxadd 2>> "$LOGFILE"
+install_init_script "$INSTALLATION_DIR"/init/vboxadd-service vboxadd-service \
+ 2>> "$LOGFILE"
+finish_init_script_install
+addrunlevel vboxadd 2>> "$LOGFILE"
+addrunlevel vboxadd-service 2>> "$LOGFILE"
+"$INSTALLATION_DIR"/init/vboxadd setup 2>&1 | tee -a "$LOGFILE"
+start_init_script vboxadd 2>> "$LOGFILE"
+start_init_script vboxadd-service 2>> "$LOGFILE"
+
+cp $ROUTINES $INSTALLATION_DIR
+echo $INSTALLATION_DIR/$ROUTINES >> "$CONFIG_DIR/$CONFIG_FILES"
+cat > $INSTALLATION_DIR/$UNINSTALL << EOF
+#!/bin/sh
+# Auto-generated uninstallation file
+
+PATH=\$PATH:/bin:/sbin:/usr/sbin
+LOGFILE="/var/log/vboxadd-uninstall.log"
+
+# Read routines.sh
+if ! test -r "$INSTALLATION_DIR/$ROUTINES"; then
+ echo 1>&2 "Required file $ROUTINES not found. Aborting..."
+ return 1
+fi
+. "$INSTALLATION_DIR/$ROUTINES"
+
+# We need to be run as root
+check_root
+
+create_log "\$LOGFILE"
+
+echo 1>&2 "Removing installed version $INSTALLATION_VER of $PACKAGE_NAME..."
+
+NO_CLEANUP=""
+if test "\$1" = "no_cleanup"; then
+ shift
+ NO_CLEANUP="no_cleanup"
+fi
+
+test -r "$CONFIG_DIR/$CONFIG_FILES" || abort "Required file $CONFIG_FILES not found. Aborting..."
+
+# Stop and clean up all services
+if test -r "$INSTALLATION_DIR"/init/vboxadd-service; then
+ stop_init_script vboxadd-service 2>> "\$LOGFILE"
+ delrunlevel vboxadd-service 2>> "\$LOGFILE"
+ remove_init_script vboxadd-service 2>> "\$LOGFILE"
+fi
+if test -r "$INSTALLATION_DIR"/init/vboxadd; then
+ stop_init_script vboxadd 2>> "\$LOGFILE"
+ test -n "\$NO_CLEANUP" || "$INSTALLATION_DIR"/init/vboxadd cleanup 2>> "\$LOGFILE"
+ delrunlevel vboxadd 2>> "\$LOGFILE"
+ remove_init_script vboxadd 2>> "\$LOGFILE"
+fi
+finish_init_script_install
+
+# Load all modules
+# Important: This needs to be done before loading the configuration
+# value below to not override values which are set to a default
+# value in the modules itself.
+for CUR_MODULE in `find "$INSTALLATION_MODULES_DIR" -name "module-*" 2>/dev/null`
+ do
+ . "\$CUR_MODULE"
+ done
+
+# Load configuration values
+test -r "$CONFIG_DIR/$CONFIG" && . "$CONFIG_DIR/$CONFIG"
+
+# Call uninstallation initialization of all modules
+for CUR_MODULE in "$INSTALLATION_MODULES_LIST"
+ do
+ if test -z "\$CUR_MODULE"; then
+ continue
+ fi
+ mod_\${CUR_MODULE}_pre_uninstall
+ if [ $? -ne 0 ]; then
+ echo 1>&2 "Module \"\$CUR_MODULE\" failed to initialize uninstallation"
+ # Continue initialization.
+ fi
+ done
+
+# Call uninstallation of all modules
+for CUR_MODULE in "$INSTALLATION_MODULES_LIST"
+ do
+ if test -z "\$CUR_MODULE"; then
+ continue
+ fi
+ mod_\${CUR_MODULE}_uninstall
+ if [ $? -ne 0 ]; then
+ echo 1>&2 "Module \"\$CUR_MODULE\" failed to uninstall"
+ # Continue uninstallation.
+ fi
+ done
+
+# And remove all files and empty installation directories
+# Remove any non-directory entries
+cat "$CONFIG_DIR/$CONFIG_FILES" | xargs rm 2>/dev/null
+# Remove any empty (of files) directories in the file list
+cat "$CONFIG_DIR/$CONFIG_FILES" |
+ while read file; do
+ case "\$file" in
+ */)
+ test -d "\$file" &&
+ find "\$file" -depth -type d -exec rmdir '{}' ';' 2>/dev/null
+ ;;
+ esac
+ done
+
+# Remove configuration files
+rm "$CONFIG_DIR/$CONFIG_FILES" 2>/dev/null
+rm "$CONFIG_DIR/$CONFIG" 2>/dev/null
+rmdir "$CONFIG_DIR" 2>/dev/null
+exit 0
+EOF
+chmod 0755 $INSTALLATION_DIR/$UNINSTALL
+echo $INSTALLATION_DIR/$UNINSTALL >> "$CONFIG_DIR/$CONFIG_FILES"
+test -n "$REMOVE_INSTALLATION_DIR" &&
+ echo "$INSTALLATION_DIR/" >> "$CONFIG_DIR/$CONFIG_FILES"
+
+cat > "${PUBLIC_UNINSTALL_HOOK}" << EOF
+#!/bin/sh
+# This executable provides a well-known way to uninstall VirtualBox Guest
+# Additions in order to re-install them from a different source. A common case
+# is uninstalling distribution-provide Additions to install the version provided
+# by VirtualBox. Distributions should put the right command in here to do the
+# removal, e.g. "dnf remove VirtualBox-guest-additions". Leaving kernel modules
+# provided by the distribution kernel package in place is acceptable if the
+# location does not clash with the VirtualBox-provided module location (misc).
+$INSTALLATION_DIR/$UNINSTALL
+EOF
+chmod 0755 "${PUBLIC_UNINSTALL_HOOK}"
+echo "$PUBLIC_UNINSTALL_HOOK" >> "$CONFIG_DIR/$CONFIG_FILES"
+
+# Test for a problem with old Mesa versions which stopped our 3D libraries
+# from working. Known to affect Debian 7.11, probably OL/RHEL 5.
+# The problem was that the system Mesa library had an ABI note, which caused
+# ldconfig to always prefer it to ours.
+if ldconfig -p | grep -q "libGL.so.1.*Linux 2.4"; then
+ gl_with_abi=`ldconfig -p | grep "libGL.so.1.*Linux 2.4" | sed 's/.*=> //'`
+ cat >&2 << EOF
+This system appears to be running a version of Mesa with a known problem
+which will prevent VirtualBox 3D pass-through from working. See
+ https://bugs.freedesktop.org/show_bug.cgi?id=26663
+The following, run as root should fix this, though you will have to run it
+again if the system version of Mesa is updated:
+EOF
+ for i in ${gl_with_abi}; do
+ echo >&2 " strip -R .note.ABI-tag ${i}"
+ done
+ echo >&2 " ldconfig"
+fi
+
+# setuid bit of our drm client as drm ioctl calls
+# need to be done by elevated privileges
+chmod 4755 "$INSTALLATION_DIR"/bin/VBoxDRMClient
+
+# And do a final test as to whether the kernel modules were properly created
+# and loaded. Return 0 if both are true, 1 if the modules could not be built
+# or loaded (except due to already running older modules) and 2 if already
+# running modules probably prevented new ones from loading. vboxvideo is
+# currently not tested.
+# Assume that we have already printed enough messages by now and stay silent.
+modinfo vboxguest >/dev/null 2>&1 || exit 1
+lsmod | grep -q vboxguest || exit 1
+test -n "${MODULES_STOPPED}" || exit 2
+exit 0
diff --git a/src/VBox/Additions/linux/installer/module-autologon.sh b/src/VBox/Additions/linux/installer/module-autologon.sh
new file mode 100644
index 00000000..5685bed4
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/module-autologon.sh
@@ -0,0 +1,182 @@
+# Oracle VM VirtualBox
+# $Id: module-autologon.sh $
+## @file
+# VirtualBox Linux Guest Additions installer - autologon module
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# @todo Document functions and their usage!
+
+MOD_AUTOLOGON_DEFAULT_LIGHTDM_CONFIG="/etc/lightdm/lightdm.conf"
+MOD_AUTOLOGON_DEFAULT_LIGHTDM_GREETER_DIR="/usr/share/xgreeters"
+
+mod_autologon_init()
+{
+ echo "Initializing auto-logon support ..."
+ return 0
+}
+
+mod_autologon_install_ex()
+{
+ info "Installing auto-logon support ..."
+
+ ## Parameters:
+ # Greeter directory. Defaults to /usr/share/xgreeters.
+ greeter_dir="$1"
+ # LightDM config. Defaults to /etc/lightdm/lightdm.conf.
+ lightdm_config="$2"
+ # Whether to force installation if non-compatible distribution
+ # is detected.
+ force="$3"
+
+ # Check for Ubuntu and derivates. @todo Debian?
+ distros="Ubuntu UbuntuStudio Edubuntu Kubuntu Lubuntu Mythbuntu Xubuntu"
+ ## @todo Map Linux Mint versions to Ubuntu ones.
+
+ ## @todo Move the distro check to a routine / globals as soon as
+ ## we have other distribution-dependent stuff.
+ which lsb_release &>/dev/null
+ if test "$?" -ne "0"; then
+ info "Error: lsb_release not found (path set?), skipping auto-logon installation"
+ return 1
+ fi
+ distro_name=$(lsb_release -si)
+ distro_ver=$(lsb_release -sr)
+
+ for distro_cur in ${distros}; do
+ if test "$distro_name" = "$distro_cur"; then
+ distro_found="true"
+ break
+ fi
+ done
+
+ if test -z "$distro_found"; then
+ if ! test "$force" = "force"; then
+ info "Error: Unsupported distribution \"$distro_name\" found, skipping auto-logon installation"
+ return 1
+ fi
+ info "Warning: Unsupported distribution \"$distro_name\" found"
+ else
+ # Do we have Ubuntu 11.10 or greater?
+ # Use AWK for comparison since we run on plan sh.
+ echo | awk 'END { exit ( !('"$distro_ver >= 11.10"') ); }'
+ if test "$?" -ne "0"; then
+ if ! test "$force" = "force"; then
+ info "Error: Version $distro_ver of \"$distro_name\" not supported, skipping auto-logon installation"
+ return 1
+ fi
+ info "Warning: Unsupported \"$distro_name\" version $distro_ver found"
+ fi
+ fi
+
+ # Install dependencies (lightdm and FLTK 1.3+) using apt-get.
+ which apt-get &>/dev/null
+ if test "$?" -ne "0"; then
+ info "Error: apt-get not found (path set?), skipping auto-logon installation"
+ return 1
+ fi
+ info "Checking and installing necessary dependencies ..."
+ apt-get -qqq -y install libfltk1.3 libfltk-images1.3 || return 1
+ apt-get -qqq -y install lightdm || return 1
+
+ # Check for LightDM config.
+ if ! test -f "$lightdm_config"; then
+ info "Error: LightDM config \"$lightdm_config\" not found (LightDM installed?), skipping auto-logon installation"
+ return 1
+ fi
+
+ # Check for /usr/share/xgreeters.
+ if ! test -d "$greeter_dir"; then
+ if ! test "$force" = "force"; then
+ info "Error: Directory \"$greeter_dir\" does not exist, skipping auto-logon installation"
+ return 1
+ fi
+ info "Warning: Directory \"$greeter_dir\" does not exist, creating it"
+ mkdir -p -m 755 "$greeter_dir" || return 1
+ fi
+
+ # Link to required greeter files into $greeter_dir.
+ add_symlink "$INSTALLATION_DIR/other/vbox-greeter.desktop" "$greeter_dir/vbox-greeter.desktop"
+
+ # Backup and activate greeter config.
+ if ! test -f "$lightdm_config.vbox-backup"; then
+ info "Backing up LightDM configuration file ..."
+ cp "$lightdm_config" "$lightdm_config.vbox-backup" || return 1
+ chmod 644 "$lightdm_config.vbox-backup" || return 1
+ fi
+ sed -i -e 's/^\s*greeter-session\s*=.*/greeter-session=vbox-greeter/g' "$lightdm_config" || return 1
+ chmod 644 "$lightdm_config" || return 1
+
+ info "Auto-logon installation successful"
+ return 0
+}
+
+mod_autologon_install()
+{
+ if [ -z "$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR" ]; then
+ MOD_AUTOLOGON_LIGHTDM_GREETER_DIR=$MOD_AUTOLOGON_DEFAULT_LIGHTDM_GREETER_DIR
+ fi
+ if [ -z "$MOD_AUTOLOGON_LIGHTDM_CONFIG" ]; then
+ MOD_AUTOLOGON_LIGHTDM_CONFIG=$MOD_AUTOLOGON_DEFAULT_LIGHTDM_CONFIG
+ fi
+
+ mod_autologon_install_ex "$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR" "$MOD_AUTOLOGON_LIGHTDM_CONFIG" "$MOD_AUTOLOGON_FORCE"
+ return $?
+}
+
+mod_autologon_pre_uninstall()
+{
+ echo "Preparing to uninstall auto-logon support ..."
+ return 0
+}
+
+mod_autologon_uninstall()
+{
+ if test -z "$MOD_AUTOLOGON_LIGHTDM_CONFIG"; then
+ return 0
+ fi
+ info "Un-installing auto-logon support ..."
+
+ # Switch back to original greeter.
+ if test -f "$MOD_AUTOLOGON_LIGHTDM_CONFIG.vbox-backup"; then
+ mv "$MOD_AUTOLOGON_LIGHTDM_CONFIG.vbox-backup" "$MOD_AUTOLOGON_LIGHTDM_CONFIG"
+ if test "$?" -ne "0"; then
+ info "Warning: Could not restore original LightDM config \"$MOD_AUTOLOGON_LIGHTDM_CONFIG\""
+ fi
+ fi
+
+ # Remove greeter directory (if not empty).
+ rm "$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR" 2>/dev/null
+
+ info "Auto-logon uninstallation successful"
+ return 0
+}
+
+mod_autologon_config_save()
+{
+ echo "
+MOD_AUTOLOGON_LIGHTDM_CONFIG='$MOD_AUTOLOGON_LIGHTDM_CONFIG'
+MOD_AUTOLOGON_LIGHTDM_GREETER_DIR='$MOD_AUTOLOGON_LIGHTDM_GREETER_DIR'"
+}
+
diff --git a/src/VBox/Additions/linux/installer/vboxadd-service.sh b/src/VBox/Additions/linux/installer/vboxadd-service.sh
new file mode 100755
index 00000000..d3ea8ebe
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/vboxadd-service.sh
@@ -0,0 +1,171 @@
+#!/bin/sh
+# $Id: vboxadd-service.sh $
+## @file
+# Linux Additions Guest Additions service daemon init script.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# X-Conflicts-With is our own invention, which we use when converting to
+# a systemd unit.
+
+# chkconfig: 345 35 65
+# description: VirtualBox Additions service
+#
+### BEGIN INIT INFO
+# Provides: vboxadd-service
+# Required-Start: vboxadd
+# Required-Stop: vboxadd
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# X-Conflicts-With: systemd-timesyncd.service
+# Description: VirtualBox Additions Service
+### END INIT INFO
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+SCRIPTNAME=vboxadd-service.sh
+
+PIDFILE="/var/run/${SCRIPTNAME}"
+
+# Preamble for Gentoo
+if [ "`which $0`" = "/sbin/rc" ]; then
+ shift
+fi
+
+begin()
+{
+ test -n "${2}" && echo "${SCRIPTNAME}: ${1}."
+ logger -t "${SCRIPTNAME}" "${1}."
+}
+
+succ_msg()
+{
+ logger -t "${SCRIPTNAME}" "${1}."
+}
+
+fail_msg()
+{
+ echo "${SCRIPTNAME}: ${1}." >&2
+ logger -t "${SCRIPTNAME}" "${1}."
+}
+
+daemon() {
+ $1 $2 $3
+}
+
+killproc() {
+ killall $1
+ rm -f $PIDFILE
+}
+
+if which start-stop-daemon >/dev/null 2>&1; then
+ daemon() {
+ start-stop-daemon --start --exec $1 -- $2 $3
+ }
+
+ killproc() {
+ start-stop-daemon --stop --retry 2 --exec $@
+ }
+fi
+
+binary=/usr/sbin/VBoxService
+
+testbinary() {
+ test -x "$binary" || {
+ echo "Cannot run $binary"
+ exit 1
+ }
+}
+
+vboxaddrunning() {
+ lsmod | grep -q "vboxguest[^_-]"
+}
+
+start() {
+ if ! test -f $PIDFILE; then
+ begin "Starting VirtualBox Guest Addition service" console;
+ vboxaddrunning || {
+ echo "VirtualBox Additions module not loaded!"
+ exit 1
+ }
+ testbinary
+ daemon $binary --pidfile $PIDFILE > /dev/null
+ RETVAL=$?
+ succ_msg "VirtualBox Guest Addition service started"
+ fi
+ return $RETVAL
+}
+
+stop() {
+ if test -f $PIDFILE; then
+ begin "Stopping VirtualBox Guest Addition service" console;
+ killproc $binary
+ RETVAL=$?
+ if ! pidof VBoxService > /dev/null 2>&1; then
+ rm -f $PIDFILE
+ succ_msg "VirtualBox Guest Addition service stopped"
+ else
+ fail_msg "VirtualBox Guest Addition service failed to stop"
+ fi
+ fi
+ return $RETVAL
+}
+
+restart() {
+ stop && start
+}
+
+status() {
+ echo -n "Checking for VBoxService"
+ if [ -f $PIDFILE ]; then
+ echo " ...running"
+ RETVAL=0
+ else
+ echo " ...not running"
+ RETVAL=1
+ fi
+
+ return $RETVAL
+}
+
+case "$1" in
+start)
+ start
+ ;;
+stop)
+ stop
+ ;;
+restart)
+ restart
+ ;;
+status)
+ status
+ ;;
+setup)
+ ;;
+cleanup)
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|status}"
+ exit 1
+esac
diff --git a/src/VBox/Additions/linux/installer/vboxadd-x11.sh b/src/VBox/Additions/linux/installer/vboxadd-x11.sh
new file mode 100755
index 00000000..fd265d34
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/vboxadd-x11.sh
@@ -0,0 +1,625 @@
+#! /bin/sh
+# $Id: vboxadd-x11.sh $
+## @file
+# Linux Additions X11 setup init script ($Revision: 155244 $)
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+# chkconfig: 35 30 70
+# description: VirtualBox Linux Additions kernel modules
+#
+### BEGIN INIT INFO
+# Provides: vboxadd-x11
+# Required-Start:
+# Required-Stop:
+# Default-Start:
+# Default-Stop:
+# Description: VirtualBox Linux Additions X11 setup
+### END INIT INFO
+
+PATH=$PATH:/bin:/sbin:/usr/sbin
+CONFIG_DIR="/var/lib/VBoxGuestAdditions"
+CONFIG="${CONFIG_DIR}/config"
+MODPROBE=/sbin/modprobe
+
+if $MODPROBE -c 2>/dev/null | grep -q '^allow_unsupported_modules *0'; then
+ MODPROBE="$MODPROBE --allow-unsupported-modules"
+fi
+
+# Find the version of X installed
+# The last of the three is for the X.org 6.7 included in Fedora Core 2
+xver=`X -version 2>&1`
+x_version=`echo "$xver" | sed -n 's/^X Window System Version \([0-9.]\+\)/\1/p'``echo "$xver" | sed -n 's/^XFree86 Version \([0-9.]\+\)/\1/p'``echo "$xver" | sed -n 's/^X Protocol Version 11, Revision 0, Release \([0-9.]\+\)/\1/p'``echo "$xver" | sed -n 's/^X.Org X Server \([0-9.]\+\)/\1/p'`
+x_version_short=`echo "${x_version}" | sed 's/\([0-9]*\.[0-9]*\)\..*/\1/'`
+# Version of Redhat or Fedora installed. Needed for setting up selinux policy.
+redhat_release=`cat /etc/redhat-release 2> /dev/null`
+# Version of OL installed. Needed for blacklisting vboxvideo.
+oracle_release=`cat /etc/oracle-release 2> /dev/null`
+# All the different possible locations for XFree86/X.Org configuration files
+# - how many of these have ever been used?
+x11conf_files="/etc/X11/xorg.conf /etc/X11/xorg.conf-4 /etc/X11/.xorg.conf \
+ /etc/xorg.conf /usr/etc/X11/xorg.conf-4 /usr/etc/X11/xorg.conf \
+ /usr/lib/X11/xorg.conf-4 /usr/lib/X11/xorg.conf /etc/X11/XF86Config-4 \
+ /etc/X11/XF86Config /etc/XF86Config /usr/X11R6/etc/X11/XF86Config-4 \
+ /usr/X11R6/etc/X11/XF86Config /usr/X11R6/lib/X11/XF86Config-4 \
+ /usr/X11R6/lib/X11/XF86Config"
+
+# Preamble for Gentoo
+if [ "`which $0`" = "/sbin/rc" ]; then
+ shift
+fi
+
+dev=/dev/vboxguest
+userdev=/dev/vboxuser
+owner=vboxadd
+group=1
+
+fail()
+{
+ echo "${1}" >&2
+ exit 1
+}
+
+# Install an X11 desktop startup application. The application should be a shell script.
+# Its name should be purely alphanumeric and should start with a two digit number (preferably
+# 98 or thereabouts) to indicate its place in the Debian Xsession startup order.
+#
+# syntax: install_x11_startup_app app desktop service_name
+install_x11_startup_app() {
+ self="install_x11_startup_app"
+ app_src=$1
+ desktop_src=$2
+ service_name=$3
+ alt_command=$4
+ test -r "$app_src" || fail "$self: no script given"
+ test -r "$desktop_src" || fail "$self: no desktop file given"
+ test -n "$service_name" || fail "$self: no service given"
+ test -n "$alt_command" || fail "$self: no service given"
+ app_dest=`basename $app_src sh`
+ app_dest_sh=`basename $app_src sh`.sh
+ desktop_dest=`basename $desktop_src`
+ found=0
+ x11_autostart="/etc/xdg/autostart"
+ kde_autostart="/usr/share/autostart"
+ redhat_dir=/etc/X11/Xsession.d
+ mandriva_dir=/etc/X11/xinit.d
+ debian_dir=/etc/X11/xinit/xinitrc.d
+ if [ -d "$mandriva_dir" -a -w "$mandriva_dir" -a -x "$mandriva_dir" ]
+ then
+ install -m 0644 $app_src "$mandriva_dir/$app_dest"
+ found=1
+ fi
+ if [ -d "$x11_autostart" -a -w "$x11_autostart" -a -x "$x11_autostart" ]
+ then
+ install -m 0644 $desktop_src "$x11_autostart/$desktop_dest"
+ found=1
+ fi
+ if [ -d "$kde_autostart" -a -w "$kde_autostart" -a -x "$kde_autostart" ]
+ then
+ install -m 0644 $desktop_src "$kde_autostart/$desktop_dest"
+ found=1
+ fi
+ if [ -d "$redhat_dir" -a -w "$redhat_dir" -a -x "$redhat_dir" ]
+ then
+ install -m 0644 $app_src "$redhat_dir/$app_dest"
+ found=1
+ fi
+ if [ -d "$debian_dir" -a -w "$debian_dir" -a -x "$debian_dir" ]
+ then
+ install -m 0755 $app_src "$debian_dir/$app_dest_sh"
+ found=1
+ fi
+ if [ $found -eq 1 ]; then
+ return 0
+ fi
+ cat >&2 << EOF
+Could not set up the $service_name desktop service.
+To start it at log-in for a given user, add the command $alt_command
+to the file .xinitrc in their home directory.
+EOF
+ return 1
+}
+
+
+start()
+{
+ # Todo: check configuration and correct it if necessary
+ return 0
+}
+
+stop()
+{
+ return 0
+}
+
+restart()
+{
+ stop && start
+ return 0
+}
+
+setup_opengl()
+{
+ # Install the guest OpenGL drivers. For now we don't support
+ # multi-architecture installations
+ rm -f /etc/ld.so.conf.d/00vboxvideo.conf
+ rm -Rf /var/lib/VBoxGuestAdditions/lib
+ if /usr/bin/VBoxClient --check3d 2>/dev/null; then
+ mkdir -p /var/lib/VBoxGuestAdditions/lib
+ ln -sf "${INSTALL_DIR}/lib/VBoxOGL.so" /var/lib/VBoxGuestAdditions/lib/libGL.so.1
+ # SELinux for the OpenGL libraries, so that gdm can load them during the
+ # acceleration support check. This prevents an "Oh no, something has gone
+ # wrong!" error when starting EL7 guests.
+ if test -e /etc/selinux/config; then
+ if command -v semanage > /dev/null; then
+ semanage fcontext -a -t lib_t "/var/lib/VBoxGuestAdditions/lib/libGL.so.1"
+ fi
+ # This is needed on old Fedora/Redhat systems. No one remembers which.
+ chcon -h -t lib_t "/var/lib/VBoxGuestAdditions/lib/libGL.so.1" 2>/dev/null
+ fi
+ echo "/var/lib/VBoxGuestAdditions/lib" > /etc/ld.so.conf.d/00vboxvideo.conf
+ fi
+ ldconfig
+}
+
+setup()
+{
+ if test -r "${CONFIG}"; then
+ . "${CONFIG}"
+ else
+ fail "Configuration file ${CONFIG} not found"
+ fi
+ test -n "$INSTALL_DIR" -a -n "$INSTALL_VER" ||
+ fail "Configuration file ${CONFIG} not complete"
+ lib_dir="${INSTALL_DIR}/other"
+ test -x "${lib_dir}" ||
+ fail "Invalid Guest Additions configuration found."
+ # By default we want to configure X
+ dox11config="true"
+ # By default, we want to run our xorg.conf setup script
+ setupxorgconf="true"
+ # All but the oldest supported X servers can automatically set up the
+ # keyboard driver.
+ autokeyboard="--autoKeyboard"
+ # On more recent servers our kernel mouse driver will be used
+ # automatically
+ automouse="--autoMouse"
+ # We need to tell our xorg.conf hacking script whether /dev/psaux exists
+ nopsaux="--nopsaux"
+ case "`uname -r`" in 2.4.*)
+ test -c /dev/psaux && nopsaux="";;
+ esac
+ # Should we use the VMSVGA driver instead of VBoxVideo?
+ if grep 80eebeef /proc/bus/pci/devices > /dev/null; then
+ vmsvga=""
+ elif grep 15ad0405 /proc/bus/pci/devices > /dev/null; then
+ vmsvga="--vmsvga"
+ else
+ dox11config=""
+ fi
+ # The video driver to install for X.Org 6.9+
+ vboxvideo_src=
+ # The mouse driver to install for X.Org 6.9+
+ vboxmouse_src=
+ # The driver extension
+ driver_ext=".so"
+ # The configuration file we generate if no original was found but we need
+ # one.
+ main_cfg="/etc/X11/xorg.conf"
+
+ modules_dir=`X -showDefaultModulePath 2>&1` || modules_dir=
+ if [ -z "$modules_dir" ]; then
+ for dir in /usr/lib64/xorg/modules /usr/lib/xorg/modules /usr/X11R6/lib64/modules /usr/X11R6/lib/modules /usr/X11R6/lib/X11/modules; do
+ if [ -d $dir ]; then
+ modules_dir=$dir
+ break
+ fi
+ done
+ fi
+
+ case "${x_version}" in
+ 4.* | 6.* | 7.* | 1.?.* | 1.1[0-6].* )
+ blacklist_vboxvideo="yes"
+ ;;
+ esac
+ case "$oracle_release" in
+ Oracle*release\ 6.* )
+ # relevant for OL6/UEK4 but cannot hurt for other kernels
+ blacklist_vboxvideo="yes"
+ esac
+ if test -n "${blacklist_vboxvideo}"; then
+ test -d /etc/modprobe.d &&
+ echo "blacklist vboxvideo" > /etc/modprobe.d/blacklist-vboxvideo.conf
+ else
+ test -f /etc/modprobe.d/blacklist-vboxvideo.conf &&
+ rm -f /etc/modprobe.d/blacklist-vboxvideo.conf
+ # We do not want to load the driver if X.Org Server is already
+ # running, as without a driver the server will touch the hardware
+ # directly, causing problems.
+ ps -Af | grep -q '[X]org' || ${MODPROBE} -q vboxvideo
+ fi
+
+ test -z "$x_version" -o -z "$modules_dir" &&
+ {
+ echo "Could not find the X.Org or XFree86 Window System, skipping." >&2
+ exit 0
+ }
+
+ # openSUSE 10.3 shipped X.Org 7.2 with X.Org Server 1.3, but didn't
+ # advertise the fact.
+ if grep -q '10\.3' /etc/SuSE-release 2>/dev/null; then
+ case $x_version in 7.2.*)
+ x_version=1.3.0;;
+ esac
+ fi
+ case $x_version in
+ 1.*.99.* )
+ echo "Warning: unsupported pre-release version of X.Org Server installed. Not installing the X.Org drivers." >&2
+ dox11config=""
+ ;;
+ 1.11.* )
+ xserver_version="X.Org Server 1.11"
+ vboxvideo_src=vboxvideo_drv_111.so
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.10.* )
+ xserver_version="X.Org Server 1.10"
+ vboxvideo_src=vboxvideo_drv_110.so
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.9.* )
+ xserver_version="X.Org Server 1.9"
+ vboxvideo_src=vboxvideo_drv_19.so
+ # Fedora 14 to 16 patched out vboxvideo detection
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.8.* )
+ xserver_version="X.Org Server 1.8"
+ vboxvideo_src=vboxvideo_drv_18.so
+ # Fedora 13 shipped without vboxvideo detection
+ test "$system" = "redhat" && test -z "${vmsvga}" || setupxorgconf=""
+ ;;
+ 1.7.* )
+ xserver_version="X.Org Server 1.7"
+ vboxvideo_src=vboxvideo_drv_17.so
+ setupxorgconf=""
+ ;;
+ 1.6.* )
+ xserver_version="X.Org Server 1.6"
+ vboxvideo_src=vboxvideo_drv_16.so
+ vboxmouse_src=vboxmouse_drv_16.so
+ # SUSE SLE* with X.Org 1.6 does not do input autodetection;
+ # openSUSE does.
+ if grep -q -E '^SLE[^ ]' /etc/SuSE-brand 2>/dev/null; then
+ automouse=""
+ else
+ test "$system" = "suse" && setupxorgconf=""
+ fi
+ ;;
+ 1.5.* )
+ xserver_version="X.Org Server 1.5"
+ vboxvideo_src=vboxvideo_drv_15.so
+ vboxmouse_src=vboxmouse_drv_15.so
+ # Historical note: SUSE with X.Org Server 1.5 disabled automatic
+ # mouse configuration and was handled specially. However since our
+ # kernel driver seems to have problems with X.Org Server 1.5 anyway
+ # we just create an X.Org configuration file and use the user space
+ # one generally, no longer just for SUSE.
+ automouse=""
+ ;;
+ 1.4.* )
+ xserver_version="X.Org Server 1.4"
+ vboxvideo_src=vboxvideo_drv_14.so
+ vboxmouse_src=vboxmouse_drv_14.so
+ automouse=""
+ ;;
+ 1.3.* )
+ # This was the first release which gave the server version number
+ # rather than the X11 release version when you did 'X -version'.
+ xserver_version="X.Org Server 1.3"
+ vboxvideo_src=vboxvideo_drv_13.so
+ vboxmouse_src=vboxmouse_drv_13.so
+ automouse=""
+ ;;
+ 7.1.* | 7.2.* )
+ xserver_version="X.Org 7.1"
+ vboxvideo_src=vboxvideo_drv_71.so
+ vboxmouse_src=vboxmouse_drv_71.so
+ automouse=""
+ ;;
+ 6.9.* | 7.0.* )
+ xserver_version="X.Org 6.9/7.0"
+ vboxvideo_src=vboxvideo_drv_70.so
+ vboxmouse_src=vboxmouse_drv_70.so
+ automouse=""
+ ;;
+ 6.7* | 6.8.* | 4.2.* | 4.3.* )
+ # As the module binaries are the same we use one text for these
+ # four server versions.
+ xserver_version="XFree86 4.2/4.3 and X.Org 6.7/6.8"
+ driver_ext=.o
+ vboxvideo_src=vboxvideo_drv.o
+ vboxmouse_src=vboxmouse_drv.o
+ automouse=""
+ autokeyboard=""
+ case $x_version in
+ 6.8.* )
+ autokeyboard="--autoKeyboard"
+ ;;
+ 4.2.* | 4.3.* )
+ main_cfg="/etc/X11/XF86Config"
+ ;;
+ esac
+ ;;
+ 1.12.* | 1.13.* | 1.14.* | 1.15.* | 1.16.* | 1.17.* | 1.18.* )
+ xserver_version="X.Org Server ${x_version_short}"
+ vboxvideo_src=vboxvideo_drv_`echo ${x_version_short} | sed 's/\.//'`.so
+ setupxorgconf=""
+ test -f "${lib_dir}/${vboxvideo_src}" ||
+ {
+ echo "Warning: unknown version of the X Window System installed. Not installing X Window System drivers." >&2
+ dox11config=""
+ vboxvideo_src=""
+ }
+ ;;
+ * )
+ # For anything else, assume kernel drivers.
+ dox11config=""
+ ;;
+ esac
+ test -n "${dox11config}" &&
+ echo "Installing $xserver_version modules" >&2
+ case "$vboxvideo_src" in
+ ?*)
+ ln -s "${lib_dir}/$vboxvideo_src" "$modules_dir/drivers/vboxvideo_drv$driver_ext.new" &&
+ mv "$modules_dir/drivers/vboxvideo_drv$driver_ext.new" "$modules_dir/drivers/vboxvideo_drv$driver_ext";;
+ *)
+ rm "$modules_dir/drivers/vboxvideo_drv$driver_ext" 2>/dev/null
+ esac
+ case "$vboxmouse_src" in
+ ?*)
+ ln -s "${lib_dir}/$vboxmouse_src" "$modules_dir/input/vboxmouse_drv$driver_ext.new" &&
+ mv "$modules_dir/input/vboxmouse_drv$driver_ext.new" "$modules_dir/input/vboxmouse_drv$driver_ext";;
+ *)
+ rm "$modules_dir/input/vboxmouse_drv$driver_ext" 2>/dev/null
+ esac
+
+ if test -n "$dox11config"; then
+ # Certain Ubuntu/Debian versions use a special PCI-id file to identify
+ # video drivers. Some versions have the directory and don't use it.
+ # Those versions can autoload vboxvideo though, so we don't need to
+ # hack the configuration file for them.
+ test "$system" = "debian" -a -d /usr/share/xserver-xorg/pci &&
+ {
+ rm -f "/usr/share/xserver-xorg/pci/vboxvideo.ids"
+ ln -s "${lib_dir}/vboxvideo.ids" /usr/share/xserver-xorg/pci 2>/dev/null
+ test -n "$automouse" && setupxorgconf=""
+ }
+
+ # Do the XF86Config/xorg.conf hack for those versions that require it
+ configured=""
+ generated=""
+ if test -n "$setupxorgconf"; then
+ for i in $x11conf_files; do
+ if test -r "$i"; then
+ if grep -q "VirtualBox generated" "$i"; then
+ generated="$generated `printf "$i\n"`"
+ else
+ "${lib_dir}/x11config.sh" $autokeyboard $automouse $nopsaux $vmsvga "$i"
+ fi
+ configured="true"
+ fi
+ # Timestamp, so that we can see if the config file is changed
+ # by someone else later
+ test -r "$i.vbox" && touch "$i.vbox"
+ done
+ # X.Org Server 1.5 and 1.6 can detect hardware they know, but they
+ # need a configuration file for VBoxVideo.
+ nobak_cfg="`expr "${main_cfg}" : '\([^.]*\)'`.vbox.nobak"
+ if test -z "$configured"; then
+ touch "$main_cfg"
+ "${lib_dir}/x11config.sh" $autokeyboard $automouse $nopsaux $vmsvga --noBak "$main_cfg"
+ touch "${nobak_cfg}"
+ fi
+ fi
+ test -n "$generated" &&
+ cat >&2 << EOF
+The following X.Org/XFree86 configuration files were originally generated by
+the VirtualBox Guest Additions and were not modified:
+
+$generated
+
+EOF
+ tty >/dev/null && cat << EOF
+You may need to restart the Window System (or just restart the guest system)
+to enable the Guest Additions.
+
+EOF
+ fi
+
+ case "$redhat_release" in
+ # Install selinux policy for Fedora 7 and 8 to allow the X server to
+ # open device files
+ Fedora\ release\ 7* | Fedora\ release\ 8* )
+ semodule -i "${lib_dir}/vbox_x11.pp" > /dev/null 2>&1
+ ;;
+ # Similar for the accelerated graphics check on Fedora 15
+ Fedora\ release\ 15* )
+ semodule -i "${lib_dir}/vbox_accel.pp" > /dev/null 2>&1
+ ;;
+ esac
+
+ # Install selinux policy for Fedora 8 to allow the X server to
+ # open our drivers
+ case "$redhat_release" in
+ Fedora\ release\ 8* )
+ chcon -u system_u -t lib_t "${lib_dir}"/*.so 2>/dev/null
+ ;;
+ esac
+
+ # Our logging code generates some glue code on 32-bit systems. At least F10
+ # needs a rule to allow this. Send all output to /dev/null in case this is
+ # completely irrelevant on the target system.
+ # chcon is needed on old Fedora/Redhat systems. No one remembers which.
+ chcon -t unconfined_execmem_exec_t '/usr/bin/VBoxClient' > /dev/null 2>&1
+ semanage fcontext -a -t unconfined_execmem_exec_t '/usr/bin/VBoxClient' > /dev/null 2>&1
+
+ # And set up VBoxClient to start when the X session does
+ install_x11_startup_app "${lib_dir}/98vboxadd-xclient" "${lib_dir}/vboxclient.desktop" VBoxClient VBoxClient-all ||
+ fail "Failed to set up VBoxClient to start automatically."
+ ln -s "${lib_dir}/98vboxadd-xclient" /usr/bin/VBoxClient-all 2>/dev/null
+ case "${x_version}" in 4.* | 6.* | 7.* | 1.?.* | 1.1* )
+ setup_opengl
+ esac
+ # Try enabling VMSVGA drm device resizing.
+ #VBoxClient --vmsvga
+}
+
+cleanup()
+{
+ # Restore xorg.conf files as far as possible
+ # List of generated files which have been changed since we generated them
+ newer=""
+ # Are we dealing with a legacy information which didn't support
+ # uninstallation?
+ legacy=""
+ # Do any of the restored configuration files still reference our drivers?
+ failed=""
+ # Have we encountered a "nobak" configuration file which means that there
+ # is no original file to restore?
+ nobak=""
+ test -r "$CONFIG_DIR/$CONFIG" || legacy="true"
+ for main_cfg in "/etc/X11/xorg.conf" "/etc/X11/XF86Config"; do
+ nobak_cfg="`expr "${main_cfg}" : '\([^.]*\)'`.vbox.nobak"
+ if test -r "${nobak_cfg}"; then
+ test -r "${main_cfg}" &&
+ if test -n "${legacy}" -o ! "${nobak_cfg}" -ot "${main_cfg}"; then
+ rm -f "${nobak_cfg}" "${main_cfg}"
+ else
+ newer="${newer}`printf " ${main_cfg} (no original)\n"`"
+ fi
+ nobak="true"
+ fi
+ done
+ if test -z "${nobak}"; then
+ for i in $x11conf_files; do
+ if test -r "$i.vbox"; then
+ if test ! "$i" -nt "$i.vbox" -o -n "$legacy"; then
+ mv -f "$i.vbox" "$i"
+ grep -q -E 'vboxvideo|vboxmouse' "$i" &&
+ failed="$failed`printf " $i\n"`"
+ else
+ newer="$newer`printf " $i ($i.vbox)\n"`"
+ fi
+ fi
+ done
+ fi
+ test -n "$newer" && cat >&2 << EOF
+
+The following X.Org/XFree86 configuration files were not restored, as they may
+have been changed since they were generated by the VirtualBox Guest Additions.
+You may wish to restore these manually. The file name in brackets is the
+original version.
+
+$newer
+
+EOF
+ test -n "$failed" && cat >&2 << EOF
+
+The following X.Org/XFree86 configuration files were restored, but still
+contain references to the Guest Additions drivers. You may wish to check and
+possibly correct the restored configuration files to be sure that the server
+will continue to work after it is restarted.
+
+$failed
+
+EOF
+
+ # Remove X.Org drivers
+ modules_dir=`X -showDefaultModulePath 2>&1` || modules_dir=
+ if [ -z "$modules_dir" ]; then
+ for dir in /usr/lib64/xorg/modules /usr/lib/xorg/modules /usr/X11R6/lib64/modules /usr/X11R6/lib/modules /usr/X11R6/lib/X11/modules; do
+ if [ -d $dir ]; then
+ modules_dir=$dir
+ break
+ fi
+ done
+ fi
+ rm -f "$modules_dir/drivers/vboxvideo_drv"* 2>/dev/null
+ rm -f "$modules_dir/input/vboxmouse_drv"* 2>/dev/null
+
+ # Remove the link to vboxvideo_dri.so
+ for dir in /usr/lib/dri /usr/lib32/dri /usr/lib64/dri \
+ /usr/lib/xorg/modules/dri /usr/lib32/xorg/modules/dri \
+ /usr/lib64/xorg/modules/dri /usr/lib/i386-linux-gnu/dri \
+ /usr/lib/x86_64-linux-gnu/dri; do
+ if [ -d $dir ]; then
+ rm -f "$dir/vboxvideo_dri.so" 2>/dev/null
+ fi
+ done
+
+ # Remove VBoxClient autostart files
+ rm /etc/X11/Xsession.d/98vboxadd-xclient 2>/dev/null
+ rm /etc/X11/xinit.d/98vboxadd-xclient 2>/dev/null
+ rm /etc/X11/xinit/xinitrc.d/98vboxadd-xclient.sh 2>/dev/null
+ rm /etc/xdg/autostart/vboxclient.desktop 2>/dev/null
+ rm /usr/share/autostart/vboxclient.desktop 2>/dev/null
+ rm /usr/bin/VBoxClient-all 2>/dev/null
+
+ # Remove other files
+ rm /usr/share/xserver-xorg/pci/vboxvideo.ids 2>/dev/null
+ return 0
+}
+
+dmnstatus()
+{
+ /bin/true
+}
+
+case "$1" in
+start)
+ start
+ ;;
+stop)
+ stop
+ ;;
+restart)
+ restart
+ ;;
+setup)
+ setup
+ ;;
+cleanup)
+ cleanup
+ ;;
+status)
+ dmnstatus
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|status}"
+ exit 1
+esac
+
+exit
diff --git a/src/VBox/Additions/linux/installer/vboxadd.sh b/src/VBox/Additions/linux/installer/vboxadd.sh
new file mode 100755
index 00000000..b6ca1a4f
--- /dev/null
+++ b/src/VBox/Additions/linux/installer/vboxadd.sh
@@ -0,0 +1,1325 @@
+#! /bin/sh
+# $Id: vboxadd.sh $
+## @file
+# Linux Additions kernel module init script ($Revision: 158810 $)
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# X-Start-Before is a Debian Addition which we use when converting to
+# a systemd unit. X-Service-Type is our own invention, also for systemd.
+
+# chkconfig: 345 10 90
+# description: VirtualBox Linux Additions kernel modules
+#
+### BEGIN INIT INFO
+# Provides: vboxadd
+# Required-Start:
+# Required-Stop:
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# X-Start-Before: display-manager
+# X-Service-Type: oneshot
+# Description: VirtualBox Linux Additions kernel modules
+### END INIT INFO
+
+## @todo This file duplicates a lot of script with vboxdrv.sh. When making
+# changes please try to reduce differences between the two wherever possible.
+
+# Testing:
+# * Should fail if the configuration file is missing or missing INSTALL_DIR or
+# INSTALL_VER entries.
+# * vboxadd, vboxsf and vboxdrmipc user groups should be created if they do not exist - test
+# by removing them before installing.
+# * Shared folders can be mounted and auto-mounts accessible to vboxsf group,
+# including on recent Fedoras with SELinux.
+# * Setting INSTALL_NO_MODULE_BUILDS inhibits modules and module automatic
+# rebuild script creation; otherwise modules, user, group, rebuild script,
+# udev rule and shared folder mount helper should be created/set up.
+# * Setting INSTALL_NO_MODULE_BUILDS inhibits module load and unload on start
+# and stop.
+# * Uninstalling the Additions and re-installing them does not trigger warnings.
+
+export LC_ALL=C
+PATH=$PATH:/bin:/sbin:/usr/sbin
+PACKAGE=VBoxGuestAdditions
+MODPROBE=/sbin/modprobe
+OLDMODULES="vboxguest vboxadd vboxsf vboxvfs vboxvideo"
+SERVICE="VirtualBox Guest Additions"
+VBOXSERVICE_PIDFILE="/var/run/vboxadd-service.sh"
+## systemd logs information about service status, otherwise do that ourselves.
+QUIET=
+test -z "${TARGET_VER}" && TARGET_VER=`uname -r`
+
+export VBOX_KBUILD_TYPE
+export USERNAME
+
+setup_log()
+{
+ test -z "${LOG}" || return 0
+ # Rotate log files
+ LOG="/var/log/vboxadd-setup.log"
+ mv -f "${LOG}.3" "${LOG}.4" 2>/dev/null
+ mv -f "${LOG}.2" "${LOG}.3" 2>/dev/null
+ mv -f "${LOG}.1" "${LOG}.2" 2>/dev/null
+ mv -f "${LOG}" "${LOG}.1" 2>/dev/null
+}
+
+if $MODPROBE -c 2>/dev/null | grep -q '^allow_unsupported_modules *0'; then
+ MODPROBE="$MODPROBE --allow-unsupported-modules"
+fi
+
+# Preamble for Gentoo
+if [ "`which $0`" = "/sbin/rc" ]; then
+ shift
+fi
+
+begin()
+{
+ test -n "${QUIET}" || echo "${SERVICE}: ${1}"
+}
+
+info()
+{
+ if test -z "${QUIET}"; then
+ echo "${SERVICE}: $1" | fold -s
+ else
+ echo "$1" | fold -s
+ fi
+}
+
+# When script is running as non-root, it does not have access to log
+# files in /var. In this case, lets print error message to stdout and
+# exit with bad status.
+early_fail()
+{
+ echo "$1" >&2
+ exit 1
+}
+
+fail()
+{
+ log "${1}"
+ echo "${SERVICE}: $1" >&2
+ echo "The log file $LOG may contain further information." >&2
+ exit 1
+}
+
+log()
+{
+ setup_log
+ echo "${1}" >> "${LOG}"
+}
+
+module_build_log()
+{
+ log "Error building the module. Build output follows."
+ echo ""
+ echo "${1}" >> "${LOG}"
+}
+
+dev=/dev/vboxguest
+userdev=/dev/vboxuser
+config=/var/lib/VBoxGuestAdditions/config
+user_config=/etc/virtualbox-guest-additions.conf
+owner=vboxadd
+group=1
+
+# Include custom user configuration file.
+[ -r "$user_config" ] && . "$user_config"
+
+if test -r $config; then
+ . $config
+else
+ fail "Configuration file $config not found"
+fi
+test -n "$INSTALL_DIR" -a -n "$INSTALL_VER" ||
+ fail "Configuration file $config not complete"
+MODULE_SRC="$INSTALL_DIR/src/vboxguest-$INSTALL_VER"
+BUILDINTMP="$MODULE_SRC/build_in_tmp"
+
+# Path to VBoxService control script.
+VBOX_SERVICE_SCRIPT="/sbin/rcvboxadd-service"
+
+# Attempt to detect VirtualBox Guest Additions version and revision information.
+VBOXCONTROL="${INSTALL_DIR}/bin/VBoxControl"
+VBOX_VERSION="`"$VBOXCONTROL" --version | cut -d r -f1`"
+[ -n "$VBOX_VERSION" ] || VBOX_VERSION='unknown'
+VBOX_REVISION="r`"$VBOXCONTROL" --version | cut -d r -f2`"
+[ "$VBOX_REVISION" != "r" ] || VBOX_REVISION='unknown'
+
+# Returns if the vboxguest module is running or not.
+#
+# Returns true if vboxguest module is running, false if not.
+running_vboxguest()
+{
+ lsmod | grep -q "vboxguest[^_-]"
+}
+
+# Returns if the vboxadd module is running or not.
+#
+# Returns true if vboxadd module is running, false if not.
+running_vboxadd()
+{
+ lsmod | grep -q "vboxadd[^_-]"
+}
+
+# Returns if the vboxsf module is running or not.
+#
+# Returns true if vboxsf module is running, false if not.
+running_vboxsf()
+{
+ lsmod | grep -q "vboxsf[^_-]"
+}
+
+# Returns if the vboxvideo module is running or not.
+#
+# Returns true if vboxvideo module is running, false if not.
+running_vboxvideo()
+{
+ lsmod | grep -q "vboxvideo[^_-]"
+}
+
+# Returns if a specific module is running or not.
+#
+# Input $1: Module name to check running status for.
+#
+# Returns true if the module is running, false if not.
+running_module()
+{
+ lsmod | grep -q "$1"
+}
+
+# Returns the version string of a currently running kernel module.
+#
+# Input $1: Module name to check.
+#
+# Returns the module version string if found, or none if not found.
+running_module_version()
+{
+ mod="$1"
+ version_string_path="/sys/module/"$mod"/version"
+
+ [ -n "$mod" ] || return
+ if [ -r "$version_string_path" ]; then
+ cat "$version_string_path"
+ else
+ echo "unknown"
+ fi
+}
+
+# Checks if a loaded kernel module version matches to the currently installed Guest Additions version and revision.
+#
+# Input $1: Module name to check.
+#
+# Returns "1" if the module matches the installed Guest Additions, or none if not.
+check_running_module_version()
+{
+ mod=$1
+ expected="$VBOX_VERSION $VBOX_REVISION"
+
+ [ -n "$mod" ] || return
+ [ -n "$expected" ] || return
+
+ [ "$expected" = "$(running_module_version "$mod")" ] || return
+}
+
+do_vboxguest_non_udev()
+{
+ if [ ! -c $dev ]; then
+ maj=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/devices`
+ if [ ! -z "$maj" ]; then
+ min=0
+ else
+ min=`sed -n 's;\([0-9]\+\) vboxguest;\1;p' /proc/misc`
+ if [ ! -z "$min" ]; then
+ maj=10
+ fi
+ fi
+ test -n "$maj" || {
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot locate the VirtualBox device"
+ }
+
+ mknod -m 0664 $dev c $maj $min || {
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot create device $dev with major $maj and minor $min"
+ }
+ fi
+ chown $owner:$group $dev 2>/dev/null || {
+ rm -f $dev 2>/dev/null
+ rm -f $userdev 2>/dev/null
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot change owner $owner:$group for device $dev"
+ }
+
+ if [ ! -c $userdev ]; then
+ maj=10
+ min=`sed -n 's;\([0-9]\+\) vboxuser;\1;p' /proc/misc`
+ if [ ! -z "$min" ]; then
+ mknod -m 0666 $userdev c $maj $min || {
+ rm -f $dev 2>/dev/null
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot create device $userdev with major $maj and minor $min"
+ }
+ chown $owner:$group $userdev 2>/dev/null || {
+ rm -f $dev 2>/dev/null
+ rm -f $userdev 2>/dev/null
+ rmmod vboxguest 2>/dev/null
+ fail "Cannot change owner $owner:$group for device $userdev"
+ }
+ fi
+ fi
+}
+
+restart()
+{
+ stop && start
+ return 0
+}
+
+## Updates the initramfs. Debian and Ubuntu put the graphics driver in, and
+# need the touch(1) command below. Everyone else that I checked just need
+# the right module alias file from depmod(1) and only use the initramfs to
+# load the root filesystem, not the boot splash. update-initramfs works
+# for the first two and dracut for every one else I checked. We are only
+# interested in distributions recent enough to use the KMS vboxvideo driver.
+update_initramfs()
+{
+ ## kernel version to update for.
+ version="${1}"
+ depmod "${version}"
+ rm -f "/lib/modules/${version}/initrd/vboxvideo"
+ test ! -d "/lib/modules/${version}/initrd" ||
+ test ! -f "/lib/modules/${version}/misc/vboxvideo.ko" ||
+ touch "/lib/modules/${version}/initrd/vboxvideo"
+
+ # Systems without systemd-inhibit probably don't need their initramfs
+ # rebuild here anyway.
+ type systemd-inhibit >/dev/null 2>&1 || return
+ if type dracut >/dev/null 2>&1; then
+ systemd-inhibit --why="Installing VirtualBox Guest Additions" \
+ dracut -f --kver "${version}"
+ elif type update-initramfs >/dev/null 2>&1; then
+ systemd-inhibit --why="Installing VirtualBox Guest Additions" \
+ update-initramfs -u -k "${version}"
+ fi
+}
+
+# Removes any existing VirtualBox guest kernel modules from the disk, but not
+# from the kernel as they may still be in use
+cleanup_modules()
+{
+ # Needed for Ubuntu and Debian, see update_initramfs
+ rm -f /lib/modules/*/initrd/vboxvideo
+ for i in /lib/modules/*/misc; do
+ KERN_VER="${i%/misc}"
+ KERN_VER="${KERN_VER#/lib/modules/}"
+ unset do_update
+ for j in ${OLDMODULES}; do
+ for mod_ext in ko ko.gz ko.xz ko.zst; do
+ test -f "${i}/${j}.${mod_ext}" && do_update=1 && rm -f "${i}/${j}.${mod_ext}"
+ done
+ done
+ test -z "$do_update" || update_initramfs "$KERN_VER"
+ # Remove empty /lib/modules folders which may have been kept around
+ rmdir -p "${i}" 2>/dev/null || true
+ unset keep
+ for j in /lib/modules/"${KERN_VER}"/*; do
+ name="${j##*/}"
+ test -d "${name}" || test "${name%%.*}" != modules && keep=1
+ done
+ if test -z "${keep}"; then
+ rm -rf /lib/modules/"${KERN_VER}"
+ rm -f /boot/initrd.img-"${KERN_VER}"
+ fi
+ done
+ for i in ${OLDMODULES}; do
+ # We no longer support DKMS, remove any leftovers.
+ rm -rf "/var/lib/dkms/${i}"*
+ done
+ rm -f /etc/depmod.d/vboxvideo-upstream.conf
+}
+
+# Secure boot state.
+case "`mokutil --sb-state 2>/dev/null`" in
+ *"disabled in shim"*) unset HAVE_SEC_BOOT;;
+ *"SecureBoot enabled"*) HAVE_SEC_BOOT=true;;
+ *) unset HAVE_SEC_BOOT;;
+esac
+# So far we can only sign modules on Ubuntu and on Debian 10 and later.
+DEB_PUB_KEY=/var/lib/shim-signed/mok/MOK.der
+DEB_PRIV_KEY=/var/lib/shim-signed/mok/MOK.priv
+# Check if key already enrolled.
+unset HAVE_DEB_KEY
+case "`mokutil --test-key "$DEB_PUB_KEY" 2>/dev/null`" in
+ *"is already"*) DEB_KEY_ENROLLED=true;;
+ *) unset DEB_KEY_ENROLLED;;
+esac
+
+# Checks if update-secureboot-policy tool supports required commandline options.
+update_secureboot_policy_supports()
+{
+ opt_name="$1"
+ [ -n "$opt_name" ] || return
+
+ [ -z "$(update-secureboot-policy --help 2>&1 | grep "$opt_name")" ] && return
+ echo "1"
+}
+
+HAVE_UPDATE_SECUREBOOT_POLICY_TOOL=
+if type update-secureboot-policy >/dev/null 2>&1; then
+ [ "$(update_secureboot_policy_supports new-key)" = "1" -a "$(update_secureboot_policy_supports enroll-key)" = "1" ] && \
+ HAVE_UPDATE_SECUREBOOT_POLICY_TOOL=true
+fi
+
+# Reads kernel configuration option.
+kernel_get_config_opt()
+{
+ kern_ver="$1"
+ opt_name="$2"
+
+ [ -n "$kern_ver" ] || return
+ [ -n "$opt_name" ] || return
+
+ # Check if there is a kernel tool which can extract config option.
+ if test -x /lib/modules/"$kern_ver"/build/scripts/config; then
+ /lib/modules/"$kern_ver"/build/scripts/config \
+ --file /lib/modules/"$kern_ver"/build/.config \
+ --state "$opt_name" 2>/dev/null
+ elif test -f /lib/modules/"$kern_ver"/build/.config; then
+ # Extract config option manually.
+ grep "$opt_name=" /lib/modules/"$kern_ver"/build/.config | sed -e "s/^$opt_name=//" -e "s/\"//g"
+ fi
+}
+
+# Reads CONFIG_MODULE_SIG_HASH from kernel config.
+kernel_module_sig_hash()
+{
+ kern_ver="$1"
+ [ -n "$kern_ver" ] || return
+
+ kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG_HASH"
+}
+
+# Returns "1" if kernel module signature hash algorithm
+# is supported by us. Or empty string otherwise.
+module_sig_hash_supported()
+{
+ sig_hashalgo="$1"
+ [ -n "$sig_hashalgo" ] || return
+
+ # Go through supported list.
+ [ "$sig_hashalgo" = "sha1" \
+ -o "$sig_hashalgo" = "sha224" \
+ -o "$sig_hashalgo" = "sha256" \
+ -o "$sig_hashalgo" = "sha384" \
+ -o "$sig_hashalgo" = "sha512" ] || return
+
+ echo "1"
+}
+
+# Check if kernel configuration requires modules signature.
+kernel_requires_module_signature()
+{
+ kern_ver="$1"
+ vbox_sys_lockdown_path="/sys/kernel/security/lockdown"
+
+ [ -n "$kern_ver" ] || return
+
+ requires=""
+ # We consider that if kernel is running in the following configurations,
+ # it will require modules to be signed.
+ if [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG")" = "y" ]; then
+
+ # Modules signature verification is hardcoded in kernel config.
+ [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_MODULE_SIG_FORCE")" = "y" ] && requires="1"
+
+ # Unsigned modules loading is restricted by "lockdown" feature in runtime.
+ if [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_LOCK_DOWN_KERNEL")" = "y" \
+ -o "$(kernel_get_config_opt "$kern_ver" "CONFIG_SECURITY_LOCKDOWN_LSM")" = "y" \
+ -o "$(kernel_get_config_opt "$kern_ver" "CONFIG_SECURITY_LOCKDOWN_LSM_EARLY")" = "y" ]; then
+
+ # Once lockdown level is set to something different from "none" (e.g., "integrity"
+ # or "confidentiality"), kernel will reject unsigned modules loading.
+ if [ -r "$vbox_sys_lockdown_path" ]; then
+ [ -n "$(cat "$vbox_sys_lockdown_path" | grep "\[integrity\]")" ] && requires="1"
+ [ -n "$(cat "$vbox_sys_lockdown_path" | grep "\[confidentiality\]")" ] && requires="1"
+ fi
+
+ # This configuration is used by a number of modern Linux distributions and restricts
+ # unsigned modules loading when Secure Boot mode is enabled.
+ [ "$(kernel_get_config_opt "$kern_ver" "CONFIG_LOCK_DOWN_IN_EFI_SECURE_BOOT")" = "y" -a -n "$HAVE_SEC_BOOT" ] && requires="1"
+ fi
+ fi
+
+ [ -n "$requires" ] && echo "1"
+}
+
+sign_modules()
+{
+ KERN_VER="$1"
+ test -n "$KERN_VER" || return 1
+
+ # Make list of mudules to sign.
+ MODULE_LIST="vboxguest vboxsf"
+ # vboxvideo might not present on for older kernels.
+ [ -f "/lib/modules/"$KERN_VER"/misc/vboxvideo.ko" ] && MODULE_LIST="$MODULE_LIST vboxvideo"
+
+ # Sign kernel modules if kernel configuration requires it.
+ if test "$(kernel_requires_module_signature $KERN_VER)" = "1"; then
+ begin "Signing VirtualBox Guest Additions kernel modules"
+
+ # Generate new signing key if needed.
+ [ -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL" ] && SHIM_NOTRIGGER=y update-secureboot-policy --new-key
+
+ # Check if signing keys are in place.
+ if test ! -f "$DEB_PUB_KEY" || ! test -f "$DEB_PRIV_KEY"; then
+ # update-secureboot-policy tool present in the system, but keys were not generated.
+ [ -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL" ] && info "
+
+update-secureboot-policy tool does not generate signing keys
+in your distribution, see below on how to generate them manually."
+ # update-secureboot-policy not present in the system, recommend generate keys manually.
+ fail "
+
+System is running in Secure Boot mode, however your distribution
+does not provide tools for automatic generation of keys needed for
+modules signing. Please consider to generate and enroll them manually:
+
+ sudo mkdir -p /var/lib/shim-signed/mok
+ sudo openssl req -nodes -new -x509 -newkey rsa:2048 -outform DER -addext \"extendedKeyUsage=codeSigning\" -keyout $DEB_PRIV_KEY -out $DEB_PUB_KEY
+ sudo mokutil --import $DEB_PUB_KEY
+ sudo reboot
+
+Restart \"rcvboxadd setup\" after system is rebooted.
+"
+ fi
+
+ # Get kernel signature hash algorithm from kernel config and validate it.
+ sig_hashalgo=$(kernel_module_sig_hash "$KERN_VER")
+ [ "$(module_sig_hash_supported $sig_hashalgo)" = "1" ] \
+ || fail "Unsupported kernel signature hash algorithm $sig_hashalgo"
+
+ # Sign modules.
+ for i in $MODULE_LIST; do
+
+ # Try to find a tool for modules signing.
+ SIGN_TOOL=$(which kmodsign 2>/dev/null)
+ # Attempt to use in-kernel signing tool if kmodsign not found.
+ if test -z "$SIGN_TOOL"; then
+ if test -x "/lib/modules/$KERN_VER/build/scripts/sign-file"; then
+ SIGN_TOOL="/lib/modules/$KERN_VER/build/scripts/sign-file"
+ fi
+ fi
+
+ # Check if signing tool is available.
+ [ -n "$SIGN_TOOL" ] || fail "Unable to find signing tool"
+
+ "$SIGN_TOOL" "$sig_hashalgo" "$DEB_PRIV_KEY" "$DEB_PUB_KEY" \
+ /lib/modules/"$KERN_VER"/misc/"$i".ko || fail "Unable to sign $i.ko"
+ done
+ # Enroll signing key if needed.
+ if test -n "$HAVE_UPDATE_SECUREBOOT_POLICY_TOOL"; then
+ # update-secureboot-policy "expects" DKMS modules.
+ # Work around this and talk to the authors as soon
+ # as possible to fix it.
+ mkdir -p /var/lib/dkms/vbox-temp
+ update-secureboot-policy --enroll-key 2>/dev/null ||
+ fail "Failed to enroll secure boot key."
+ rmdir -p /var/lib/dkms/vbox-temp 2>/dev/null
+
+ # Indicate that key has been enrolled and reboot is needed.
+ HAVE_DEB_KEY=true
+ fi
+ fi
+}
+
+# Build and install the VirtualBox guest kernel modules
+setup_modules()
+{
+ KERN_VER="$1"
+ test -n "$KERN_VER" || return 1
+ # Match (at least): vboxguest.o; vboxguest.ko; vboxguest.ko.xz
+ set /lib/modules/"$KERN_VER"/misc/vboxguest.*o*
+ #test ! -f "$1" || return 0
+ test -d /lib/modules/"$KERN_VER"/build || return 0
+ export KERN_VER
+ info "Building the modules for kernel $KERN_VER."
+
+ # Prepend PATH for building UEK7 on OL8 distribution.
+ case "$KERN_VER" in
+ 5.15.0-*.el8uek*) PATH="/opt/rh/gcc-toolset-11/root/usr/bin:$PATH";;
+ esac
+
+ # Detect if kernel was built with clang.
+ unset LLVM
+ vbox_cc_is_clang=$(kernel_get_config_opt "$KERN_VER" "CONFIG_CC_IS_CLANG")
+ if test "${vbox_cc_is_clang}" = "y"; then
+ info "Using clang compiler."
+ export LLVM=1
+ fi
+
+ log "Building the main Guest Additions $INSTALL_VER module for kernel $KERN_VER."
+ if ! myerr=`$BUILDINTMP \
+ --save-module-symvers /tmp/vboxguest-Module.symvers \
+ --module-source $MODULE_SRC/vboxguest \
+ --no-print-directory install 2>&1`; then
+ # If check_module_dependencies.sh fails it prints a message itself.
+ module_build_log "$myerr"
+ "${INSTALL_DIR}"/other/check_module_dependencies.sh 2>&1 &&
+ info "Look at $LOG to find out what went wrong"
+ return 0
+ fi
+ log "Building the shared folder support module."
+ if ! myerr=`$BUILDINTMP \
+ --use-module-symvers /tmp/vboxguest-Module.symvers \
+ --module-source $MODULE_SRC/vboxsf \
+ --no-print-directory install 2>&1`; then
+ module_build_log "$myerr"
+ info "Look at $LOG to find out what went wrong"
+ return 0
+ fi
+ log "Building the graphics driver module."
+ if ! myerr=`$BUILDINTMP \
+ --use-module-symvers /tmp/vboxguest-Module.symvers \
+ --module-source $MODULE_SRC/vboxvideo \
+ --no-print-directory install 2>&1`; then
+ module_build_log "$myerr"
+ info "Look at $LOG to find out what went wrong"
+ fi
+ [ -d /etc/depmod.d ] || mkdir /etc/depmod.d
+ echo "override vboxguest * misc" > /etc/depmod.d/vboxvideo-upstream.conf
+ echo "override vboxsf * misc" >> /etc/depmod.d/vboxvideo-upstream.conf
+ echo "override vboxvideo * misc" >> /etc/depmod.d/vboxvideo-upstream.conf
+
+ sign_modules "${KERN_VER}"
+
+ update_initramfs "${KERN_VER}"
+
+ return 0
+}
+
+create_vbox_user()
+{
+ # This is the LSB version of useradd and should work on recent
+ # distributions
+ useradd -d /var/run/vboxadd -g 1 -r -s /bin/false vboxadd >/dev/null 2>&1 || true
+ # And for the others, we choose a UID ourselves
+ useradd -d /var/run/vboxadd -g 1 -u 501 -o -s /bin/false vboxadd >/dev/null 2>&1 || true
+
+}
+
+create_udev_rule()
+{
+ # Create udev description file
+ if [ -d /etc/udev/rules.d ]; then
+ udev_call=""
+ udev_app=`which udevadm 2> /dev/null`
+ if [ $? -eq 0 ]; then
+ udev_call="${udev_app} version 2> /dev/null"
+ else
+ udev_app=`which udevinfo 2> /dev/null`
+ if [ $? -eq 0 ]; then
+ udev_call="${udev_app} -V 2> /dev/null"
+ fi
+ fi
+ udev_fix="="
+ if [ "${udev_call}" != "" ]; then
+ udev_out=`${udev_call}`
+ udev_ver=`expr "$udev_out" : '[^0-9]*\([0-9]*\)'`
+ if [ "$udev_ver" = "" -o "$udev_ver" -lt 55 ]; then
+ udev_fix=""
+ fi
+ fi
+ ## @todo 60-vboxadd.rules -> 60-vboxguest.rules ?
+ echo "KERNEL=${udev_fix}\"vboxguest\", NAME=\"vboxguest\", OWNER=\"vboxadd\", MODE=\"0660\"" > /etc/udev/rules.d/60-vboxadd.rules
+ echo "KERNEL=${udev_fix}\"vboxuser\", NAME=\"vboxuser\", OWNER=\"vboxadd\", MODE=\"0666\"" >> /etc/udev/rules.d/60-vboxadd.rules
+ # Make sure the new rule is noticed.
+ udevadm control --reload >/dev/null 2>&1 || true
+ udevcontrol reload_rules >/dev/null 2>&1 || true
+ fi
+}
+
+create_module_rebuild_script()
+{
+ # And a post-installation script for rebuilding modules when a new kernel
+ # is installed.
+ mkdir -p /etc/kernel/postinst.d /etc/kernel/prerm.d
+ cat << EOF > /etc/kernel/postinst.d/vboxadd
+#!/bin/sh
+# This only works correctly on Debian derivatives - Red Hat calls it before
+# installing the right header files.
+/sbin/rcvboxadd quicksetup "\${1}"
+exit 0
+EOF
+ cat << EOF > /etc/kernel/prerm.d/vboxadd
+#!/bin/sh
+for i in ${OLDMODULES}; do rm -f /lib/modules/"\${1}"/misc/"\${i}".ko; done
+rmdir -p /lib/modules/"\$1"/misc 2>/dev/null || true
+exit 0
+EOF
+ chmod 0755 /etc/kernel/postinst.d/vboxadd /etc/kernel/prerm.d/vboxadd
+}
+
+shared_folder_setup()
+{
+ # Add a group "vboxsf" for Shared Folders access
+ # All users which want to access the auto-mounted Shared Folders have to
+ # be added to this group.
+ groupadd -r -f vboxsf >/dev/null 2>&1
+
+ # Put the mount.vboxsf mount helper in the right place.
+ ## @todo It would be nicer if the kernel module just parsed parameters
+ # itself instead of needing a separate binary to do that.
+ ln -sf "${INSTALL_DIR}/other/mount.vboxsf" /sbin
+ # SELinux security context for the mount helper.
+ if test -e /etc/selinux/config; then
+ # This is correct. semanage maps this to the real path, and it aborts
+ # with an error, telling you what you should have typed, if you specify
+ # the real path. The "chcon" is there as a back-up for old guests.
+ command -v semanage > /dev/null &&
+ semanage fcontext -a -t mount_exec_t "${INSTALL_DIR}/other/mount.vboxsf"
+ chcon -t mount_exec_t "${INSTALL_DIR}/other/mount.vboxsf" 2>/dev/null
+ fi
+}
+
+# Returns path to a module file as seen by modinfo(8), or none if not found.
+#
+# Input $1: Module name to get path for.
+#
+# Returns the module path as a string.
+module_path()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ modinfo "$mod" 2>/dev/null | grep -e "^filename:" | tr -s ' ' | cut -d " " -f2
+}
+
+# Returns module version if module is available, or none if not found.
+#
+# Input $1: Module name to get version for.
+#
+# Returns the module version as a string.
+module_version()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f2
+}
+
+# Returns the module revision if module is available in the system, or none if not found.
+#
+# Input $1: Module name to get revision for.
+#
+# Returns the module revision as a string.
+module_revision()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ modinfo "$mod" 2>/dev/null | grep -e "^version:" | tr -s ' ' | cut -d " " -f3
+}
+
+# Checks if a given kernel module is properly signed or not.
+#
+# Input $1: Module name to check.
+#
+# Returns "1" if module is signed and signature can be verified
+# with public key provided in DEB_PUB_KEY, or none otherwise.
+module_signed()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ # Be nice with distributions which do not provide tools which we
+ # use in order to verify module signature. This variable needs to
+ # be explicitly set by administrator. This script will look for it
+ # in /etc/virtualbox-guest-additions.conf. Make sure that you know
+ # what you do!
+ if [ "$VBOX_BYPASS_MODULES_SIGNATURE_CHECK" = "1" ]; then
+ echo "1"
+ return
+ fi
+
+ extraction_tool=/lib/modules/"$(uname -r)"/build/scripts/extract-module-sig.pl
+ mod_path=$(module_path "$mod" 2>/dev/null)
+ openssl_tool=$(which openssl 2>/dev/null)
+ # Do not use built-in printf!
+ printf_tool=$(which printf 2>/dev/null)
+
+ # Make sure all the tools required for signature validation are available.
+ [ -x "$extraction_tool" ] || return
+ [ -n "$mod_path" ] || return
+ [ -n "$openssl_tool" ] || return
+ [ -n "$printf_tool" ] || return
+
+ # Make sure openssl can handle hash algorithm.
+ sig_hashalgo=$(modinfo -F sig_hashalgo "$mod" 2>/dev/null)
+ [ "$(module_sig_hash_supported $sig_hashalgo)" = "1" ] || return
+
+ # Generate file names for temporary stuff.
+ mod_pub_key=$(mktemp -u)
+ mod_signature=$(mktemp -u)
+ mod_unsigned=$(mktemp -u)
+
+ # Convert public key in DER format into X509 certificate form.
+ "$openssl_tool" x509 -pubkey -inform DER -in "$DEB_PUB_KEY" -out "$mod_pub_key" 2>/dev/null
+ # Extract raw module signature and convert it into binary format.
+ "$printf_tool" \\x$(modinfo -F signature "$mod" | sed -z 's/[ \t\n]//g' | sed -e "s/:/\\\x/g") 2>/dev/null > "$mod_signature"
+ # Extract unsigned module for further digest calculation.
+ "$extraction_tool" -0 "$mod_path" 2>/dev/null > "$mod_unsigned"
+
+ # Verify signature.
+ rc=""
+ "$openssl_tool" dgst "-$sig_hashalgo" -binary -verify "$mod_pub_key" -signature "$mod_signature" "$mod_unsigned" 2>&1 >/dev/null && rc="1"
+ # Clean up.
+ rm -f $mod_pub_key $mod_signature $mod_unsigned
+
+ # Check result.
+ [ "$rc" = "1" ] || return
+
+ echo "1"
+}
+
+# Checks if a given kernel module matches the installed VirtualBox Guest Additions version.
+#
+# Input $1: Module name to check.
+#
+# Returns "1" if externally built module is available in the system and its
+# version and revision number do match to current VirtualBox installation.
+# None otherwise.
+module_available()
+{
+ mod="$1"
+ [ -n "$mod" ] || return
+
+ [ "$VBOX_VERSION" = "$(module_version "$mod")" ] || return
+ [ "$VBOX_REVISION" = "$(module_revision "$mod")" ] || return
+
+ # Check if module belongs to VirtualBox installation.
+ #
+ # We have a convention that only modules from /lib/modules/*/misc
+ # belong to us. Modules from other locations are treated as
+ # externally built.
+ mod_path="$(module_path "$mod")"
+
+ # If module path points to a symbolic link, resolve actual file location.
+ [ -L "$mod_path" ] && mod_path="$(readlink -e -- "$mod_path")"
+
+ # File exists?
+ [ -f "$mod_path" ] || return
+
+ # Extract last component of module path and check whether it is located
+ # outside of /lib/modules/*/misc.
+ mod_dir="$(dirname "$mod_path" | sed 's;^.*/;;')"
+ [ "$mod_dir" = "misc" ] || return
+
+ # In case if kernel configuration (for currently loaded kernel) requires
+ # module signature, check if module is signed.
+ if test "$(kernel_requires_module_signature $(uname -r))" = "1"; then
+ [ "$(module_signed "$mod")" = "1" ] || return
+ fi
+
+ echo "1"
+}
+
+# Check if required modules are installed in the system and versions match.
+#
+# Returns "1" on success, none otherwise.
+setup_complete()
+{
+ [ "$(module_available vboxguest)" = "1" ] || return
+ [ "$(module_available vboxsf)" = "1" ] || return
+
+ # All modules are in place.
+ echo "1"
+}
+
+# setup_script
+setup()
+{
+ info "Setting up modules"
+
+ # chcon is needed on old Fedora/Redhat systems. No one remembers which.
+ test ! -e /etc/selinux/config ||
+ chcon -t bin_t "$BUILDINTMP" 2>/dev/null
+
+ if test -z "$INSTALL_NO_MODULE_BUILDS"; then
+ # Check whether modules setup is already complete for currently running kernel.
+ # Prevent unnecessary rebuilding in order to speed up booting process.
+ if test "$(setup_complete)" = "1"; then
+ info "VirtualBox Guest Additions kernel modules $VBOX_VERSION $VBOX_REVISION are \
+already available for kernel $TARGET_VER and do not require to be rebuilt."
+ else
+ info "Building the VirtualBox Guest Additions kernel modules. This may take a while."
+ info "To build modules for other installed kernels, run"
+ info " /sbin/rcvboxadd quicksetup <version>"
+ info "or"
+ info " /sbin/rcvboxadd quicksetup all"
+ if test -d /lib/modules/"$TARGET_VER"/build; then
+ setup_modules "$TARGET_VER"
+ depmod
+ else
+ info "Kernel headers not found for target kernel $TARGET_VER. \
+Please install them and execute
+ /sbin/rcvboxadd setup"
+ fi
+ fi
+ fi
+ create_vbox_user
+ create_udev_rule
+ test -n "${INSTALL_NO_MODULE_BUILDS}" || create_module_rebuild_script
+ shared_folder_setup
+ # Create user group which will have permissive access to DRP IPC server socket.
+ groupadd -r -f vboxdrmipc >/dev/null 2>&1
+
+ if running_vboxguest || running_vboxadd; then
+ # Only warn user if currently loaded modules version do not match Guest Additions Installation.
+ check_running_module_version "vboxguest" || info "Running kernel modules will not be replaced until the system is restarted or 'rcvboxadd reload' triggered"
+ fi
+
+ # Put the X.Org driver in place. This is harmless if it is not needed.
+ # Also set up the OpenGL library.
+ myerr=`"${INSTALL_DIR}/init/vboxadd-x11" setup 2>&1`
+ test -z "${myerr}" || log "${myerr}"
+
+ return 0
+}
+
+# cleanup_script
+cleanup()
+{
+ if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
+ # Delete old versions of VBox modules.
+ cleanup_modules
+ depmod
+
+ # Remove old module sources
+ for i in $OLDMODULES; do
+ rm -rf /usr/src/$i-*
+ done
+ fi
+
+ # Clean-up X11-related bits
+ "${INSTALL_DIR}/init/vboxadd-x11" cleanup
+
+ # Remove other files
+ if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
+ rm -f /etc/kernel/postinst.d/vboxadd /etc/kernel/prerm.d/vboxadd
+ rmdir -p /etc/kernel/postinst.d /etc/kernel/prerm.d 2>/dev/null || true
+ fi
+ rm -f /sbin/mount.vboxsf 2>/dev/null
+ rm -f /etc/udev/rules.d/60-vboxadd.rules 2>/dev/null
+ udevadm control --reload >/dev/null 2>&1 || true
+ udevcontrol reload_rules >/dev/null 2>&1 || true
+}
+
+start()
+{
+ begin "Starting."
+
+ # Check if kernel modules for currently running kernel are ready
+ # and rebuild them if needed.
+ test "$(setup_complete)" = "1" || setup
+
+ # Warn if Secure Boot setup not yet complete.
+ if test "$(kernel_requires_module_signature)" = "1" && test -z "$DEB_KEY_ENROLLED"; then
+ if test -n "$HAVE_DEB_KEY"; then
+ info "You must re-start your system to finish secure boot set-up."
+ else
+ info "You must sign vboxguest, vboxsf and
+vboxvideo (if present) kernel modules before using
+VirtualBox Guest Additions. See the documentation
+for your Linux distribution."
+ fi
+ fi
+
+ if test -z "${INSTALL_NO_MODULE_BUILDS}"; then
+ test -d /sys &&
+ ps -A -o comm | grep -q '/*udevd$' 2>/dev/null ||
+ no_udev=1
+ check_running_module_version "vboxguest" || {
+ rm -f $dev || {
+ fail "Cannot remove $dev"
+ }
+ rm -f $userdev || {
+ fail "Cannot remove $userdev"
+ }
+ # Assuming modules were just (re-)built, try to reload everything.
+ reload
+ }
+ case "$no_udev" in 1)
+ do_vboxguest_non_udev;;
+ esac
+ fi # INSTALL_NO_MODULE_BUILDS
+
+ return 0
+}
+
+stop()
+{
+ begin "Stopping."
+
+ if test -r /etc/ld.so.conf.d/00vboxvideo.conf; then
+ rm /etc/ld.so.conf.d/00vboxvideo.conf
+ ldconfig
+ fi
+ if ! umount -a -t vboxsf 2>/dev/null; then
+ # Make sure we only fail, if there are truly no more vboxsf
+ # mounts in the system.
+ [ -n "$(findmnt -t vboxsf)" ] && fail "Cannot unmount vboxsf folders"
+ fi
+ test -n "${INSTALL_NO_MODULE_BUILDS}" ||
+ info "You may need to restart your guest system to finish removing guest drivers or consider running 'rcvboxadd reload'."
+ return 0
+}
+
+check_root()
+{
+ # Check if script is running with root privileges and exit if it does not.
+ [ `id -u` -eq 0 ] || early_fail "root privileges are required"
+}
+
+# Check if process with this PID is running.
+check_pid()
+{
+ pid="$1"
+
+ test -n "$pid" -a -d "/proc/$pid"
+}
+
+# A wrapper for check_running_module_version.
+# Go through the list of Guest Additions' modules and
+# verify if they are loaded and running version matches
+# to current installation version. Skip vboxvideo since
+# it is not loaded for old guests.
+check_status_kernel()
+{
+ for mod in vboxguest vboxsf; do
+
+ for attempt in 1 2 3 4 5; do
+
+ # Wait before the next attempt.
+ [ $? -ne 0 ] && sleep 1
+
+ running_module "$mod"
+ if [ $? -eq 0 ]; then
+ mod_is_running="1"
+ check_running_module_version "$mod"
+ [ $? -eq 0 ] && break
+ else
+ mod_is_running=""
+ false
+ fi
+
+ done
+
+ # In case of error, try to print out proper reason of failure.
+ if [ $? -ne 0 ]; then
+ # Was module loaded?
+ if [ -z "$mod_is_running" ]; then
+ info "module $mod is not loaded"
+ else
+ # If module was loaded it means that it has incorrect version.
+ info "currently loaded module $mod version ($(running_module_version "$mod")) does not match to VirtualBox Guest Additions installation version ($VBOX_VERSION $VBOX_REVISION)"
+ fi
+
+ # Set "bad" rc.
+ false
+ fi
+
+ done
+}
+
+# Check whether user-land processes are running.
+# Currently only check for VBoxService.
+check_status_user()
+{
+ [ -r "$VBOXSERVICE_PIDFILE" ] && check_pid "$(cat $VBOXSERVICE_PIDFILE)" >/dev/null 2>&1
+}
+
+send_signal_by_pidfile()
+{
+ sig="$1"
+ pidfile="$2"
+
+ if [ -f "$pidfile" ]; then
+ check_pid $(cat "$pidfile")
+ if [ $? -eq 0 ]; then
+ kill "$sig" $(cat "$pidfile") >/dev/null 2>&1
+ else
+ # Do not spoil $?.
+ true
+ fi
+ else
+ # Do not spoil $?.
+ true
+ fi
+}
+
+# SIGUSR1 is used in order to notify VBoxClient processes that system
+# update is started or kernel modules are going to be reloaded,
+# so VBoxClient can release vboxguest.ko resources and then restart itself.
+send_signal()
+{
+ sig="$1"
+ # Specify whether we sending signal to VBoxClient parent (control)
+ # process or a child (actual service) process.
+ process_type="$2"
+
+ pidfile_postfix=""
+ [ -z "$process_type" ] || pidfile_postfix="-$process_type"
+
+ for user_name in $(getent passwd | cut -d ':' -f 1); do
+
+ # Filter out empty login names (paranoia).
+ [ -n "$user_name" ] || continue
+
+ user_shell=$(getent passwd "$user_name" | cut -d ':' -f 7)
+
+ # Filter out login names with not specified shells (paranoia).
+ [ -n "$user_shell" ] || continue
+
+ # Filter out know non-login account names.
+ case "$user_shell" in
+ *nologin) skip_user_home="1";;
+ *sync) skip_user_home="1";;
+ *shutdown) skip_user_home="1";;
+ *halt) skip_user_home="1";;
+ *) skip_user_home=""
+ esac
+ [ -z "$skip_user_home" ] || continue;
+
+ user_home=$(getent passwd "$user_name" | cut -d ':' -f 6)
+
+ for pid_file in "$user_home"/.vboxclient-*"$pidfile_postfix".pid; do
+
+ [ -r "$pid_file" ] || continue
+
+ # If process type was not specified, we assume that signal supposed
+ # to be sent to legacy VBoxClient processes which have different
+ # pidfile name pattern (it does not contain "control" or "service").
+ # Skip those pidfiles who has.
+ [ -z "$process_type" -a -n "$(echo "$pid_file" | grep "control")" ] && continue
+ [ -z "$process_type" -a -n "$(echo "$pid_file" | grep "service")" ] && continue
+
+ send_signal_by_pidfile -USR1 "$pid_file"
+
+ done
+ done
+}
+
+# Helper function which executes a command, prints error message if command fails,
+# and preserves command execution status for further processing.
+try_load_preserve_rc()
+{
+ cmd="$1"
+ msg="$2"
+
+ $cmd >/dev/null 2>&1
+
+ rc=$?
+ [ $rc -eq 0 ] || info "$msg"
+
+ return $rc
+}
+
+reload()
+{
+ begin "reloading kernel modules and services"
+
+ # Stop VBoxService if running.
+ $VBOX_SERVICE_SCRIPT status >/dev/null 2>&1
+ if [ $? -eq 0 ]; then
+ $VBOX_SERVICE_SCRIPT stop >/dev/null 2>&1 || fail "unable to stop VBoxService"
+ fi
+
+ # Unmount Shared Folders.
+ umount -a -t vboxsf >/dev/null 2>&1 || fail "unable to unmount shared folders, mount point(s) might be still in use"
+
+ # Stop VBoxDRMClient.
+ send_signal_by_pidfile "-USR1" "/var/run/VBoxDRMClient" || fail "unable to stop VBoxDRMClient"
+
+ if [ $? -eq 0 ]; then
+ # Tell legacy VBoxClient processes to release vboxguest.ko references.
+ send_signal "-USR1" ""
+
+ # Tell compatible VBoxClient processes to release vboxguest.ko references.
+ send_signal "-USR1" "service"
+
+ # Try unload.
+ for attempt in 1 2 3 4 5; do
+
+ # Give VBoxClient processes some time to close reference to vboxguest module.
+ [ $? -ne 0 ] && sleep 1
+
+ # Try unload drivers unconditionally (ignore previous command exit code).
+ # If final goal of unloading vboxguest.ko won't be met, we will fail on
+ # the next step anyway.
+ running_vboxsf && modprobe -r vboxsf >/dev/null 2>&1
+ running_vboxguest
+ if [ $? -eq 0 ]; then
+ modprobe -r vboxguest >/dev/null 2>&1
+ [ $? -eq 0 ] && break
+ else
+ # Do not spoil $?.
+ true
+ fi
+ done
+
+ # Check if we succeeded with unloading vboxguest after several attempts.
+ running_vboxguest
+ if [ $? -eq 0 ]; then
+ info "cannot reload kernel modules: one or more module(s) is still in use"
+ false
+ else
+ # Do not spoil $?.
+ true
+ fi
+
+ # Load drivers (skip vboxvideo since it is not loaded for very old guests).
+ [ $? -eq 0 ] && try_load_preserve_rc "modprobe vboxguest" "unable to load vboxguest kernel module, see dmesg"
+ [ $? -eq 0 ] && try_load_preserve_rc "modprobe vboxsf" "unable to load vboxsf kernel module, see dmesg"
+
+ # Start VBoxService and VBoxDRMClient.
+ [ $? -eq 0 ] && try_load_preserve_rc "$VBOX_SERVICE_SCRIPT start" "unable to start VBoxService"
+
+ # Reload VBoxClient processes.
+ [ $? -eq 0 ] && try_load_preserve_rc "send_signal -USR1 control" "unable to reload user session services"
+
+ # Check if we just loaded modules of correct version.
+ [ $? -eq 0 ] && try_load_preserve_rc "check_status_kernel" "kernel modules were not reloaded"
+
+ # Check if user-land processes were restarted as well.
+ [ $? -eq 0 ] && try_load_preserve_rc "check_status_user" "user-land services were not started"
+
+ if [ $? -eq 0 ]; then
+ # Take reported version of running Guest Additions from running vboxguest module (as a paranoia check).
+ info "kernel modules and services $(running_module_version "vboxguest") reloaded"
+ info "NOTE: you may still consider to re-login if some user session specific services (Shared Clipboard, Drag and Drop, Seamless or Guest Screen Resize) were not restarted automatically"
+ else
+ # In case of failure, sent SIGTERM to abandoned control processes to remove leftovers from failed reloading.
+ send_signal "-TERM" "control"
+
+ fail "kernel modules and services were not reloaded"
+ fi
+ else
+ fail "cannot stop user services"
+ fi
+}
+
+dmnstatus()
+{
+ if running_vboxguest; then
+ echo "The VirtualBox Additions are currently running."
+ else
+ echo "The VirtualBox Additions are not currently running."
+ fi
+}
+
+for i; do
+ case "$i" in quiet) QUIET=yes;; esac
+done
+case "$1" in
+# Does setup without clean-up first and marks all kernels currently found on the
+# system so that we can see later if any were added.
+start)
+ check_root
+ start
+ ;;
+# Tries to build kernel modules for kernels added since start. Tries to unmount
+# shared folders. Uninstalls our Chromium 3D libraries since we can't always do
+# this fast enough at start time if we discover we do not want to use them.
+stop)
+ check_root
+ stop
+ ;;
+restart)
+ check_root
+ restart
+ ;;
+# Tries to reload kernel modules and restart user processes.
+reload)
+ check_root
+ reload
+ ;;
+# Setup does a clean-up (see below) and re-does all Additions-specific
+# configuration of the guest system, including building kernel modules for the
+# current kernel.
+setup)
+ check_root
+ cleanup && start
+ ;;
+# Builds kernel modules for the specified kernels if they are not already built.
+quicksetup)
+ check_root
+ if test x"$2" = xall; then
+ for topi in /lib/modules/*; do
+ KERN_VER="${topi%/misc}"
+ KERN_VER="${KERN_VER#/lib/modules/}"
+ setup_modules "$KERN_VER"
+ done
+ elif test -n "$2"; then
+ setup_modules "$2"
+ else
+ setup_modules "$TARGET_VER"
+ fi
+ ;;
+# Clean-up removes all Additions-specific configuration of the guest system,
+# including all kernel modules.
+cleanup)
+ check_root
+ cleanup
+ ;;
+status)
+ dmnstatus
+ ;;
+status-kernel)
+ check_root
+ check_status_kernel
+ if [ $? -eq 0 ]; then
+ info "kernel modules $VBOX_VERSION $VBOX_REVISION are loaded"
+ else
+ info "kernel modules $VBOX_VERSION $VBOX_REVISION were not loaded"
+ false
+ fi
+ ;;
+status-user)
+ check_root
+ check_status_user
+ if [ $? -eq 0 ]; then
+ info "user-land services $VBOX_VERSION $VBOX_REVISION are running"
+ else
+ info "user-land services $VBOX_VERSION $VBOX_REVISION are not running"
+ false
+ fi
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|reload|status|status-kernel|status-user|setup|quicksetup|cleanup} [quiet]"
+ exit 1
+esac
+
+exit
diff --git a/src/VBox/Additions/linux/lightdm-greeter/.scm-settings b/src/VBox/Additions/linux/lightdm-greeter/.scm-settings
new file mode 100644
index 00000000..0d59d2e8
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/.scm-settings
@@ -0,0 +1,30 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for light-dm greeter.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+/liblightdm-gobject-*/*.c|/liblightdm-gobject-*/*.h: --external-copyright --no-strip-trailing-blanks --no-convert-tabs --lgpl-disclaimer --no-fix-header-guards
+
diff --git a/src/VBox/Additions/linux/lightdm-greeter/Config.kmk b/src/VBox/Additions/linux/lightdm-greeter/Config.kmk
new file mode 100644
index 00000000..08e02b00
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/Config.kmk
@@ -0,0 +1,41 @@
+# $Id: Config.kmk $
+## @file
+# kBuild Configuration file for the lightdm-greeter
+#
+
+#
+# Copyright (C) 2016-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+VBOX_LIGHTDM_GREETER_CONFIG_KMK_INCLUDED = 1
+
+# Include the top-level configure file.
+ifndef VBOX_ROOT_CONFIG_KMK_INCLUDED
+ include $(PATH_ROOT)/Config.kmk
+endif
+
+SDK_VBoxGlib20WithIo = glib-2.0 and gio-2.0
+SDK_VBoxGlib20WithIo_VBOX_PKG_CONFIG_CFLAGS := $(shell pkg-config gio-2.0 glib-2.0 --cflags)
+SDK_VBoxGlib20WithIo_INCS = $(patsubst -I%,%,$(filter -I%,$(SDK_VBoxGlib20WithIo_VBOX_PKG_CONFIG_CFLAGS)))
+SDK_VBoxGlib20WithIo_CFLAGS = $(filter-out -I%,$(SDK_VBoxGlib20WithIo_VBOX_PKG_CONFIG_CFLAGS))
+SDK_VBoxGlib20WithIo_LDFLAGS := $(shell pkg-config gio-2.0 glib-2.0 --libs)
+
diff --git a/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk b/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk
new file mode 100644
index 00000000..887514df
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/Makefile.kmk
@@ -0,0 +1,116 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for VBox LightDM greeter for providing automated logons.
+#
+# Note! This isn't compiled any more. Remove?
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifndef VBOX_LIGHTDM_GREETER_CONFIG_KMK_INCLUDED
+ include $(PATH_SUB_CURRENT)/Config.kmk
+endif
+
+ifndef VBOX_OSE
+ include $(PATH_SUB_CURRENT)/liblightdm-gobject-1.5.0/Makefile.kmk
+endif
+
+# Enable building with FLTK UI + PNG support.
+VBOX_WITH_FLTK := 1
+VBOX_GREETER_WITH_PNG_SUPPORT := 1
+
+# The greeter module.
+PROGRAMS += vbox-greeter
+vbox-greeter_TEMPLATE = VBoxGuestR3Exe
+vbox-greeter_SDKS = VBoxGlib20WithIo
+vbox-greeter_DEFS = LOG_TO_BACKDOOR VBOX_WITH_HGCM
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ vbox-greeter_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ vbox-greeter_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+vbox-greeter_DEFS += \
+ $(if $(VBOX_WITH_GUEST_PROPS),VBOX_WITH_GUEST_PROPS,) \
+ $(if $(VBOX_GREETER_WITH_PNG_SUPPORT),VBOX_GREETER_WITH_PNG_SUPPORT,)
+ifdef VBOX_WITH_FLTK
+ vbox-greeter_DEFS += \
+ VBOX_WITH_FLTK
+else
+ vbox-greeter_DEFS += \
+ GTK_DISABLE_SINGLE_INCLUDES \
+ GDK_DISABLE_DEPRECATED
+endif
+vbox-greeter_CFLAGS := $(if $(VBOX_OSE),%(filter-out -I%,$(shell pkg-config --cflags liblightdm-gobject-1)),)
+## @todo r=bird: Why are we cooking our own lightdm-gobject-1 but using system headers?
+## That sounds like a very risky business to me. I've added liblightdm-gobject-1.5.0
+## to the INCS, however lightdm.h is missing and will be taken from the system.
+vbox-greeter_INCS := \
+ /usr/lib/i386-linux-gnu/glib-2.0/include \
+ /usr/lib/x86_64-linux-gnu/glib-2.0/include \
+ /usr/include/glib-2.0 \
+ $(if $(VBOX_OSE),,liblightdm-gobject-1.5.0) \
+ /usr/include/lightdm-gobject-1 \
+ $(if $(VBOX_OSE),$(patsubst -I%,%,%(filter -I%,$(shell pkg-config --cflags liblightdm-gobject-1))),)
+ifndef VBOX_WITH_FLTK
+ vbox-greeter_INCS += \
+ /usr/include/glib-2.0 \
+ /usr/include/gtk-3.0 \
+ /usr/include/pango-1.0 \
+ /usr/include/cairo \
+ /usr/include/gdk-pixbuf-2.0 \
+ /usr/include/atk-1.0
+endif
+
+vbox-greeter_SOURCES = \
+ vbox-greeter.cpp
+
+vbox-greeter_LIBS := \
+ $(if $(VBOX_OSE),lightdm-gobject-1,$(VBOX_PATH_ADDITIONS_LIB)/VBox-liblightdm-gobject$(VBOX_SUFF_LIB)) \
+ glib-2.0 \
+ gio-2.0 \
+ gobject-2.0 \
+ $(VBOX_LIB_IPRT_GUEST_R3_SHARED) \
+ $(VBOX_LIB_VBGL_R3_SHARED) \
+ $(VBOX_LIB_IPRT_GUEST_R3_SHARED)
+ifdef VBOX_WITH_FLTK
+ vbox-greeter_LIBS += fltk
+ ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ vbox-greeter_LIBS += fltk_images
+ endif
+ if $(HOSTNAME) == "3960x.dev" && $(USER) == "bird" # whatever.
+ vbox-greeter_LIBS += stdc++
+ endif
+else
+ vbox-greeter_LIBS += gtk-3
+endif
+
+vbox-greeter_LDFLAGS = $(if $(VBOX_OSE),$(shell pkg-config --libs liblightdm-gobject-1),)
+ifdef VBOX_WITH_FLTK
+ #vbox-greeter_LDFLAGS = -Wl,-Bsymbolic-functions -Wl,-z,relro /usr/lib/i386-linux-gnu/libfltk.a -lXext -lXft -lfontconfig -lfontconfig -lXinerama -ldl -lm -lX11
+ vbox-greeter_LDFLAGS += -s
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/linux/lightdm-greeter/banner-dummy.png b/src/VBox/Additions/linux/lightdm-greeter/banner-dummy.png
new file mode 100644
index 00000000..d74e9c1a
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/banner-dummy.png
Binary files differ
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk
new file mode 100644
index 00000000..dfae0972
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/Makefile.kmk
@@ -0,0 +1,61 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for liblighdm-gobject 1.5.0
+#
+
+#
+# Copyright (C) 2013-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# The greeter module.
+LIBRARIES += VBox-liblightdm-gobject
+
+VBox-liblightdm-gobject_TEMPLATE = VBoxGuestR3Lib
+VBox-liblightdm-gobject_SDKS = VBoxGlib20WithIo
+VBox-liblightdm-gobject_INCS = \
+ /usr/include/glib-2.0 \
+ /usr/lib/i386-linux-gnu/glib-2.0/include \
+ /usr/lib/x86_64-linux-gnu/glib-2.0/include \
+ /usr/include/gio-unix-2.0
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBox-liblightdm-gobject_DEFS = \
+ CONFIG_DIR="/etc/lightdm" \
+ XSESSIONS_DIR="/usr/share/xsessions" \
+ REMOTE_SESSIONS_DIR="/usr/share/lightdm/remote-sessions"
+else
+ VBox-liblightdm-gobject_DEFS = \
+ CONFIG_DIR=\"/etc/lightdm\" \
+ XSESSIONS_DIR=\"/usr/share/xsessions\" \
+ REMOTE_SESSIONS_DIR=\"/usr/share/lightdm/remote-sessions\"
+endif
+VBox-liblightdm-gobject_SOURCES = \
+ greeter.c \
+ language.c \
+ layout.c \
+ power.c \
+ session.c \
+ system.c \
+ user.c
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h
new file mode 100644
index 00000000..c06fc08d
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/config.h
@@ -0,0 +1,90 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Gettext package */
+#define GETTEXT_PACKAGE "lightdm"
+
+/* Greeter session */
+#define GREETER_SESSION "default"
+
+/* User to run greeter as */
+#define GREETER_USER "lightdm"
+
+/* Define to 1 if you have the `clearenv' function. */
+#define HAVE_CLEARENV 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <security/pam_appl.h> header file. */
+#define HAVE_SECURITY_PAM_APPL_H 1
+
+/* Define to 1 if you have the `setresgid' function. */
+#define HAVE_SETRESGID 1
+
+/* Define to 1 if you have the `setresuid' function. */
+#define HAVE_SETRESUID 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "lightdm"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "lightdm"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "lightdm 1.5.0"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "lightdm"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.5.0"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* User session */
+#define USER_SESSION "default"
+
+/* Version number of package */
+#define VERSION "1.5.0"
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c
new file mode 100644
index 00000000..3d44b562
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/greeter.c
@@ -0,0 +1,1442 @@
+/*
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <security/pam_appl.h>
+
+#include "lightdm/greeter.h"
+
+enum {
+ PROP_0,
+ PROP_DEFAULT_SESSION_HINT,
+ PROP_HIDE_USERS_HINT,
+ PROP_SHOW_MANUAL_LOGIN_HINT,
+ PROP_SHOW_REMOTE_LOGIN_HINT,
+ PROP_LOCK_HINT,
+ PROP_HAS_GUEST_ACCOUNT_HINT,
+ PROP_SELECT_USER_HINT,
+ PROP_SELECT_GUEST_HINT,
+ PROP_AUTOLOGIN_USER_HINT,
+ PROP_AUTOLOGIN_GUEST_HINT,
+ PROP_AUTOLOGIN_TIMEOUT_HINT,
+ PROP_AUTHENTICATION_USER,
+ PROP_IN_AUTHENTICATION,
+ PROP_IS_AUTHENTICATED,
+};
+
+enum {
+ SHOW_PROMPT,
+ SHOW_MESSAGE,
+ AUTHENTICATION_COMPLETE,
+ AUTOLOGIN_TIMER_EXPIRED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = { 0 };
+
+typedef struct
+{
+ gboolean connected;
+
+ GIOChannel *to_server_channel, *from_server_channel;
+ guint8 *read_buffer;
+ gsize n_read;
+
+ gsize n_responses_waiting;
+ GList *responses_received;
+
+ GHashTable *hints;
+ guint autologin_timeout;
+
+ gchar *authentication_user;
+ gboolean in_authentication;
+ gboolean is_authenticated;
+ guint32 authenticate_sequence_number;
+ gboolean cancelling_authentication;
+} LightDMGreeterPrivate;
+
+G_DEFINE_TYPE (LightDMGreeter, lightdm_greeter, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_GREETER, LightDMGreeterPrivate)
+
+#define HEADER_SIZE 8
+#define MAX_MESSAGE_LENGTH 1024
+
+/* Messages from the greeter to the server */
+typedef enum
+{
+ GREETER_MESSAGE_CONNECT = 0,
+ GREETER_MESSAGE_AUTHENTICATE,
+ GREETER_MESSAGE_AUTHENTICATE_AS_GUEST,
+ GREETER_MESSAGE_CONTINUE_AUTHENTICATION,
+ GREETER_MESSAGE_START_SESSION,
+ GREETER_MESSAGE_CANCEL_AUTHENTICATION,
+ GREETER_MESSAGE_SET_LANGUAGE,
+ GREETER_MESSAGE_AUTHENTICATE_REMOTE
+} GreeterMessage;
+
+/* Messages from the server to the greeter */
+typedef enum
+{
+ SERVER_MESSAGE_CONNECTED = 0,
+ SERVER_MESSAGE_PROMPT_AUTHENTICATION,
+ SERVER_MESSAGE_END_AUTHENTICATION,
+ SERVER_MESSAGE_SESSION_RESULT
+} ServerMessage;
+
+/**
+ * lightdm_greeter_new:
+ *
+ * Create a new greeter.
+ *
+ * Return value: the new #LightDMGreeter
+ **/
+LightDMGreeter *
+lightdm_greeter_new ()
+{
+ return g_object_new (LIGHTDM_TYPE_GREETER, NULL);
+}
+
+static gboolean
+timed_login_cb (gpointer data)
+{
+ LightDMGreeter *greeter = data;
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+
+ priv->autologin_timeout = 0;
+ g_signal_emit (G_OBJECT (greeter), signals[AUTOLOGIN_TIMER_EXPIRED], 0);
+
+ return FALSE;
+}
+
+static guint32
+int_length ()
+{
+ return 4;
+}
+
+static void
+write_int (guint8 *buffer, gint buffer_length, guint32 value, gsize *offset)
+{
+ if (*offset + 4 >= buffer_length)
+ return;
+ buffer[*offset] = value >> 24;
+ buffer[*offset+1] = (value >> 16) & 0xFF;
+ buffer[*offset+2] = (value >> 8) & 0xFF;
+ buffer[*offset+3] = value & 0xFF;
+ *offset += 4;
+}
+
+static void
+write_string (guint8 *buffer, gint buffer_length, const gchar *value, gsize *offset)
+{
+ gint length = 0;
+
+ if (value)
+ length = strlen (value);
+ write_int (buffer, buffer_length, length, offset);
+ if (*offset + length >= buffer_length)
+ return;
+ memcpy (buffer + *offset, value, length);
+ *offset += length;
+}
+
+static guint32
+read_int (guint8 *message, gsize message_length, gsize *offset)
+{
+ guint32 value;
+ guint8 *buffer;
+
+ if (message_length - *offset < int_length ())
+ {
+ g_warning ("Not enough space for int, need %i, got %zi", int_length (), message_length - *offset);
+ return 0;
+ }
+
+ buffer = message + *offset;
+ value = buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
+ *offset += int_length ();
+
+ return value;
+}
+
+static gchar *
+read_string (guint8 *message, gsize message_length, gsize *offset)
+{
+ guint32 length;
+ gchar *value;
+
+ length = read_int (message, message_length, offset);
+ if (message_length - *offset < length)
+ {
+ g_warning ("Not enough space for string, need %u, got %zu", length, message_length - *offset);
+ return g_strdup ("");
+ }
+
+ value = g_malloc (sizeof (gchar) * (length + 1));
+ memcpy (value, message + *offset, length);
+ value[length] = '\0';
+ *offset += length;
+
+ return value;
+}
+
+static guint32
+string_length (const gchar *value)
+{
+ if (value)
+ return int_length () + strlen (value);
+ else
+ return int_length ();
+}
+
+static void
+write_header (guint8 *buffer, gint buffer_length, guint32 id, guint32 length, gsize *offset)
+{
+ write_int (buffer, buffer_length, id, offset);
+ write_int (buffer, buffer_length, length, offset);
+}
+
+static guint32
+get_message_length (guint8 *message, gsize message_length)
+{
+ gsize offset = 4;
+ return read_int (message, message_length, &offset);
+}
+
+static void
+write_message (LightDMGreeter *greeter, guint8 *message, gsize message_length)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ GIOStatus status;
+ GError *error = NULL;
+ guint32 stated_length;
+
+ /* Double check that we're sending well-formed messages. If we say we're
+ sending more than we do, we end up DOS'ing lightdm as it waits for the
+ rest. If we say we're sending less than we do, we confuse the heck out
+ of lightdm, as it starts reading headers from the middle of our
+ messages. */
+ stated_length = HEADER_SIZE + get_message_length (message, message_length);
+ if (stated_length != message_length)
+ {
+ g_warning ("Refusing to write malformed packet to daemon: declared size is %u, but actual size is %zu", stated_length, message_length);
+ return;
+ }
+
+ status = g_io_channel_write_chars (priv->to_server_channel, (gchar *) message, message_length, NULL, &error);
+ if (error)
+ g_warning ("Error writing to daemon: %s", error->message);
+ g_clear_error (&error);
+ if (status == G_IO_STATUS_NORMAL)
+ g_debug ("Wrote %zi bytes to daemon", message_length);
+ g_io_channel_flush (priv->to_server_channel, NULL);
+}
+
+static void
+handle_connected (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ gchar *version;
+ GString *hint_string;
+ int timeout;
+
+ version = read_string (message, message_length, offset);
+ hint_string = g_string_new ("");
+ while (*offset < message_length)
+ {
+ gchar *name, *value;
+
+ name = read_string (message, message_length, offset);
+ value = read_string (message, message_length, offset);
+ g_hash_table_insert (priv->hints, name, value);
+ g_string_append_printf (hint_string, " %s=%s", name, value);
+ }
+
+ g_debug ("Connected version=%s%s", version, hint_string->str);
+ g_free (version);
+ g_string_free (hint_string, TRUE);
+
+ /* Set timeout for default login */
+ timeout = lightdm_greeter_get_autologin_timeout_hint (greeter);
+ if (timeout)
+ {
+ g_debug ("Setting autologin timer for %d seconds", timeout);
+ priv->autologin_timeout = g_timeout_add (timeout * 1000, timed_login_cb, greeter);
+ }
+}
+
+static void
+handle_prompt_authentication (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ guint32 sequence_number, n_messages, i;
+ gchar *username;
+
+ sequence_number = read_int (message, message_length, offset);
+ if (sequence_number != priv->authenticate_sequence_number)
+ {
+ g_debug ("Ignoring prompt authentication with invalid sequence number %d", sequence_number);
+ return;
+ }
+
+ if (priv->cancelling_authentication)
+ {
+ g_debug ("Ignoring prompt authentication as waiting for it to cancel");
+ return;
+ }
+
+ /* Update username */
+ username = read_string (message, message_length, offset);
+ if (strcmp (username, "") == 0)
+ {
+ g_free (username);
+ username = NULL;
+ }
+ g_free (priv->authentication_user);
+ priv->authentication_user = username;
+
+ g_list_free_full (priv->responses_received, g_free);
+ priv->responses_received = NULL;
+ priv->n_responses_waiting = 0;
+
+ n_messages = read_int (message, message_length, offset);
+ g_debug ("Prompt user with %d message(s)", n_messages);
+
+ for (i = 0; i < n_messages; i++)
+ {
+ int style;
+ gchar *text;
+
+ style = read_int (message, message_length, offset);
+ text = read_string (message, message_length, offset);
+
+ // FIXME: Should stop on prompts?
+ switch (style)
+ {
+ case PAM_PROMPT_ECHO_OFF:
+ priv->n_responses_waiting++;
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_PROMPT], 0, text, LIGHTDM_PROMPT_TYPE_SECRET);
+ break;
+ case PAM_PROMPT_ECHO_ON:
+ priv->n_responses_waiting++;
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_PROMPT], 0, text, LIGHTDM_PROMPT_TYPE_QUESTION);
+ break;
+ case PAM_ERROR_MSG:
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_MESSAGE], 0, text, LIGHTDM_MESSAGE_TYPE_ERROR);
+ break;
+ case PAM_TEXT_INFO:
+ g_signal_emit (G_OBJECT (greeter), signals[SHOW_MESSAGE], 0, text, LIGHTDM_MESSAGE_TYPE_INFO);
+ break;
+ }
+
+ g_free (text);
+ }
+}
+
+static void
+handle_end_authentication (LightDMGreeter *greeter, guint8 *message, gsize message_length, gsize *offset)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ guint32 sequence_number, return_code;
+ gchar *username;
+
+ sequence_number = read_int (message, message_length, offset);
+
+ if (sequence_number != priv->authenticate_sequence_number)
+ {
+ g_debug ("Ignoring end authentication with invalid sequence number %d", sequence_number);
+ return;
+ }
+
+ username = read_string (message, message_length, offset);
+ return_code = read_int (message, message_length, offset);
+
+ g_debug ("Authentication complete for user %s with return code %d", username, return_code);
+
+ /* Update username */
+ if (strcmp (username, "") == 0)
+ {
+ g_free (username);
+ username = NULL;
+ }
+ g_free (priv->authentication_user);
+ priv->authentication_user = username;
+
+ priv->cancelling_authentication = FALSE;
+ priv->is_authenticated = (return_code == 0);
+
+ priv->in_authentication = FALSE;
+ g_signal_emit (G_OBJECT (greeter), signals[AUTHENTICATION_COMPLETE], 0);
+}
+
+static guint8 *
+read_message (LightDMGreeter *greeter, gsize *length, gboolean block)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+ gsize n_to_read, n_read;
+ guint8 *buffer;
+ GError *error = NULL;
+
+ /* Read the header, or the whole message if we already have that */
+ n_to_read = HEADER_SIZE;
+ if (priv->n_read >= HEADER_SIZE)
+ n_to_read += get_message_length (priv->read_buffer, priv->n_read);
+
+ do
+ {
+ GIOStatus status;
+ status = g_io_channel_read_chars (priv->from_server_channel,
+ (gchar *) priv->read_buffer + priv->n_read,
+ n_to_read - priv->n_read,
+ &n_read,
+ &error);
+ if (error)
+ g_warning ("Error reading from server: %s", error->message);
+ g_clear_error (&error);
+ if (status != G_IO_STATUS_NORMAL)
+ break;
+
+ g_debug ("Read %zi bytes from daemon", n_read);
+
+ priv->n_read += n_read;
+ } while (priv->n_read < n_to_read && block);
+
+ /* Stop if haven't got all the data we want */
+ if (priv->n_read != n_to_read)
+ return FALSE;
+
+ /* If have header, rerun for content */
+ if (priv->n_read == HEADER_SIZE)
+ {
+ n_to_read = get_message_length (priv->read_buffer, priv->n_read);
+ if (n_to_read > 0)
+ {
+ priv->read_buffer = g_realloc (priv->read_buffer, HEADER_SIZE + n_to_read);
+ return read_message (greeter, length, block);
+ }
+ }
+
+ buffer = priv->read_buffer;
+ *length = priv->n_read;
+
+ priv->read_buffer = g_malloc (priv->n_read);
+ priv->n_read = 0;
+
+ return buffer;
+}
+
+static gboolean
+from_server_cb (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ LightDMGreeter *greeter = data;
+ guint8 *message;
+ gsize message_length, offset;
+ guint32 id;
+
+ message = read_message (greeter, &message_length, FALSE);
+ if (!message)
+ return TRUE;
+
+ offset = 0;
+ id = read_int (message, message_length, &offset);
+ read_int (message, message_length, &offset);
+ switch (id)
+ {
+ case SERVER_MESSAGE_PROMPT_AUTHENTICATION:
+ handle_prompt_authentication (greeter, message, message_length, &offset);
+ break;
+ case SERVER_MESSAGE_END_AUTHENTICATION:
+ handle_end_authentication (greeter, message, message_length, &offset);
+ break;
+ default:
+ g_warning ("Unknown message from server: %d", id);
+ break;
+ }
+ g_free (message);
+
+ return TRUE;
+}
+
+/**
+ * lightdm_greeter_connect_sync:
+ * @greeter: The greeter to connect
+ * @error: return location for a #GError, or %NULL
+ *
+ * Connects the greeter to the display manager. Will block until connected.
+ *
+ * Return value: #TRUE if successfully connected
+ **/
+gboolean
+lightdm_greeter_connect_sync (LightDMGreeter *greeter, GError **error)
+{
+ LightDMGreeterPrivate *priv;
+ const gchar *fd;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ guint8 *response;
+ gsize response_length, offset = 0;
+ guint32 id;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+
+ priv = GET_PRIVATE (greeter);
+
+ fd = g_getenv ("LIGHTDM_TO_SERVER_FD");
+ if (!fd)
+ {
+ g_warning ("No LIGHTDM_TO_SERVER_FD environment variable");
+ return FALSE;
+ }
+ priv->to_server_channel = g_io_channel_unix_new (atoi (fd));
+ g_io_channel_set_encoding (priv->to_server_channel, NULL, NULL);
+
+ fd = g_getenv ("LIGHTDM_FROM_SERVER_FD");
+ if (!fd)
+ {
+ g_warning ("No LIGHTDM_FROM_SERVER_FD environment variable");
+ return FALSE;
+ }
+ priv->from_server_channel = g_io_channel_unix_new (atoi (fd));
+ g_io_channel_set_encoding (priv->from_server_channel, NULL, NULL);
+ g_io_add_watch (priv->from_server_channel, G_IO_IN, from_server_cb, greeter);
+
+ g_debug ("Connecting to display manager...");
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CONNECT, string_length (VERSION), &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, VERSION, &offset);
+ write_message (greeter, message, offset);
+
+ response = read_message (greeter, &response_length, TRUE);
+ if (!response)
+ return FALSE;
+
+ offset = 0;
+ id = read_int (response, response_length, &offset);
+ read_int (response, response_length, &offset);
+ if (id == SERVER_MESSAGE_CONNECTED)
+ handle_connected (greeter, response, response_length, &offset);
+ g_free (response);
+ if (id != SERVER_MESSAGE_CONNECTED)
+ {
+ g_warning ("Expected CONNECTED message, got %d", id);
+ return FALSE;
+ }
+
+ priv->connected = TRUE;
+
+ return TRUE;
+}
+
+/**
+ * lightdm_greeter_get_hint:
+ * @greeter: A #LightDMGreeter
+ * @name: The hint name to query.
+ *
+ * Get a hint.
+ *
+ * Return value: The value for this hint or #NULL if not set.
+ **/
+const gchar *
+lightdm_greeter_get_hint (LightDMGreeter *greeter, const gchar *name)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return g_hash_table_lookup (GET_PRIVATE (greeter)->hints, name);
+}
+
+/**
+ * lightdm_greeter_get_default_session_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the default session to use.
+ *
+ * Return value: The session name
+ **/
+const gchar *
+lightdm_greeter_get_default_session_hint (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return lightdm_greeter_get_hint (greeter, "default-session");
+}
+
+/**
+ * lightdm_greeter_get_hide_users_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if user accounts should be shown. If this is TRUE then the list of
+ * accounts should be taken from #LightDMUserList and displayed in the greeter
+ * for the user to choose from. Note that this list can be empty and it is
+ * recommended you show a method for the user to enter a username manually.
+ *
+ * If this option is shown the greeter should only allow these users to be
+ * chosen for login unless the manual login hint is set.
+ *
+ * Return value: #TRUE if the available users should not be shown.
+ */
+gboolean
+lightdm_greeter_get_hide_users_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "hide-users");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_show_manual_login_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if a manual login option should be shown. If set the GUI
+ * should provide a way for a username to be entered manually.
+ * Without this hint a greeter which is showing a user list can
+ * limit logins to only those users.
+ *
+ * Return value: #TRUE if a manual login option should be shown.
+ */
+gboolean
+lightdm_greeter_get_show_manual_login_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "show-manual-login");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_show_remote_login_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if a remote login option should be shown. If set the GUI
+ * should provide a way for a user to log into a remote desktop server.
+ *
+ * Return value: #TRUE if a remote login option should be shown.
+ */
+gboolean
+lightdm_greeter_get_show_remote_login_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "show-remote-login");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_lock_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if the greeter is acting as a lock screen.
+ *
+ * Return value: #TRUE if the greeter was triggered by locking the seat.
+ */
+gboolean
+lightdm_greeter_get_lock_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "lock-screen");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_has_guest_account_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if guest sessions are supported.
+ *
+ * Return value: #TRUE if guest sessions are supported.
+ */
+gboolean
+lightdm_greeter_get_has_guest_account_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "has-guest-account");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_select_user_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the user to select by default.
+ *
+ * Return value: A username
+ */
+const gchar *
+lightdm_greeter_get_select_user_hint (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return lightdm_greeter_get_hint (greeter, "select-user");
+}
+
+/**
+ * lightdm_greeter_get_select_guest_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if the guest account should be selected by default.
+ *
+ * Return value: #TRUE if the guest account should be selected by default.
+ */
+gboolean
+lightdm_greeter_get_select_guest_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "select-guest");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_autologin_user_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the user account to automatically logg into when the timer expires.
+ *
+ * Return value: The user account to automatically log into.
+ */
+const gchar *
+lightdm_greeter_get_autologin_user_hint (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return lightdm_greeter_get_hint (greeter, "autologin-user");
+}
+
+/**
+ * lightdm_greeter_get_autologin_guest_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Check if the guest account should be automatically logged into when the timer expires.
+ *
+ * Return value: #TRUE if the guest account should be automatically logged into.
+ */
+gboolean
+lightdm_greeter_get_autologin_guest_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "autologin-guest");
+
+ return g_strcmp0 (value, "true") == 0;
+}
+
+/**
+ * lightdm_greeter_get_autologin_timeout_hint:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the number of seconds to wait before automaitcally logging in.
+ *
+ * Return value: The number of seconds to wait before automatically logging in or 0 for no timeout.
+ */
+gint
+lightdm_greeter_get_autologin_timeout_hint (LightDMGreeter *greeter)
+{
+ const gchar *value;
+ gint timeout = 0;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ value = lightdm_greeter_get_hint (greeter, "autologin-timeout");
+ if (value)
+ timeout = atoi (value);
+ if (timeout < 0)
+ timeout = 0;
+
+ return timeout;
+}
+
+/**
+ * lightdm_greeter_cancel_autologin:
+ * @greeter: A #LightDMGreeter
+ *
+ * Cancel the automatic login.
+ */
+void
+lightdm_greeter_cancel_autologin (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ if (priv->autologin_timeout)
+ g_source_remove (priv->autologin_timeout);
+ priv->autologin_timeout = 0;
+}
+
+/**
+ * lightdm_greeter_authenticate:
+ * @greeter: A #LightDMGreeter
+ * @username: (allow-none): A username or #NULL to prompt for a username.
+ *
+ * Starts the authentication procedure for a user.
+ **/
+void
+lightdm_greeter_authenticate (LightDMGreeter *greeter, const gchar *username)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = FALSE;
+ priv->authenticate_sequence_number++;
+ priv->in_authentication = TRUE;
+ priv->is_authenticated = FALSE;
+ if (username != priv->authentication_user)
+ {
+ g_free (priv->authentication_user);
+ priv->authentication_user = g_strdup (username);
+ }
+
+ g_debug ("Starting authentication for user %s...", username);
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE, int_length () + string_length (username), &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, username, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_authenticate_as_guest:
+ * @greeter: A #LightDMGreeter
+ *
+ * Starts the authentication procedure for the guest user.
+ **/
+void
+lightdm_greeter_authenticate_as_guest (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = FALSE;
+ priv->authenticate_sequence_number++;
+ priv->in_authentication = TRUE;
+ priv->is_authenticated = FALSE;
+ g_free (priv->authentication_user);
+ priv->authentication_user = NULL;
+
+ g_debug ("Starting authentication for guest account...");
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE_AS_GUEST, int_length (), &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_authenticate_autologin:
+ * @greeter: A #LightDMGreeter
+ *
+ * Starts the authentication procedure for the automatic login user.
+ **/
+void
+lightdm_greeter_authenticate_autologin (LightDMGreeter *greeter)
+{
+ const gchar *user;
+
+ user = lightdm_greeter_get_autologin_user_hint (greeter);
+ if (lightdm_greeter_get_autologin_guest_hint (greeter))
+ lightdm_greeter_authenticate_as_guest (greeter);
+ else if (user)
+ lightdm_greeter_authenticate (greeter, user);
+}
+
+/**
+ * lightdm_greeter_authenticate_remote:
+ * @greeter: A #LightDMGreeter
+ * @session: The name of a remote session
+ * @username: (allow-none): A username of #NULL to prompt for a username.
+ *
+ * Start authentication for a remote session type.
+ **/
+void
+lightdm_greeter_authenticate_remote (LightDMGreeter *greeter, const gchar *session, const gchar *username)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = FALSE;
+ priv->authenticate_sequence_number++;
+ priv->in_authentication = TRUE;
+ priv->is_authenticated = FALSE;
+ g_free (priv->authentication_user);
+ priv->authentication_user = NULL;
+
+ if (username)
+ g_debug ("Starting authentication for remote session %s as user %s...", session, username);
+ else
+ g_debug ("Starting authentication for remote session %s...", session);
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_AUTHENTICATE_REMOTE, int_length () + string_length (session) + string_length (username), &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, priv->authenticate_sequence_number, &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, session, &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, username, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_respond:
+ * @greeter: A #LightDMGreeter
+ * @response: Response to a prompt
+ *
+ * Provide response to a prompt. May be one in a series.
+ **/
+void
+lightdm_greeter_respond (LightDMGreeter *greeter, const gchar *response)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+ g_return_if_fail (response != NULL);
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+ g_return_if_fail (priv->n_responses_waiting > 0);
+
+ priv->n_responses_waiting--;
+ priv->responses_received = g_list_append (priv->responses_received, g_strdup (response));
+
+ if (priv->n_responses_waiting == 0)
+ {
+ guint32 msg_length;
+ GList *iter;
+
+ g_debug ("Providing response to display manager");
+
+ msg_length = int_length ();
+ for (iter = priv->responses_received; iter; iter = iter->next)
+ {
+ msg_length += string_length ((gchar *)iter->data);
+ }
+
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CONTINUE_AUTHENTICATION, msg_length, &offset);
+ write_int (message, MAX_MESSAGE_LENGTH, g_list_length (priv->responses_received), &offset);
+ for (iter = priv->responses_received; iter; iter = iter->next)
+ {
+ write_string (message, MAX_MESSAGE_LENGTH, (gchar *)iter->data, &offset);
+ }
+ write_message (greeter, message, offset);
+
+ g_list_free_full (priv->responses_received, g_free);
+ priv->responses_received = NULL;
+ }
+}
+
+/**
+ * lightdm_greeter_cancel_authentication:
+ * @greeter: A #LightDMGreeter
+ *
+ * Cancel the current user authentication.
+ **/
+void
+lightdm_greeter_cancel_authentication (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ priv->cancelling_authentication = TRUE;
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_CANCEL_AUTHENTICATION, 0, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_get_in_authentication:
+ * @greeter: A #LightDMGreeter
+ *
+ * Checks if the greeter is in the process of authenticating.
+ *
+ * Return value: #TRUE if the greeter is authenticating a user.
+ **/
+gboolean
+lightdm_greeter_get_in_authentication (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ return GET_PRIVATE (greeter)->in_authentication;
+}
+
+/**
+ * lightdm_greeter_get_is_authenticated:
+ * @greeter: A #LightDMGreeter
+ *
+ * Checks if the greeter has successfully authenticated.
+ *
+ * Return value: #TRUE if the greeter is authenticated for login.
+ **/
+gboolean
+lightdm_greeter_get_is_authenticated (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+ return GET_PRIVATE (greeter)->is_authenticated;
+}
+
+/**
+ * lightdm_greeter_get_authentication_user:
+ * @greeter: A #LightDMGreeter
+ *
+ * Get the user that is being authenticated.
+ *
+ * Return value: The username of the authentication user being authenticated or #NULL if no authentication in progress.
+ */
+const gchar *
+lightdm_greeter_get_authentication_user (LightDMGreeter *greeter)
+{
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), NULL);
+ return GET_PRIVATE (greeter)->authentication_user;
+}
+
+/**
+ * lightdm_greeter_set_language:
+ * @greeter: A #LightDMGreeter
+ * @language: The language to use for this user.
+ *
+ * Set the language for the currently authenticated user.
+ **/
+void
+lightdm_greeter_set_language (LightDMGreeter *greeter, const gchar *language)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ gsize offset = 0;
+
+ g_return_if_fail (LIGHTDM_IS_GREETER (greeter));
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_if_fail (priv->connected);
+
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_SET_LANGUAGE, string_length (language), &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, language, &offset);
+ write_message (greeter, message, offset);
+}
+
+/**
+ * lightdm_greeter_start_session_sync:
+ * @greeter: A #LightDMGreeter
+ * @session: (allow-none): The session to log into or #NULL to use the default.
+ * @error: return location for a #GError, or %NULL
+ *
+ * Start a session for the authenticated user.
+ *
+ * Return value: TRUE if the session was started.
+ **/
+gboolean
+lightdm_greeter_start_session_sync (LightDMGreeter *greeter, const gchar *session, GError **error)
+{
+ LightDMGreeterPrivate *priv;
+ guint8 message[MAX_MESSAGE_LENGTH];
+ guint8 *response;
+ gsize response_length, offset = 0;
+ guint32 id, return_code = 1;
+
+ g_return_val_if_fail (LIGHTDM_IS_GREETER (greeter), FALSE);
+
+ priv = GET_PRIVATE (greeter);
+
+ g_return_val_if_fail (priv->connected, FALSE);
+ g_return_val_if_fail (priv->is_authenticated, FALSE);
+
+ if (session)
+ g_debug ("Starting session %s", session);
+ else
+ g_debug ("Starting default session");
+
+ write_header (message, MAX_MESSAGE_LENGTH, GREETER_MESSAGE_START_SESSION, string_length (session), &offset);
+ write_string (message, MAX_MESSAGE_LENGTH, session, &offset);
+ write_message (greeter, message, offset);
+
+ response = read_message (greeter, &response_length, TRUE);
+ if (!response)
+ return FALSE;
+
+ offset = 0;
+ id = read_int (response, response_length, &offset);
+ read_int (response, response_length, &offset);
+ if (id == SERVER_MESSAGE_SESSION_RESULT)
+ return_code = read_int (response, response_length, &offset);
+ else
+ g_warning ("Expected SESSION_RESULT message, got %d", id);
+
+ g_free (response);
+
+ return return_code == 0;
+}
+
+static void
+lightdm_greeter_init (LightDMGreeter *greeter)
+{
+ LightDMGreeterPrivate *priv = GET_PRIVATE (greeter);
+
+ priv->read_buffer = g_malloc (HEADER_SIZE);
+ priv->hints = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+}
+
+static void
+lightdm_greeter_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_greeter_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMGreeter *self;
+
+ self = LIGHTDM_GREETER (object);
+
+ switch (prop_id) {
+ case PROP_DEFAULT_SESSION_HINT:
+ g_value_set_string (value, lightdm_greeter_get_default_session_hint (self));
+ break;
+ case PROP_HIDE_USERS_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_hide_users_hint (self));
+ break;
+ case PROP_SHOW_MANUAL_LOGIN_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_show_manual_login_hint (self));
+ break;
+ case PROP_SHOW_REMOTE_LOGIN_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_show_remote_login_hint (self));
+ break;
+ case PROP_LOCK_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_lock_hint (self));
+ break;
+ case PROP_HAS_GUEST_ACCOUNT_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_has_guest_account_hint (self));
+ break;
+ case PROP_SELECT_USER_HINT:
+ g_value_set_string (value, lightdm_greeter_get_select_user_hint (self));
+ break;
+ case PROP_SELECT_GUEST_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_select_guest_hint (self));
+ break;
+ case PROP_AUTOLOGIN_USER_HINT:
+ g_value_set_string (value, lightdm_greeter_get_autologin_user_hint (self));
+ break;
+ case PROP_AUTOLOGIN_GUEST_HINT:
+ g_value_set_boolean (value, lightdm_greeter_get_autologin_guest_hint (self));
+ break;
+ case PROP_AUTOLOGIN_TIMEOUT_HINT:
+ g_value_set_int (value, lightdm_greeter_get_autologin_timeout_hint (self));
+ break;
+ case PROP_AUTHENTICATION_USER:
+ g_value_set_string (value, lightdm_greeter_get_authentication_user (self));
+ break;
+ case PROP_IN_AUTHENTICATION:
+ g_value_set_boolean (value, lightdm_greeter_get_in_authentication (self));
+ break;
+ case PROP_IS_AUTHENTICATED:
+ g_value_set_boolean (value, lightdm_greeter_get_is_authenticated (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+marshal_VOID__STRING_INT (GClosure *closure,
+ GValue *return_value G_GNUC_UNUSED,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint G_GNUC_UNUSED,
+ gpointer marshal_data)
+{
+ typedef void (*GMarshalFunc_VOID__STRING_INT) (gpointer data1,
+ gpointer arg_1,
+ gint arg_2,
+ gpointer data2);
+ register GMarshalFunc_VOID__STRING_INT callback;
+ register GCClosure *cc = (GCClosure*) closure;
+ register gpointer data1, data2;
+
+ g_return_if_fail (n_param_values == 3);
+
+ if (G_CCLOSURE_SWAP_DATA (closure))
+ {
+ data1 = closure->data;
+ data2 = g_value_peek_pointer (param_values + 0);
+ }
+ else
+ {
+ data1 = g_value_peek_pointer (param_values + 0);
+ data2 = closure->data;
+ }
+ callback = (GMarshalFunc_VOID__STRING_INT) (marshal_data ? marshal_data : cc->callback);
+
+ callback (data1,
+ (param_values + 1)->data[0].v_pointer,
+ (param_values + 2)->data[0].v_int,
+ data2);
+}
+
+static void
+lightdm_greeter_finalize (GObject *object)
+{
+ LightDMGreeter *self = LIGHTDM_GREETER (object);
+ LightDMGreeterPrivate *priv = GET_PRIVATE (self);
+
+ if (priv->to_server_channel)
+ g_io_channel_unref (priv->to_server_channel);
+ if (priv->from_server_channel)
+ g_io_channel_unref (priv->from_server_channel);
+ g_free (priv->authentication_user);
+ g_hash_table_unref (priv->hints);
+
+ G_OBJECT_CLASS (lightdm_greeter_parent_class)->finalize (object);
+}
+
+static void
+lightdm_greeter_class_init (LightDMGreeterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMGreeterPrivate));
+
+ object_class->set_property = lightdm_greeter_set_property;
+ object_class->get_property = lightdm_greeter_get_property;
+ object_class->finalize = lightdm_greeter_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_DEFAULT_SESSION_HINT,
+ g_param_spec_string ("default-session-hint",
+ "default-session-hint",
+ "Default session hint",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_HIDE_USERS_HINT,
+ g_param_spec_boolean ("hide-users-hint",
+ "hide-users-hint",
+ "Hide users hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_MANUAL_LOGIN_HINT,
+ g_param_spec_boolean ("show-manual-login-hint",
+ "show-manual-login-hint",
+ "Show manual login hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_REMOTE_LOGIN_HINT,
+ g_param_spec_boolean ("show-remote-login-hint",
+ "show-remote-login-hint",
+ "Show remote login hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_LOCK_HINT,
+ g_param_spec_boolean ("lock-hint",
+ "lock-hint",
+ "Lock hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_HAS_GUEST_ACCOUNT_HINT,
+ g_param_spec_boolean ("has-guest-account-hint",
+ "has-guest-account-hint",
+ "Has guest account hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SELECT_USER_HINT,
+ g_param_spec_string ("select-user-hint",
+ "select-user-hint",
+ "Select user hint",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_SELECT_GUEST_HINT,
+ g_param_spec_boolean ("select-guest-hint",
+ "select-guest-hint",
+ "Select guest account hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTOLOGIN_USER_HINT,
+ g_param_spec_string ("autologin-user-hint",
+ "autologin-user-hint",
+ "Autologin user hint",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTOLOGIN_GUEST_HINT,
+ g_param_spec_boolean ("autologin-guest-hint",
+ "autologin-guest-hint",
+ "Autologin guest account hint",
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTOLOGIN_TIMEOUT_HINT,
+ g_param_spec_int ("autologin-timeout-hint",
+ "autologin-timeout-hint",
+ "Autologin timeout hint",
+ 0, G_MAXINT, 0,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class,
+ PROP_AUTHENTICATION_USER,
+ g_param_spec_string ("authentication-user",
+ "authentication-user",
+ "The user being authenticated",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_IN_AUTHENTICATION,
+ g_param_spec_boolean ("in-authentication",
+ "in-authentication",
+ "TRUE if a user is being authenticated",
+ FALSE,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_IS_AUTHENTICATED,
+ g_param_spec_boolean ("is-authenticated",
+ "is-authenticated",
+ "TRUE if the selected user is authenticated",
+ FALSE,
+ G_PARAM_READABLE));
+
+ /**
+ * LightDMGreeter::show-prompt:
+ * @greeter: A #LightDMGreeter
+ * @text: Prompt text
+ * @type: Prompt type
+ *
+ * The ::show-prompt signal gets emitted when the greeter should show a
+ * prompt to the user. The given text should be displayed and an input
+ * field for the user to provide a response.
+ *
+ * Call lightdm_greeter_respond() with the resultant input or
+ * lightdm_greeter_cancel_authentication() to abort the authentication.
+ **/
+ signals[SHOW_PROMPT] =
+ g_signal_new ("show-prompt",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, show_prompt),
+ NULL, NULL,
+ marshal_VOID__STRING_INT,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
+
+ /**
+ * LightDMGreeter::show-message:
+ * @greeter: A #LightDMGreeter
+ * @text: Message text
+ * @type: Message type
+ *
+ * The ::show-message signal gets emitted when the greeter
+ * should show a message to the user.
+ **/
+ signals[SHOW_MESSAGE] =
+ g_signal_new ("show-message",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, show_message),
+ NULL, NULL,
+ marshal_VOID__STRING_INT,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
+
+ /**
+ * LightDMGreeter::authentication-complete:
+ * @greeter: A #LightDMGreeter
+ *
+ * The ::authentication-complete signal gets emitted when the greeter
+ * has completed authentication.
+ *
+ * Call lightdm_greeter_get_is_authenticated() to check if the authentication
+ * was successful.
+ **/
+ signals[AUTHENTICATION_COMPLETE] =
+ g_signal_new ("authentication-complete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, authentication_complete),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * LightDMGreeter::autologin-timer-expired:
+ * @greeter: A #LightDMGreeter
+ *
+ * The ::timed-login signal gets emitted when the automatic login timer has expired.
+ * The application should then call lightdm_greeter_login().
+ **/
+ signals[AUTOLOGIN_TIMER_EXPIRED] =
+ g_signal_new ("autologin-timer-expired",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMGreeterClass, autologin_timer_expired),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c
new file mode 100644
index 00000000..9709619e
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/language.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <string.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <stdio.h>
+#include <glib/gi18n.h>
+
+#include "lightdm/language.h"
+
+enum {
+ PROP_0,
+ PROP_CODE,
+ PROP_NAME,
+ PROP_TERRITORY
+};
+
+typedef struct
+{
+ gchar *code;
+ gchar *name;
+ gchar *territory;
+} LightDMLanguagePrivate;
+
+G_DEFINE_TYPE (LightDMLanguage, lightdm_language, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_LANGUAGE, LightDMLanguagePrivate)
+
+static gboolean have_languages = FALSE;
+static GList *languages = NULL;
+
+static void
+update_languages (void)
+{
+ gchar *command = "locale -a";
+ gchar *stdout_text = NULL, *stderr_text = NULL;
+ gint exit_status;
+ gboolean result;
+ GError *error = NULL;
+
+ if (have_languages)
+ return;
+
+ result = g_spawn_command_line_sync (command, &stdout_text, &stderr_text, &exit_status, &error);
+ if (error)
+ {
+ g_warning ("Failed to run '%s': %s", command, error->message);
+ g_clear_error (&error);
+ }
+ else if (exit_status != 0)
+ g_warning ("Failed to get languages, '%s' returned %d", command, exit_status);
+ else if (result)
+ {
+ gchar **tokens;
+ int i;
+
+ tokens = g_strsplit_set (stdout_text, "\n\r", -1);
+ for (i = 0; tokens[i]; i++)
+ {
+ LightDMLanguage *language;
+ gchar *code;
+
+ code = g_strchug (tokens[i]);
+ if (code[0] == '\0')
+ continue;
+
+ /* Ignore the non-interesting languages */
+ if (strcmp (command, "locale -a") == 0 && !g_strrstr (code, ".utf8"))
+ continue;
+
+ language = g_object_new (LIGHTDM_TYPE_LANGUAGE, "code", code, NULL);
+ languages = g_list_append (languages, language);
+ }
+
+ g_strfreev (tokens);
+ }
+
+ g_free (stdout_text);
+ g_free (stderr_text);
+
+ have_languages = TRUE;
+}
+
+static gboolean
+is_utf8 (const gchar *code)
+{
+ return g_strrstr (code, ".utf8") || g_strrstr (code, ".UTF-8");
+}
+
+/* Get a valid locale name that can be passed to setlocale(), so we always can use nl_langinfo() to get language and country names. */
+static gchar *
+get_locale_name (const gchar *code)
+{
+ gchar *locale = NULL, *language;
+ char *at;
+ static gchar **avail_locales;
+ gint i;
+
+ if (is_utf8 (code))
+ return (gchar *) code;
+
+ if ((at = strchr (code, '@')))
+ language = g_strndup (code, at - code);
+ else
+ language = g_strdup (code);
+
+ if (!avail_locales)
+ {
+ gchar *locales;
+ GError *error = NULL;
+
+ if (g_spawn_command_line_sync ("locale -a", &locales, NULL, NULL, &error))
+ {
+ avail_locales = g_strsplit (g_strchomp (locales), "\n", -1);
+ g_free (locales);
+ }
+ else
+ {
+ g_warning ("Failed to run 'locale -a': %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ if (avail_locales)
+ {
+ for (i = 0; avail_locales[i]; i++)
+ {
+ gchar *loc = avail_locales[i];
+ if (!g_strrstr (loc, ".utf8"))
+ continue;
+ if (g_str_has_prefix (loc, language))
+ {
+ locale = g_strdup (loc);
+ break;
+ }
+ }
+ }
+
+ g_free (language);
+
+ return locale;
+}
+
+/**
+ * lightdm_get_language:
+ *
+ * Get the current language.
+ *
+ * Return value: (transfer none): The current language or #NULL if no language.
+ **/
+LightDMLanguage *
+lightdm_get_language (void)
+{
+ const gchar *lang;
+ GList *link;
+
+ lang = g_getenv ("LANG");
+ if (!lang)
+ return NULL;
+
+ for (link = lightdm_get_languages (); link; link = link->next)
+ {
+ LightDMLanguage *language = link->data;
+ if (lightdm_language_matches (language, lang))
+ return language;
+ }
+
+ return NULL;
+}
+
+/**
+ * lightdm_get_languages:
+ *
+ * Get a list of languages to present to the user.
+ *
+ * Return value: (element-type LightDMLanguage) (transfer none): A list of #LightDMLanguage that should be presented to the user.
+ **/
+GList *
+lightdm_get_languages (void)
+{
+ update_languages ();
+ return languages;
+}
+
+/**
+ * lightdm_language_get_code:
+ * @language: A #LightDMLanguage
+ *
+ * Get the code of a language.
+ *
+ * Return value: The code of the language
+ **/
+const gchar *
+lightdm_language_get_code (LightDMLanguage *language)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL);
+ return GET_PRIVATE (language)->code;
+}
+
+/**
+ * lightdm_language_get_name:
+ * @language: A #LightDMLanguage
+ *
+ * Get the name of a language.
+ *
+ * Return value: The name of the language
+ **/
+const gchar *
+lightdm_language_get_name (LightDMLanguage *language)
+{
+ LightDMLanguagePrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL);
+
+ priv = GET_PRIVATE (language);
+
+ if (!priv->name)
+ {
+ gchar *locale = get_locale_name (priv->code);
+ if (locale)
+ {
+ gchar *current = setlocale (LC_ALL, NULL);
+ setlocale (LC_IDENTIFICATION, locale);
+ setlocale (LC_MESSAGES, "");
+
+ gchar *language_en = nl_langinfo (_NL_IDENTIFICATION_LANGUAGE);
+ if (language_en && strlen (language_en) > 0)
+ priv->name = g_strdup (dgettext ("iso_639_3", language_en));
+
+ setlocale (LC_ALL, current);
+ }
+ if (!priv->name)
+ {
+ gchar **tokens = g_strsplit_set (priv->code, "_.@", 2);
+ priv->name = g_strdup (tokens[0]);
+ g_strfreev (tokens);
+ }
+ }
+
+ return priv->name;
+}
+
+/**
+ * lightdm_language_get_territory:
+ * @language: A #LightDMLanguage
+ *
+ * Get the territory the language is used in.
+ *
+ * Return value: The territory the language is used in.
+ **/
+const gchar *
+lightdm_language_get_territory (LightDMLanguage *language)
+{
+ LightDMLanguagePrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), NULL);
+
+ priv = GET_PRIVATE (language);
+
+ if (!priv->territory && strchr (priv->code, '_'))
+ {
+ gchar *locale = get_locale_name (priv->code);
+ if (locale)
+ {
+ gchar *current = setlocale (LC_ALL, NULL);
+ setlocale (LC_IDENTIFICATION, locale);
+ setlocale (LC_MESSAGES, "");
+
+ gchar *country_en = nl_langinfo (_NL_IDENTIFICATION_TERRITORY);
+ if (country_en && strlen (country_en) > 0 && g_strcmp0 (country_en, "ISO") != 0)
+ priv->territory = g_strdup (dgettext ("iso_3166", country_en));
+
+ setlocale (LC_ALL, current);
+ }
+ if (!priv->territory)
+ {
+ gchar **tokens = g_strsplit_set (priv->code, "_.@", 3);
+ priv->territory = g_strdup (tokens[1]);
+ g_strfreev (tokens);
+ }
+ }
+
+ return priv->territory;
+}
+
+/**
+ * lightdm_language_matches:
+ * @language: A #LightDMLanguage
+ * @code: A language code
+ *
+ * Check if a language code matches this language.
+ *
+ * Return value: #TRUE if the code matches this language.
+ **/
+gboolean
+lightdm_language_matches (LightDMLanguage *language, const gchar *code)
+{
+ LightDMLanguagePrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_LANGUAGE (language), FALSE);
+ g_return_val_if_fail (code != NULL, FALSE);
+
+ priv = GET_PRIVATE (language);
+
+ /* Handle the fact the UTF-8 is specified both as '.utf8' and '.UTF-8' */
+ if (is_utf8 (priv->code) && is_utf8 (code))
+ {
+ /* Match the characters before the '.' */
+ int i;
+ for (i = 0; priv->code[i] && code[i] && priv->code[i] == code[i] && code[i] != '.' ; i++);
+ return priv->code[i] == '.' && code[i] == '.';
+ }
+
+ return g_str_equal (priv->code, code);
+}
+
+static void
+lightdm_language_init (LightDMLanguage *language)
+{
+}
+
+static void
+lightdm_language_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLanguage *self = LIGHTDM_LANGUAGE (object);
+ LightDMLanguagePrivate *priv = GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_CODE:
+ g_free (priv->name);
+ priv->code = g_strdup (g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_language_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLanguage *self;
+
+ self = LIGHTDM_LANGUAGE (object);
+
+ switch (prop_id) {
+ case PROP_CODE:
+ g_value_set_string (value, lightdm_language_get_code (self));
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, lightdm_language_get_name (self));
+ break;
+ case PROP_TERRITORY:
+ g_value_set_string (value, lightdm_language_get_territory (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_language_class_init (LightDMLanguageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMLanguagePrivate));
+
+ object_class->set_property = lightdm_language_set_property;
+ object_class->get_property = lightdm_language_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_CODE,
+ g_param_spec_string ("code",
+ "code",
+ "Language code",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Name of the language",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_TERRITORY,
+ g_param_spec_string ("territory",
+ "territory",
+ "Territory the language is from",
+ NULL,
+ G_PARAM_READABLE));
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c
new file mode 100644
index 00000000..d5057450
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/layout.c
@@ -0,0 +1,344 @@
+/* -*- Mode: C; indent-tabs-mode:nil; tab-width:4 -*-
+ *
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <libxklavier/xklavier.h>
+
+#include "lightdm/layout.h"
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_SHORT_DESCRIPTION,
+ PROP_DESCRIPTION
+};
+
+typedef struct
+{
+ gchar *name;
+ gchar *short_description;
+ gchar *description;
+} LightDMLayoutPrivate;
+
+G_DEFINE_TYPE (LightDMLayout, lightdm_layout, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_LAYOUT, LightDMLayoutPrivate)
+
+static gboolean have_layouts = FALSE;
+static Display *display = NULL;
+static XklEngine *xkl_engine = NULL;
+static XklConfigRec *xkl_config = NULL;
+static GList *layouts = NULL;
+static LightDMLayout *default_layout = NULL;
+
+static gchar *
+make_layout_string (const gchar *layout, const gchar *variant)
+{
+ if (!layout || layout[0] == 0)
+ return NULL;
+ else if (!variant || variant[0] == 0)
+ return g_strdup (layout);
+ else
+ return g_strdup_printf ("%s\t%s", layout, variant);
+}
+
+static void
+parse_layout_string (const gchar *name, gchar **layout, gchar **variant)
+{
+ gchar **split;
+
+ *layout = NULL;
+ *variant = NULL;
+
+ if (!name)
+ return;
+
+ split = g_strsplit (name, "\t", 2);
+ if (split[0])
+ {
+ *layout = g_strdup (split[0]);
+ if (split[1])
+ *variant = g_strdup (split[1]);
+ }
+ g_strfreev (split);
+}
+
+static void
+variant_cb (XklConfigRegistry *config,
+ const XklConfigItem *item,
+ gpointer data)
+{
+ LightDMLayout *layout;
+ gchar *full_name;
+
+ full_name = make_layout_string (data, item->name);
+
+ layout = g_object_new (LIGHTDM_TYPE_LAYOUT, "name", full_name, "short-description", item->short_description, "description", item->description, NULL);
+ layouts = g_list_append (layouts, layout);
+
+ g_free (full_name);
+}
+
+static void
+layout_cb (XklConfigRegistry *config,
+ const XklConfigItem *item,
+ gpointer data)
+{
+ LightDMLayout *layout;
+
+ layout = g_object_new (LIGHTDM_TYPE_LAYOUT, "name", item->name, "short-description", item->short_description, "description", item->description, NULL);
+ layouts = g_list_append (layouts, layout);
+
+ xkl_config_registry_foreach_layout_variant (config, item->name, variant_cb, (gpointer) item->name);
+}
+
+/**
+ * lightdm_get_layouts:
+ *
+ * Get a list of keyboard layouts to present to the user.
+ *
+ * Return value: (element-type LightDMLayout) (transfer none): A list of #LightDMLayout that should be presented to the user.
+ **/
+GList *
+lightdm_get_layouts (void)
+{
+ XklConfigRegistry *registry;
+
+ if (have_layouts)
+ return layouts;
+
+ display = XOpenDisplay (NULL);
+ xkl_engine = xkl_engine_get_instance (display);
+ xkl_config = xkl_config_rec_new ();
+ if (!xkl_config_rec_get_from_server (xkl_config, xkl_engine))
+ g_warning ("Failed to get Xkl configuration from server");
+
+ registry = xkl_config_registry_get_instance (xkl_engine);
+ xkl_config_registry_load (registry, FALSE);
+ xkl_config_registry_foreach_layout (registry, layout_cb, NULL);
+ g_object_unref (registry);
+
+ have_layouts = TRUE;
+
+ return layouts;
+}
+
+/**
+ * lightdm_set_layout:
+ * @layout: The layout to use
+ *
+ * Set the layout for this session.
+ **/
+void
+lightdm_set_layout (LightDMLayout *dmlayout)
+{
+ XklConfigRec *config;
+ gchar *layout, *variant;
+
+ g_return_if_fail (dmlayout != NULL);
+
+ g_debug ("Setting keyboard layout to '%s'", lightdm_layout_get_name (dmlayout));
+
+ parse_layout_string (lightdm_layout_get_name (dmlayout), &layout, &variant);
+
+ config = xkl_config_rec_new ();
+ config->layouts = g_malloc (sizeof (gchar *) * 2);
+ config->variants = g_malloc (sizeof (gchar *) * 2);
+ config->model = g_strdup (xkl_config->model);
+ config->layouts[0] = layout;
+ config->layouts[1] = NULL;
+ config->variants[0] = variant;
+ config->variants[1] = NULL;
+ if (!xkl_config_rec_activate (config, xkl_engine))
+ g_warning ("Failed to activate XKL config");
+ g_object_unref (config);
+}
+
+/**
+ * lightdm_get_layout:
+ *
+ * Get the current keyboard layout.
+ *
+ * Return value: (transfer none): The currently active layout for this user.
+ **/
+LightDMLayout *
+lightdm_get_layout (void)
+{
+ lightdm_get_layouts ();
+
+ if (layouts && xkl_config && !default_layout)
+ {
+ gchar *full_name;
+ GList *item;
+
+ full_name = make_layout_string (xkl_config->layouts ? xkl_config->layouts[0] : NULL,
+ xkl_config->variants ? xkl_config->variants[0] : NULL);
+
+ for (item = layouts; item; item = item->next)
+ {
+ LightDMLayout *iter_layout = (LightDMLayout *) item->data;
+ if (g_strcmp0 (lightdm_layout_get_name (iter_layout), full_name) == 0)
+ {
+ default_layout = iter_layout;
+ break;
+ }
+ }
+
+ g_free (full_name);
+ }
+
+ return default_layout;
+}
+
+/**
+ * lightdm_layout_get_name:
+ * @layout: A #LightDMLayout
+ *
+ * Get the name of a layout.
+ *
+ * Return value: The name of the layout
+ **/
+const gchar *
+lightdm_layout_get_name (LightDMLayout *layout)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL);
+ return GET_PRIVATE (layout)->name;
+}
+
+/**
+ * lightdm_layout_get_short_description:
+ * @layout: A #LightDMLayout
+ *
+ * Get the short description of a layout.
+ *
+ * Return value: A short description of the layout
+ **/
+const gchar *
+lightdm_layout_get_short_description (LightDMLayout *layout)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL);
+ return GET_PRIVATE (layout)->short_description;
+}
+
+/**
+ * lightdm_layout_get_description:
+ * @layout: A #LightDMLayout
+ *
+ * Get the long description of a layout.
+ *
+ * Return value: A long description of the layout
+ **/
+const gchar *
+lightdm_layout_get_description (LightDMLayout *layout)
+{
+ g_return_val_if_fail (LIGHTDM_IS_LAYOUT (layout), NULL);
+ return GET_PRIVATE (layout)->description;
+}
+
+static void
+lightdm_layout_init (LightDMLayout *layout)
+{
+}
+
+static void
+lightdm_layout_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLayout *self = LIGHTDM_LAYOUT (object);
+ LightDMLayoutPrivate *priv = GET_PRIVATE (self);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_free (priv->name);
+ priv->name = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_SHORT_DESCRIPTION:
+ g_free (priv->short_description);
+ priv->short_description = g_strdup (g_value_get_string (value));
+ break;
+ case PROP_DESCRIPTION:
+ g_free (priv->description);
+ priv->description = g_strdup (g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_layout_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMLayout *self;
+
+ self = LIGHTDM_LAYOUT (object);
+
+ switch (prop_id) {
+ case PROP_NAME:
+ g_value_set_string (value, lightdm_layout_get_name (self));
+ break;
+ case PROP_SHORT_DESCRIPTION:
+ g_value_set_string (value, lightdm_layout_get_short_description (self));
+ break;
+ case PROP_DESCRIPTION:
+ g_value_set_string (value, lightdm_layout_get_description (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_layout_class_init (LightDMLayoutClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMLayoutPrivate));
+
+ object_class->set_property = lightdm_layout_set_property;
+ object_class->get_property = lightdm_layout_get_property;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Name of the layout",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_SHORT_DESCRIPTION,
+ g_param_spec_string ("short-description",
+ "short-description",
+ "Short description of the layout",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (object_class,
+ PROP_DESCRIPTION,
+ g_param_spec_string ("description",
+ "description",
+ "Long description of the layout",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c
new file mode 100644
index 00000000..f7830ff2
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/power.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2010-2011 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <gio/gio.h>
+
+#include "lightdm/power.h"
+
+static GDBusProxy *upower_proxy = NULL;
+static GDBusProxy *ck_proxy = NULL;
+
+static gboolean
+upower_call_function (const gchar *function, gboolean default_result, GError **error)
+{
+ GVariant *result;
+ gboolean function_result = FALSE;
+
+ if (!upower_proxy)
+ {
+ upower_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.UPower",
+ "/org/freedesktop/UPower",
+ "org.freedesktop.UPower",
+ NULL,
+ error);
+ if (!upower_proxy)
+ return FALSE;
+ }
+
+ result = g_dbus_proxy_call_sync (upower_proxy,
+ function,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ error);
+ if (!result)
+ return default_result;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(b)")))
+ g_variant_get (result, "(b)", &function_result);
+
+ g_variant_unref (result);
+ return function_result;
+}
+
+/**
+ * lightdm_get_can_suspend:
+ *
+ * Checks if authorized to do a system suspend.
+ *
+ * Return value: #TRUE if can suspend the system
+ **/
+gboolean
+lightdm_get_can_suspend (void)
+{
+ return upower_call_function ("SuspendAllowed", FALSE, NULL);
+}
+
+/**
+ * lightdm_suspend:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system suspend.
+ *
+ * Return value: #TRUE if suspend initiated.
+ **/
+gboolean
+lightdm_suspend (GError **error)
+{
+ return upower_call_function ("Suspend", TRUE, error);
+}
+
+/**
+ * lightdm_get_can_hibernate:
+ *
+ * Checks if is authorized to do a system hibernate.
+ *
+ * Return value: #TRUE if can hibernate the system
+ **/
+gboolean
+lightdm_get_can_hibernate (void)
+{
+ return upower_call_function ("HibernateAllowed", FALSE, NULL);
+}
+
+/**
+ * lightdm_hibernate:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system hibernate.
+ *
+ * Return value: #TRUE if hibernate initiated.
+ **/
+gboolean
+lightdm_hibernate (GError **error)
+{
+ return upower_call_function ("Hibernate", TRUE, error);
+}
+
+static gboolean
+ck_call_function (const gchar *function, gboolean default_result, GError **error)
+{
+ GVariant *result;
+ gboolean function_result = FALSE;
+
+ if (!ck_proxy)
+ {
+ ck_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.ConsoleKit",
+ "/org/freedesktop/ConsoleKit/Manager",
+ "org.freedesktop.ConsoleKit.Manager",
+ NULL,
+ error);
+ if (!ck_proxy)
+ return FALSE;
+ }
+
+ result = g_dbus_proxy_call_sync (ck_proxy,
+ function,
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ error);
+
+ if (!result)
+ return default_result;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(b)")))
+ g_variant_get (result, "(b)", &function_result);
+
+ g_variant_unref (result);
+ return function_result;
+}
+
+/**
+ * lightdm_get_can_restart:
+ *
+ * Checks if is authorized to do a system restart.
+ *
+ * Return value: #TRUE if can restart the system
+ **/
+gboolean
+lightdm_get_can_restart (void)
+{
+ return ck_call_function ("CanRestart", FALSE, NULL);
+}
+
+/**
+ * lightdm_restart:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system restart.
+ *
+ * Return value: #TRUE if restart initiated.
+ **/
+gboolean
+lightdm_restart (GError **error)
+{
+ return ck_call_function ("Restart", TRUE, error);
+}
+
+/**
+ * lightdm_get_can_shutdown:
+ *
+ * Checks if is authorized to do a system shutdown.
+ *
+ * Return value: #TRUE if can shutdown the system
+ **/
+gboolean
+lightdm_get_can_shutdown (void)
+{
+ return ck_call_function ("CanStop", FALSE, NULL);
+}
+
+/**
+ * lightdm_shutdown:
+ * @error: return location for a #GError, or %NULL
+ *
+ * Triggers a system shutdown.
+ *
+ * Return value: #TRUE if shutdown initiated.
+ **/
+gboolean
+lightdm_shutdown (GError **error)
+{
+ return ck_call_function ("Stop", TRUE, error);
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c
new file mode 100644
index 00000000..2df3a1af
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/session.c
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <string.h>
+#include <gio/gdesktopappinfo.h>
+
+#include "lightdm/session.h"
+
+enum {
+ PROP_0,
+ PROP_KEY,
+ PROP_NAME,
+ PROP_COMMENT
+};
+
+typedef struct
+{
+ gchar *key;
+ gchar *name;
+ gchar *comment;
+} LightDMSessionPrivate;
+
+G_DEFINE_TYPE (LightDMSession, lightdm_session, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_SESSION, LightDMSessionPrivate)
+
+static gboolean have_sessions = FALSE;
+static GList *local_sessions = NULL;
+static GList *remote_sessions = NULL;
+
+static gint
+compare_session (gconstpointer a, gconstpointer b)
+{
+ LightDMSessionPrivate *priv_a = GET_PRIVATE (a);
+ LightDMSessionPrivate *priv_b = GET_PRIVATE (b);
+ return strcmp (priv_a->name, priv_b->name);
+}
+
+static LightDMSession *
+load_session (GKeyFile *key_file, const gchar *key)
+{
+ gchar *domain, *name;
+ LightDMSession *session;
+ LightDMSessionPrivate *priv;
+ gchar *try_exec;
+
+ if (g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) ||
+ g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL))
+ return NULL;
+
+#ifdef G_KEY_FILE_DESKTOP_KEY_GETTEXT_DOMAIN
+ domain = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_GETTEXT_DOMAIN, NULL);
+#else
+ domain = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL);
+#endif
+ name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, domain, NULL);
+ if (!name)
+ {
+ g_warning ("Ignoring session without name");
+ g_free (domain);
+ return NULL;
+ }
+
+ try_exec = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, domain, NULL);
+ if (try_exec)
+ {
+ gchar *full_path;
+
+ full_path = g_find_program_in_path (try_exec);
+ g_free (try_exec);
+
+ if (!full_path)
+ {
+ g_free (name);
+ g_free (domain);
+ return NULL;
+ }
+ g_free (full_path);
+ }
+
+ session = g_object_new (LIGHTDM_TYPE_SESSION, NULL);
+ priv = GET_PRIVATE (session);
+
+ g_free (priv->key);
+ priv->key = g_strdup (key);
+
+ g_free (priv->name);
+ priv->name = name;
+
+ g_free (priv->comment);
+ priv->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, domain, NULL);
+ if (!priv->comment)
+ priv->comment = g_strdup ("");
+
+ g_free (domain);
+
+ return session;
+}
+
+static GList *
+load_sessions (const gchar *sessions_dir)
+{
+ GDir *directory;
+ GList *sessions = NULL;
+ GError *error = NULL;
+
+ directory = g_dir_open (sessions_dir, 0, &error);
+ if (error)
+ g_warning ("Failed to open sessions directory: %s", error->message);
+ g_clear_error (&error);
+ if (!directory)
+ return NULL;
+
+ while (TRUE)
+ {
+ const gchar *filename;
+ gchar *path;
+ GKeyFile *key_file;
+ gboolean result;
+
+ filename = g_dir_read_name (directory);
+ if (filename == NULL)
+ break;
+
+ if (!g_str_has_suffix (filename, ".desktop"))
+ continue;
+
+ path = g_build_filename (sessions_dir, filename, NULL);
+
+ key_file = g_key_file_new ();
+ result = g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, &error);
+ if (error)
+ g_warning ("Failed to load session file %s: %s:", path, error->message);
+ g_clear_error (&error);
+
+ if (result)
+ {
+ gchar *key;
+ LightDMSession *session;
+
+ key = g_strndup (filename, strlen (filename) - strlen (".desktop"));
+ session = load_session (key_file, key);
+ if (session)
+ {
+ g_debug ("Loaded session %s (%s, %s)", path, GET_PRIVATE (session)->name, GET_PRIVATE (session)->comment);
+ sessions = g_list_insert_sorted (sessions, session, compare_session);
+ }
+ else
+ g_debug ("Ignoring session %s", path);
+ g_free (key);
+ }
+
+ g_free (path);
+ g_key_file_free (key_file);
+ }
+
+ g_dir_close (directory);
+
+ return sessions;
+}
+
+static void
+update_sessions (void)
+{
+ GKeyFile *config_key_file = NULL;
+ gchar *config_path = NULL;
+ gchar *xsessions_dir;
+ gchar *remote_sessions_dir;
+ gboolean result;
+ GError *error = NULL;
+
+ if (have_sessions)
+ return;
+
+ xsessions_dir = g_strdup (XSESSIONS_DIR);
+ remote_sessions_dir = g_strdup (REMOTE_SESSIONS_DIR);
+
+ /* Use session directory from configuration */
+ /* FIXME: This should be sent in the greeter connection */
+ config_path = g_build_filename (CONFIG_DIR, "lightdm.conf", NULL);
+ config_key_file = g_key_file_new ();
+ result = g_key_file_load_from_file (config_key_file, config_path, G_KEY_FILE_NONE, &error);
+ if (error)
+ g_warning ("Failed to open configuration file: %s", error->message);
+ g_clear_error (&error);
+ if (result)
+ {
+ gchar *value;
+
+ value = g_key_file_get_string (config_key_file, "LightDM", "xsessions-directory", NULL);
+ if (value)
+ {
+ g_free (xsessions_dir);
+ xsessions_dir = value;
+ }
+
+ value = g_key_file_get_string (config_key_file, "LightDM", "remote-sessions-directory", NULL);
+ if (value)
+ {
+ g_free (remote_sessions_dir);
+ remote_sessions_dir = value;
+ }
+ }
+ g_key_file_free (config_key_file);
+ g_free (config_path);
+
+ local_sessions = load_sessions (xsessions_dir);
+ remote_sessions = load_sessions (remote_sessions_dir);
+
+ g_free (xsessions_dir);
+ g_free (remote_sessions_dir);
+
+ have_sessions = TRUE;
+}
+
+/**
+ * lightdm_get_sessions:
+ *
+ * Get the available sessions.
+ *
+ * Return value: (element-type LightDMSession) (transfer none): A list of #LightDMSession
+ **/
+GList *
+lightdm_get_sessions (void)
+{
+ update_sessions ();
+ return local_sessions;
+}
+
+/**
+ * lightdm_get_remote_sessions:
+ *
+ * Get the available remote sessions.
+ *
+ * Return value: (element-type LightDMSession) (transfer none): A list of #LightDMSession
+ **/
+GList *
+lightdm_get_remote_sessions (void)
+{
+ update_sessions ();
+ return remote_sessions;
+}
+
+/**
+ * lightdm_session_get_key:
+ * @session: A #LightDMSession
+ *
+ * Get the key for a session
+ *
+ * Return value: The session key
+ **/
+const gchar *
+lightdm_session_get_key (LightDMSession *session)
+{
+ g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL);
+ return GET_PRIVATE (session)->key;
+}
+
+/**
+ * lightdm_session_get_name:
+ * @session: A #LightDMSession
+ *
+ * Get the name for a session
+ *
+ * Return value: The session name
+ **/
+const gchar *
+lightdm_session_get_name (LightDMSession *session)
+{
+ g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL);
+ return GET_PRIVATE (session)->name;
+}
+
+/**
+ * lightdm_session_get_comment:
+ * @session: A #LightDMSession
+ *
+ * Get the comment for a session
+ *
+ * Return value: The session comment
+ **/
+const gchar *
+lightdm_session_get_comment (LightDMSession *session)
+{
+ g_return_val_if_fail (LIGHTDM_IS_SESSION (session), NULL);
+ return GET_PRIVATE (session)->comment;
+}
+
+static void
+lightdm_session_init (LightDMSession *session)
+{
+}
+
+static void
+lightdm_session_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_session_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMSession *self;
+
+ self = LIGHTDM_SESSION (object);
+
+ switch (prop_id) {
+ case PROP_KEY:
+ g_value_set_string (value, lightdm_session_get_key (self));
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, lightdm_session_get_name (self));
+ break;
+ case PROP_COMMENT:
+ g_value_set_string (value, lightdm_session_get_comment (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_session_finalize (GObject *object)
+{
+ LightDMSession *self = LIGHTDM_SESSION (object);
+ LightDMSessionPrivate *priv = GET_PRIVATE (self);
+
+ g_free (priv->key);
+ g_free (priv->name);
+ g_free (priv->comment);
+}
+
+static void
+lightdm_session_class_init (LightDMSessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMSessionPrivate));
+
+ object_class->set_property = lightdm_session_set_property;
+ object_class->get_property = lightdm_session_get_property;
+ object_class->finalize = lightdm_session_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_KEY,
+ g_param_spec_string ("key",
+ "key",
+ "Session key",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Session name",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ PROP_COMMENT,
+ g_param_spec_string ("comment",
+ "comment",
+ "Session comment",
+ NULL,
+ G_PARAM_READABLE));
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c
new file mode 100644
index 00000000..77f8a182
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/system.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010-2011 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include <sys/utsname.h>
+
+#include "lightdm/system.h"
+
+static gchar *hostname = NULL;
+
+/**
+ * lightdm_get_hostname:
+ *
+ * Return value: The name of the host we are running on.
+ **/
+const gchar *
+lightdm_get_hostname (void)
+{
+ if (!hostname)
+ {
+ struct utsname info;
+ uname (&info);
+ hostname = g_strdup (info.nodename);
+ }
+
+ return hostname;
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c
new file mode 100644
index 00000000..8df19079
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/liblightdm-gobject-1.5.0/user.c
@@ -0,0 +1,1655 @@
+/* -*- Mode: C; indent-tabs-mode:nil; tab-width:4 -*-
+ *
+ * Copyright (C) 2010 Robert Ancell.
+ * Author: Robert Ancell <robert.ancell@canonical.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2 or version 3 of the License.
+ * See http://www.gnu.org/copyleft/lgpl.html the full text of the license.
+ */
+
+/*
+ * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
+ * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
+ * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
+ * a choice of LGPL license versions is made available with the language indicating
+ * that LGPLv2 or any later version may be used, or where a choice of which version
+ * of the LGPL is applied is otherwise unspecified.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/utsname.h>
+#include <pwd.h>
+#include <gio/gio.h>
+
+#include "lightdm/user.h"
+
+enum
+{
+ LIST_PROP_0,
+ LIST_PROP_NUM_USERS,
+ LIST_PROP_USERS,
+};
+
+enum
+{
+ USER_PROP_0,
+ USER_PROP_NAME,
+ USER_PROP_REAL_NAME,
+ USER_PROP_DISPLAY_NAME,
+ USER_PROP_HOME_DIRECTORY,
+ USER_PROP_IMAGE,
+ USER_PROP_BACKGROUND,
+ USER_PROP_LANGUAGE,
+ USER_PROP_LAYOUT,
+ USER_PROP_LAYOUTS,
+ USER_PROP_SESSION,
+ USER_PROP_LOGGED_IN,
+ USER_PROP_HAS_MESSAGES
+};
+
+enum
+{
+ USER_ADDED,
+ USER_CHANGED,
+ USER_REMOVED,
+ LAST_LIST_SIGNAL
+};
+static guint list_signals[LAST_LIST_SIGNAL] = { 0 };
+
+enum
+{
+ CHANGED,
+ LAST_USER_SIGNAL
+};
+static guint user_signals[LAST_USER_SIGNAL] = { 0 };
+
+typedef struct
+{
+ /* Connection to AccountsService */
+ GDBusProxy *accounts_service_proxy;
+ GList *user_account_objects;
+
+ /* Connection to DisplayManager */
+ GDBusProxy *display_manager_proxy;
+
+ /* File monitor for password file */
+ GFileMonitor *passwd_monitor;
+
+ /* TRUE if have scanned users */
+ gboolean have_users;
+
+ /* List of users */
+ GList *users;
+
+ /* List of sessions */
+ GList *sessions;
+} LightDMUserListPrivate;
+
+typedef struct
+{
+ GDBusProxy *proxy;
+ LightDMUser *user;
+} UserAccountObject;
+
+typedef struct
+{
+ LightDMUserList *user_list;
+
+ gchar *name;
+ gchar *real_name;
+ gchar *home_directory;
+ gchar *image;
+ gchar *background;
+ gboolean has_messages;
+
+ GKeyFile *dmrc_file;
+ gchar *language;
+ gchar **layouts;
+ gchar *session;
+} LightDMUserPrivate;
+
+typedef struct
+{
+ GObject parent_instance;
+ gchar *path;
+ gchar *username;
+} Session;
+
+typedef struct
+{
+ GObjectClass parent_class;
+} SessionClass;
+
+G_DEFINE_TYPE (LightDMUserList, lightdm_user_list, G_TYPE_OBJECT);
+G_DEFINE_TYPE (LightDMUser, lightdm_user, G_TYPE_OBJECT);
+#define SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), session_get_type (), Session))
+GType session_get_type (void);
+G_DEFINE_TYPE (Session, session, G_TYPE_OBJECT);
+
+#define GET_LIST_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER_LIST, LightDMUserListPrivate)
+#define GET_USER_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE ((obj), LIGHTDM_TYPE_USER, LightDMUserPrivate)
+
+#define PASSWD_FILE "/etc/passwd"
+#define USER_CONFIG_FILE "/etc/lightdm/users.conf"
+
+static LightDMUserList *singleton = NULL;
+
+/**
+ * lightdm_user_list_get_instance:
+ *
+ * Get the user list.
+ *
+ * Return value: (transfer none): the #LightDMUserList
+ **/
+LightDMUserList *
+lightdm_user_list_get_instance (void)
+{
+ if (!singleton)
+ singleton = g_object_new (LIGHTDM_TYPE_USER_LIST, NULL);
+ return singleton;
+}
+
+static LightDMUser *
+get_user_by_name (LightDMUserList *user_list, const gchar *username)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GList *link;
+
+ for (link = priv->users; link; link = link->next)
+ {
+ LightDMUser *user = link->data;
+ if (strcmp (lightdm_user_get_name (user), username) == 0)
+ return user;
+ }
+
+ return NULL;
+}
+
+static gint
+compare_user (gconstpointer a, gconstpointer b)
+{
+ LightDMUser *user_a = (LightDMUser *) a, *user_b = (LightDMUser *) b;
+ return strcmp (lightdm_user_get_display_name (user_a), lightdm_user_get_display_name (user_b));
+}
+
+static gboolean
+update_passwd_user (LightDMUser *user, const gchar *real_name, const gchar *home_directory, const gchar *image)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+
+ if (g_strcmp0 (lightdm_user_get_real_name (user), real_name) == 0 &&
+ g_strcmp0 (lightdm_user_get_home_directory (user), home_directory) == 0 &&
+ g_strcmp0 (lightdm_user_get_image (user), image) == 0)
+ return FALSE;
+
+ g_free (priv->real_name);
+ priv->real_name = g_strdup (real_name);
+ g_free (priv->home_directory);
+ priv->home_directory = g_strdup (home_directory);
+ g_free (priv->image);
+ priv->image = g_strdup (image);
+
+ return TRUE;
+}
+
+static void
+user_changed_cb (LightDMUser *user, LightDMUserList *user_list)
+{
+ g_signal_emit (user_list, list_signals[USER_CHANGED], 0, user);
+}
+
+static void
+load_passwd_file (LightDMUserList *user_list, gboolean emit_add_signal)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GKeyFile *config;
+ gchar *value;
+ gint minimum_uid;
+ gchar **hidden_users, **hidden_shells;
+ GList *users = NULL, *old_users, *new_users = NULL, *changed_users = NULL, *link;
+ GError *error = NULL;
+
+ g_debug ("Loading user config from %s", USER_CONFIG_FILE);
+
+ config = g_key_file_new ();
+ g_key_file_load_from_file (config, USER_CONFIG_FILE, G_KEY_FILE_NONE, &error);
+ if (error && !g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
+ g_warning ("Failed to load configuration from %s: %s", USER_CONFIG_FILE, error->message); // FIXME: Don't make warning on no file, just info
+ g_clear_error (&error);
+
+ if (g_key_file_has_key (config, "UserList", "minimum-uid", NULL))
+ minimum_uid = g_key_file_get_integer (config, "UserList", "minimum-uid", NULL);
+ else
+ minimum_uid = 500;
+
+ value = g_key_file_get_string (config, "UserList", "hidden-users", NULL);
+ if (!value)
+ value = g_strdup ("nobody nobody4 noaccess");
+ hidden_users = g_strsplit (value, " ", -1);
+ g_free (value);
+
+ value = g_key_file_get_string (config, "UserList", "hidden-shells", NULL);
+ if (!value)
+ value = g_strdup ("/bin/false /usr/sbin/nologin");
+ hidden_shells = g_strsplit (value, " ", -1);
+ g_free (value);
+
+ g_key_file_free (config);
+
+ setpwent ();
+
+ while (TRUE)
+ {
+ struct passwd *entry;
+ LightDMUser *user;
+ LightDMUserPrivate *user_priv;
+ char **tokens;
+ gchar *real_name, *image;
+ int i;
+
+ errno = 0;
+ entry = getpwent ();
+ if (!entry)
+ break;
+
+ /* Ignore system users */
+ if (entry->pw_uid < minimum_uid)
+ continue;
+
+ /* Ignore users disabled by shell */
+ if (entry->pw_shell)
+ {
+ for (i = 0; hidden_shells[i] && strcmp (entry->pw_shell, hidden_shells[i]) != 0; i++);
+ if (hidden_shells[i])
+ continue;
+ }
+
+ /* Ignore certain users */
+ for (i = 0; hidden_users[i] && strcmp (entry->pw_name, hidden_users[i]) != 0; i++);
+ if (hidden_users[i])
+ continue;
+
+ tokens = g_strsplit (entry->pw_gecos, ",", -1);
+ if (tokens[0] != NULL && tokens[0][0] != '\0')
+ real_name = g_strdup (tokens[0]);
+ else
+ real_name = g_strdup ("");
+ g_strfreev (tokens);
+
+ image = g_build_filename (entry->pw_dir, ".face", NULL);
+ if (!g_file_test (image, G_FILE_TEST_EXISTS))
+ {
+ g_free (image);
+ image = g_build_filename (entry->pw_dir, ".face.icon", NULL);
+ if (!g_file_test (image, G_FILE_TEST_EXISTS))
+ {
+ g_free (image);
+ image = NULL;
+ }
+ }
+
+ user = g_object_new (LIGHTDM_TYPE_USER, NULL);
+ user_priv = GET_USER_PRIVATE (user);
+ user_priv->user_list = user_list;
+ g_free (user_priv->name);
+ user_priv->name = g_strdup (entry->pw_name);
+ g_free (user_priv->real_name);
+ user_priv->real_name = real_name;
+ g_free (user_priv->home_directory);
+ user_priv->home_directory = g_strdup (entry->pw_dir);
+ g_free (user_priv->image);
+ user_priv->image = image;
+
+ /* Update existing users if have them */
+ for (link = priv->users; link; link = link->next)
+ {
+ LightDMUser *info = link->data;
+ if (strcmp (lightdm_user_get_name (info), lightdm_user_get_name (user)) == 0)
+ {
+ if (update_passwd_user (info, lightdm_user_get_real_name (user), lightdm_user_get_home_directory (user), lightdm_user_get_image (user)))
+ changed_users = g_list_insert_sorted (changed_users, info, compare_user);
+ g_object_unref (user);
+ user = info;
+ break;
+ }
+ }
+ if (!link)
+ {
+ /* Only notify once we have loaded the user list */
+ if (priv->have_users)
+ new_users = g_list_insert_sorted (new_users, user, compare_user);
+ }
+ users = g_list_insert_sorted (users, user, compare_user);
+ }
+ g_strfreev (hidden_users);
+ g_strfreev (hidden_shells);
+
+ if (errno != 0)
+ g_warning ("Failed to read password database: %s", strerror (errno));
+
+ endpwent ();
+
+ /* Use new user list */
+ old_users = priv->users;
+ priv->users = users;
+
+ /* Notify of changes */
+ for (link = new_users; link; link = link->next)
+ {
+ LightDMUser *info = link->data;
+ g_debug ("User %s added", lightdm_user_get_name (info));
+ g_signal_connect (info, "changed", G_CALLBACK (user_changed_cb), user_list);
+ if (emit_add_signal)
+ g_signal_emit (user_list, list_signals[USER_ADDED], 0, info);
+ }
+ g_list_free (new_users);
+ for (link = changed_users; link; link = link->next)
+ {
+ LightDMUser *info = link->data;
+ g_debug ("User %s changed", lightdm_user_get_name (info));
+ g_signal_emit (info, user_signals[CHANGED], 0);
+ }
+ g_list_free (changed_users);
+ for (link = old_users; link; link = link->next)
+ {
+ GList *new_link;
+
+ /* See if this user is in the current list */
+ for (new_link = priv->users; new_link; new_link = new_link->next)
+ {
+ if (new_link->data == link->data)
+ break;
+ }
+
+ if (!new_link)
+ {
+ LightDMUser *info = link->data;
+ g_debug ("User %s removed", lightdm_user_get_name (info));
+ g_signal_emit (user_list, list_signals[USER_REMOVED], 0, info);
+ g_object_unref (info);
+ }
+ }
+ g_list_free (old_users);
+}
+
+static void
+passwd_changed_cb (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, LightDMUserList *user_list)
+{
+ if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+ {
+ g_debug ("%s changed, reloading user list", g_file_get_path (file));
+ load_passwd_file (user_list, TRUE);
+ }
+}
+
+static gboolean
+update_user (UserAccountObject *object)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (object->user);
+ GVariant *result, *value;
+ GVariantIter *iter;
+ gchar *name;
+ GError *error = NULL;
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (object->proxy),
+ "org.freedesktop.Accounts",
+ g_dbus_proxy_get_object_path (object->proxy),
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ g_variant_new ("(s)", "org.freedesktop.Accounts.User"),
+ G_VARIANT_TYPE ("(a{sv})"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error updating user %s: %s", g_dbus_proxy_get_object_path (object->proxy), error->message);
+ g_clear_error (&error);
+ if (!result)
+ return FALSE;
+
+ g_variant_get (result, "(a{sv})", &iter);
+ while (g_variant_iter_loop (iter, "{&sv}", &name, &value))
+ {
+ if (strcmp (name, "UserName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *user_name;
+ g_variant_get (value, "&s", &user_name);
+ g_free (priv->name);
+ priv->name = g_strdup (user_name);
+ }
+ else if (strcmp (name, "RealName") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *real_name;
+ g_variant_get (value, "&s", &real_name);
+ g_free (priv->real_name);
+ priv->real_name = g_strdup (real_name);
+ }
+ else if (strcmp (name, "HomeDirectory") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *home_directory;
+ g_variant_get (value, "&s", &home_directory);
+ g_free (priv->home_directory);
+ priv->home_directory = g_strdup (home_directory);
+ }
+ else if (strcmp (name, "IconFile") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *icon_file;
+ g_variant_get (value, "&s", &icon_file);
+ g_free (priv->image);
+ if (strcmp (icon_file, "") == 0)
+ priv->image = NULL;
+ else
+ priv->image = g_strdup (icon_file);
+ }
+ else if (strcmp (name, "BackgroundFile") == 0 && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
+ {
+ gchar *background_file;
+ g_variant_get (value, "&s", &background_file);
+ g_free (priv->background);
+ if (strcmp (background_file, "") == 0)
+ priv->background = NULL;
+ else
+ priv->background = g_strdup (background_file);
+ }
+ }
+ g_variant_iter_free (iter);
+
+ g_variant_unref (result);
+
+ return TRUE;
+}
+
+static void
+user_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UserAccountObject *object)
+{
+ if (strcmp (signal_name, "Changed") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("()")))
+ {
+ g_debug ("User %s changed", g_dbus_proxy_get_object_path (object->proxy));
+ update_user (object);
+ g_signal_emit (object->user, user_signals[CHANGED], 0);
+ }
+ else
+ g_warning ("Got org.freedesktop.Accounts.User signal Changed with unknown parameters %s", g_variant_get_type_string (parameters));
+ }
+}
+
+static UserAccountObject *
+user_account_object_new (LightDMUserList *user_list, const gchar *path)
+{
+ GDBusProxy *proxy;
+ UserAccountObject *object;
+ GError *error = NULL;
+
+ proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.Accounts",
+ path,
+ "org.freedesktop.Accounts.User",
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting user %s: %s", path, error->message);
+ g_clear_error (&error);
+ if (!proxy)
+ return NULL;
+
+ object = g_malloc0 (sizeof (UserAccountObject));
+ object->user = g_object_new (LIGHTDM_TYPE_USER, NULL);
+ GET_USER_PRIVATE (object->user)->user_list = user_list;
+ object->proxy = proxy;
+ g_signal_connect (proxy, "g-signal", G_CALLBACK (user_signal_cb), object);
+
+ return object;
+}
+
+static void
+user_account_object_free (UserAccountObject *object)
+{
+ if (!object)
+ return;
+ g_object_unref (object->user);
+ g_object_unref (object->proxy);
+ g_free (object);
+}
+
+static UserAccountObject *
+find_user_account_object (LightDMUserList *user_list, const gchar *path)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GList *link;
+
+ for (link = priv->user_account_objects; link; link = link->next)
+ {
+ UserAccountObject *object = link->data;
+ if (strcmp (g_dbus_proxy_get_object_path (object->proxy), path) == 0)
+ return object;
+ }
+
+ return NULL;
+}
+
+static void
+user_accounts_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+
+ if (strcmp (signal_name, "UserAdded") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ UserAccountObject *object;
+
+ g_variant_get (parameters, "(&o)", &path);
+
+ /* Ignore duplicate requests */
+ object = find_user_account_object (user_list, path);
+ if (object)
+ return;
+
+ object = user_account_object_new (user_list, path);
+ if (object && update_user (object))
+ {
+ g_debug ("User %s added", path);
+ priv->user_account_objects = g_list_append (priv->user_account_objects, object);
+ priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user);
+ g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list);
+ g_signal_emit (user_list, list_signals[USER_ADDED], 0, object->user);
+ }
+ else
+ user_account_object_free (object);
+ }
+ else
+ g_warning ("Got UserAccounts signal UserAdded with unknown parameters %s", g_variant_get_type_string (parameters));
+ }
+ else if (strcmp (signal_name, "UserDeleted") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ UserAccountObject *object;
+
+ g_variant_get (parameters, "(&o)", &path);
+
+ object = find_user_account_object (user_list, path);
+ if (!object)
+ return;
+
+ g_debug ("User %s deleted", path);
+ priv->users = g_list_remove (priv->users, object->user);
+ g_object_unref (object->user);
+
+ g_signal_emit (user_list, list_signals[USER_REMOVED], 0, object->user);
+
+ priv->user_account_objects = g_list_remove (priv->user_account_objects, object);
+ user_account_object_free (object);
+ }
+ else
+ g_warning ("Got UserAccounts signal UserDeleted with unknown parameters %s", g_variant_get_type_string (parameters));
+ }
+}
+
+static Session *
+load_session (LightDMUserList *user_list, const gchar *path)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ Session *session = NULL;
+ GVariant *result, *username;
+ GError *error = NULL;
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy),
+ "org.freedesktop.DisplayManager",
+ path,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)", "org.freedesktop.DisplayManager.Session", "UserName"),
+ G_VARIANT_TYPE ("(v)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting UserName from org.freedesktop.DisplayManager.Session: %s", error->message);
+ g_clear_error (&error);
+ if (!result)
+ return NULL;
+
+ g_variant_get (result, "(v)", &username);
+ if (g_variant_is_of_type (username, G_VARIANT_TYPE_STRING))
+ {
+ gchar *name;
+
+ g_variant_get (username, "&s", &name);
+
+ g_debug ("Loaded session %s (%s)", path, name);
+ session = g_object_new (session_get_type (), NULL);
+ session->username = g_strdup (name);
+ session->path = g_strdup (path);
+ priv->sessions = g_list_append (priv->sessions, session);
+ }
+ g_variant_unref (username);
+ g_variant_unref (result);
+
+ return session;
+}
+
+static void
+display_manager_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, LightDMUserList *user_list)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+
+ if (strcmp (signal_name, "SessionAdded") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ Session *session;
+ LightDMUser *user = NULL;
+
+ g_variant_get (parameters, "(&o)", &path);
+ session = load_session (user_list, path);
+ if (session)
+ user = get_user_by_name (user_list, session->username);
+ if (user)
+ g_signal_emit (user, user_signals[CHANGED], 0);
+ }
+ }
+ else if (strcmp (signal_name, "SessionRemoved") == 0)
+ {
+ if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")))
+ {
+ gchar *path;
+ GList *link;
+
+ g_variant_get (parameters, "(&o)", &path);
+
+ for (link = priv->sessions; link; link = link->next)
+ {
+ Session *session = link->data;
+ if (strcmp (session->path, path) == 0)
+ {
+ LightDMUser *user;
+
+ g_debug ("Session %s removed", path);
+ priv->sessions = g_list_remove_link (priv->sessions, link);
+ user = get_user_by_name (user_list, session->username);
+ if (user)
+ g_signal_emit (user, user_signals[CHANGED], 0);
+ g_object_unref (session);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void
+update_users (LightDMUserList *user_list)
+{
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (user_list);
+ GError *error = NULL;
+
+ if (priv->have_users)
+ return;
+ priv->have_users = TRUE;
+
+ priv->accounts_service_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.Accounts",
+ "/org/freedesktop/Accounts",
+ "org.freedesktop.Accounts",
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error contacting org.freedesktop.Accounts: %s", error->message);
+ g_clear_error (&error);
+
+ /* Check if the service exists */
+ if (priv->accounts_service_proxy)
+ {
+ gchar *name;
+
+ name = g_dbus_proxy_get_name_owner (priv->accounts_service_proxy);
+ if (!name)
+ {
+ g_debug ("org.freedesktop.Accounts does not exist, falling back to passwd file");
+ g_object_unref (priv->accounts_service_proxy);
+ priv->accounts_service_proxy = NULL;
+ }
+ g_free (name);
+ }
+
+ if (priv->accounts_service_proxy)
+ {
+ GVariant *result;
+
+ g_signal_connect (priv->accounts_service_proxy, "g-signal", G_CALLBACK (user_accounts_signal_cb), user_list);
+
+ result = g_dbus_proxy_call_sync (priv->accounts_service_proxy,
+ "ListCachedUsers",
+ g_variant_new ("()"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting user list from org.freedesktop.Accounts: %s", error->message);
+ g_clear_error (&error);
+ if (!result)
+ return;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(ao)")))
+ {
+ GVariantIter *iter;
+ const gchar *path;
+
+ g_debug ("Loading users from org.freedesktop.Accounts");
+ g_variant_get (result, "(ao)", &iter);
+ while (g_variant_iter_loop (iter, "&o", &path))
+ {
+ UserAccountObject *object;
+
+ g_debug ("Loading user %s", path);
+
+ object = user_account_object_new (user_list, path);
+ if (object && update_user (object))
+ {
+ priv->user_account_objects = g_list_append (priv->user_account_objects, object);
+ priv->users = g_list_insert_sorted (priv->users, g_object_ref (object->user), compare_user);
+ g_signal_connect (object->user, "changed", G_CALLBACK (user_changed_cb), user_list);
+ }
+ else
+ user_account_object_free (object);
+ }
+ g_variant_iter_free (iter);
+ }
+ else
+ g_warning ("Unexpected type from ListCachedUsers: %s", g_variant_get_type_string (result));
+
+ g_variant_unref (result);
+ }
+ else
+ {
+ GFile *passwd_file;
+
+ load_passwd_file (user_list, FALSE);
+
+ /* Watch for changes to user list */
+
+ passwd_file = g_file_new_for_path (PASSWD_FILE);
+ priv->passwd_monitor = g_file_monitor (passwd_file, G_FILE_MONITOR_NONE, NULL, &error);
+ g_object_unref (passwd_file);
+ if (error)
+ g_warning ("Error monitoring %s: %s", PASSWD_FILE, error->message);
+ else
+ g_signal_connect (priv->passwd_monitor, "changed", G_CALLBACK (passwd_changed_cb), user_list);
+ g_clear_error (&error);
+ }
+
+ priv->display_manager_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL,
+ "org.freedesktop.DisplayManager",
+ "/org/freedesktop/DisplayManager",
+ "org.freedesktop.DisplayManager",
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error contacting org.freedesktop.DisplayManager: %s", error->message);
+ g_clear_error (&error);
+
+ if (priv->display_manager_proxy)
+ {
+ GVariant *result;
+
+ g_signal_connect (priv->display_manager_proxy, "g-signal", G_CALLBACK (display_manager_signal_cb), user_list);
+
+ result = g_dbus_connection_call_sync (g_dbus_proxy_get_connection (priv->display_manager_proxy),
+ "org.freedesktop.DisplayManager",
+ "/org/freedesktop/DisplayManager",
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)", "org.freedesktop.DisplayManager", "Sessions"),
+ G_VARIANT_TYPE ("(v)"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (error)
+ g_warning ("Error getting session list from org.freedesktop.DisplayManager: %s", error->message);
+ g_clear_error (&error);
+ if (!result)
+ return;
+
+ if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(v)")))
+ {
+ GVariant *value;
+ GVariantIter *iter;
+ const gchar *path;
+
+ g_variant_get (result, "(v)", &value);
+
+ g_debug ("Loading sessions from org.freedesktop.DisplayManager");
+ g_variant_get (value, "ao", &iter);
+ while (g_variant_iter_loop (iter, "&o", &path))
+ load_session (user_list, path);
+ g_variant_iter_free (iter);
+
+ g_variant_unref (value);
+ }
+ else
+ g_warning ("Unexpected type from org.freedesktop.DisplayManager.Sessions: %s", g_variant_get_type_string (result));
+
+ g_variant_unref (result);
+ }
+}
+
+/**
+ * lightdm_user_list_get_length:
+ * @user_list: a #LightDMUserList
+ *
+ * Return value: The number of users able to log in
+ **/
+gint
+lightdm_user_list_get_length (LightDMUserList *user_list)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), 0);
+ update_users (user_list);
+ return g_list_length (GET_LIST_PRIVATE (user_list)->users);
+}
+
+/**
+ * lightdm_user_list_get_users:
+ * @user_list: A #LightDMUserList
+ *
+ * Get a list of users to present to the user. This list may be a subset of the
+ * available users and may be empty depending on the server configuration.
+ *
+ * Return value: (element-type LightDMUser) (transfer none): A list of #LightDMUser that should be presented to the user.
+ **/
+GList *
+lightdm_user_list_get_users (LightDMUserList *user_list)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL);
+ update_users (user_list);
+ return GET_LIST_PRIVATE (user_list)->users;
+}
+
+/**
+ * lightdm_user_list_get_user_by_name:
+ * @user_list: A #LightDMUserList
+ * @username: Name of user to get.
+ *
+ * Get infomation about a given user or #NULL if this user doesn't exist.
+ *
+ * Return value: (transfer none): A #LightDMUser entry for the given user.
+ **/
+LightDMUser *
+lightdm_user_list_get_user_by_name (LightDMUserList *user_list, const gchar *username)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER_LIST (user_list), NULL);
+ g_return_val_if_fail (username != NULL, NULL);
+
+ update_users (user_list);
+
+ return get_user_by_name (user_list, username);
+}
+
+static void
+lightdm_user_list_init (LightDMUserList *user_list)
+{
+}
+
+static void
+lightdm_user_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_user_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMUserList *self;
+
+ self = LIGHTDM_USER_LIST (object);
+
+ switch (prop_id)
+ {
+ case LIST_PROP_NUM_USERS:
+ g_value_set_int (value, lightdm_user_list_get_length (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_user_list_finalize (GObject *object)
+{
+ LightDMUserList *self = LIGHTDM_USER_LIST (object);
+ LightDMUserListPrivate *priv = GET_LIST_PRIVATE (self);
+
+ if (priv->accounts_service_proxy)
+ g_object_unref (priv->accounts_service_proxy);
+ g_list_free_full (priv->user_account_objects, (GDestroyNotify) user_account_object_free);
+ if (priv->passwd_monitor)
+ g_object_unref (priv->passwd_monitor);
+ g_list_free_full (priv->users, g_object_unref);
+ g_list_free_full (priv->sessions, g_object_unref);
+
+ G_OBJECT_CLASS (lightdm_user_list_parent_class)->finalize (object);
+}
+
+static void
+lightdm_user_list_class_init (LightDMUserListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMUserListPrivate));
+
+ object_class->set_property = lightdm_user_list_set_property;
+ object_class->get_property = lightdm_user_list_get_property;
+ object_class->finalize = lightdm_user_list_finalize;
+
+ g_object_class_install_property (object_class,
+ LIST_PROP_NUM_USERS,
+ g_param_spec_int ("num-users",
+ "num-users",
+ "Number of login users",
+ 0, G_MAXINT, 0,
+ G_PARAM_READABLE));
+ /**
+ * LightDMUserList::user-added:
+ * @user_list: A #LightDMUserList
+ * @user: The #LightDM user that has been added.
+ *
+ * The ::user-added signal gets emitted when a user account is created.
+ **/
+ list_signals[USER_ADDED] =
+ g_signal_new ("user-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserListClass, user_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
+
+ /**
+ * LightDMUserList::user-changed:
+ * @user_list: A #LightDMUserList
+ * @user: The #LightDM user that has been changed.
+ *
+ * The ::user-changed signal gets emitted when a user account is modified.
+ **/
+ list_signals[USER_CHANGED] =
+ g_signal_new ("user-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserListClass, user_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
+
+ /**
+ * LightDMUserList::user-removed:
+ * @user_list: A #LightDMUserList
+ * @user: The #LightDM user that has been removed.
+ *
+ * The ::user-removed signal gets emitted when a user account is removed.
+ **/
+ list_signals[USER_REMOVED] =
+ g_signal_new ("user-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserListClass, user_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, LIGHTDM_TYPE_USER);
+}
+
+/**
+ * lightdm_user_get_name:
+ * @user: A #LightDMUser
+ *
+ * Get the name of a user.
+ *
+ * Return value: The name of the given user
+ **/
+const gchar *
+lightdm_user_get_name (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->name;
+}
+
+/**
+ * lightdm_user_get_real_name:
+ * @user: A #LightDMUser
+ *
+ * Get the real name of a user.
+ *
+ * Return value: The real name of the given user
+ **/
+const gchar *
+lightdm_user_get_real_name (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->real_name;
+}
+
+/**
+ * lightdm_user_get_display_name:
+ * @user: A #LightDMUser
+ *
+ * Get the display name of a user.
+ *
+ * Return value: The display name of the given user
+ **/
+const gchar *
+lightdm_user_get_display_name (LightDMUser *user)
+{
+ LightDMUserPrivate *priv;
+
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+
+ priv = GET_USER_PRIVATE (user);
+ if (strcmp (priv->real_name, ""))
+ return priv->real_name;
+ else
+ return priv->name;
+}
+
+/**
+ * lightdm_user_get_home_directory:
+ * @user: A #LightDMUser
+ *
+ * Get the home directory for a user.
+ *
+ * Return value: The users home directory
+ */
+const gchar *
+lightdm_user_get_home_directory (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->home_directory;
+}
+
+/**
+ * lightdm_user_get_image:
+ * @user: A #LightDMUser
+ *
+ * Get the image URI for a user.
+ *
+ * Return value: The image URI for the given user or #NULL if no URI
+ **/
+const gchar *
+lightdm_user_get_image (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->image;
+}
+
+/**
+ * lightdm_user_get_background:
+ * @user: A #LightDMUser
+ *
+ * Get the background file path for a user.
+ *
+ * Return value: The background file path for the given user or #NULL if no path
+ **/
+const gchar *
+lightdm_user_get_background (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ return GET_USER_PRIVATE (user)->background;
+}
+
+static void
+load_dmrc (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+ gchar *path;
+ //gboolean have_dmrc;
+
+ if (!priv->dmrc_file)
+ priv->dmrc_file = g_key_file_new ();
+
+ /* Load from the user directory */
+ path = g_build_filename (priv->home_directory, ".dmrc", NULL);
+ /*have_dmrc = */g_key_file_load_from_file (priv->dmrc_file, path, G_KEY_FILE_KEEP_COMMENTS, NULL);
+ g_free (path);
+
+ /* If no ~/.dmrc, then load from the cache */
+ // FIXME
+
+ // FIXME: Watch for changes
+
+ /* The Language field is actually a locale, strip the codeset off it to get the language */
+ if (priv->language)
+ g_free (priv->language);
+ priv->language = g_key_file_get_string (priv->dmrc_file, "Desktop", "Language", NULL);
+ if (priv->language)
+ {
+ gchar *codeset = strchr (priv->language, '.');
+ if (codeset)
+ *codeset = '\0';
+ }
+
+ if (priv->layouts)
+ {
+ g_strfreev (priv->layouts);
+ priv->layouts = NULL;
+ }
+ if (g_key_file_has_key (priv->dmrc_file, "Desktop", "Layout", NULL))
+ {
+ priv->layouts = g_malloc (sizeof (gchar *) * 2);
+ priv->layouts[0] = g_key_file_get_string (priv->dmrc_file, "Desktop", "Layout", NULL);
+ priv->layouts[1] = NULL;
+ }
+
+ if (priv->session)
+ g_free (priv->session);
+ priv->session = g_key_file_get_string (priv->dmrc_file, "Desktop", "Session", NULL);
+}
+
+static GVariant *
+get_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+
+ if (!proxy)
+ return NULL;
+
+ answer = g_dbus_proxy_get_cached_property (proxy, property);
+
+ if (!answer)
+ {
+ g_warning ("Could not get accounts property %s", property);
+ return NULL;
+ }
+
+ return answer;
+}
+
+static gboolean
+get_boolean_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+ gboolean rv;
+
+ answer = get_property (proxy, property);
+ if (!g_variant_is_of_type (answer, G_VARIANT_TYPE_BOOLEAN))
+ {
+ g_warning ("Unexpected accounts property type for %s: %s",
+ property, g_variant_get_type_string (answer));
+ g_variant_unref (answer);
+ return FALSE;
+ }
+
+ rv = g_variant_get_boolean (answer);
+ g_variant_unref (answer);
+
+ return rv;
+}
+
+static gchar *
+get_string_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+ gchar *rv;
+
+ answer = get_property (proxy, property);
+ if (!g_variant_is_of_type (answer, G_VARIANT_TYPE_STRING))
+ {
+ g_warning ("Unexpected accounts property type for %s: %s",
+ property, g_variant_get_type_string (answer));
+ g_variant_unref (answer);
+ return NULL;
+ }
+
+ rv = g_strdup (g_variant_get_string (answer, NULL));
+ if (strcmp (rv, "") == 0)
+ {
+ g_free (rv);
+ rv = NULL;
+ }
+ g_variant_unref (answer);
+
+ return rv;
+}
+
+static gchar **
+get_string_array_property (GDBusProxy *proxy, const gchar *property)
+{
+ GVariant *answer;
+ gchar **rv;
+
+ if (!proxy)
+ return NULL;
+
+ answer = g_dbus_proxy_get_cached_property (proxy, property);
+
+ if (!answer)
+ {
+ g_warning ("Could not get accounts property %s", property);
+ return NULL;
+ }
+
+ if (!g_variant_is_of_type (answer, G_VARIANT_TYPE ("as")))
+ {
+ g_warning ("Unexpected accounts property type for %s: %s",
+ property, g_variant_get_type_string (answer));
+ g_variant_unref (answer);
+ return NULL;
+ }
+
+ rv = g_variant_dup_strv (answer, NULL);
+
+ g_variant_unref (answer);
+ return rv;
+}
+
+static gboolean
+load_accounts_service (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+ LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list);
+ UserAccountObject *account = NULL;
+ GList *iter;
+ gchar **value;
+
+ /* First, find AccountObject proxy */
+ for (iter = list_priv->user_account_objects; iter; iter = iter->next)
+ {
+ UserAccountObject *a = iter->data;
+ if (a->user == user)
+ {
+ account = a;
+ break;
+ }
+ }
+ if (!account)
+ return FALSE;
+
+ /* We have proxy, let's grab some properties */
+ if (priv->language)
+ g_free (priv->language);
+ priv->language = get_string_property (account->proxy, "Language");
+ if (priv->session)
+ g_free (priv->session);
+ priv->session = get_string_property (account->proxy, "XSession");
+
+ value = get_string_array_property (account->proxy, "XKeyboardLayouts");
+ if (value)
+ {
+ if (value[0])
+ {
+ g_strfreev (priv->layouts);
+ priv->layouts = value;
+ }
+ else
+ g_strfreev (value);
+ }
+
+ priv->has_messages = get_boolean_property (account->proxy, "XHasMessages");
+
+ return TRUE;
+}
+
+/* Loads language/layout/session info for user */
+static void
+load_user_values (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+
+ load_dmrc (user);
+ load_accounts_service (user); // overrides dmrc values
+
+ /* Ensure a few guarantees */
+ if (priv->layouts == NULL)
+ {
+ priv->layouts = g_malloc (sizeof (gchar *) * 1);
+ priv->layouts[0] = NULL;
+ }
+}
+
+/**
+ * lightdm_user_get_language:
+ * @user: A #LightDMUser
+ *
+ * Get the language for a user.
+ *
+ * Return value: The language for the given user or #NULL if using system defaults.
+ **/
+const gchar *
+lightdm_user_get_language (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->language;
+}
+
+/**
+ * lightdm_user_get_layout:
+ * @user: A #LightDMUser
+ *
+ * Get the keyboard layout for a user.
+ *
+ * Return value: The keyboard layout for the given user or #NULL if using system defaults. Copy the value if you want to use it long term.
+ **/
+const gchar *
+lightdm_user_get_layout (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->layouts[0];
+}
+
+/**
+ * lightdm_user_get_layouts:
+ * @user: A #LightDMUser
+ *
+ * Get the configured keyboard layouts for a user.
+ *
+ * Return value: (transfer none): A NULL-terminated array of keyboard layouts for the given user. Copy the values if you want to use them long term.
+ **/
+const gchar * const *
+lightdm_user_get_layouts (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return (const gchar * const *) GET_USER_PRIVATE (user)->layouts;
+}
+
+/**
+ * lightdm_user_get_session:
+ * @user: A #LightDMUser
+ *
+ * Get the session for a user.
+ *
+ * Return value: The session for the given user or #NULL if using system defaults.
+ **/
+const gchar *
+lightdm_user_get_session (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), NULL);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->session;
+}
+
+/**
+ * lightdm_user_get_logged_in:
+ * @user: A #LightDMUser
+ *
+ * Check if a user is logged in.
+ *
+ * Return value: #TRUE if the user is currently logged in.
+ **/
+gboolean
+lightdm_user_get_logged_in (LightDMUser *user)
+{
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (user);
+ LightDMUserListPrivate *list_priv = GET_LIST_PRIVATE (priv->user_list);
+ GList *link;
+
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), FALSE);
+
+ for (link = list_priv->sessions; link; link = link->next)
+ {
+ Session *session = link->data;
+ if (strcmp (session->username, priv->name) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * lightdm_user_get_has_messages:
+ * @user: A #LightDMUser
+ *
+ * Check if a user has waiting messages.
+ *
+ * Return value: #TRUE if the user has waiting messages.
+ **/
+gboolean
+lightdm_user_get_has_messages (LightDMUser *user)
+{
+ g_return_val_if_fail (LIGHTDM_IS_USER (user), FALSE);
+ load_user_values (user);
+ return GET_USER_PRIVATE (user)->has_messages;
+}
+
+static void
+lightdm_user_init (LightDMUser *user)
+{
+}
+
+static void
+lightdm_user_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+}
+
+static void
+lightdm_user_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ LightDMUser *self;
+
+ self = LIGHTDM_USER (object);
+
+ switch (prop_id)
+ {
+ case USER_PROP_NAME:
+ g_value_set_string (value, lightdm_user_get_name (self));
+ break;
+ case USER_PROP_REAL_NAME:
+ g_value_set_string (value, lightdm_user_get_real_name (self));
+ break;
+ case USER_PROP_DISPLAY_NAME:
+ g_value_set_string (value, lightdm_user_get_display_name (self));
+ break;
+ case USER_PROP_HOME_DIRECTORY:
+ g_value_set_string (value, lightdm_user_get_home_directory (self));
+ break;
+ case USER_PROP_IMAGE:
+ g_value_set_string (value, lightdm_user_get_image (self));
+ break;
+ case USER_PROP_BACKGROUND:
+ g_value_set_string (value, lightdm_user_get_background (self));
+ break;
+ case USER_PROP_LANGUAGE:
+ g_value_set_string (value, lightdm_user_get_language (self));
+ break;
+ case USER_PROP_LAYOUT:
+ g_value_set_string (value, lightdm_user_get_layout (self));
+ break;
+ case USER_PROP_LAYOUTS:
+ g_value_set_boxed (value, g_strdupv ((gchar **) lightdm_user_get_layouts (self)));
+ break;
+ case USER_PROP_SESSION:
+ g_value_set_string (value, lightdm_user_get_session (self));
+ break;
+ case USER_PROP_LOGGED_IN:
+ g_value_set_boolean (value, lightdm_user_get_logged_in (self));
+ break;
+ case USER_PROP_HAS_MESSAGES:
+ g_value_set_boolean (value, lightdm_user_get_has_messages (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+lightdm_user_finalize (GObject *object)
+{
+ LightDMUser *self = LIGHTDM_USER (object);
+ LightDMUserPrivate *priv = GET_USER_PRIVATE (self);
+
+ g_free (priv->name);
+ g_free (priv->real_name);
+ g_free (priv->home_directory);
+ g_free (priv->image);
+ g_free (priv->background);
+ g_strfreev (priv->layouts);
+ if (priv->dmrc_file)
+ g_key_file_free (priv->dmrc_file);
+}
+
+static void
+lightdm_user_class_init (LightDMUserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (LightDMUserPrivate));
+
+ object_class->set_property = lightdm_user_set_property;
+ object_class->get_property = lightdm_user_get_property;
+ object_class->finalize = lightdm_user_finalize;
+
+ g_object_class_install_property (object_class,
+ USER_PROP_NAME,
+ g_param_spec_string ("name",
+ "name",
+ "Username",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_REAL_NAME,
+ g_param_spec_string ("real-name",
+ "real-name",
+ "Users real name",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_DISPLAY_NAME,
+ g_param_spec_string ("display-name",
+ "display-name",
+ "Users display name",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_HOME_DIRECTORY,
+ g_param_spec_string ("home-directory",
+ "home-directory",
+ "Home directory",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_IMAGE,
+ g_param_spec_string ("image",
+ "image",
+ "Avatar image",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_BACKGROUND,
+ g_param_spec_string ("background",
+ "background",
+ "User background",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LANGUAGE,
+ g_param_spec_string ("language",
+ "language",
+ "Language used by this user",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LAYOUT,
+ g_param_spec_string ("layout",
+ "layout",
+ "Keyboard layout used by this user",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LAYOUTS,
+ g_param_spec_boxed ("layouts",
+ "layouts",
+ "Keyboard layouts used by this user",
+ G_TYPE_STRV,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_SESSION,
+ g_param_spec_string ("session",
+ "session",
+ "Session used by this user",
+ NULL,
+ G_PARAM_READABLE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LOGGED_IN,
+ g_param_spec_boolean ("logged-in",
+ "logged-in",
+ "TRUE if the user is currently in a session",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ USER_PROP_LOGGED_IN,
+ g_param_spec_boolean ("has-messages",
+ "has-messages",
+ "TRUE if the user is has waiting messages",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * LightDMUser::changed:
+ * @user: A #LightDMUser
+ *
+ * The ::changed signal gets emitted this user account is modified.
+ **/
+ user_signals[CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (LightDMUserClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+session_init (Session *session)
+{
+}
+
+static void
+session_finalize (GObject *object)
+{
+ Session *self = SESSION (object);
+
+ g_free (self->path);
+ g_free (self->username);
+}
+
+static void
+session_class_init (SessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = session_finalize;
+}
diff --git a/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp
new file mode 100644
index 00000000..b160c9d5
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.cpp
@@ -0,0 +1,1531 @@
+/* $Id: vbox-greeter.cpp $ */
+/** @file
+ * vbox-greeter - an own LightDM greeter module supporting auto-logons
+ * controlled by the host.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define GLIB_DISABLE_DEPRECATION_WARNINGS 1 /* g_type_init() is deprecated */
+#include <pwd.h>
+#include <syslog.h>
+#include <stdlib.h>
+
+#include <lightdm.h>
+#ifdef VBOX_WITH_FLTK
+# include <FL/Fl.H>
+# include <FL/fl_ask.H> /* Yes, the casing is correct for 1.3.0 -- d'oh. */
+# include <FL/Fl_Box.H>
+# include <FL/Fl_Button.H>
+# include <FL/fl_draw.H> /* Same as above. */
+# include <FL/Fl_Double_Window.H>
+# include <FL/Fl_Input.H>
+# include <FL/Fl_Menu_Button.H>
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+# include <FL/Fl_PNG_Image.H>
+# include <FL/Fl_Shared_Image.H>
+# endif
+# include <FL/Fl_Secret_Input.H>
+#else
+# include <cairo-xlib.h>
+# include <gtk/gtk.h>
+# include <gdk/gdkx.h>
+#endif
+
+#include <package-generated.h>
+#include "product-generated.h"
+
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+
+/* The greeter's full name for logging. */
+#define VBOX_MODULE_NAME "vbox-lightdm-greeter"
+
+/* UI elements used in this greeter. */
+#define VBOX_GREETER_UI_WND_GREETER "wnd_greeter"
+
+#define VBOX_GREETER_UI_EDT_USER "edt_username"
+#define VBOX_GREETER_UI_EDT_PASSWORD "edt_password"
+#define VBOX_GREETER_UI_BTN_LOGIN "btn_login"
+#define VBOX_GREETER_UI_LBL_INFO "lbl_info"
+
+/* UI display options. */
+/** Show the restart menu entry / button. */
+#define VBOX_GREETER_UI_SHOW_RESTART RT_BIT(0)
+/** Show the shutdown menu entry / button. */
+#define VBOX_GREETER_UI_SHOW_SHUTDOWN RT_BIT(1)
+/** Show the (customized) top banner. */
+#define VBOX_GREETER_UI_SHOW_BANNER RT_BIT(2)
+/** Enable custom colors */
+#define VBOX_GREETER_UI_USE_THEMING RT_BIT(3)
+
+/** Extracts the 8-bit red component from an uint32_t. */
+#define VBOX_RGB_COLOR_RED(uColor) uColor & 0xFF
+/** Extracts the 8-bit green component from an uint32_t. */
+#define VBOX_RGB_COLOR_GREEN(uColor) (uColor >> 8) & 0xFF
+/** Extracts the 8-bit blue component from an uint32_t. */
+#define VBOX_RGB_COLOR_BLUE(uColor) (uColor >> 16) & 0xFF
+
+#include <VBox/log.h>
+#ifdef VBOX_WITH_GUEST_PROPS
+# include <VBox/HostServices/GuestPropertySvc.h>
+#endif
+
+/** The program name (derived from argv[0]). */
+char *g_pszProgName = (char *)"";
+/** For debugging. */
+#ifdef DEBUG
+ static int g_iVerbosity = 99;
+#else
+ static int g_iVerbosity = 0;
+#endif
+static bool g_fRunning = true;
+
+/** Logging parameters. */
+/** @todo Make this configurable later. */
+static PRTLOGGER g_pLoggerRelease = NULL;
+static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */
+static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */
+static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */
+
+/**
+ * Context structure which contains all needed
+ * data within callbacks.
+ */
+typedef struct VBOXGREETERCTX
+{
+ /** Pointer to this greeter instance. */
+ LightDMGreeter *pGreeter;
+#ifdef VBOX_WITH_FLTK
+ Fl_Button *pBtnLogin;
+ Fl_Input *pEdtUsername;
+ Fl_Secret_Input *pEdtPassword;
+ Fl_Box *pLblInfo;
+#else
+ /** The GTK builder instance for accessing
+ * the UI elements. */
+ GtkBuilder *pBuilder;
+#endif
+ /** The timeout (in ms) to wait for credentials. */
+ uint32_t uTimeoutMS;
+ /** The starting timestamp (in ms) to calculate
+ * the timeout. */
+ uint64_t uStartMS;
+ /** Timestamp of last abort message. */
+ uint64_t uTsAbort;
+ /** The HGCM client ID. */
+ uint32_t uClientId;
+ /** The credential password. */
+ char *pszPassword;
+} VBOXGREETERCTX, *PVBOXGREETERCTX;
+
+static void vboxGreeterError(const char *pszFormat, ...)
+{
+ va_list va;
+ char *buf;
+ va_start(va, pszFormat);
+ if (RTStrAPrintfV(&buf, pszFormat, va))
+ {
+ RTLogRelPrintf("%s: error: %s", VBOX_MODULE_NAME, buf);
+ RTStrFree(buf);
+ }
+ va_end(va);
+}
+
+static void vboxGreeterLog(const char *pszFormat, ...)
+{
+ if (g_iVerbosity)
+ {
+ va_list va;
+ char *buf;
+ va_start(va, pszFormat);
+ if (RTStrAPrintfV(&buf, pszFormat, va))
+ {
+ /* Only do normal logging in debug mode; could contain
+ * sensitive data! */
+ RTLogRelPrintf("%s: %s", VBOX_MODULE_NAME, buf);
+ RTStrFree(buf);
+ }
+ va_end(va);
+ }
+}
+
+/** @tood Move the following two functions to VbglR3 (also see pam_vbox). */
+#ifdef VBOX_WITH_GUEST_PROPS
+
+/**
+ * Reads a guest property.
+ *
+ * @return IPRT status code.
+ * @param hPAM PAM handle.
+ * @param uClientID Guest property service client ID.
+ * @param pszKey Key (name) of guest property to read.
+ * @param fReadOnly Indicates whether this key needs to be
+ * checked if it only can be read (and *not* written)
+ * by the guest.
+ * @param pszValue Buffer where to store the key's value.
+ * @param cbValue Size of buffer (in bytes).
+ * @param puTimestamp Timestamp of the value
+ * retrieved. Optional.
+ */
+static int vbox_read_prop(uint32_t uClientID,
+ const char *pszKey, bool fReadOnly,
+ char *pszValue, size_t cbValue, uint64_t *puTimestamp)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ /* puTimestamp is optional. */
+
+ int rc;
+
+ uint64_t u64Timestamp = 0;
+ char *pszValTemp = NULL;
+ char *pszFlags = NULL;
+ /* The buffer for storing the data and its initial size. We leave a bit
+ * of space here in case the maximum values are raised. */
+ void *pvBuf = NULL;
+ uint32_t cbBuf = GUEST_PROP_MAX_VALUE_LEN + GUEST_PROP_MAX_FLAGS_LEN + _1K;
+
+ /* Because there is a race condition between our reading the size of a
+ * property and the guest updating it, we loop a few times here and
+ * hope. Actually this should never go wrong, as we are generous
+ * enough with buffer space. */
+ for (unsigned i = 0; i < 10; i++)
+ {
+ pvBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvBuf)
+ {
+ rc = VbglR3GuestPropRead(uClientID, pszKey, pvBuf, cbBuf,
+ &pszValTemp, &u64Timestamp, &pszFlags,
+ &cbBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ switch (rc)
+ {
+ case VERR_BUFFER_OVERFLOW:
+ {
+ /* Buffer too small, try it with a bigger one next time. */
+ cbBuf += _1K;
+ continue; /* Try next round. */
+ }
+
+ default:
+ break;
+ }
+
+ /* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Check security bits. */
+ if (pszFlags)
+ {
+ if ( fReadOnly
+ && !RTStrStr(pszFlags, "RDONLYGUEST"))
+ {
+ /* If we want a property which is read-only on the guest
+ * and it is *not* marked as such, deny access! */
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else /* No flags, no access! */
+ rc = VERR_ACCESS_DENIED;
+
+ if (RT_SUCCESS(rc))
+ {
+ /* If everything went well copy property value to our destination buffer. */
+ if (!RTStrPrintf(pszValue, cbValue, "%s", pszValTemp))
+ rc = VERR_BUFFER_OVERFLOW;
+
+ if (puTimestamp)
+ *puTimestamp = u64Timestamp;
+ }
+ }
+
+#ifdef DEBUG
+ vboxGreeterLog("Read guest property \"%s\"=\"%s\" (Flags: %s, TS: %RU64): %Rrc\n",
+ pszKey, pszValTemp ? pszValTemp : "<None>",
+ pszFlags ? pszFlags : "<None>", u64Timestamp, rc);
+#endif
+
+ if (pvBuf)
+ RTMemFree(pvBuf);
+
+ return rc;
+}
+
+# if 0 /* unused */
+/**
+ * Waits for a guest property to be changed.
+ *
+ * @return IPRT status code.
+ * @param hPAM PAM handle.
+ * @param uClientID Guest property service client ID.
+ * @param pszKey Key (name) of guest property to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait for the change. Specify
+ * RT_INDEFINITE_WAIT to wait indefinitly.
+ */
+static int vbox_wait_prop(uint32_t uClientID,
+ const char *pszKey, uint32_t uTimeoutMS)
+{
+ AssertReturn(uClientID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+
+ int rc;
+
+ /* The buffer for storing the data and its initial size. We leave a bit
+ * of space here in case the maximum values are raised. */
+ void *pvBuf = NULL;
+ uint32_t cbBuf = MAX_NAME_LEN + MAX_VALUE_LEN + MAX_FLAGS_LEN + _1K;
+
+ for (int i = 0; i < 10; i++)
+ {
+ void *pvTmpBuf = RTMemRealloc(pvBuf, cbBuf);
+ if (pvTmpBuf)
+ {
+ char *pszName = NULL;
+ char *pszValue = NULL;
+ uint64_t u64TimestampOut = 0;
+ char *pszFlags = NULL;
+
+ pvBuf = pvTmpBuf;
+ rc = VbglR3GuestPropWait(uClientID, pszKey, pvBuf, cbBuf,
+ 0 /* Last timestamp; just wait for next event */, uTimeoutMS,
+ &pszName, &pszValue, &u64TimestampOut,
+ &pszFlags, &cbBuf, NULL);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* Buffer too small, try it with a bigger one next time. */
+ cbBuf += _1K;
+ continue; /* Try next round. */
+ }
+
+ /* Everything except VERR_BUFFER_OVERLOW makes us bail out ... */
+ break;
+ }
+
+ return rc;
+}
+# endif /* unused */
+
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+/**
+ * Checks for credentials provided by the host / HGCM.
+ *
+ * @return IPRT status code. VERR_NOT_FOUND if no credentials are available,
+ * VINF_SUCCESS on successful retrieval or another IPRT error.
+ * @param pCtx Greeter context.
+ */
+static int vboxGreeterCheckCreds(PVBOXGREETERCTX pCtx)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+ static bool s_fCredsNotFoundMsgShown = false;
+ int rc = VbglR3CredentialsQueryAvailability();
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_NOT_FOUND)
+ vboxGreeterError("vboxGreeterCheckCreds: could not query for credentials! rc=%Rrc. Aborting\n", rc);
+ else if (!s_fCredsNotFoundMsgShown)
+ {
+ vboxGreeterLog("vboxGreeterCheckCreds: no credentials available\n");
+ s_fCredsNotFoundMsgShown = true;
+ }
+ }
+ else
+ {
+ /** @todo Domain handling needed? */
+ char *pszUsername; /* User name only is kept local. */
+ char *pszDomain = NULL;
+ rc = VbglR3CredentialsRetrieve(&pszUsername, &pCtx->pszPassword, &pszDomain);
+ if (RT_FAILURE(rc))
+ {
+ vboxGreeterError("vboxGreeterCheckCreds: could not retrieve credentials! rc=%Rrc. Aborting\n", rc);
+ }
+ else
+ {
+ vboxGreeterLog("vboxGreeterCheckCreds: credentials retrieved: user=%s, password=%s, domain=%s\n",
+ pszUsername,
+#ifdef DEBUG
+ pCtx->pszPassword,
+#else
+ "XXX",
+#endif
+ pszDomain);
+ /* Trigger LightDM authentication with the user name just retrieved. */
+ lightdm_greeter_authenticate(pCtx->pGreeter, pszUsername); /* Must be the real user name from host! */
+
+ /* Securely wipe the user name + domain again. */
+ VbglR3CredentialsDestroy(pszUsername, NULL /* pszPassword */, pszDomain,
+ 3 /* Three wipe passes */);
+ }
+ }
+
+#ifdef DEBUG
+ vboxGreeterLog("vboxGreeterCheckCreds: returned with rc=%Rrc\n", rc);
+#endif
+ return rc;
+}
+
+/**
+ * Called by LightDM when greeter is not needed anymore.
+ *
+ * @param signum Signal number.
+ */
+static void cb_sigterm(int signum)
+{
+ RT_NOREF(signum);
+
+ /* Note: This handler must be reentrant-safe. */
+#ifdef VBOX_WITH_FLTK
+ g_fRunning = false;
+#else
+ exit(RTEXITCODE_SUCCESS);
+#endif
+}
+
+/**
+ * Callback for showing a user prompt, issued by the LightDM server.
+ *
+ * @param pGreeter Pointer to this greeter instance.
+ * @param pszText Text to display.
+ * @param enmType Type of prompt to display.
+ * @param pvData Pointer to user-supplied data.
+ */
+static void cb_lightdm_show_prompt(LightDMGreeter *pGreeter,
+ const gchar *pszText, LightDMPromptType enmType,
+ gpointer pvData)
+{
+ vboxGreeterLog("cb_lightdm_show_prompt: text=%s, type=%d\n", pszText, enmType);
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+
+ switch (enmType)
+ {
+ case 1: /* Password. */
+ {
+ if (pCtx->pszPassword)
+ {
+ lightdm_greeter_respond(pGreeter, pCtx->pszPassword);
+ }
+ else
+ {
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pEdtPassword);
+ const char *pszPwd = pCtx->pEdtPassword->value();
+#else
+ GtkEntry *pEdtPwd = GTK_ENTRY(gtk_builder_get_object(pCtx->pBuilder, "edt_password"));
+ AssertPtr(pEdtPwd);
+ const gchar *pszPwd = gtk_entry_get_text(pEdtPwd);
+#endif
+ lightdm_greeter_respond(pGreeter, pszPwd);
+ }
+ break;
+ }
+ /** @todo Other fields? */
+
+ default:
+ break;
+ }
+
+ VbglR3CredentialsDestroy(NULL /* pszUsername */, pCtx->pszPassword, NULL /* pszDomain */,
+ 3 /* Three wipe passes */);
+ pCtx->pszPassword = NULL;
+}
+
+/**
+ * Callback for showing a message, issued by the LightDM server.
+ *
+ * @param pGreeter Pointer to this greeter instance.
+ * @param pszText Text to display.
+ * @param enmType Type of message to display.
+ * @param pvData Pointer to user-supplied data.
+ */
+static void cb_lightdm_show_message(LightDMGreeter *pGreeter,
+ const gchar *pszText, LightDMPromptType enmType,
+ gpointer pvData)
+{
+ RT_NOREF(pGreeter);
+ vboxGreeterLog("cb_lightdm_show_message: text=%s, type=%d\n", pszText, enmType);
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtrReturnVoid(pCtx);
+
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pLblInfo);
+ pCtx->pLblInfo->copy_label(pszText);
+#else
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pCtx->pBuilder, "lbl_info"));
+ AssertPtr(pLblInfo);
+ gtk_label_set_text(pLblInfo, pszText);
+#endif
+}
+
+/**
+ * Callback for authentication completion, issued by the LightDM server.
+ *
+ * @param pGreeter Pointer to this greeter instance.
+ */
+static void cb_lightdm_auth_complete(LightDMGreeter *pGreeter)
+{
+ vboxGreeterLog("cb_lightdm_auth_complete\n");
+
+ const gchar *pszUser = lightdm_greeter_get_authentication_user(pGreeter);
+ vboxGreeterLog("authenticating user: %s\n", pszUser ? pszUser : "<NULL>");
+
+ if (lightdm_greeter_get_is_authenticated(pGreeter))
+ {
+ /** @todo Add non-default session support. */
+ gchar *pszSession = g_strdup(lightdm_greeter_get_default_session_hint(pGreeter));
+ if (pszSession)
+ {
+ vboxGreeterLog("starting session: %s\n", pszSession);
+ GError *pError = NULL;
+ if (!lightdm_greeter_start_session_sync(pGreeter, pszSession, &pError))
+ {
+ vboxGreeterError("unable to start session '%s': %s\n",
+ pszSession, pError ? pError->message : "Unknown error");
+ }
+ else
+ {
+ AssertPtr(pszSession);
+ vboxGreeterLog("session '%s' successfully started\n", pszSession);
+ }
+ if (pError)
+ g_error_free(pError);
+ g_free(pszSession);
+ }
+ else
+ vboxGreeterError("unable to get default session\n");
+ }
+ else
+ vboxGreeterLog("user not authenticated successfully (yet)\n");
+}
+
+/**
+ * Callback for clicking on the "Login" button.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_login(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_login(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ RT_NOREF(pWidget);
+ AssertPtr(pCtx);
+
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pEdtUsername);
+ const char *pszUser = pCtx->pEdtUsername->value();
+ AssertPtr(pCtx->pEdtPassword);
+ const char *pszPwd = pCtx->pEdtPassword->value();
+#else
+ GtkEntry *pEdtUser = GTK_ENTRY(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_EDT_USER));
+ AssertPtr(pEdtUser);
+ const gchar *pszUser = gtk_entry_get_text(pEdtUser);
+
+ GtkEntry *pEdtPwd = GTK_ENTRY(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_EDT_PASSWORD));
+ AssertPtr(pEdtPwd);
+ const gchar *pszPwd = gtk_entry_get_text(pEdtPwd);
+#endif
+
+ /** @todo Add domain handling? */
+ vboxGreeterLog("login button pressed: greeter=%p, user=%s, password=%s\n",
+ pCtx->pGreeter,
+ pszUser ? pszUser : "<NONE>",
+#ifdef DEBUG
+ pszPwd ? pszPwd : "<NONE>");
+#else
+ /* Don't log passwords in release mode! */
+ "XXX");
+#endif
+ if (strlen(pszUser)) /* Only authenticate if username is given. */
+ {
+ lightdm_greeter_respond(pCtx->pGreeter, pszPwd);
+ lightdm_greeter_authenticate(pCtx->pGreeter, pszUser);
+ }
+}
+
+/**
+ * Callback for clicking on the "Menu" button.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_menu(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_menu(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("menu button pressed\n");
+}
+
+/**
+ * Callback for clicking on the "Restart" button / menu entry.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_restart(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_restart(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("restart button pressed\n");
+
+ bool fRestart = true;
+#ifdef VBOX_WITH_FLTK
+ int rc = fl_choice("Really restart the system?", "Yes", "No", NULL);
+ fRestart = rc == 0;
+#endif
+
+ if (fRestart)
+ {
+ vboxGreeterLog("restart requested\n");
+#ifndef DEBUG
+ lightdm_restart(NULL);
+#endif
+ }
+}
+
+/**
+ * Callback for clicking on the "Shutdown" button / menu entry.
+ *
+ * @param pWidget Widget this callback is bound to.
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+void cb_btn_shutdown(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_btn_shutdown(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("shutdown button pressed\n");
+
+ bool fShutdown = true;
+#ifdef VBOX_WITH_FLTK
+ int rc = fl_choice("Really shutdown the system?", "Yes", "No", NULL);
+ fShutdown = rc == 0;
+#endif
+
+ if (fShutdown)
+ {
+ vboxGreeterLog("shutdown requested\n");
+#ifndef DEBUG
+ lightdm_shutdown(NULL);
+#endif
+ }
+}
+
+#ifdef VBOX_WITH_FLTK
+void cb_edt_username(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_edt_username(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget);
+ vboxGreeterLog("cb_edt_username called\n");
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pEdtPassword);
+ Fl::focus(pCtx->pEdtPassword);
+#endif
+}
+
+#ifdef VBOX_WITH_FLTK
+void cb_edt_password(Fl_Widget *pWidget, void *pvData)
+#else
+void cb_edt_password(GtkWidget *pWidget, gpointer pvData)
+#endif
+{
+ RT_NOREF(pWidget, pvData);
+ vboxGreeterLog("cb_edt_password called\n");
+
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+#ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pBtnLogin);
+ cb_btn_login(pCtx->pBtnLogin, pvData);
+#endif
+}
+
+/**
+ * Callback for the timer event which is checking for new credentials
+ * from the host.
+ *
+ * @param pvData Pointer to user-supplied data.
+ */
+#ifdef VBOX_WITH_FLTK
+static void cb_check_creds(void *pvData)
+#else
+static gboolean cb_check_creds(gpointer pvData)
+#endif
+{
+ PVBOXGREETERCTX pCtx = (PVBOXGREETERCTX)pvData;
+ AssertPtr(pCtx);
+
+#ifdef DEBUG
+ vboxGreeterLog("cb_check_creds called, clientId=%RU32, timeoutMS=%RU32\n",
+ pCtx->uClientId, pCtx->uTimeoutMS);
+#endif
+
+ int rc = VINF_SUCCESS;
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ bool fAbort = false;
+ char szVal[255];
+ if (pCtx->uClientId)
+ {
+ uint64_t tsAbort;
+ rc = vbox_read_prop(pCtx->uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitAbort",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), &tsAbort);
+ switch (rc)
+ {
+ case VINF_SUCCESS:
+# ifdef DEBUG
+ vboxGreeterLog("cb_check_creds: tsAbort %RU64 <-> %RU64\n",
+ pCtx->uTsAbort, tsAbort);
+# endif
+ if (tsAbort != pCtx->uTsAbort)
+ fAbort = true; /* Timestamps differs, abort. */
+ pCtx->uTsAbort = tsAbort;
+ break;
+
+ case VERR_TOO_MUCH_DATA:
+ vboxGreeterError("cb_check_creds: temporarily unable to get abort notification\n");
+ break;
+
+ case VERR_NOT_FOUND:
+ /* Value not found, continue checking for credentials. */
+ break;
+
+ default:
+ vboxGreeterError("cb_check_creds: the abort notification request failed with rc=%Rrc\n", rc);
+ fAbort = true; /* Abort on error. */
+ break;
+ }
+ }
+
+ if (fAbort)
+ {
+ /* Get optional message. */
+ szVal[0] = '\0';
+ int rc2 = vbox_read_prop(pCtx->uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaitAbort",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_NOT_FOUND)
+ vboxGreeterError("cb_check_creds: getting wait abort message failed with rc=%Rrc\n", rc2);
+# ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pLblInfo);
+ pCtx->pLblInfo->copy_label(szVal);
+# else /* !VBOX_WITH_FLTK */
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_LBL_INFO));
+ AssertPtr(pLblInfo);
+ gtk_label_set_text(pLblInfo, szVal);
+# endif /* !VBOX_WITH_FLTK */
+ vboxGreeterLog("cb_check_creds: got notification from host to abort waiting\n");
+ }
+ else
+ {
+#endif /* VBOX_WITH_GUEST_PROPS */
+ rc = vboxGreeterCheckCreds(pCtx);
+ if (RT_SUCCESS(rc))
+ {
+ /* Credentials retrieved. */
+ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ /* No credentials found, but try next round (if there's
+ * time left for) ... */
+ }
+#ifdef VBOX_WITH_GUEST_PROPS
+ }
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ if (rc == VERR_NOT_FOUND) /* No credential found this round. */
+ {
+ /* Calculate timeout value left after process has been started. */
+ uint64_t u64Elapsed = RTTimeMilliTS() - pCtx->uStartMS;
+ /* Is it time to bail out? */
+ if (pCtx->uTimeoutMS < u64Elapsed)
+ {
+#ifdef VBOX_WITH_GUEST_PROPS
+ szVal[0] = '\0';
+ int rc2 = vbox_read_prop(pCtx->uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaitTimeout",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_NOT_FOUND)
+ vboxGreeterError("cb_check_creds: getting wait timeout message failed with rc=%Rrc\n", rc2);
+# ifdef VBOX_WITH_FLTK
+ AssertPtr(pCtx->pLblInfo);
+ pCtx->pLblInfo->copy_label(szVal);
+# else
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pCtx->pBuilder, VBOX_GREETER_UI_LBL_INFO));
+ AssertPtr(pLblInfo);
+ gtk_label_set_text(pLblInfo, szVal);
+# endif
+#endif /* VBOX_WITH_GUEST_PROPS */
+ vboxGreeterLog("cb_check_creds: no credentials retrieved within time (%RU32ms), giving up\n",
+ pCtx->uTimeoutMS);
+ rc = VERR_TIMEOUT;
+ }
+ }
+
+#ifdef DEBUG
+ vboxGreeterLog("cb_check_creds returned with rc=%Rrc\n", rc);
+#endif
+
+ /* At the moment we only allow *one* shot from the host,
+ * so setting credentials in a second attempt won't be possible
+ * intentionally. */
+
+ if (rc == VERR_NOT_FOUND)
+#ifdef VBOX_WITH_FLTK
+ Fl::repeat_timeout(0.5 /* 500 ms */, cb_check_creds, pvData);
+#else
+ return TRUE; /* No credentials found, do another round. */
+
+ return FALSE; /* Remove timer source on every other error / status. */
+#endif
+}
+
+/**
+ * Release logger callback.
+ *
+ * @return IPRT status code.
+ * @param pLoggerRelease
+ * @param enmPhase
+ * @param pfnLog
+ */
+static DECLCALLBACK(void) vboxGreeterLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog)
+{
+ /* Some introductory information. */
+ static RTTIMESPEC s_TimeSpec;
+ char szTmp[256];
+ if (enmPhase == RTLOGPHASE_BEGIN)
+ RTTimeNow(&s_TimeSpec);
+ RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp));
+
+ switch (enmPhase)
+ {
+ case RTLOGPHASE_BEGIN:
+ {
+ pfnLog(pLoggerRelease,
+ "vbox-greeter %s r%s (verbosity: %d) %s (%s %s) release log\n"
+ "Log opened %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_iVerbosity, VBOX_BUILD_TARGET,
+ __DATE__, __TIME__, szTmp);
+
+ int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp);
+ if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW)
+ pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp);
+
+ /* the package type is interesting for Linux distributions */
+ char szExecName[RTPATH_MAX];
+ char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName));
+ pfnLog(pLoggerRelease,
+ "Executable: %s\n"
+ "Process ID: %u\n"
+ "Package type: %s"
+#ifdef VBOX_OSE
+ " (OSE)"
+#endif
+ "\n",
+ pszExecName ? pszExecName : "unknown",
+ RTProcSelf(),
+ VBOX_PACKAGE_STRING);
+ break;
+ }
+
+ case RTLOGPHASE_PREROTATE:
+ pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_POSTROTATE:
+ pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp);
+ break;
+
+ case RTLOGPHASE_END:
+ pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp);
+ break;
+
+ default:
+ /* nothing */;
+ }
+}
+
+/**
+ * Creates the default release logger outputting to the specified file.
+ *
+ * @return IPRT status code.
+ * @param pszLogFile Filename for log output. Optional.
+ */
+static int vboxGreeterLogCreate(const char *pszLogFile)
+{
+ /* Create release logger (stdout + file). */
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXGREETER_RELEASE_LOG", fFlags, "all",
+ RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT,
+ vboxGreeterLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, pszLogFile);
+ if (RT_SUCCESS(rc))
+ {
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(g_pLoggerRelease);
+
+ /* Explicitly flush the log in case of VBOXGREETER_RELEASE_LOG_FLAGS=buffered. */
+ RTLogFlush(g_pLoggerRelease);
+ }
+
+ return rc;
+}
+
+static void vboxGreeterLogDestroy(void)
+{
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+}
+
+static int vboxGreeterUsage(void)
+{
+ RTPrintf("Usage:\n"
+ " %-12s [-h|-?|--help] [-F|--logfile <file>]\n"
+ " [-v|--verbose] [-V|--version]\n", g_pszProgName);
+
+ RTPrintf("\n"
+ " Copyright (C) 2012-" VBOX_C_YEAR " " VBOX_VENDOR "\n");
+
+ return RTEXITCODE_SYNTAX;
+}
+
+int main(int argc, char **argv)
+{
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+ g_pszProgName = RTPathFilename(argv[0]);
+
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--logfile", 'F', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING }
+ };
+
+ char szLogFile[RTPATH_MAX + 128] = "";
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv,
+ s_aOptions, RT_ELEMENTS(s_aOptions),
+ 1 /*iFirst*/, RTGETOPTINIT_FLAGS_OPTS_FIRST);
+
+ while ( (ch = RTGetOpt(&GetState, &ValueUnion))
+ && RT_SUCCESS(rc))
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'F':
+ if (!RTStrPrintf(szLogFile, sizeof(szLogFile), "%s", ValueUnion.psz))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to get prepare log file name");
+ break;
+
+ case 'h':
+ case '?':
+ return vboxGreeterUsage();
+
+ case 'v': /* Raise verbosity. */
+ g_iVerbosity++;
+ break;
+
+ case 'V': /* Print version and exit. */
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+ break; /* Never reached. */
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_SYNTAX;
+
+ rc = VbglR3InitUser();
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to init Vbgl (%Rrc)", rc);
+
+ rc = vboxGreeterLogCreate(strlen(szLogFile) ? szLogFile : NULL);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create release log (%s, %Rrc)",
+ strlen(szLogFile) ? szLogFile : "<None>", rc);
+
+ vboxGreeterLog("init\n");
+
+ signal(SIGTERM, cb_sigterm);
+
+ /** @todo This function already is too long. Move code into
+ * functions. */
+
+ VBOXGREETERCTX ctx;
+ RT_ZERO(ctx);
+
+ /* UI parameters. */
+ uint32_t uBgColor = 0; /* The background color. */
+ uint32_t uLogonDlgHdrColor = 0;
+ uint32_t uLogonDlgBgColor = 0; /* The greeter's dialog color. */
+ uint32_t uLogonDlgBtnColor = 0; /* The greeter's button color. */
+
+#ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ char szBannerPath[RTPATH_MAX];
+#endif
+
+ /* By default most UI elements are shown. */
+ uint32_t uOptsUI = VBOX_GREETER_UI_SHOW_RESTART
+ | VBOX_GREETER_UI_SHOW_SHUTDOWN;
+#ifdef VBOX_WITH_GUEST_PROPS
+ uint32_t uClientId = 0;
+ rc = VbglR3GuestPropConnect(&uClientId);
+ if (RT_SUCCESS(rc))
+ {
+ vboxGreeterLog("clientId=%RU32\n", uClientId);
+
+ ctx.uClientId = uClientId;
+
+ char szVal[256];
+ int rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/HideRestart",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_SUCCESS(rc2)
+ && !RTStrICmp(szVal, "1"))
+ {
+ uOptsUI &= ~VBOX_GREETER_UI_SHOW_RESTART;
+ }
+
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/HideShutdown",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_SUCCESS(rc2)
+ && !RTStrICmp(szVal, "1"))
+ {
+ uOptsUI &= ~VBOX_GREETER_UI_SHOW_SHUTDOWN;
+ }
+
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ /* Load the banner. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/BannerPath",
+ true /* Read-only on guest */,
+ szBannerPath, sizeof(szBannerPath), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ if (RTFileExists(szBannerPath))
+ {
+ vboxGreeterLog("showing banner from '%s'\n", szBannerPath);
+ uOptsUI |= VBOX_GREETER_UI_SHOW_BANNER;
+ }
+ else
+ vboxGreeterLog("warning: unable to find banner at '%s', skipping\n", szBannerPath);
+ }
+# endif /* VBOX_GREETER_WITH_PNG_SUPPORT */
+
+ /* Use theming?. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/UseTheming",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if ( RT_SUCCESS(rc2)
+ && !RTStrICmp(szVal, "1"))
+ {
+ vboxGreeterLog("custom theming enabled\n");
+ uOptsUI |= VBOX_GREETER_UI_USE_THEMING;
+ }
+
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ {
+ /* Get background color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/BackgroundColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uBgColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+
+ /* Logon dialog. */
+
+ /* Get header color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/LogonDialog/HeaderColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uLogonDlgHdrColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+
+ /* Get dialog color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/LogonDialog/BackgroundColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uLogonDlgBgColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+
+ /* Get button color. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/Greeter/Theme/LogonDialog/ButtonColor",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uLogonDlgBtnColor = strtol(szVal, NULL,
+ /* Change conversion base when having a 0x prefix. */
+ RTStrStr(szVal, "0x") == szVal ? 0 : 16);
+ }
+ }
+ }
+ else
+ vboxGreeterError("unable to connect to guest property service, rc=%Rrc\n", rc);
+#endif
+ vboxGreeterLog("UI options are: %RU32\n", uOptsUI);
+
+#ifdef VBOX_WITH_FLTK
+ int rc2 = Fl::scheme("plastic");
+ if (!rc2)
+ vboxGreeterLog("warning: unable to set visual scheme\n");
+
+ Fl::visual(FL_DOUBLE | FL_INDEX);
+ Fl_Double_Window *pWndMain = new Fl_Double_Window(Fl::w(), Fl::h(), "VirtualBox Guest Additions");
+ AssertPtr(pWndMain);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pWndMain->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uBgColor),
+ VBOX_RGB_COLOR_GREEN(uBgColor),
+ VBOX_RGB_COLOR_BLUE(uBgColor)));
+ else /* Default colors. */
+ pWndMain->color(fl_rgb_color(0x73, 0x7F, 0x8C));
+
+ Fl_Double_Window *pWndGreeter = new Fl_Double_Window(500, 350);
+ AssertPtr(pWndGreeter);
+ pWndGreeter->set_modal();
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pWndGreeter->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgBgColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgBgColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgBgColor)));
+ else /* Default colors. */
+ pWndGreeter->color(fl_rgb_color(255, 255, 255));
+
+ uint32_t uOffsetX = 130;
+ /**
+ * For now we're using a simple Y offset for moving all elements
+ * down if a banner needs to be shown on top of the greeter. Not
+ * very clean but does the job. Use some more layouting stuff
+ * when this gets more complex.
+ */
+ uint32_t uOffsetY = 80;
+
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ fl_register_images();
+
+ /** @todo Add basic image type detection based on file
+ * extension. */
+
+ Fl_PNG_Image *pImgBanner = NULL;
+ if (uOptsUI & VBOX_GREETER_UI_SHOW_BANNER)
+ {
+ pImgBanner = new Fl_PNG_Image(szBannerPath);
+ AssertPtr(pImgBanner);
+
+ /** @todo Make the banner size configurable via guest
+ * properties. For now it's hardcoded to 460 x 90px. */
+ Fl_Box *pBoxBanner = new Fl_Box(20, uOffsetY, 460, 90, "");
+ AssertPtr(pBoxBanner);
+ pBoxBanner->image(pImgBanner);
+
+ uOffsetY = 120;
+ }
+# endif
+
+ Fl_Box *pLblHeader = new Fl_Box(FL_NO_BOX, 242, uOffsetY, 300, 20,
+ "Desktop Login");
+ AssertPtr(pLblHeader);
+
+ /** Note to use an own font:
+ * Fl_Font myfnt = FL_FREE_FONT + 1;
+ * Fl::set_font(myfnt, "MyFont"); */
+ Fl_Font fntHeader = FL_FREE_FONT;
+ Fl::set_font(fntHeader, "Courier");
+
+ pLblHeader->align(FL_ALIGN_LEFT);
+ pLblHeader->labelfont(FL_BOLD);
+ pLblHeader->labelsize(24);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pLblHeader->labelcolor(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgHdrColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgHdrColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgHdrColor)));
+ else /* Default color. */
+ pLblHeader->labelcolor(fl_rgb_color(0x51, 0x5F, 0x77));
+ uOffsetY += 40;
+
+ /** @todo Add basic NLS support. */
+
+ Fl_Input *pEdtUsername = new Fl_Input(uOffsetX, uOffsetY,
+ 300, 20, "User Name");
+ AssertPtr(pEdtUsername);
+ pEdtUsername->callback(cb_edt_username, &ctx);
+ pEdtUsername->when(FL_WHEN_ENTER_KEY_ALWAYS);
+ Fl::focus(pEdtUsername);
+ ctx.pEdtUsername = pEdtUsername;
+
+ Fl_Secret_Input *pEdtPassword = new Fl_Secret_Input(uOffsetX, uOffsetY + 40,
+ 300, 20, "Password");
+ AssertPtr(pEdtPassword);
+ pEdtPassword->callback(cb_edt_password, &ctx);
+ pEdtPassword->when(FL_WHEN_ENTER_KEY_ALWAYS);
+ ctx.pEdtPassword = pEdtPassword;
+
+ Fl_Button *pBtnLogin = new Fl_Button(uOffsetX, uOffsetY + 70,
+ 100, 40, "Log In");
+ AssertPtr(pBtnLogin);
+ pBtnLogin->callback(cb_btn_login, &ctx);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pBtnLogin->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgBtnColor)));
+ else /* Default color. */
+ pBtnLogin->color(fl_rgb_color(255, 255, 255));
+ ctx.pBtnLogin = pBtnLogin;
+
+ Fl_Menu_Button *pBtnMenu = new Fl_Menu_Button(uOffsetX + 120, uOffsetY + 70,
+ 100, 40, "Options");
+ AssertPtr(pBtnMenu);
+ pBtnMenu->callback(cb_btn_menu, &ctx);
+ if (uOptsUI & VBOX_GREETER_UI_USE_THEMING)
+ pBtnMenu->color(fl_rgb_color(VBOX_RGB_COLOR_RED(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_GREEN(uLogonDlgBtnColor),
+ VBOX_RGB_COLOR_BLUE(uLogonDlgBtnColor)));
+ else /* Default color. */
+ pBtnMenu->color(fl_rgb_color(255, 255, 255));
+
+ if (uOptsUI & VBOX_GREETER_UI_SHOW_RESTART)
+ pBtnMenu->add("Restart", "" /* Shortcut */, cb_btn_restart, &ctx, 0 /* Flags */);
+ if (uOptsUI & VBOX_GREETER_UI_SHOW_SHUTDOWN)
+ pBtnMenu->add("Shutdown", "" /* Shortcut */, cb_btn_shutdown, &ctx, 0 /* Flags */);
+
+ char szLabel[255];
+ RTStrPrintf(szLabel, sizeof(szLabel), "Oracle VM VirtualBox Guest Additions %sr%s",
+ RTBldCfgVersion(), RTBldCfgRevisionStr());
+ Fl_Box *pLblInfo = new Fl_Box(FL_NO_BOX , 50, uOffsetY + 150,
+ 400, 20, szLabel);
+ AssertPtr(pLblInfo);
+ ctx.pLblInfo = pLblInfo;
+
+ pWndGreeter->end();
+ pWndGreeter->position((Fl::w() - pWndGreeter->w()) / 2,
+ (Fl::h() - pWndGreeter->h()) / 2);
+
+ pWndMain->fullscreen();
+ pWndMain->show(argc, argv);
+ pWndMain->end();
+
+ pWndGreeter->show();
+#else /* !VBOX_WITH_FLTK */
+ gtk_init(&argc, &argv);
+
+ /* Set default cursor */
+ gdk_window_set_cursor(gdk_get_default_root_window(), gdk_cursor_new(GDK_LEFT_PTR));
+
+ GError *pError = NULL;
+ GtkBuilder *pBuilder = gtk_builder_new();
+ AssertPtr(pBuilder);
+ if (!gtk_builder_add_from_file(pBuilder, "/usr/share/xgreeters/vbox-greeter.ui", &pError))
+ {
+ AssertPtr(pError);
+ vboxGreeterError("unable to load UI: %s", pError->message);
+ return RTEXITCODE_FAILURE;
+ }
+
+ GtkWindow *pWndGreeter = GTK_WINDOW(gtk_builder_get_object(pBuilder, VBOX_GREETER_UI_WND_GREETER));
+ AssertPtr(pWndGreeter);
+ GtkButton *pBtnLogin = GTK_BUTTON(gtk_builder_get_object(pBuilder, VBOX_GREETER_UI_BTN_LOGIN));
+ AssertPtr(pBtnLogin);
+ GtkLabel *pLblInfo = GTK_LABEL(gtk_builder_get_object(pBuilder, VBOX_GREETER_UI_LBL_INFO));
+ AssertPtr(pLblInfo);
+
+ ctx.pBuilder = pBuilder;
+
+ g_signal_connect(G_OBJECT(pBtnLogin), "clicked", G_CALLBACK(cb_btn_login), &ctx);
+
+ GdkRectangle rectScreen;
+ gdk_screen_get_monitor_geometry(gdk_screen_get_default(), gdk_screen_get_primary_monitor(gdk_screen_get_default()), &rectScreen);
+ vboxGreeterLog("monitor (default) is %dx%d\n", rectScreen.width, rectScreen.height);
+
+ gint iWndX, iWndY;
+ gtk_window_get_default_size(pWndGreeter, &iWndX, &iWndY);
+ vboxGreeterLog("greeter is %dx%d\n", iWndX, iWndY);
+
+ gtk_window_move(pWndGreeter,
+ (rectScreen.width / 2) - (iWndX / 2),
+ (rectScreen.height / 2) - (iWndY / 2));
+ gtk_widget_show(GTK_WIDGET(pWndGreeter));
+
+ g_clear_error(&pError);
+#endif /* !VBOX_WITH_FLTK */
+
+ /* GType is needed in any case (for LightDM), whether we
+ * use GTK3 or not. */
+ g_type_init();
+
+ GMainLoop *pMainLoop = g_main_loop_new(NULL, FALSE /* Not yet running */);
+ AssertPtr(pMainLoop); NOREF(pMainLoop);
+
+ LightDMGreeter *pGreeter = lightdm_greeter_new();
+ AssertPtr(pGreeter);
+
+ g_signal_connect(pGreeter, "show-prompt", G_CALLBACK(cb_lightdm_show_prompt), &ctx);
+ g_signal_connect(pGreeter, "show-message", G_CALLBACK(cb_lightdm_show_message), &ctx);
+ g_signal_connect(pGreeter, "authentication-complete", G_CALLBACK(cb_lightdm_auth_complete), &ctx);
+
+ ctx.pGreeter = pGreeter;
+
+ if (!lightdm_greeter_connect_sync(pGreeter, NULL))
+ {
+ vboxGreeterError("unable to connect to LightDM server, aborting\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ vboxGreeterLog("connected to LightDM server\n");
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ bool fCheckCreds = false;
+ if (uClientId) /* Connected to guest property service? */
+ {
+ char szVal[256];
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWait",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uint32_t uTimeoutMS = RT_INDEFINITE_WAIT; /* Wait infinite by default. */
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitTimeout",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+ uTimeoutMS = RTStrToUInt32(szVal);
+ if (!uTimeoutMS)
+ {
+ vboxGreeterError("pam_vbox_authenticate: invalid waiting timeout value specified, defaulting to infinite timeout\n");
+ uTimeoutMS = RT_INDEFINITE_WAIT;
+ }
+ else
+ uTimeoutMS = uTimeoutMS * 1000; /* Make ms out of s. */
+ }
+
+ ctx.uTimeoutMS = uTimeoutMS;
+
+ rc2 = vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsMsgWaiting",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), NULL /* Timestamp. */);
+ if (RT_SUCCESS(rc2))
+ {
+# ifdef VBOX_WITH_FLTK
+ Assert(pLblInfo);
+ pLblInfo->copy_label(szVal);
+# else
+ gtk_label_set_text(pLblInfo, szVal);
+# endif
+ }
+
+ /* Get initial timestamp so that we can compare the time
+ * whether the value has been changed or not in our event callback. */
+ vbox_read_prop(uClientId,
+ "/VirtualBox/GuestAdd/PAM/CredsWaitAbort",
+ true /* Read-only on guest */,
+ szVal, sizeof(szVal), &ctx.uTsAbort);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Before we actuall wait for credentials just make sure we didn't already get credentials
+ * set so that we can skip waiting for them ... */
+ rc2 = vboxGreeterCheckCreds(&ctx);
+ if (rc2 == VERR_NOT_FOUND)
+ {
+ /* Get current time stamp to later calculate rest of timeout left. */
+ ctx.uStartMS = RTTimeMilliTS();
+
+ fCheckCreds = true;
+ }
+ }
+ }
+
+ /* Start the timer to check credentials availability. */
+ if (fCheckCreds)
+ {
+ vboxGreeterLog("No credentials available on startup, starting to check periodically ...\n");
+# ifdef VBOX_WITH_FLTK
+ Fl::add_timeout(0.5 /* 500 ms */, cb_check_creds, &ctx);
+# else
+ g_timeout_add(500 /* ms */, (GSourceFunc)cb_check_creds, &ctx);
+# endif
+ }
+ }
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+#ifdef VBOX_WITH_FLTK
+ /*
+ * Do own GDK main loop processing because FLTK also needs
+ * to have the chance of processing its events.
+ */
+ GMainContext *pMainCtx = g_main_context_default();
+ AssertPtr(pMainCtx);
+
+ while (g_fRunning)
+ {
+ g_main_context_iteration(pMainCtx,
+ FALSE /* No blocking */);
+ Fl::check();
+ RTThreadSleep(10); /* Wait a bit, don't hog the CPU too much. */
+ }
+
+ g_main_context_unref(pMainCtx);
+
+# ifdef VBOX_GREETER_WITH_PNG_SUPPORT
+ if (pImgBanner)
+ {
+ delete pImgBanner; /* Call destructor to free bitmap data. */
+ pImgBanner = NULL;
+ }
+# endif /* VBOX_GREETER_WITH_PNG_SUPPORT */
+#else /* !VBOX_WITH_FLTK */
+ gtk_main();
+ /** @todo Never reached so far. LightDM sends a SIGTERM. */
+#endif /* !VBOX_WITH_FLTK */
+
+ vboxGreeterLog("terminating\n");
+
+#ifdef VBOX_WITH_GUEST_PROPS
+ if (uClientId)
+ {
+ rc2 = VbglR3GuestPropDisconnect(uClientId);
+ AssertRC(rc2);
+ }
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ VbglR3Term();
+
+ RTEXITCODE rcExit = RT_SUCCESS(rc)
+ ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+
+ vboxGreeterLog("terminated with exit code %ld (rc=%Rrc)\n",
+ rcExit, rc);
+
+ vboxGreeterLogDestroy();
+
+ return rcExit;
+}
+
+#ifdef DEBUG
+DECLEXPORT(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ RTAssertMsg1(pszExpr, uLine, pszFile, pszFunction);
+}
+#endif
+
diff --git a/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop
new file mode 100644
index 00000000..b3946b38
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.desktop
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Name=VirtualBox Greeter
+Comment=Provides Single Sign-On (SSO) support
+Exec=/usr/sbin/vbox-greeter
+Type=Application
diff --git a/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui
new file mode 100644
index 00000000..76719b39
--- /dev/null
+++ b/src/VBox/Additions/linux/lightdm-greeter/vbox-greeter.ui
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkWindow" id="wnd_greeter">
+ <property name="width_request">512</property>
+ <property name="height_request">348</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">VirtualBox Guest Additions Login</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center</property>
+ <property name="default_width">512</property>
+ <property name="default_height">348</property>
+ <property name="has_resize_grip">False</property>
+ <child>
+ <object class="GtkNotebook" id="nb_greeter">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkDrawingArea" id="drawingarea1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkAspectFrame" id="aspectframe3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkGrid" id="grid2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkEntry" id="edt_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="edt_username">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="invisible_char_set">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">20</property>
+ <property name="xalign">0</property>
+ <property name="ypad">10</property>
+ <property name="label" translatable="yes">Username</property>
+ <property name="justify">center</property>
+ <attributes>
+ <attribute name="weight" value="ultraheavy"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">20</property>
+ <property name="xalign">0</property>
+ <property name="ypad">10</property>
+ <property name="label" translatable="yes">Password</property>
+ <attributes>
+ <attribute name="weight" value="ultraheavy"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="btn_login">
+ <property name="label" translatable="yes">Login</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lbl_info">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">end</property>
+ <property name="label" translatable="yes">VirtualBox Guest Additions</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="lblLogin">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Login</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAspectFrame" id="aspectframe2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">VirtualBox Guest Additions Auto-Logon</property>
+ <property name="justify">center</property>
+ <attributes>
+ <attribute name="weight" value="ultraheavy"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="lblInformation">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Information</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
+
diff --git a/src/VBox/Additions/linux/sharedfolders/.scm-settings b/src/VBox/Additions/linux/sharedfolders/.scm-settings
new file mode 100644
index 00000000..64190d85
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/.scm-settings
@@ -0,0 +1,39 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for linux shared folders module.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+/*.c|/*.h: --no-convert-tabs
+/Makefile.module: --treat-as Makefile
+
+# MIT licence to make it easier to re-import code from the in-kernel version.
+/dirops.c: --license-mit
+/lnkops.c: --license-mit
+/regops.c: --license-mit
+/utils.c: --license-mit
+/vbsfmount.h: --license-mit
+/vfsmod.c: --license-mit
+/vfsmod.h: --license-mit
diff --git a/src/VBox/Additions/linux/sharedfolders/Makefile.kmk b/src/VBox/Additions/linux/sharedfolders/Makefile.kmk
new file mode 100644
index 00000000..50992172
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/Makefile.kmk
@@ -0,0 +1,57 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the vboxsf (linux shared folders module).
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+#
+# Populate FILES_VBOXSF_NOBIN
+#
+INSTALLS += vboxsf-src
+include $(PATH_SUB_CURRENT)/files_vboxsf
+vboxsf-src_INST = $(INST_ADDITIONS)src/vboxsf/
+vboxsf-src_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXSF_NOBIN))
+vboxsf-src_EXEC_SOURCES = \
+ $(subst $(DQUOTE),,$(FILES_VBOXSF_BIN))
+
+# Build test for the Guest Additions kernel modules (kmk check).
+$(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,vboxsf-src,vboxguest-src,)
+
+#
+# The mount util.
+#
+PROGRAMS += mount.vboxsf
+mount.vboxsf_TEMPLATE = VBoxGuestR3Exe
+mount.vboxsf_DEFS = _GNU_SOURCE
+mount.vboxsf_SOURCES = \
+ mount.vboxsf.c \
+ vbsfmount.c
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/linux/sharedfolders/Makefile.module b/src/VBox/Additions/linux/sharedfolders/Makefile.module
new file mode 100644
index 00000000..3eeb976c
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/Makefile.module
@@ -0,0 +1,119 @@
+# $Id: Makefile.module $
+## @file
+# VBox Linux Shared Folders VFS Module Makefile.
+#
+# (For 2.6.x this file must be 'Makefile'!)
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Linux kbuild sets this to our source directory if we are called from there
+obj ?= $(CURDIR)
+include $(obj)/Makefile-header.gmk
+VBOXSF_DIR = $(VBOX_MODULE_SRC_DIR)
+
+# Allow building directly from the subdirectory without assuming the toplevel
+# makefile has done the copying. Not the default use case, but can be handy.
+ifndef KBUILD_EXTRA_SYMBOLS
+KBUILD_EXTRA_SYMBOLS=$(abspath $(VBOXSF_DIR)/../vboxguest/Module.symvers)
+endif
+
+VBOXMOD_NAME = vboxsf
+VBOXMOD_OBJS = \
+ vfsmod.o \
+ dirops.o \
+ lnkops.o \
+ regops.o \
+ utils.o \
+ VBoxGuestR0LibGenericRequest.o \
+ VBoxGuestR0LibHGCM.o \
+ VBoxGuestR0LibIdc.o \
+ VBoxGuestR0LibIdc-unix.o \
+ VBoxGuestR0LibInit.o \
+ VBoxGuestR0LibPhysHeap.o \
+ VBoxGuestR0LibSharedFolders.o
+ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86)
+VBOXMOD_OBJS += \
+ divdi3.o \
+ moddi3.o \
+ udivdi3.o \
+ udivmoddi4.o \
+ umoddi3.o \
+ qdivrem.o
+endif
+VBOXMOD_INCL = \
+ $(VBOXSF_DIR) \
+ $(VBOXSF_DIR)include \
+ $(VBOXSF_DIR)r0drv/linux
+VBOXMOD_DEFS = \
+ RT_OS_LINUX \
+ IN_RING0 \
+ IN_RT_R0 \
+ IN_SUP_R0 \
+ VBOX \
+ VBOX_WITH_HGCM \
+ IN_MODULE \
+ IN_GUEST \
+ IN_GUEST_R0 \
+ RT_NO_EXPORT_SYMBOL
+ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64)
+VBOXMOD_DEFS += VBOX_WITH_64_BITS_GUESTS
+endif
+ifneq ($(filter %uek.x86_64,$(KERN_VER)),)
+VBOXMOD_DEFS += VBOX_UEK
+endif
+VBOXMOD_CFLAGS := $(call VBOX_GCC_CHECK_CC,-Wno-declaration-after-statement,-Wno-declaration-after-statement,,)
+VBOXMOD_CFLAGS += $(call VBOX_GCC_CHECK_CC,-fno-pie,-fno-pie,,)
+ifneq ($(KERN_VERSION),24)
+VBOXMOD_CFLAGS += -include $(VBOXSF_DIR)/include/VBox/VBoxGuestMangling.h
+## @todo r-bird: What's with -fshort-wchar here?? We either need that or we dont, right? It should be 2.6+ only.
+VBOXMOD_CFLAGS += -fshort-wchar
+endif
+ifdef VBOX_NO_OMIT_FRAME_POINTER
+VBOXMOD_CFLAGS += -fno-omit-frame-pointer
+endif
+
+ifneq ($(KERN_VERSION),24)
+# special hack for Fedora Core 6 2.6.18 (fc6), rhel5 2.6.18 (el5),
+# ClarkConnect 4.3 (cc4) and ClarkConnect 5 (v5)
+ ifeq ($(KERNELRELEASE),)
+VBOXMOD_CFLAGS += $(foreach inc,$(KERN_INCL),\
+ $(if $(wildcard $(inc)/linux/utsrelease.h),\
+ $(if $(shell grep '"2.6.18.*fc6.*"' $(inc)/linux/utsrelease.h; \
+ grep '"2.6.18.*el5.*"' $(inc)/linux/utsrelease.h; \
+ grep '"2.6.18.*v5.*"' $(inc)/linux/utsrelease.h; \
+ grep '"2.6.18.*cc4.*"' $(inc)/linux/utsrelease.h),\
+ -DKERNEL_FC6,),))
+ else
+VBOXMOD_CFLAGS += $(if $(shell echo "$(KERNELRELEASE)"|grep '2.6.18.*fc6.*';\
+ echo "$(KERNELRELEASE)"|grep '2.6.18.*el5.*';\
+ echo "$(KERNELRELEASE)"|grep '2.6.18.*v5.*';\
+ echo "$(KERNELRELEASE)"|grep '2.6.18.*cc4.*'),\
+ -DKERNEL_FC6,)
+ endif
+endif
+
+VBOXMOD_CLEAN = . linux r0drv r0drv/linux
+
+include $(obj)/Makefile-footer.gmk
+
diff --git a/src/VBox/Additions/linux/sharedfolders/dirops.c b/src/VBox/Additions/linux/sharedfolders/dirops.c
new file mode 100644
index 00000000..fb8c8d59
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/dirops.c
@@ -0,0 +1,1427 @@
+/* $Id: dirops.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, directory inode and file operations.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+#include <iprt/err.h>
+
+#if RTLNX_VER_MAX(4,7,0)
+# define d_in_lookup(a_pDirEntry) (d_unhashed(a_pDirEntry))
+#endif
+
+
+
+/**
+ * Open a directory (implements file_operations::open).
+ *
+ * @returns 0 on success, negative errno otherwise.
+ * @param inode inode
+ * @param file file
+ */
+static int vbsf_dir_open(struct inode *inode, struct file *file)
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct dentry *dentry = VBSF_GET_F_DENTRY(file);
+ struct vbsf_dir_info *sf_d;
+ int rc;
+
+ SFLOGFLOW(("vbsf_dir_open: inode=%p file=%p %s\n", inode, file, sf_i && sf_i->path ? sf_i->path->String.ach : NULL));
+ AssertReturn(pSuperInfo, -EINVAL);
+ AssertReturn(sf_i, -EINVAL);
+ AssertReturn(!file->private_data, 0);
+
+ /*
+ * Allocate and initialize our directory info structure.
+ * We delay buffer allocation until vbsf_getdent is actually used.
+ */
+ sf_d = kmalloc(sizeof(*sf_d), GFP_KERNEL);
+ if (sf_d) {
+ VBOXSFCREATEREQ *pReq;
+ RT_ZERO(*sf_d);
+ sf_d->u32Magic = VBSF_DIR_INFO_MAGIC;
+ sema_init(&sf_d->Lock, 1);
+
+ /*
+ * Try open the directory.
+ */
+ pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFCREATEREQ, StrPath.String) + sf_i->path->u16Size);
+ if (pReq) {
+ RT_BCOPY_UNFORTIFIED(&pReq->StrPath, sf_i->path, SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ RT_ZERO(pReq->CreateParms);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_DIRECTORY
+ | SHFL_CF_ACT_OPEN_IF_EXISTS
+ | SHFL_CF_ACT_FAIL_IF_NEW
+ | SHFL_CF_ACCESS_READ;
+
+ LogFunc(("calling VbglR0SfHostReqCreate on folder %s, flags %#x\n",
+ sf_i->path->String.utf8, pReq->CreateParms.CreateFlags));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ Assert(pReq->CreateParms.Handle != SHFL_HANDLE_NIL);
+
+ /*
+ * Update the inode info with fresh stats and increase the TTL for the
+ * dentry cache chain that got us here.
+ */
+ vbsf_update_inode(inode, sf_i, &pReq->CreateParms.Info, pSuperInfo,
+ true /*fLocked*/ /** @todo inode locking */, 0 /*fSetAttrs*/);
+ vbsf_dentry_chain_increase_ttl(dentry);
+
+ sf_d->Handle.hHost = pReq->CreateParms.Handle;
+ sf_d->Handle.cRefs = 1;
+ sf_d->Handle.fFlags = VBSF_HANDLE_F_READ | VBSF_HANDLE_F_DIR | VBSF_HANDLE_F_MAGIC;
+ vbsf_handle_append(sf_i, &sf_d->Handle);
+
+ file->private_data = sf_d;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_dir_open(%p,%p): returns 0; hHost=%#llx\n", inode, file, sf_d->Handle.hHost));
+ return 0;
+
+ }
+ Assert(pReq->CreateParms.Handle == SHFL_HANDLE_NIL);
+
+ /*
+ * Directory does not exist, so we probably got some invalid
+ * dir cache and inode info.
+ */
+ /** @todo do more to invalidate dentry and inode here. */
+ vbsf_dentry_invalidate_ttl(dentry);
+ sf_i->force_restat = true;
+ rc = -ENOENT;
+ } else
+ rc = -EPERM;
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ LogRelMaxFunc(64, ("failed to allocate %zu bytes for '%s'\n",
+ RT_UOFFSETOF(VBOXSFCREATEREQ, StrPath.String) + sf_i->path->u16Size, sf_i->path->String.ach));
+ rc = -ENOMEM;
+ }
+ sf_d->u32Magic = VBSF_DIR_INFO_MAGIC_DEAD;
+ kfree(sf_d);
+ } else
+ rc = -ENOMEM;
+ SFLOGFLOW(("vbsf_dir_open(%p,%p): returns %d\n", inode, file, rc));
+ return rc;
+}
+
+
+/**
+ * This is called when reference count of [file] goes to zero. Notify
+ * the host that it can free whatever is associated with this directory
+ * and deallocate our own internal buffers
+ *
+ * @param inode inode
+ * @param file file
+ * returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_dir_release(struct inode *inode, struct file *file)
+{
+ struct vbsf_dir_info *sf_d = (struct vbsf_dir_info *)file->private_data;
+
+ SFLOGFLOW(("vbsf_dir_release(%p,%p): sf_d=%p hHost=%#llx\n", inode, file, sf_d, sf_d ? sf_d->Handle.hHost : SHFL_HANDLE_NIL));
+
+ if (sf_d) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+
+ /* Invalidate the non-handle part. */
+ sf_d->u32Magic = VBSF_DIR_INFO_MAGIC_DEAD;
+ sf_d->cEntriesLeft = 0;
+ sf_d->cbValid = 0;
+ sf_d->pEntry = NULL;
+ sf_d->fNoMoreFiles = false;
+ if (sf_d->pBuf) {
+ kfree(sf_d->pBuf);
+ sf_d->pBuf = NULL;
+ }
+
+ /* Closes the handle and frees the structure when the last reference is released. */
+ vbsf_handle_release(&sf_d->Handle, pSuperInfo, "vbsf_dir_release");
+ }
+
+ return 0;
+}
+
+
+/**
+ * Translate RTFMODE into DT_xxx (in conjunction to rtDirType()).
+ * returns d_type
+ * @param fMode file mode
+ */
+DECLINLINE(int) vbsf_get_d_type(RTFMODE fMode)
+{
+ switch (fMode & RTFS_TYPE_MASK) {
+ case RTFS_TYPE_FIFO: return DT_FIFO;
+ case RTFS_TYPE_DEV_CHAR: return DT_CHR;
+ case RTFS_TYPE_DIRECTORY: return DT_DIR;
+ case RTFS_TYPE_DEV_BLOCK: return DT_BLK;
+ case RTFS_TYPE_FILE: return DT_REG;
+ case RTFS_TYPE_SYMLINK: return DT_LNK;
+ case RTFS_TYPE_SOCKET: return DT_SOCK;
+ case RTFS_TYPE_WHITEOUT: return DT_WHT;
+ }
+ return DT_UNKNOWN;
+}
+
+
+/**
+ * Refills the buffer with more entries.
+ *
+ * @returns 0 on success, negative errno on error,
+ */
+static int vbsf_dir_read_more(struct vbsf_dir_info *sf_d, struct vbsf_super_info *pSuperInfo, bool fRestart)
+{
+ int rc;
+ VBOXSFLISTDIRREQ *pReq;
+
+ /*
+ * Don't call the host again if we've reached the end of the
+ * directory entries already.
+ */
+ if (sf_d->fNoMoreFiles) {
+ if (!fRestart) {
+ SFLOGFLOW(("vbsf_dir_read_more: no more files\n"));
+ return 0;
+ }
+ sf_d->fNoMoreFiles = false;
+ }
+
+ /*
+ * Make sure we've got some kind of buffers.
+ */
+ if (sf_d->pBuf) {
+ /* Likely, except for the first time. */
+ } else {
+ sf_d->pBuf = (PSHFLDIRINFO)kmalloc(pSuperInfo->cbDirBuf, GFP_KERNEL);
+ if (sf_d->pBuf)
+ sf_d->cbBuf = pSuperInfo->cbDirBuf;
+ else {
+ sf_d->pBuf = (PSHFLDIRINFO)kmalloc(_4K, GFP_KERNEL);
+ if (!sf_d->pBuf) {
+ LogRelMax(10, ("vbsf_dir_read_more: Failed to allocate buffer!\n"));
+ return -ENOMEM;
+ }
+ sf_d->cbBuf = _4K;
+ }
+ }
+
+ /*
+ * Allocate a request buffer.
+ */
+ pReq = (VBOXSFLISTDIRREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ rc = VbglR0SfHostReqListDirContig2x(pSuperInfo->map.root, pReq, sf_d->Handle.hHost, NULL, NIL_RTGCPHYS64,
+ fRestart ? SHFL_LIST_RESTART : SHFL_LIST_NONE,
+ sf_d->pBuf, virt_to_phys(sf_d->pBuf), sf_d->cbBuf);
+ if (RT_SUCCESS(rc)) {
+ sf_d->pEntry = sf_d->pBuf;
+ sf_d->cbValid = pReq->Parms.cb32Buffer.u.value32;
+ sf_d->cEntriesLeft = pReq->Parms.c32Entries.u.value32;
+ sf_d->fNoMoreFiles = pReq->Parms.f32More.u.value32 == 0;
+ } else {
+ sf_d->pEntry = sf_d->pBuf;
+ sf_d->cbValid = 0;
+ sf_d->cEntriesLeft = 0;
+ if (rc == VERR_NO_MORE_FILES) {
+ sf_d->fNoMoreFiles = true;
+ rc = 0;
+ } else {
+ /* In theory we could end up here with a buffer overflow, but
+ with a 4KB minimum buffer size that's very unlikely with the
+ typical filename length of today's file systems (2019). */
+ LogRelMax(16, ("vbsf_dir_read_more: VbglR0SfHostReqListDirContig2x -> %Rrc\n", rc));
+ rc = -EPROTO;
+ }
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ SFLOGFLOW(("vbsf_dir_read_more: returns %d; cbValid=%#x cEntriesLeft=%#x fNoMoreFiles=%d\n",
+ rc, sf_d->cbValid, sf_d->cEntriesLeft, sf_d->fNoMoreFiles));
+ return rc;
+}
+
+
+/**
+ * Helper function for when we need to convert the name, avoids wasting stack in
+ * the UTF-8 code path.
+ */
+DECL_NO_INLINE(static, bool) vbsf_dir_emit_nls(
+# if RTLNX_VER_MIN(3,11,0)
+ struct dir_context *ctx,
+# else
+ void *opaque, filldir_t filldir, loff_t offPos,
+# endif
+ const char *pszSrcName, uint16_t cchSrcName, ino_t d_ino, int d_type,
+ struct vbsf_super_info *pSuperInfo)
+{
+ char szDstName[NAME_MAX];
+ int rc = vbsf_nlscpy(pSuperInfo, szDstName, sizeof(szDstName), pszSrcName, cchSrcName);
+ if (rc == 0) {
+#if RTLNX_VER_MIN(3,11,0)
+ return dir_emit(ctx, szDstName, strlen(szDstName), d_ino, d_type);
+#else
+ return filldir(opaque, szDstName, strlen(szDstName), offPos, d_ino, d_type) == 0;
+#endif
+ }
+
+ /* Assuming this is a buffer overflow issue, just silently skip it. */
+ SFLOGFLOW(("vbsf_dir_emit_nls: vbsf_nlscopy failed with %d for '%s'\n", rc, pszSrcName));
+ return true;
+}
+
+
+/**
+ * This is called when vfs wants to populate internal buffers with
+ * directory [dir]s contents. [opaque] is an argument to the
+ * [filldir]. [filldir] magically modifies it's argument - [opaque]
+ * and takes following additional arguments (which i in turn get from
+ * the host via vbsf_getdent):
+ *
+ * name : name of the entry (i must also supply it's length huh?)
+ * type : type of the entry (FILE | DIR | etc) (i ellect to use DT_UNKNOWN)
+ * pos : position/index of the entry
+ * ino : inode number of the entry (i fake those)
+ *
+ * [dir] contains:
+ * f_pos : cursor into the directory listing
+ * private_data : mean of communication with the host side
+ *
+ * Extract elements from the directory listing (incrementing f_pos
+ * along the way) and feed them to [filldir] until:
+ *
+ * a. there are no more entries (i.e. vbsf_getdent set done to 1)
+ * b. failure to compute fake inode number
+ * c. filldir returns an error (see comment on that)
+ */
+#if RTLNX_VER_MIN(3,11,0)
+static int vbsf_dir_iterate(struct file *dir, struct dir_context *ctx)
+#else
+static int vbsf_dir_read(struct file *dir, void *opaque, filldir_t filldir)
+#endif
+{
+#if RTLNX_VER_MIN(3,11,0)
+ loff_t offPos = ctx->pos;
+#else
+ loff_t offPos = dir->f_pos;
+#endif
+ struct vbsf_dir_info *sf_d = (struct vbsf_dir_info *)dir->private_data;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(VBSF_GET_F_DENTRY(dir)->d_sb);
+ int rc;
+
+ /*
+ * Lock the directory info structures.
+ */
+ if (RT_LIKELY(down_interruptible(&sf_d->Lock) == 0)) {
+ /* likely */
+ } else
+ return -ERESTARTSYS;
+
+ /*
+ * Any seek performed in the mean time?
+ */
+ if (offPos == sf_d->offPos) {
+ /* likely */
+ } else {
+ /* Restart the search if iPos is lower than the current buffer position. */
+ loff_t offCurEntry = sf_d->offPos;
+ if (offPos < offCurEntry) {
+ rc = vbsf_dir_read_more(sf_d, pSuperInfo, true /*fRestart*/);
+ if (rc == 0)
+ offCurEntry = 0;
+ else {
+ up(&sf_d->Lock);
+ return rc;
+ }
+ }
+
+ /* Skip ahead to offPos. */
+ while (offCurEntry < offPos) {
+ uint32_t cEntriesLeft = sf_d->cEntriesLeft;
+ if ((uint64_t)(offPos - offCurEntry) >= cEntriesLeft) {
+ /* Skip the current buffer and read the next: */
+ offCurEntry += cEntriesLeft;
+ sf_d->offPos = offCurEntry;
+ sf_d->cEntriesLeft = 0;
+ rc = vbsf_dir_read_more(sf_d, pSuperInfo, false /*fRestart*/);
+ if (rc != 0 || sf_d->cEntriesLeft == 0) {
+ up(&sf_d->Lock);
+ return rc;
+ }
+ } else {
+ do
+ {
+ PSHFLDIRINFO pEntry = sf_d->pEntry;
+ pEntry = (PSHFLDIRINFO)&pEntry->name.String.utf8[pEntry->name.u16Length];
+ AssertLogRelBreakStmt( cEntriesLeft == 1
+ || (uintptr_t)pEntry - (uintptr_t)sf_d->pBuf
+ <= sf_d->cbValid - RT_UOFFSETOF(SHFLDIRINFO, name.String),
+ sf_d->cEntriesLeft = 0);
+ sf_d->cEntriesLeft = --cEntriesLeft;
+ sf_d->offPos = ++offCurEntry;
+ } while (offPos < sf_d->offPos);
+ }
+ }
+ }
+
+ /*
+ * Handle '.' and '..' specially so we get the inode numbers right.
+ * We'll skip any '.' or '..' returned by the host (included in pos,
+ * however, to simplify the above skipping code).
+ */
+ if (offPos < 2) {
+#if RTLNX_VER_MIN(3,11,0)
+ if (offPos == 0) {
+ if (dir_emit_dot(dir, ctx))
+ dir->f_pos = ctx->pos = sf_d->offPos = offPos = 1;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+ if (offPos == 1) {
+ if (dir_emit_dotdot(dir, ctx))
+ dir->f_pos = ctx->pos = sf_d->offPos = offPos = 2;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+#else
+ if (offPos == 0) {
+ rc = filldir(opaque, ".", 1, 0, VBSF_GET_F_DENTRY(dir)->d_inode->i_ino, DT_DIR);
+ if (!rc)
+ dir->f_pos = sf_d->offPos = offPos = 1;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+ if (offPos == 1) {
+# if RTLNX_VER_MIN(2,5,5)
+ rc = filldir(opaque, "..", 2, 1, parent_ino(VBSF_GET_F_DENTRY(dir)), DT_DIR);
+# else
+ rc = filldir(opaque, "..", 2, 1, VBSF_GET_F_DENTRY(dir)->d_parent->d_inode->i_ino, DT_DIR);
+# endif
+ if (!rc)
+ dir->f_pos = sf_d->offPos = offPos = 2;
+ else {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+#endif
+ }
+
+ /*
+ * Produce stuff.
+ */
+ Assert(offPos == sf_d->offPos);
+ for (;;) {
+ PSHFLDIRINFO pBuf;
+ PSHFLDIRINFO pEntry;
+
+ /*
+ * Do we need to read more?
+ */
+ uint32_t cbValid = sf_d->cbValid;
+ uint32_t cEntriesLeft = sf_d->cEntriesLeft;
+ if (!cEntriesLeft) {
+ rc = vbsf_dir_read_more(sf_d, pSuperInfo, false /*fRestart*/);
+ if (rc == 0) {
+ cEntriesLeft = sf_d->cEntriesLeft;
+ if (!cEntriesLeft) {
+ up(&sf_d->Lock);
+ return 0;
+ }
+ cbValid = sf_d->cbValid;
+ } else {
+ up(&sf_d->Lock);
+ return rc;
+ }
+ }
+
+ /*
+ * Feed entries to the caller.
+ */
+ pBuf = sf_d->pBuf;
+ pEntry = sf_d->pEntry;
+ do {
+ /*
+ * Validate the entry in case the host is messing with us.
+ * We're ASSUMING the host gives us a zero terminated string (UTF-8) here.
+ */
+ uintptr_t const offEntryInBuf = (uintptr_t)pEntry - (uintptr_t)pBuf;
+ uint16_t cbSrcName;
+ uint16_t cchSrcName;
+ AssertLogRelMsgBreak(offEntryInBuf + RT_UOFFSETOF(SHFLDIRINFO, name.String) <= cbValid,
+ ("%#llx + %#x vs %#x\n", offEntryInBuf, RT_UOFFSETOF(SHFLDIRINFO, name.String), cbValid));
+ cbSrcName = pEntry->name.u16Size;
+ cchSrcName = pEntry->name.u16Length;
+ AssertLogRelBreak(offEntryInBuf + RT_UOFFSETOF(SHFLDIRINFO, name.String) + cbSrcName <= cbValid);
+ AssertLogRelBreak(cchSrcName < cbSrcName);
+ AssertLogRelBreak(pEntry->name.String.ach[cchSrcName] == '\0');
+
+ /*
+ * Filter out '.' and '..' entires.
+ */
+ if ( cchSrcName > 2
+ || pEntry->name.String.ach[0] != '.'
+ || ( cchSrcName == 2
+ && pEntry->name.String.ach[1] != '.')) {
+ int const d_type = vbsf_get_d_type(pEntry->Info.Attr.fMode);
+ ino_t const d_ino = (ino_t)offPos + 0xbeef; /* very fake */
+ bool fContinue;
+ if (pSuperInfo->fNlsIsUtf8) {
+#if RTLNX_VER_MIN(3,11,0)
+ fContinue = dir_emit(ctx, pEntry->name.String.ach, cchSrcName, d_ino, d_type);
+#else
+ fContinue = filldir(opaque, pEntry->name.String.ach, cchSrcName, offPos, d_ino, d_type) == 0;
+#endif
+ } else {
+#if RTLNX_VER_MIN(3,11,0)
+ fContinue = vbsf_dir_emit_nls(ctx, pEntry->name.String.ach, cchSrcName, d_ino, d_type, pSuperInfo);
+#else
+ fContinue = vbsf_dir_emit_nls(opaque, filldir, offPos, pEntry->name.String.ach, cchSrcName,
+ d_ino, d_type, pSuperInfo);
+#endif
+ }
+ if (fContinue) {
+ /* likely */
+ } else {
+ sf_d->cEntriesLeft = cEntriesLeft;
+ sf_d->pEntry = pEntry;
+ sf_d->offPos = offPos;
+ up(&sf_d->Lock);
+ return 0;
+ }
+ }
+
+ /*
+ * Advance to the next entry.
+ */
+ pEntry = (PSHFLDIRINFO)((uintptr_t)pEntry + RT_UOFFSETOF(SHFLDIRINFO, name.String) + cbSrcName);
+ offPos += 1;
+ dir->f_pos = offPos;
+#if RTLNX_VER_MIN(3,11,0)
+ ctx->pos = offPos;
+#endif
+ cEntriesLeft -= 1;
+ } while (cEntriesLeft > 0);
+
+ /* Done with all available entries. */
+ sf_d->offPos = offPos + cEntriesLeft;
+ sf_d->pEntry = pBuf;
+ sf_d->cEntriesLeft = 0;
+ }
+}
+
+
+/**
+ * Directory file operations.
+ */
+struct file_operations vbsf_dir_fops = {
+ .open = vbsf_dir_open,
+#if RTLNX_VER_MIN(4,7,0)
+ .iterate_shared = vbsf_dir_iterate,
+#elif RTLNX_VER_MIN(3,11,0)
+ .iterate = vbsf_dir_iterate,
+#else
+ .readdir = vbsf_dir_read,
+#endif
+ .release = vbsf_dir_release,
+ .read = generic_read_dir,
+#if RTLNX_VER_MIN(2,6,37)
+ .llseek = generic_file_llseek
+#endif
+};
+
+
+
+/*********************************************************************************************************************************
+* Directory Inode Operations *
+*********************************************************************************************************************************/
+
+/**
+ * Worker for vbsf_inode_lookup(), vbsf_create_worker() and
+ * vbsf_inode_instantiate().
+ */
+static struct inode *vbsf_create_inode(struct inode *parent, struct dentry *dentry, PSHFLSTRING path,
+ PSHFLFSOBJINFO pObjInfo, struct vbsf_super_info *pSuperInfo, bool fInstantiate)
+{
+ /*
+ * Allocate memory for our additional inode info and create an inode.
+ */
+ struct vbsf_inode_info *sf_new_i = (struct vbsf_inode_info *)kmalloc(sizeof(*sf_new_i), GFP_KERNEL);
+ if (sf_new_i) {
+ ino_t iNodeNo = iunique(parent->i_sb, 16);
+#if RTLNX_VER_MIN(2,4,25)
+ struct inode *pInode = iget_locked(parent->i_sb, iNodeNo);
+#else
+ struct inode *pInode = iget(parent->i_sb, iNodeNo);
+#endif
+ if (pInode) {
+ /*
+ * Initialize the two structures.
+ */
+#ifdef VBOX_STRICT
+ sf_new_i->u32Magic = SF_INODE_INFO_MAGIC;
+#endif
+ sf_new_i->path = path;
+ sf_new_i->force_restat = false;
+ sf_new_i->ts_up_to_date = jiffies;
+ RTListInit(&sf_new_i->HandleList);
+ sf_new_i->handle = SHFL_HANDLE_NIL;
+
+ VBSF_SET_INODE_INFO(pInode, sf_new_i);
+ vbsf_init_inode(pInode, sf_new_i, pObjInfo, pSuperInfo);
+
+ /*
+ * Before we unlock the new inode, we may need to call d_instantiate.
+ */
+ if (fInstantiate)
+ d_instantiate(dentry, pInode);
+#if RTLNX_VER_MIN(2,4,25)
+ unlock_new_inode(pInode);
+#endif
+ return pInode;
+
+ }
+ LogFunc(("iget failed\n"));
+ kfree(sf_new_i);
+ } else
+ LogRelFunc(("could not allocate memory for new inode info\n"));
+ return NULL;
+}
+
+
+/** Helper for vbsf_create_worker() and vbsf_inode_lookup() that wraps
+ * d_add() and setting d_op. */
+DECLINLINE(void) vbsf_d_add_inode(struct dentry *dentry, struct inode *pNewInode)
+{
+#if RTLNX_VER_MIN(2,6,38)
+ Assert(dentry->d_op == &vbsf_dentry_ops); /* (taken from the superblock) */
+#else
+ dentry->d_op = &vbsf_dentry_ops;
+#endif
+ d_add(dentry, pNewInode);
+}
+
+
+/**
+ * This is called when vfs failed to locate dentry in the cache. The
+ * job of this function is to allocate inode and link it to dentry.
+ * [dentry] contains the name to be looked in the [parent] directory.
+ * Failure to locate the name is not a "hard" error, in this case NULL
+ * inode is added to [dentry] and vfs should proceed trying to create
+ * the entry via other means. NULL(or "positive" pointer) ought to be
+ * returned in case of success and "negative" pointer on error
+ */
+static struct dentry *vbsf_inode_lookup(struct inode *parent, struct dentry *dentry
+#if RTLNX_VER_MIN(3,6,0)
+ , unsigned int flags
+#elif RTLNX_VER_MIN(2,6,0)
+ , struct nameidata *nd
+#endif
+ )
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(parent);
+ SHFLSTRING *path;
+ struct dentry *dret;
+ int rc;
+
+#if RTLNX_VER_MIN(3,6,0)
+ SFLOGFLOW(("vbsf_inode_lookup: parent=%p dentry=%p flags=%#x\n", parent, dentry, flags));
+#elif RTLNX_VER_MIN(2,6,0)
+ SFLOGFLOW(("vbsf_inode_lookup: parent=%p dentry=%p nd=%p{.flags=%#x}\n", parent, dentry, nd, nd ? nd->flags : 0));
+#else
+ SFLOGFLOW(("vbsf_inode_lookup: parent=%p dentry=%p\n", parent, dentry));
+#endif
+
+ Assert(pSuperInfo);
+ Assert(sf_i && sf_i->u32Magic == SF_INODE_INFO_MAGIC);
+
+ /*
+ * Build the path. We'll associate the path with dret's inode on success.
+ */
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_i, dentry, &path, __func__);
+ if (rc == 0) {
+ /*
+ * Do a lookup on the host side.
+ */
+ VBOXSFCREATEREQ *pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + path->u16Size);
+ if (pReq) {
+ struct inode *pInode = NULL;
+
+ RT_ZERO(*pReq);
+ RT_BCOPY_UNFORTIFIED(&pReq->StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ SFLOG2(("vbsf_inode_lookup: Calling VbglR0SfHostReqCreate on %s\n", path->String.utf8));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ /*
+ * Create an inode for the result. Since this also confirms
+ * the existence of all parent dentries, we increase their TTL.
+ */
+ pInode = vbsf_create_inode(parent, dentry, path, &pReq->CreateParms.Info, pSuperInfo, false /*fInstantiate*/);
+ if (rc == 0) {
+ path = NULL; /* given to the inode */
+ dret = dentry;
+ } else
+ dret = (struct dentry *)ERR_PTR(-ENOMEM);
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ } else if ( pReq->CreateParms.Result == SHFL_FILE_NOT_FOUND
+ || pReq->CreateParms.Result == SHFL_PATH_NOT_FOUND /*this probably should happen*/) {
+ dret = dentry;
+ } else {
+ AssertMsgFailed(("%d\n", pReq->CreateParms.Result));
+ dret = (struct dentry *)ERR_PTR(-EPROTO);
+ }
+ } else if (rc == VERR_INVALID_NAME) {
+ SFLOGFLOW(("vbsf_inode_lookup: VERR_INVALID_NAME\n"));
+ dret = dentry; /* this can happen for names like 'foo*' on a Windows host */
+ } else if (rc == VERR_FILENAME_TOO_LONG) {
+ SFLOG(("vbsf_inode_lookup: VbglR0SfHostReqCreate failed on %s: VERR_FILENAME_TOO_LONG\n", path->String.utf8));
+ dret = (struct dentry *)ERR_PTR(-ENAMETOOLONG);
+ } else {
+ SFLOG(("vbsf_inode_lookup: VbglR0SfHostReqCreate failed on %s: %Rrc\n", path->String.utf8, rc));
+ dret = (struct dentry *)ERR_PTR(-EPROTO);
+ }
+ VbglR0PhysHeapFree(pReq);
+
+ /*
+ * When dret is set to dentry we got something to insert,
+ * though it may be negative (pInode == NULL).
+ */
+ if (dret == dentry) {
+ vbsf_dentry_set_update_jiffies(dentry, jiffies);
+ vbsf_d_add_inode(dentry, pInode);
+ dret = NULL;
+ }
+ } else {
+ SFLOGFLOW(("vbsf_inode_lookup: -ENOMEM (phys heap)\n"));
+ dret = (struct dentry *)ERR_PTR(-ENOMEM);
+ }
+ if (path)
+ kfree(path);
+ } else {
+ SFLOG(("vbsf_inode_lookup: vbsf_path_from_dentry failed: %d\n", rc));
+ dret = (struct dentry *)ERR_PTR(rc);
+ }
+ return dret;
+}
+
+
+/**
+ * This should allocate memory for vbsf_inode_info, compute a unique inode
+ * number, get an inode from vfs, initialize inode info, instantiate
+ * dentry.
+ *
+ * @param parent inode entry of the directory
+ * @param dentry directory cache entry
+ * @param path path name. Consumed on success.
+ * @param info file information
+ * @param handle handle
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_inode_instantiate(struct inode *parent, struct dentry *dentry, PSHFLSTRING path,
+ PSHFLFSOBJINFO info, SHFLHANDLE handle)
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ struct inode *pInode = vbsf_create_inode(parent, dentry, path, info, pSuperInfo, true /*fInstantiate*/);
+ if (pInode) {
+ /* Store this handle if we leave the handle open. */
+ struct vbsf_inode_info *sf_new_i = VBSF_GET_INODE_INFO(pInode);
+ sf_new_i->handle = handle;
+ return 0;
+ }
+ return -ENOMEM;
+}
+
+
+/**
+ * Create a new regular file / directory.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param mode file mode
+ * @param fCreateFlags SHFL_CF_XXX.
+ * @param fStashHandle Whether the resulting handle should be stashed in
+ * the inode for a subsequent open call.
+ * @param fDoLookup Whether we're doing a lookup and need to d_add the
+ * inode we create to dentry.
+ * @param phHostFile Where to return the handle to the create file/dir.
+ * @param pfCreated Where to indicate whether the file/dir was created
+ * or not. Optional.
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_create_worker(struct inode *parent, struct dentry *dentry, umode_t mode, uint32_t fCreateFlags,
+ bool fStashHandle, bool fDoLookup, SHFLHANDLE *phHostFile, bool *pfCreated)
+
+{
+#ifdef SFLOG_ENABLED
+ const char * const pszPrefix = S_ISDIR(mode) ? "vbsf_create_worker/dir:" : "vbsf_create_worker/file:";
+#endif
+ struct vbsf_inode_info *sf_parent_i = VBSF_GET_INODE_INFO(parent);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ PSHFLSTRING path;
+ int rc;
+
+ if (pfCreated)
+ *pfCreated = false;
+ AssertReturn(sf_parent_i, -EINVAL);
+ AssertReturn(pSuperInfo, -EINVAL);
+
+ /*
+ * Build a path. We'll donate this to the inode on success.
+ */
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_parent_i, dentry, &path, __func__);
+ if (rc == 0) {
+ /*
+ * Allocate, initialize and issue the SHFL_CREATE request.
+ */
+ /** @todo combine with vbsf_path_from_dentry? */
+ union CreateAuxReq
+ {
+ VBOXSFCREATEREQ Create;
+ VBOXSFCLOSEREQ Close;
+ } *pReq = (union CreateAuxReq *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFCREATEREQ, StrPath.String) + path->u16Size);
+ if (pReq) {
+ RT_BCOPY_UNFORTIFIED(&pReq->Create.StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ RT_ZERO(pReq->Create.CreateParms);
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->Create.CreateParms.CreateFlags = fCreateFlags;
+ pReq->Create.CreateParms.Info.Attr.fMode = (S_ISDIR(mode) ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE)
+ | sf_access_permissions_to_vbox(mode);
+ pReq->Create.CreateParms.Info.Attr.enmAdditional = SHFLFSOBJATTRADD_NOTHING;
+
+ SFLOGFLOW(("%s calling VbglR0SfHostReqCreate(%s, %#x)\n", pszPrefix, path->String.ach, pReq->Create.CreateParms.CreateFlags));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, &pReq->Create);
+ if (RT_SUCCESS(rc)) {
+ SFLOGFLOW(("%s VbglR0SfHostReqCreate returned %Rrc Result=%d Handle=%#llx\n",
+ pszPrefix, rc, pReq->Create.CreateParms.Result, pReq->Create.CreateParms.Handle));
+
+ /*
+ * Work the dentry cache and inode restatting.
+ */
+ if ( pReq->Create.CreateParms.Result == SHFL_FILE_CREATED
+ || pReq->Create.CreateParms.Result == SHFL_FILE_REPLACED) {
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ sf_parent_i->force_restat = 1;
+ } else if ( pReq->Create.CreateParms.Result == SHFL_FILE_EXISTS
+ || pReq->Create.CreateParms.Result == SHFL_FILE_NOT_FOUND)
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+
+ /*
+ * If we got a handle back, we're good. Create an inode for it and return.
+ */
+ if (pReq->Create.CreateParms.Handle != SHFL_HANDLE_NIL) {
+ struct inode *pNewInode = vbsf_create_inode(parent, dentry, path, &pReq->Create.CreateParms.Info, pSuperInfo,
+ !fDoLookup /*fInstantiate*/);
+ if (pNewInode) {
+ struct vbsf_inode_info *sf_new_i = VBSF_GET_INODE_INFO(pNewInode);
+ if (phHostFile) {
+ *phHostFile = pReq->Create.CreateParms.Handle;
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ } else if (fStashHandle) {
+ sf_new_i->handle = pReq->Create.CreateParms.Handle;
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ }
+ if (pfCreated)
+ *pfCreated = pReq->Create.CreateParms.Result == SHFL_FILE_CREATED;
+ if (fDoLookup)
+ vbsf_d_add_inode(dentry, pNewInode);
+ path = NULL;
+ } else {
+ SFLOGFLOW(("%s vbsf_create_inode failed: -ENOMEM (path %s)\n", pszPrefix, rc, path->String.ach));
+ rc = -ENOMEM;
+ }
+ } else if (pReq->Create.CreateParms.Result == SHFL_FILE_EXISTS) {
+ /*
+ * For atomic_open (at least), we should create an inode and
+ * convert the dentry from a negative to a positive one.
+ */
+ SFLOGFLOW(("%s SHFL_FILE_EXISTS for %s\n", pszPrefix, sf_parent_i->path->String.ach));
+ if (fDoLookup) {
+ struct inode *pNewInode = vbsf_create_inode(parent, dentry, path, &pReq->Create.CreateParms.Info,
+ pSuperInfo, false /*fInstantiate*/);
+ if (pNewInode)
+ vbsf_d_add_inode(dentry, pNewInode);
+ path = NULL;
+ }
+ rc = -EEXIST;
+ } else if (pReq->Create.CreateParms.Result == SHFL_FILE_NOT_FOUND) {
+ SFLOGFLOW(("%s SHFL_FILE_NOT_FOUND for %s\n", pszPrefix, sf_parent_i->path->String.ach));
+ rc = -ENOENT;
+ } else if (pReq->Create.CreateParms.Result == SHFL_PATH_NOT_FOUND) {
+ SFLOGFLOW(("%s SHFL_PATH_NOT_FOUND for %s\n", pszPrefix, sf_parent_i->path->String.ach));
+ rc = -ENOENT;
+ } else {
+ AssertMsgFailed(("result=%d creating '%s'\n", pReq->Create.CreateParms.Result, sf_parent_i->path->String.ach));
+ rc = -EPERM;
+ }
+ } else {
+ int const vrc = rc;
+ rc = -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("%s SHFL_FN_CREATE(%s) failed vrc=%Rrc rc=%d\n", pszPrefix, path->String.ach, vrc, rc));
+ }
+
+ /* Cleanups. */
+ if (pReq->Create.CreateParms.Handle != SHFL_HANDLE_NIL) {
+ AssertCompile(RTASSERT_OFFSET_OF(VBOXSFCREATEREQ, CreateParms.Handle) > sizeof(VBOXSFCLOSEREQ)); /* no aliasing issues */
+ int rc2 = VbglR0SfHostReqClose(pSuperInfo->map.root, &pReq->Close, pReq->Create.CreateParms.Handle);
+ if (RT_FAILURE(rc2))
+ SFLOGFLOW(("%s VbglR0SfHostReqCloseSimple failed rc=%Rrc\n", pszPrefix, rc2));
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ if (path)
+ kfree(path);
+ }
+ return rc;
+}
+
+
+#if RTLNX_VER_MIN(3,16,0)
+/**
+ * More atomic way of handling creation.
+ *
+ * Older kernels would first to a lookup that created the file, followed by
+ * an open call. We've got this horrid vbsf_inode_info::handle member because
+ * of that approach. The call combines the lookup and open.
+ */
+static int vbsf_inode_atomic_open(struct inode *pDirInode, struct dentry *dentry, struct file *file, unsigned fOpen,
+ umode_t fMode
+# if RTLNX_VER_MAX(4,19,0)
+ , int *opened
+# endif
+ )
+{
+ SFLOGFLOW(("vbsf_inode_atomic_open: pDirInode=%p dentry=%p file=%p fOpen=%#x, fMode=%#x\n", pDirInode, dentry, file, fOpen, fMode));
+ int rc;
+
+ /* Code assumes negative dentry. */
+ Assert(dentry->d_inode == NULL);
+
+ /** @todo see if we can do this for non-create calls too, as it may save us a
+ * host call to revalidate the dentry. (Can't see anyone else doing
+ * this, so playing it safe for now.) */
+ if (fOpen & O_CREAT) {
+ /*
+ * Prepare our file info structure.
+ */
+ struct vbsf_reg_info *sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL);
+ if (sf_r) {
+ bool fCreated = false;
+ uint32_t fCreateFlags;
+
+ RTListInit(&sf_r->Handle.Entry);
+ sf_r->Handle.cRefs = 1;
+ sf_r->Handle.fFlags = !(fOpen & O_DIRECTORY)
+ ? VBSF_HANDLE_F_FILE | VBSF_HANDLE_F_MAGIC
+ : VBSF_HANDLE_F_DIR | VBSF_HANDLE_F_MAGIC;
+ sf_r->Handle.hHost = SHFL_HANDLE_NIL;
+
+ /*
+ * Try create it.
+ */
+ /* vbsf_create_worker uses the type from fMode, so match it up to O_DIRECTORY. */
+ AssertMsg(!(fMode & S_IFMT) || (fMode & S_IFMT) == (fOpen & O_DIRECTORY ? S_IFDIR : S_IFREG), ("0%o\n", fMode));
+ if (!(fOpen & O_DIRECTORY))
+ fMode = (fMode & ~S_IFMT) | S_IFREG;
+ else
+ fMode = (fMode & ~S_IFMT) | S_IFDIR;
+
+ fCreateFlags = vbsf_linux_oflags_to_vbox(fOpen, &sf_r->Handle.fFlags, __FUNCTION__);
+
+ rc = vbsf_create_worker(pDirInode, dentry, fMode, fCreateFlags, false /*fStashHandle*/, true /*fDoLookup*/,
+ &sf_r->Handle.hHost, &fCreated);
+ if (rc == 0) {
+ struct inode *inode = dentry->d_inode;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+
+ /*
+ * Set FMODE_CREATED according to the action taken by SHFL_CREATE
+ * and call finish_open() to do the remaining open() work.
+ */
+# if RTLNX_VER_MIN(4,19,0)
+ if (fCreated)
+ file->f_mode |= FMODE_CREATED;
+ rc = finish_open(file, dentry, generic_file_open);
+# else
+ if (fCreated)
+ *opened |= FILE_CREATED;
+ rc = finish_open(file, dentry, generic_file_open, opened);
+# endif
+ if (rc == 0) {
+ /*
+ * Now that the file is fully opened, associate sf_r with it
+ * and link the handle to the inode.
+ */
+ vbsf_handle_append(sf_i, &sf_r->Handle);
+ file->private_data = sf_r;
+ SFLOGFLOW(("vbsf_inode_atomic_open: create succeeded; hHost=%#llx path='%s'\n",
+ rc, sf_r->Handle.hHost, sf_i->path->String.ach));
+ sf_r = NULL; /* don't free it */
+ } else {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pDirInode->i_sb);
+ SFLOGFLOW(("vbsf_inode_atomic_open: finish_open failed: %d (path='%s'\n", rc, sf_i->path->String.ach));
+ VbglR0SfHostReqCloseSimple(pSuperInfo->map.root, sf_r->Handle.hHost);
+ sf_r->Handle.hHost = SHFL_HANDLE_NIL;
+ }
+ } else
+ SFLOGFLOW(("vbsf_inode_atomic_open: vbsf_create_worker failed: %d\n", rc));
+ if (sf_r)
+ kfree(sf_r);
+ } else {
+ LogRelMaxFunc(64, ("could not allocate reg info\n"));
+ rc = -ENOMEM;
+ }
+ }
+ /*
+ * Not creating anything.
+ * Do we need to do a lookup or should we just fail?
+ */
+ else if (d_in_lookup(dentry)) {
+ struct dentry *pResult = vbsf_inode_lookup(pDirInode, dentry, 0 /*fFlags*/);
+ if (!IS_ERR(pResult))
+ rc = finish_no_open(file, pResult);
+ else
+ rc = PTR_ERR(pResult);
+ SFLOGFLOW(("vbsf_inode_atomic_open: open -> %d (%p)\n", rc, pResult));
+ } else {
+ SFLOGFLOW(("vbsf_inode_atomic_open: open -> -ENOENT\n"));
+ rc = -ENOENT;
+ }
+ return rc;
+}
+#endif /* 3.6.0 */
+
+
+/**
+ * Create a new regular file.
+ *
+ * @param idmap idmap of the mount.
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param mode file mode
+ * @param excl Possible O_EXCL...
+ * @returns 0 on success, Linux error code otherwise
+ */
+#if RTLNX_VER_MIN(6,3,0) || defined(DOXYGEN_RUNNING)
+static int vbsf_inode_create(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, umode_t mode, bool excl)
+#elif RTLNX_VER_MIN(5,12,0)
+static int vbsf_inode_create(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, umode_t mode, bool excl)
+#elif RTLNX_VER_MIN(3,6,0)
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, umode_t mode, bool excl)
+#elif RTLNX_VER_MIN(3,3,0)
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, umode_t mode, struct nameidata *nd)
+#elif RTLNX_VER_MIN(2,5,75)
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, int mode, struct nameidata *nd)
+#else
+static int vbsf_inode_create(struct inode *parent, struct dentry *dentry, int mode)
+#endif
+{
+ uint32_t fCreateFlags = SHFL_CF_ACT_CREATE_IF_NEW
+ | SHFL_CF_ACT_FAIL_IF_EXISTS
+ | SHFL_CF_ACCESS_READWRITE;
+#if RTLNX_VER_RANGE(2,5,75, 3,6,0)
+ /* Clear the RD flag if write-only access requested. Otherwise assume we
+ need write access to create stuff. */
+ if (!(nd->intent.open.flags & 1) ) {
+ fCreateFlags &= SHFL_CF_ACCESS_READWRITE;
+ fCreateFlags |= SHFL_CF_ACCESS_WRITE;
+ }
+ /* (file since 2.6.15) */
+#endif
+ TRACE();
+ AssertMsg(!(mode & S_IFMT) || (mode & S_IFMT) == S_IFREG, ("0%o\n", mode));
+ return vbsf_create_worker(parent, dentry, (mode & ~S_IFMT) | S_IFREG, fCreateFlags,
+ true /*fStashHandle*/, false /*fDoLookup*/, NULL /*phHandle*/, NULL /*fCreated*/);
+}
+
+
+/**
+ * Create a new directory.
+ *
+ * @param idmap idmap of the mount.
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param mode file mode
+ * @returns 0 on success, Linux error code otherwise
+ */
+#if RTLNX_VER_MIN(6,3,0) || defined(DOXYGEN_RUNNING)
+static int vbsf_inode_mkdir(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, umode_t mode)
+#elif RTLNX_VER_MIN(5,12,0)
+static int vbsf_inode_mkdir(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, umode_t mode)
+#elif RTLNX_VER_MIN(3,3,0)
+static int vbsf_inode_mkdir(struct inode *parent, struct dentry *dentry, umode_t mode)
+#else
+static int vbsf_inode_mkdir(struct inode *parent, struct dentry *dentry, int mode)
+#endif
+{
+ TRACE();
+ AssertMsg(!(mode & S_IFMT) || (mode & S_IFMT) == S_IFDIR, ("0%o\n", mode));
+ return vbsf_create_worker(parent, dentry, (mode & ~S_IFMT) | S_IFDIR,
+ SHFL_CF_ACT_CREATE_IF_NEW
+ | SHFL_CF_ACT_FAIL_IF_EXISTS
+ | SHFL_CF_ACCESS_READWRITE
+ | SHFL_CF_DIRECTORY,
+ false /*fStashHandle*/, false /*fDoLookup*/, NULL /*phHandle*/, NULL /*fCreated*/);
+}
+
+
+/**
+ * Remove a regular file / directory.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @param fDirectory true if directory, false otherwise
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_unlink_worker(struct inode *parent, struct dentry *dentry, int fDirectory)
+{
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ struct vbsf_inode_info *sf_parent_i = VBSF_GET_INODE_INFO(parent);
+ SHFLSTRING *path;
+ int rc;
+
+ TRACE();
+
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_parent_i, dentry, &path, __func__);
+ if (!rc) {
+ VBOXSFREMOVEREQ *pReq = (VBOXSFREMOVEREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFREMOVEREQ, StrPath.String)
+ + path->u16Size);
+ if (pReq) {
+ RT_BCOPY_UNFORTIFIED(&pReq->StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ uint32_t fFlags = fDirectory ? SHFL_REMOVE_DIR : SHFL_REMOVE_FILE;
+ if (dentry->d_inode && ((dentry->d_inode->i_mode & S_IFLNK) == S_IFLNK))
+ fFlags |= SHFL_REMOVE_SYMLINK;
+
+ rc = VbglR0SfHostReqRemove(pSuperInfo->map.root, pReq, fFlags);
+
+ if (dentry->d_inode) {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(dentry->d_inode);
+ sf_i->force_restat = true;
+ }
+
+ if (RT_SUCCESS(rc)) {
+ sf_parent_i->force_restat = true; /* directory access/change time changed */
+ rc = 0;
+ } else if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) {
+ /* Probably deleted on the host while the guest had it cached, so don't complain: */
+ LogFunc(("(%d): VbglR0SfRemove(%s) failed rc=%Rrc; calling d_drop on %p\n",
+ fDirectory, path->String.ach, rc, dentry));
+ sf_parent_i->force_restat = true;
+ d_drop(dentry);
+ rc = 0;
+ } else {
+ LogFunc(("(%d): VbglR0SfRemove(%s) failed rc=%Rrc\n", fDirectory, path->String.ach, rc));
+ rc = -RTErrConvertToErrno(rc);
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ kfree(path);
+ }
+ return rc;
+}
+
+
+/**
+ * Remove a regular file.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_inode_unlink(struct inode *parent, struct dentry *dentry)
+{
+ TRACE();
+ return vbsf_unlink_worker(parent, dentry, false /*fDirectory*/);
+}
+
+
+/**
+ * Remove a directory.
+ *
+ * @param parent inode of the directory
+ * @param dentry directory cache entry
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_inode_rmdir(struct inode *parent, struct dentry *dentry)
+{
+ TRACE();
+ return vbsf_unlink_worker(parent, dentry, true /*fDirectory*/);
+}
+
+
+/**
+ * Rename a regular file / directory.
+ *
+ * @param idmap idmap of the mount.
+ * @param old_parent inode of the old parent directory
+ * @param old_dentry old directory cache entry
+ * @param new_parent inode of the new parent directory
+ * @param new_dentry new directory cache entry
+ * @param flags flags
+ * @returns 0 on success, Linux error code otherwise
+ */
+#if RTLNX_VER_MIN(6,3,0) || defined(DOXYGEN_RUNNING)
+static int vbsf_inode_rename(struct mnt_idmap *idmap,
+ struct inode *old_parent, struct dentry *old_dentry,
+ struct inode *new_parent, struct dentry *new_dentry, unsigned flags)
+#elif RTLNX_VER_MIN(5,12,0)
+static int vbsf_inode_rename(struct user_namespace *ns,
+ struct inode *old_parent, struct dentry *old_dentry,
+ struct inode *new_parent, struct dentry *new_dentry, unsigned flags)
+#else
+static int vbsf_inode_rename(struct inode *old_parent, struct dentry *old_dentry,
+ struct inode *new_parent, struct dentry *new_dentry, unsigned flags)
+#endif
+{
+ /*
+ * Deal with flags.
+ */
+ int rc;
+ uint32_t fRename = (old_dentry->d_inode->i_mode & S_IFDIR ? SHFL_RENAME_DIR : SHFL_RENAME_FILE)
+ | SHFL_RENAME_REPLACE_IF_EXISTS;
+#if RTLNX_VER_MIN(3,15,0)
+ if (!(flags & ~RENAME_NOREPLACE)) {
+ if (flags & RENAME_NOREPLACE)
+ fRename &= ~SHFL_RENAME_REPLACE_IF_EXISTS;
+#endif
+ /*
+ * Check that they are on the same mount.
+ */
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(old_parent->i_sb);
+ if (pSuperInfo == VBSF_GET_SUPER_INFO(new_parent->i_sb)) {
+ /*
+ * Build the new path.
+ */
+ struct vbsf_inode_info *sf_new_parent_i = VBSF_GET_INODE_INFO(new_parent);
+ PSHFLSTRING pNewPath;
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_new_parent_i, new_dentry, &pNewPath, __func__);
+ if (rc == 0) {
+ /*
+ * Create and issue the rename request.
+ */
+ VBOXSFRENAMEWITHSRCBUFREQ *pReq;
+ pReq = (VBOXSFRENAMEWITHSRCBUFREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF(VBOXSFRENAMEWITHSRCBUFREQ, StrDstPath.String)
+ + pNewPath->u16Size);
+ if (pReq) {
+ struct vbsf_inode_info *sf_file_i = VBSF_GET_INODE_INFO(old_dentry->d_inode);
+ PSHFLSTRING pOldPath = sf_file_i->path;
+
+ RT_BCOPY_UNFORTIFIED(&pReq->StrDstPath, pNewPath, SHFLSTRING_HEADER_SIZE + pNewPath->u16Size);
+ rc = VbglR0SfHostReqRenameWithSrcContig(pSuperInfo->map.root, pReq, pOldPath, virt_to_phys(pOldPath), fRename);
+ VbglR0PhysHeapFree(pReq);
+ if (RT_SUCCESS(rc)) {
+ /*
+ * On success we replace the path in the inode and trigger
+ * restatting of both parent directories.
+ */
+ struct vbsf_inode_info *sf_old_parent_i = VBSF_GET_INODE_INFO(old_parent);
+ SFLOGFLOW(("vbsf_inode_rename: %s -> %s (%#x)\n", pOldPath->String.ach, pNewPath->String.ach, fRename));
+
+ sf_file_i->path = pNewPath;
+ kfree(pOldPath);
+ pNewPath = NULL;
+
+ sf_new_parent_i->force_restat = 1;
+ sf_old_parent_i->force_restat = 1;
+
+ vbsf_dentry_chain_increase_parent_ttl(old_dentry);
+ vbsf_dentry_chain_increase_parent_ttl(new_dentry);
+
+ rc = 0;
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: VbglR0SfHostReqRenameWithSrcContig(%s,%s,%#x) failed -> %d\n",
+ pOldPath->String.ach, pNewPath->String.ach, fRename, rc));
+ if (rc == VERR_IS_A_DIRECTORY || rc == VERR_IS_A_FILE)
+ vbsf_dentry_invalidate_ttl(old_dentry);
+ rc = -RTErrConvertToErrno(rc);
+ }
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: failed to allocate request (%#x bytes)\n",
+ RT_UOFFSETOF(VBOXSFRENAMEWITHSRCBUFREQ, StrDstPath.String) + pNewPath->u16Size));
+ rc = -ENOMEM;
+ }
+ if (pNewPath)
+ kfree(pNewPath);
+ } else
+ SFLOGFLOW(("vbsf_inode_rename: vbsf_path_from_dentry failed: %d\n", rc));
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: rename with different roots (%#x vs %#x)\n",
+ pSuperInfo->map.root, VBSF_GET_SUPER_INFO(new_parent->i_sb)->map.root));
+ rc = -EXDEV;
+ }
+#if RTLNX_VER_MIN(3,15,0)
+ } else {
+ SFLOGFLOW(("vbsf_inode_rename: Unsupported flags: %#x\n", flags));
+ rc = -EINVAL;
+ }
+#else
+ RT_NOREF(flags);
+#endif
+ return rc;
+}
+
+
+#if RTLNX_VER_MAX(4,9,0)
+/**
+ * The traditional rename interface without any flags.
+ */
+static int vbsf_inode_rename_no_flags(struct inode *old_parent, struct dentry *old_dentry,
+ struct inode *new_parent, struct dentry *new_dentry)
+{
+ return vbsf_inode_rename(old_parent, old_dentry, new_parent, new_dentry, 0);
+}
+#endif
+
+
+/**
+ * Create a symbolic link.
+ */
+#if RTLNX_VER_MIN(6,3,0)
+static int vbsf_inode_symlink(struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, const char *target)
+#elif RTLNX_VER_MIN(5,12,0)
+static int vbsf_inode_symlink(struct user_namespace *ns, struct inode *parent, struct dentry *dentry, const char *target)
+#else
+static int vbsf_inode_symlink(struct inode *parent, struct dentry *dentry, const char *target)
+#endif
+{
+ /*
+ * Turn the target into a string (contiguous physcial memory).
+ */
+ /** @todo we can save a kmalloc here if we switch to embedding the target rather
+ * than the symlink path into the request. Will require more NLS helpers. */
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(parent->i_sb);
+ PSHFLSTRING pTarget = NULL;
+ int rc = vbsf_nls_to_shflstring(pSuperInfo, target, &pTarget);
+ if (rc == 0) {
+ /*
+ * Create a full path for the symlink name.
+ */
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(parent);
+ PSHFLSTRING pPath = NULL;
+ rc = vbsf_path_from_dentry(pSuperInfo, sf_i, dentry, &pPath, __func__);
+ if (rc == 0) {
+ /*
+ * Create the request and issue it.
+ */
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFCREATESYMLINKREQ, StrSymlinkPath.String) + pPath->u16Size;
+ VBOXSFCREATESYMLINKREQ *pReq = (VBOXSFCREATESYMLINKREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ RT_ZERO(*pReq);
+ RT_BCOPY_UNFORTIFIED(&pReq->StrSymlinkPath, pPath, SHFLSTRING_HEADER_SIZE + pPath->u16Size);
+
+ rc = VbglR0SfHostReqCreateSymlinkContig(pSuperInfo->map.root, pTarget, virt_to_phys(pTarget), pReq);
+ if (RT_SUCCESS(rc)) {
+ sf_i->force_restat = 1;
+
+ /*
+ * Instantiate a new inode for the symlink.
+ */
+ rc = vbsf_inode_instantiate(parent, dentry, pPath, &pReq->ObjInfo, SHFL_HANDLE_NIL);
+ if (rc == 0) {
+ SFLOGFLOW(("vbsf_inode_symlink: Successfully created '%s' -> '%s'\n", pPath->String.ach, pTarget->String.ach));
+ pPath = NULL; /* consumed by inode */
+ vbsf_dentry_chain_increase_ttl(dentry);
+ } else {
+ SFLOGFLOW(("vbsf_inode_symlink: Failed to create inode for '%s': %d\n", pPath->String.ach, rc));
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ vbsf_dentry_invalidate_ttl(dentry);
+ }
+ } else {
+ int const vrc = rc;
+ if (vrc == VERR_WRITE_PROTECT)
+ rc = -EPERM; /* EPERM: Symlink creation not supported according to the linux manpage as of 2017-09-15.
+ "VBoxInternal2/SharedFoldersEnableSymlinksCreate/<share>" is not 1. */
+ else
+ rc = -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_inode_symlink: VbglR0SfHostReqCreateSymlinkContig failed for '%s' -> '%s': %Rrc (-> %d)\n",
+ pPath->String.ach, pTarget->String.ach, vrc, rc));
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ SFLOGFLOW(("vbsf_inode_symlink: failed to allocate %u phys heap for the request!\n", cbReq));
+ rc = -ENOMEM;
+ }
+ if (pPath)
+ kfree(pPath);
+ }
+ kfree(pTarget);
+ }
+ return rc;
+}
+
+
+/**
+ * Directory inode operations.
+ */
+struct inode_operations vbsf_dir_iops = {
+ .lookup = vbsf_inode_lookup,
+#if RTLNX_VER_MIN(3,16,0)
+ .atomic_open = vbsf_inode_atomic_open,
+#endif
+ .create = vbsf_inode_create,
+ .symlink = vbsf_inode_symlink,
+ .mkdir = vbsf_inode_mkdir,
+ .rmdir = vbsf_inode_rmdir,
+ .unlink = vbsf_inode_unlink,
+#if RTLNX_VER_MIN(4,9,0)
+ .rename = vbsf_inode_rename,
+#else
+# if RTLNX_VER_MAX(3,17,0)
+ .rename = vbsf_inode_rename_no_flags,
+# endif
+# if RTLNX_VER_MIN(3,15,0)
+ .rename2 = vbsf_inode_rename,
+# endif
+#endif
+#if RTLNX_VER_MIN(2,5,18)
+ .getattr = vbsf_inode_getattr,
+#else
+ .revalidate = vbsf_inode_revalidate,
+#endif
+ .setattr = vbsf_inode_setattr,
+};
+
diff --git a/src/VBox/Additions/linux/sharedfolders/files_vboxsf b/src/VBox/Additions/linux/sharedfolders/files_vboxsf
new file mode 100755
index 00000000..8187ad33
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/files_vboxsf
@@ -0,0 +1,107 @@
+#!/bin/sh
+# $Id: files_vboxsf $
+## @file
+# Shared file between Makefile.kmk and export_modules.sh.
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+FILES_VBOXSF_NOBIN=" \
+ ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \
+ ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \
+ ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \
+ ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \
+ ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \
+ ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \
+ ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \
+ ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \
+ ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \
+ ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \
+ ${PATH_ROOT}/include/iprt/fs.h=>include/iprt/fs.h \
+ ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \
+ ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \
+ ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \
+ ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \
+ ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \
+ ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \
+ ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \
+ ${PATH_ROOT}/include/iprt/path.h=>include/iprt/path.h \
+ ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \
+ ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \
+ ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \
+ ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \
+ ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \
+ ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \
+ ${PATH_ROOT}/include/iprt/uint64.h=>include/iprt/uint64.h \
+ ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \
+ ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \
+ ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \
+ ${PATH_ROOT}/include/iprt/linux/version.h=>include/iprt/linux/version.h \
+ ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \
+ ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \
+ ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \
+ ${PATH_ROOT}/include/VBox/ostypes.h=>include/VBox/ostypes.h \
+ ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \
+ ${PATH_ROOT}/include/VBox/shflsvc.h=>include/VBox/shflsvc.h \
+ ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuest.h=>include/VBox/VBoxGuest.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestCoreTypes.h=>include/VBox/VBoxGuestCoreTypes.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestLib.h=>include/VBox/VBoxGuestLib.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestLibSharedFolders.h=>include/VBox/VBoxGuestLibSharedFolders.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestLibSharedFoldersInline.h=>include/VBox/VBoxGuestLibSharedFoldersInline.h \
+ ${PATH_ROOT}/include/VBox/VBoxGuestMangling.h=>include/VBox/VBoxGuestMangling.h \
+ ${PATH_ROOT}/include/VBox/VMMDev.h=>include/VBox/VMMDev.h \
+ ${PATH_ROOT}/include/VBox/VMMDevCoreTypes.h=>include/VBox/VMMDevCoreTypes.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInternal.h=>VBoxGuestR0LibInternal.h \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibGenericRequest.cpp=>VBoxGuestR0LibGenericRequest.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibHGCM.cpp=>VBoxGuestR0LibHGCM.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc.cpp=>VBoxGuestR0LibIdc.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibIdc-unix.cpp=>VBoxGuestR0LibIdc-unix.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibInit.cpp=>VBoxGuestR0LibInit.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibPhysHeap.cpp=>VBoxGuestR0LibPhysHeap.c \
+ ${PATH_ROOT}/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR0LibSharedFolders.c=>VBoxGuestR0LibSharedFolders.c \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \
+ ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divdi3.c=>divdi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/moddi3.c=>moddi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/qdivrem.c=>qdivrem.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/quad.h=>quad.h \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivdi3.c=>udivdi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivmoddi4.c=>udivmoddi4.c \
+ ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/umoddi3.c=>umoddi3.c \
+ ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/Makefile.module=>Makefile \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/dirops.c=>dirops.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/lnkops.c=>lnkops.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/regops.c=>regops.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/utils.c=>utils.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/vbsfmount.h=>vbsfmount.h \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/vfsmod.c=>vfsmod.c \
+ ${PATH_ROOT}/src/VBox/Additions/linux/sharedfolders/vfsmod.h=>vfsmod.h \
+ ${PATH_OUT}/version-generated.h=>version-generated.h \
+ ${PATH_OUT}/revision-generated.h=>revision-generated.h \
+ ${PATH_OUT}/product-generated.h=>product-generated.h \
+"
+
+FILES_VBOXSF_BIN=" \
+"
diff --git a/src/VBox/Additions/linux/sharedfolders/lnkops.c b/src/VBox/Additions/linux/sharedfolders/lnkops.c
new file mode 100644
index 00000000..0d37a155
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/lnkops.c
@@ -0,0 +1,305 @@
+/* $Id: lnkops.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, operations for symbolic links.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+
+
+/**
+ * Converts error codes as best we can.
+ */
+DECLINLINE(int) vbsf_convert_symlink_error(int vrc)
+{
+ if ( vrc == VERR_IS_A_DIRECTORY
+ || vrc == VERR_IS_A_FIFO
+ || vrc == VERR_IS_A_FILE
+ || vrc == VERR_IS_A_BLOCK_DEVICE
+ || vrc == VERR_IS_A_CHAR_DEVICE
+ || vrc == VERR_IS_A_SOCKET
+ || vrc == VERR_NOT_SYMLINK)
+ return -EINVAL;
+ if (vrc == VERR_PATH_NOT_FOUND)
+ return -ENOTDIR;
+ if (vrc == VERR_FILE_NOT_FOUND)
+ return -ENOENT;
+ return -EPROTO;
+}
+
+
+/**
+ * Does the NLS conversion of the symlink target.
+ */
+static int vbsf_symlink_nls_convert_slow(struct vbsf_super_info *pSuperInfo, char *pszTarget, size_t cbTargetBuf)
+{
+ int rc;
+ size_t const cchUtf8 = RTStrNLen(pszTarget, cbTargetBuf);
+ if (cchUtf8 < cbTargetBuf) {
+ /*
+ * If the target is short and there is a lot of space left in the target
+ * buffer (typically PAGE_SIZE in size), we move the input to the end
+ * instead of allocating a temporary buffer for it. This works because
+ * there shouldn't be anything that is more than 8x worse than UTF-8
+ * when it comes to efficiency.
+ */
+ char *pszFree = NULL;
+ char *pszUtf8;
+ if (cchUtf8 - 1 <= cbTargetBuf / 8) {
+ pszUtf8 = &pszTarget[cbTargetBuf - cchUtf8 - 1];
+ cbTargetBuf -= cchUtf8 - 1;
+ } else {
+ pszFree = pszUtf8 = kmalloc(cchUtf8 + 1, GFP_KERNEL);
+ if (RT_UNLIKELY(!pszUtf8)) {
+ LogRelMax(50, ("vbsf_symlink_nls_convert_slow: failed to allocate %u bytes\n", cchUtf8 + 1));
+ return -ENOMEM;
+ }
+ }
+ memcpy(pszUtf8, pszTarget, cchUtf8);
+ pszUtf8[cchUtf8] = '\0';
+
+ rc = vbsf_nlscpy(pSuperInfo, pszTarget, cbTargetBuf, pszUtf8, cchUtf8);
+ if (pszFree)
+ kfree(pszFree);
+ } else {
+ SFLOGFLOW(("vbsf_symlink_nls_convert_slow: Impossible! Unterminated target!\n"));
+ rc = -ENAMETOOLONG;
+ }
+ return rc;
+}
+
+
+/**
+ * Does NLS conversion if needed.
+ */
+DECLINLINE(int) vbsf_symlink_nls_convert(struct vbsf_super_info *pSuperInfo, char *pszTarget, size_t cbTargetBuf)
+{
+ if (pSuperInfo->fNlsIsUtf8)
+ return 0;
+ return vbsf_symlink_nls_convert_slow(pSuperInfo, pszTarget, cbTargetBuf);
+}
+
+#if RTLNX_VER_MIN(4,5,0)
+
+/**
+ * Get symbolic link.
+ */
+static const char *vbsf_get_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done)
+{
+ char *pszTarget;
+ if (dentry) {
+ pszTarget = (char *)kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (pszTarget) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ int rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length,
+ pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1));
+ if (RT_SUCCESS(rc)) {
+ pszTarget[PAGE_SIZE - 1] = '\0';
+ SFLOGFLOW(("vbsf_get_link: %s -> %s\n", sf_i->path->String.ach, pszTarget));
+ rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE);
+ if (rc == 0) {
+ vbsf_dentry_chain_increase_ttl(dentry);
+ set_delayed_call(done, kfree_link, pszTarget);
+ return pszTarget;
+ }
+ } else {
+ SFLOGFLOW(("vbsf_get_link: VbglR0SfHostReqReadLinkContigSimple failed on '%s': %Rrc\n",
+ sf_i->path->String.ach, rc));
+ }
+ kfree(pszTarget);
+ pszTarget = ERR_PTR(vbsf_convert_symlink_error(rc));
+ } else
+ pszTarget = ERR_PTR(-ENOMEM);
+ } else
+ pszTarget = ERR_PTR(-ECHILD);
+ return pszTarget;
+}
+
+#else /* < 4.5 */
+
+# if RTLNX_VER_MAX(2,6,8)
+/**
+ * Reads the link into the given buffer.
+ */
+static int vbsf_readlink(struct dentry *dentry, char *buffer, int len)
+{
+ int rc;
+ char *pszTarget = (char *)get_zeroed_page(GFP_KERNEL);
+ if (pszTarget) {
+ struct inode *inode = dentry->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length,
+ pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1));
+ if (RT_SUCCESS(rc)) {
+ pszTarget[PAGE_SIZE - 1] = '\0';
+ SFLOGFLOW(("vbsf_readlink: %s -> %*s\n", sf_i->path->String.ach, pszTarget));
+ rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE);
+ if (rc == 0) {
+ vbsf_dentry_chain_increase_ttl(dentry);
+ rc = vfs_readlink(dentry, buffer, len, pszTarget);
+ }
+ } else {
+ SFLOGFLOW(("vbsf_readlink: VbglR0SfHostReqReadLinkContigSimple failed on '%s': %Rrc\n", sf_i->path->String.ach, rc));
+ rc = vbsf_convert_symlink_error(rc);
+ }
+ free_page((unsigned long)pszTarget);
+ } else
+ rc = -ENOMEM;
+ return rc;
+}
+# endif /* < 2.6.8 */
+
+/**
+ * Follow link in dentry.
+ */
+# if RTLNX_VER_MIN(4,2,0)
+static const char *vbsf_follow_link(struct dentry *dentry, void **cookie)
+# elif RTLNX_VER_MIN(2,6,13)
+static void *vbsf_follow_link(struct dentry *dentry, struct nameidata *nd)
+# else
+static int vbsf_follow_link(struct dentry *dentry, struct nameidata *nd)
+# endif
+{
+ int rc;
+ char *pszTarget = (char *)get_zeroed_page(GFP_KERNEL);
+ if (pszTarget) {
+ struct inode *inode = dentry->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+
+ rc = VbglR0SfHostReqReadLinkContigSimple(pSuperInfo->map.root, sf_i->path->String.ach, sf_i->path->u16Length,
+ pszTarget, virt_to_phys(pszTarget), RT_MIN(PATH_MAX, PAGE_SIZE - 1));
+ if (RT_SUCCESS(rc)) {
+ pszTarget[PAGE_SIZE - 1] = '\0';
+ SFLOGFLOW(("vbsf_follow_link: %s -> %s\n", sf_i->path->String.ach, pszTarget));
+ rc = vbsf_symlink_nls_convert(pSuperInfo, pszTarget, PAGE_SIZE);
+ if (rc == 0) {
+ /*
+ * Succeeded. For 2.6.8 and later the page gets associated
+ * with the caller-cookie or nameidata structure and freed
+ * later by vbsf_put_link(). On earlier kernels we have to
+ * call vfs_follow_link() which will try continue the walking
+ * using the buffer we pass it here.
+ */
+ vbsf_dentry_chain_increase_ttl(dentry);
+# if RTLNX_VER_MIN(4,2,0)
+ *cookie = pszTarget;
+ return pszTarget;
+# elif RTLNX_VER_MIN(2,6,8)
+ nd_set_link(nd, pszTarget);
+# if RTLNX_VER_MIN(2,6,13)
+ return NULL;
+# else
+ return 0;
+# endif
+# else /* < 2.6.8 */
+ rc = vfs_follow_link(nd, pszTarget);
+ free_page((unsigned long)pszTarget);
+ return rc;
+# endif
+ }
+
+ /*
+ * Failed.
+ */
+ } else {
+ LogFunc(("VbglR0SfReadLink failed, caller=%s, rc=%Rrc\n", __func__, rc));
+ rc = vbsf_convert_symlink_error(rc);
+ }
+ free_page((unsigned long)pszTarget);
+ } else {
+ rc = -ENOMEM;
+ }
+# if RTLNX_VER_MIN(4,2,0)
+ *cookie = ERR_PTR(rc);
+ return (const char *)ERR_PTR(rc);
+# elif RTLNX_VER_MIN(2,6,8)
+ nd_set_link(nd, (char *)ERR_PTR(rc));
+# if RTLNX_VER_MIN(2,6,13)
+ return NULL;
+# else
+ return 0;
+# endif
+# else /* < 2.6.8 */
+ return rc;
+# endif /* < 2.6.8 */
+}
+
+# if RTLNX_VER_MIN(2,6,8)
+/**
+ * For freeing target link buffer allocated by vbsf_follow_link.
+ *
+ * For kernels before 2.6.8 memory isn't being kept around.
+ */
+# if RTLNX_VER_MIN(4,2,0)
+static void vbsf_put_link(struct inode *inode, void *cookie)
+# elif RTLNX_VER_MIN(2,6,13)
+static void vbsf_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
+# else
+static void vbsf_put_link(struct dentry *dentry, struct nameidata *nd)
+# endif
+{
+# if RTLNX_VER_MIN(2,6,13)
+ char *page = cookie;
+# else
+ char *page = nd_get_link(nd);
+# endif
+ SFLOGFLOW(("vbsf_put_link: page=%p\n", page));
+ if (!IS_ERR(page))
+ free_page((unsigned long)page);
+}
+# endif /* >= 2.6.8 */
+
+#endif /* < 4.5.0 */
+
+/**
+ * Symlink inode operations.
+ */
+struct inode_operations vbsf_lnk_iops = {
+#if RTLNX_VER_MAX(4,10,0)
+# if RTLNX_VER_MIN(2,6,8)
+ .readlink = generic_readlink,
+# else
+ .readlink = vbsf_readlink,
+# endif
+#endif
+#if RTLNX_VER_MIN(4,5,0)
+ .get_link = vbsf_get_link
+#else
+ .follow_link = vbsf_follow_link,
+# if RTLNX_VER_MIN(2,6,8)
+ .put_link = vbsf_put_link,
+# endif
+#endif
+};
+
diff --git a/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c b/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c
new file mode 100644
index 00000000..bcf0c59b
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/mount.vboxsf.c
@@ -0,0 +1,702 @@
+/* $Id: mount.vboxsf.c $ */
+/** @file
+ * VirtualBox Guest Additions for Linux - mount(8) helper.
+ *
+ * Parses options provided by mount (or user directly)
+ * Packs them into struct vbsfmount and passes to mount(2)
+ * Optionally adds entries to mtab
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+/* #define DEBUG */
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <mntent.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <mntent.h>
+#include <limits.h>
+#include <iconv.h>
+#include <sys/utsname.h>
+#include <linux/version.h>
+
+#include "vbsfmount.h"
+
+#include <iprt/assertcompile.h>
+#include <iprt/param.h> /* PAGE_SIZE (used by MAX_MNTOPT_STR) */
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define PANIC_ATTR __attribute ((noreturn, __format__ (__printf__, 1, 2)))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+struct vbsf_mount_opts
+{
+ unsigned long fFlags; /**< MS_XXX */
+
+ /** @name Preformatted option=value or empty if not specified.
+ * Helps eliminate duplicate options as well as simplifying concatting.
+ * @{ */
+ char szTTL[32];
+ char szMsDirCacheTTL[32];
+ char szMsInodeTTL[32];
+ char szMaxIoPages[32];
+ char szDirBuf[32];
+ char szCacheMode[32];
+ char szUid[32];
+ char szGid[32];
+ char szDMode[32];
+ char szFMode[32];
+ char szDMask[32];
+ char szFMask[32];
+ char szIoCharset[32];
+ /** @} */
+
+ bool fSloppy;
+ char *pszConvertCp;
+};
+
+
+static void PANIC_ATTR
+panic(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+static void PANIC_ATTR
+panic_err(const char *fmt, ...)
+{
+ va_list ap;
+ int errno_code = errno;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", strerror(errno_code));
+ exit(EXIT_FAILURE);
+}
+
+static int
+safe_atoi(const char *s, size_t size, int base)
+{
+ char *endptr;
+ long long int val = strtoll(s, &endptr, base);
+
+ if ( val < INT_MIN
+ || ( val > INT_MAX
+ && (base != 8 || val != UINT_MAX) ) /* hack for printf("%o", -1) - 037777777777 */
+ || endptr < s + size)
+ {
+ errno = ERANGE;
+ panic_err("could not convert %.*s to integer, result = %lld (%d)",
+ (int)size, s, val, (int)val);
+ }
+ return (int)val;
+}
+
+static unsigned
+safe_atoiu(const char *s, size_t size, int base)
+{
+ char *endptr;
+ long long int val = strtoll(s, &endptr, base);
+
+ if ( val < 0
+ || val > UINT_MAX
+ || endptr < s + size)
+ {
+ errno = ERANGE;
+ panic_err("could not convert %.*s to unsigned integer, result = %lld (%#llx)",
+ (int)size, s, val, val);
+ }
+ return (unsigned)val;
+}
+
+static void
+process_mount_opts(const char *s, struct vbsf_mount_opts *opts)
+{
+ const char *next = s;
+ size_t len;
+ typedef enum handler_opt
+ {
+ HO_RW,
+ HO_RO,
+ HO_UID,
+ HO_GID,
+ HO_TTL,
+ HO_DENTRY_TTL,
+ HO_INODE_TTL,
+ HO_MAX_IO_PAGES,
+ HO_DIR_BUF,
+ HO_CACHE,
+ HO_DMODE,
+ HO_FMODE,
+ HO_UMASK,
+ HO_DMASK,
+ HO_FMASK,
+ HO_IOCHARSET,
+ HO_NLS,
+ HO_CONVERTCP,
+ HO_NOEXEC,
+ HO_EXEC,
+ HO_NODEV,
+ HO_DEV,
+ HO_NOSUID,
+ HO_SUID,
+ HO_REMOUNT,
+ HO_NOAUTO,
+ HO_NIGNORE
+ } handler_opt;
+ struct
+ {
+ const char *name;
+ handler_opt opt;
+ int has_arg;
+ const char *desc;
+ } handlers[] =
+ {
+ {"rw", HO_RW, 0, "mount read write (default)"},
+ {"ro", HO_RO, 0, "mount read only"},
+ {"uid", HO_UID, 1, "default file owner user id"},
+ {"gid", HO_GID, 1, "default file owner group id"},
+ {"ttl", HO_TTL, 1, "time to live for dentries & inode info"},
+ {"dcachettl", HO_DENTRY_TTL, 1, "time to live for dentries"},
+ {"inodettl", HO_INODE_TTL, 1, "time to live for inode info"},
+ {"maxiopages", HO_MAX_IO_PAGES, 1, "max buffer size for I/O with host"},
+ {"dirbuf", HO_DIR_BUF, 1, "directory buffer size (0 for default)"},
+ {"cache", HO_CACHE, 1, "cache mode: none, strict (default), read, readwrite"},
+ {"iocharset", HO_IOCHARSET, 1, "i/o charset (default utf8)"},
+ {"nls", HO_NLS, 1, "i/o charset (default utf8)"},
+ {"convertcp", HO_CONVERTCP, 1, "convert share name from given charset to utf8"},
+ {"dmode", HO_DMODE, 1, "mode of all directories"},
+ {"fmode", HO_FMODE, 1, "mode of all regular files"},
+ {"umask", HO_UMASK, 1, "umask of directories and regular files"},
+ {"dmask", HO_DMASK, 1, "umask of directories"},
+ {"fmask", HO_FMASK, 1, "umask of regular files"},
+ {"noexec", HO_NOEXEC, 0, NULL}, /* don't document these options directly here */
+ {"exec", HO_EXEC, 0, NULL}, /* as they are well known and described in the */
+ {"nodev", HO_NODEV, 0, NULL}, /* usual manpages */
+ {"dev", HO_DEV, 0, NULL},
+ {"nosuid", HO_NOSUID, 0, NULL},
+ {"suid", HO_SUID, 0, NULL},
+ {"remount", HO_REMOUNT, 0, NULL},
+ {"noauto", HO_NOAUTO, 0, NULL},
+ {"_netdev", HO_NIGNORE, 0, NULL},
+ {"relatime", HO_NIGNORE, 0, NULL},
+ {NULL, 0, 0, NULL}
+ }, *handler;
+
+ while (next)
+ {
+ const char *val;
+ size_t key_len, val_len;
+
+ s = next;
+ next = strchr(s, ',');
+ if (!next)
+ {
+ len = strlen(s);
+ }
+ else
+ {
+ len = next - s;
+ next += 1;
+ if (!*next)
+ next = 0;
+ }
+
+ val = NULL;
+ val_len = 0;
+ for (key_len = 0; key_len < len; ++key_len)
+ {
+ if (s[key_len] == '=')
+ {
+ if (key_len + 1 < len)
+ {
+ val = s + key_len + 1;
+ val_len = len - key_len - 1;
+ }
+ break;
+ }
+ }
+
+ for (handler = handlers; handler->name; ++handler)
+ {
+ size_t j;
+ for (j = 0; j < key_len && handler->name[j] == s[j]; ++j)
+ ;
+
+ if (j == key_len && !handler->name[j])
+ {
+ if (handler->has_arg)
+ {
+ if (!(val && *val))
+ {
+ panic("%.*s requires an argument (i.e. %.*s=<arg>)\n",
+ (int)len, s, (int)len, s);
+ }
+ }
+
+ switch (handler->opt)
+ {
+ case HO_RW:
+ opts->fFlags &= ~MS_RDONLY;
+ break;
+ case HO_RO:
+ opts->fFlags |= MS_RDONLY;
+ break;
+ case HO_NOEXEC:
+ opts->fFlags |= MS_NOEXEC;
+ break;
+ case HO_EXEC:
+ opts->fFlags &= ~MS_NOEXEC;
+ break;
+ case HO_NODEV:
+ opts->fFlags |= MS_NODEV;
+ break;
+ case HO_DEV:
+ opts->fFlags &= ~MS_NODEV;
+ break;
+ case HO_NOSUID:
+ opts->fFlags |= MS_NOSUID;
+ break;
+ case HO_SUID:
+ opts->fFlags &= ~MS_NOSUID;
+ break;
+ case HO_REMOUNT:
+ opts->fFlags |= MS_REMOUNT;
+ break;
+ case HO_TTL:
+ snprintf(opts->szTTL, sizeof(opts->szTTL),
+ "ttl=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_DENTRY_TTL:
+ snprintf(opts->szMsDirCacheTTL, sizeof(opts->szMsDirCacheTTL),
+ "dcachettl=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_INODE_TTL:
+ snprintf(opts->szMsInodeTTL, sizeof(opts->szMsInodeTTL),
+ "inodettl=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_MAX_IO_PAGES:
+ snprintf(opts->szMaxIoPages, sizeof(opts->szMaxIoPages),
+ "maxiopages=%d", safe_atoiu(val, val_len, 10));
+ break;
+ case HO_DIR_BUF:
+ snprintf(opts->szDirBuf, sizeof(opts->szDirBuf),
+ "dirbuf=%d", safe_atoiu(val, val_len, 10));
+ break;
+ case HO_CACHE:
+#define IS_EQUAL(a_sz) (val_len == sizeof(a_sz) - 1U && strncmp(val, a_sz, sizeof(a_sz) - 1U) == 0)
+ if (IS_EQUAL("default"))
+ strcpy(opts->szCacheMode, "cache=default");
+ else if (IS_EQUAL("none"))
+ strcpy(opts->szCacheMode, "cache=none");
+ else if (IS_EQUAL("strict"))
+ strcpy(opts->szCacheMode, "cache=strict");
+ else if (IS_EQUAL("read"))
+ strcpy(opts->szCacheMode, "cache=read");
+ else if (IS_EQUAL("readwrite"))
+ strcpy(opts->szCacheMode, "cache=readwrite");
+ else
+ panic("invalid cache mode '%.*s'\n"
+ "Valid cache modes are: default, none, strict, read, readwrite\n",
+ (int)val_len, val);
+ break;
+ case HO_UID:
+ /** @todo convert string to id. */
+ snprintf(opts->szUid, sizeof(opts->szUid),
+ "uid=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_GID:
+ /** @todo convert string to id. */
+ snprintf(opts->szGid, sizeof(opts->szGid),
+ "gid=%d", safe_atoi(val, val_len, 10));
+ break;
+ case HO_DMODE:
+ snprintf(opts->szDMode, sizeof(opts->szDMode),
+ "dmode=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_FMODE:
+ snprintf(opts->szFMode, sizeof(opts->szFMode),
+ "fmode=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_UMASK:
+ {
+ int fMask = safe_atoi(val, val_len, 8);
+ snprintf(opts->szDMask, sizeof(opts->szDMask), "dmask=0%o", fMask);
+ snprintf(opts->szFMask, sizeof(opts->szFMask), "fmask=0%o", fMask);
+ break;
+ }
+ case HO_DMASK:
+ snprintf(opts->szDMask, sizeof(opts->szDMask),
+ "dmask=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_FMASK:
+ snprintf(opts->szFMask, sizeof(opts->szFMask),
+ "fmask=0%o", safe_atoi(val, val_len, 8));
+ break;
+ case HO_IOCHARSET:
+ case HO_NLS:
+ if (val_len >= MAX_NLS_NAME)
+ panic("the character set name for I/O is too long: %*.*s\n", (int)val_len, (int)val_len, val);
+ snprintf(opts->szIoCharset, sizeof(opts->szIoCharset),
+ "%s=%*.*s", handler->opt == HO_IOCHARSET ? "iocharset" : "nls", (int)val_len, (int)val_len, val);
+ break;
+ case HO_CONVERTCP:
+ opts->pszConvertCp = malloc(val_len + 1);
+ if (!opts->pszConvertCp)
+ panic_err("could not allocate memory");
+ memcpy(opts->pszConvertCp, val, val_len);
+ opts->pszConvertCp[val_len] = '\0';
+ break;
+ case HO_NOAUTO:
+ case HO_NIGNORE:
+ break;
+ }
+ break;
+ }
+ continue;
+ }
+
+ if ( !handler->name
+ && !opts->fSloppy)
+ {
+ fprintf(stderr, "unknown mount option `%.*s'\n", (int)len, s);
+ fprintf(stderr, "valid options:\n");
+
+ for (handler = handlers; handler->name; ++handler)
+ {
+ if (handler->desc)
+ fprintf(stderr, " %-10s%s %s\n", handler->name,
+ handler->has_arg ? "=<arg>" : "", handler->desc);
+ }
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+/** Appends @a pszOptVal to pszOpts if not empty. */
+static size_t append_option(char *pszOpts, size_t cbOpts, size_t offOpts, const char *pszOptVal)
+{
+ if (*pszOptVal != '\0')
+ {
+ size_t cchOptVal = strlen(pszOptVal);
+ if (offOpts + (offOpts > 0) + cchOptVal < cbOpts)
+ {
+ if (offOpts)
+ pszOpts[offOpts++] = ',';
+ memcpy(&pszOpts[offOpts], pszOptVal, cchOptVal);
+ offOpts += cchOptVal;
+ pszOpts[offOpts] = '\0';
+ }
+ else
+ panic("Too many options!");
+ }
+ return offOpts;
+}
+
+static void
+convertcp(char *in_codeset, char *pszSharedFolder, char *pszDst)
+{
+ char *i = pszSharedFolder;
+ char *o = pszDst;
+ size_t ib = strlen(pszSharedFolder);
+ size_t ob = MAX_HOST_NAME - 1;
+ iconv_t cd;
+
+ cd = iconv_open("UTF-8", in_codeset);
+ if (cd == (iconv_t)-1)
+ {
+ panic_err("could not convert share name, iconv_open `%s' failed",
+ in_codeset);
+ }
+
+ while (ib)
+ {
+ size_t c = iconv(cd, &i, &ib, &o, &ob);
+ if (c == (size_t)-1)
+ {
+ panic_err("could not convert share name(%s) at %d",
+ pszSharedFolder, (int)(strlen(pszSharedFolder) - ib));
+ }
+ }
+ *o = 0;
+}
+
+
+/**
+ * Print out a usage message and exit.
+ *
+ * @returns 1
+ * @param argv0 The name of the application
+ */
+static int usage(char *argv0)
+{
+ printf("Usage: %s [OPTIONS] NAME MOUNTPOINT\n"
+ "Mount the VirtualBox shared folder NAME from the host system to MOUNTPOINT.\n"
+ "\n"
+ " -w mount the shared folder writable (the default)\n"
+ " -r mount the shared folder read-only\n"
+ " -n do not create an mtab entry\n"
+ " -s sloppy parsing, ignore unrecognized mount options\n"
+ " -o OPTION[,OPTION...] use the mount options specified\n"
+ "\n", argv0);
+ printf("Available mount options are:\n"
+ " rw mount writable (the default)\n"
+ " ro mount read only\n"
+ " uid=UID set the default file owner user id to UID\n"
+ " gid=GID set the default file owner group id to GID\n");
+ printf(" ttl=MILLIESECSONDS set the \"time to live\" for both the directory cache\n"
+ " and inode info. -1 for kernel default, 0 disables it.\n"
+ " dcachettl=MILLIES set the \"time to live\" for the directory cache,\n"
+ " overriding the 'ttl' option. Ignored if negative.\n"
+ " inodettl=MILLIES set the \"time to live\" for the inode information,\n"
+ " overriding the 'ttl' option. Ignored if negative.\n");
+ printf(" maxiopages=PAGES set the max host I/O buffers size in pages. Uses\n"
+ " default if zero.\n"
+ " dirbuf=BYTES set the directory enumeration buffer size in bytes.\n"
+ " Uses default size if zero.\n");
+ printf(" cache=MODE set the caching mode for the mount. Allowed values:\n"
+ " default: use the kernel default (strict)\n"
+ " none: no caching; may experience guest side\n"
+ " coherence issues between mmap and read.\n");
+ printf(" strict: no caching, except for writably mapped\n"
+ " files (for guest side coherence)\n"
+ " read: read via the page cache; host changes\n"
+ " may be completely ignored\n");
+ printf(" readwrite: read and write via the page cache; host\n"
+ " changes may be completely ignored and\n"
+ " guest changes takes a while to reach the host\n");
+ printf(" dmode=MODE override the mode of all directories to (octal) MODE\n"
+ " fmode=MODE override the mode of all regular files to (octal) MODE\n"
+ " umask=UMASK set the umask to (octal) UMASK\n");
+ printf(" dmask=UMASK set the umask applied to directories only\n"
+ " fmask=UMASK set the umask applied to regular files only\n"
+ " iocharset CHARSET use the character set CHARSET for I/O operations\n"
+ " (default set is utf8)\n"
+ " convertcp CHARSET convert the folder name from CHARSET to utf8\n"
+ "\n");
+ printf("Less common used options:\n"
+ " noexec,exec,nodev,dev,nosuid,suid\n");
+ return EXIT_FAILURE;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c;
+ int err;
+ int saved_errno;
+ int nomtab = 0;
+ char *pszSharedFolder;
+ char *pszMountPoint;
+ struct utsname uts;
+ int major, minor, patch;
+ size_t offOpts;
+ static const char s_szSfNameOpt[] = "sf_name=";
+ char szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1 + MAX_HOST_NAME];
+ char szOpts[MAX_MNTOPT_STR];
+ struct vbsf_mount_opts opts =
+ {
+ MS_NODEV,
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ false, /*fSloppy*/
+ NULL,
+ };
+
+ AssertCompile(sizeof(uid_t) == sizeof(int));
+ AssertCompile(sizeof(gid_t) == sizeof(int));
+
+ if (getuid())
+ panic("Only root can mount shared folders from the host.\n");
+
+ if (!argv[0])
+ argv[0] = "mount.vboxsf";
+
+ /*
+ * Parse options.
+ */
+ while ((c = getopt(argc, argv, "rwsno:h")) != -1)
+ {
+ switch (c)
+ {
+ default:
+ fprintf(stderr, "unknown option `%c:%#x'\n", c, c);
+ RT_FALL_THRU();
+ case '?':
+ case 'h':
+ return usage(argv[0]);
+
+ case 'r':
+ opts.fFlags |= MS_RDONLY;
+ break;
+
+ case 'w':
+ opts.fFlags &= ~MS_RDONLY;
+ break;
+
+ case 's':
+ opts.fSloppy = true;
+ break;
+
+ case 'o':
+ process_mount_opts(optarg, &opts);
+ break;
+
+ case 'n':
+ nomtab = 1;
+ break;
+ }
+ }
+
+ if (argc - optind < 2)
+ return usage(argv[0]);
+
+ pszSharedFolder = argv[optind];
+ pszMountPoint = argv[optind + 1];
+ if (opts.pszConvertCp)
+ {
+ convertcp(opts.pszConvertCp, pszSharedFolder, &szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1]);
+ pszSharedFolder = &szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1];
+ }
+
+ /*
+ * Concat option strings.
+ */
+ offOpts = 0;
+ szOpts[0] = '\0';
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szTTL);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMsDirCacheTTL);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMsInodeTTL);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szMaxIoPages);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDirBuf);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szCacheMode);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szUid);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szGid);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDMode);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szFMode);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szDMask);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szFMask);
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, opts.szIoCharset);
+
+ /* For pre-2.6 kernels we have to supply the shared folder name as a
+ string option because the kernel hides the device name from us. */
+ RT_ZERO(uts);
+ if ( uname(&uts) == -1
+ || sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3)
+ major = minor = patch = 5;
+
+ if (KERNEL_VERSION(major, minor, patch) < KERNEL_VERSION(2,6,0))
+ {
+ memcpy(szSharedFolderIconved, s_szSfNameOpt, sizeof(s_szSfNameOpt) - 1);
+ if (!opts.pszConvertCp)
+ {
+ if (strlen(pszSharedFolder) >= MAX_HOST_NAME)
+ panic("%s: shared folder name is too long (max %d)", argv[0], (int)MAX_HOST_NAME - 1);
+ strcpy(&szSharedFolderIconved[sizeof(s_szSfNameOpt) - 1], pszSharedFolder);
+ }
+ offOpts = append_option(szOpts, sizeof(szOpts), offOpts, szSharedFolderIconved);
+ }
+
+ /*
+ * Do the actual mounting.
+ */
+ err = mount(pszSharedFolder, pszMountPoint, "vboxsf", opts.fFlags, szOpts);
+ saved_errno = errno;
+
+ if (err)
+ {
+ if (saved_errno == ENXIO)
+ panic("%s: shared folder '%s' was not found (check VM settings / spelling)\n", argv[0], pszSharedFolder);
+ else
+ panic_err("%s: mounting failed with the error", argv[0]);
+ }
+
+ if (!nomtab)
+ {
+ err = vbsfmount_complete(pszSharedFolder, pszMountPoint, opts.fFlags, szOpts);
+ switch (err)
+ {
+ case 0: /* Success. */
+ break;
+
+ case 1:
+ panic_err("%s: Could not update mount table (out of memory).", argv[0]);
+ break;
+
+ case 2:
+ panic_err("%s: Could not open mount table for update.", argv[0]);
+ break;
+
+ case 3:
+ /* panic_err("%s: Could not add an entry to the mount table.", argv[0]); */
+ break;
+
+ default:
+ panic_err("%s: Unknown error while completing mount operation: %d", argv[0], err);
+ break;
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
diff --git a/src/VBox/Additions/linux/sharedfolders/regops.c b/src/VBox/Additions/linux/sharedfolders/regops.c
new file mode 100644
index 00000000..ca604090
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/regops.c
@@ -0,0 +1,3902 @@
+/* $Id: regops.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, regular file inode and file operations.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+#include <linux/uio.h>
+#if RTLNX_VER_MIN(2,5,32)
+# include <linux/aio.h> /* struct kiocb before 4.1 */
+#endif
+#if RTLNX_VER_MIN(2,5,12)
+# include <linux/buffer_head.h>
+#endif
+#if RTLNX_VER_RANGE(2,5,12, 2,6,31)
+# include <linux/writeback.h>
+#endif
+#if RTLNX_VER_RANGE(2,6,23, 3,16,0)
+# include <linux/splice.h>
+#endif
+#if RTLNX_VER_RANGE(2,6,17, 2,6,23)
+# include <linux/pipe_fs_i.h>
+#endif
+#if RTLNX_VER_MIN(2,4,10)
+# include <linux/swap.h> /* for mark_page_accessed */
+#endif
+#include <iprt/err.h>
+
+#if RTLNX_VER_MAX(2,6,18)
+# define SEEK_END 2
+#endif
+
+#if RTLNX_VER_MIN(6,4,0)
+# define VBOX_ITER_IOV_ADDR(a_iter) iter_iov_addr(a_iter)
+#elif RTLNX_VER_MIN(3,19,0)
+# define VBOX_ITER_IOV_ADDR(a_iter) (a_iter->kvec->iov_base + a_iter->iov_offset)
+#else
+# define VBOX_ITER_IOV_ADDR(a_iter) (a_iter->iov->iov_base + a_iter->iov_offset)
+#endif
+
+#if RTLNX_VER_MAX(3,16,0)
+# define iter_is_iovec(a_pIter) ( !((a_pIter)->type & ITER_KVEC) )
+#elif RTLNX_VER_MAX(3,19,0)
+# define iter_is_iovec(a_pIter) ( !((a_pIter)->type & (ITER_KVEC | ITER_BVEC)) )
+#endif
+
+#if RTLNX_VER_MAX(4,17,0)
+# define vm_fault_t int
+#endif
+
+#if RTLNX_VER_MAX(2,5,20)
+# define pgoff_t unsigned long
+#endif
+
+#if RTLNX_VER_MAX(2,5,12)
+# define PageUptodate(a_pPage) Page_Uptodate(a_pPage)
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def VBSF_GET_ITER_TYPE
+ * Accessor for getting iov iter type member which changed name in 5.14. */
+#if RTLNX_VER_MIN(5,14,0)
+# define VBSF_GET_ITER_TYPE(a_pIter) ((a_pIter)->iter_type)
+#else
+# define VBSF_GET_ITER_TYPE(a_pIter) ((a_pIter)->type)
+#endif
+
+/** Starting from 6.4.0, iter_iov() macro should be used in order to access to iov field
+ * of struct iov_iter. */
+#if RTLNX_VER_MIN(6,4,0) || RTLNX_RHEL_RANGE(9,4, 9,99)
+# define VBSF_GET_ITER_IOV(_iter) iter_iov(_iter)
+#else
+# define VBSF_GET_ITER_IOV(_iter) iter->iov
+#endif
+
+/** @def VBOX_IOV_ITER_IS_KVEC
+ * Test if iov iter type is ITER_KVEC. */
+#if RTLNX_VER_MIN(4,20,0)
+# define VBOX_IOV_ITER_IS_KVEC(a_iter) iov_iter_is_kvec(a_iter)
+#else
+# define VBOX_IOV_ITER_IS_KVEC(a_iter) (VBSF_GET_ITER_TYPE(iter) & ITER_KVEC)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#if RTLNX_VER_MAX(3,16,0)
+struct vbsf_iov_iter {
+ unsigned int type;
+ unsigned int v_write : 1;
+ size_t iov_offset;
+ size_t nr_segs;
+ struct iovec const *iov;
+# ifdef VBOX_STRICT
+ struct iovec const *iov_org;
+ size_t nr_segs_org;
+# endif
+};
+# ifdef VBOX_STRICT
+# define VBSF_IOV_ITER_INITIALIZER(a_cSegs, a_pIov, a_fWrite) \
+ { vbsf_iov_iter_detect_type(a_pIov, a_cSegs), a_fWrite, 0, a_cSegs, a_pIov, a_pIov, a_cSegs }
+# else
+# define VBSF_IOV_ITER_INITIALIZER(a_cSegs, a_pIov, a_fWrite) \
+ { vbsf_iov_iter_detect_type(a_pIov, a_cSegs), a_fWrite, 0, a_cSegs, a_pIov }
+# endif
+# define ITER_KVEC 1
+# define iov_iter vbsf_iov_iter
+#endif
+
+#if RTLNX_VER_MIN(2,6,19)
+/** Used by vbsf_iter_lock_pages() to keep the first page of the next segment. */
+struct vbsf_iter_stash {
+ struct page *pPage;
+ size_t off;
+ size_t cb;
+# if RTLNX_VER_MAX(4,11,0)
+ size_t offFromEnd;
+ struct iov_iter Copy;
+# endif
+};
+#endif /* >= 3.16.0 */
+/** Initializer for struct vbsf_iter_stash. */
+#if RTLNX_VER_MIN(4,11,0)
+# define VBSF_ITER_STASH_INITIALIZER { NULL, 0 }
+#else
+# define VBSF_ITER_STASH_INITIALIZER { NULL, 0, ~(size_t)0 }
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLINLINE(void) vbsf_put_page(struct page *pPage);
+static void vbsf_unlock_user_pages(struct page **papPages, size_t cPages, bool fSetDirty, bool fLockPgHack);
+static void vbsf_reg_write_sync_page_cache(struct address_space *mapping, loff_t offFile, uint32_t cbRange,
+ uint8_t const *pbSrcBuf, struct page **papSrcPages,
+ uint32_t offSrcPage, size_t cSrcPages);
+
+
+/*********************************************************************************************************************************
+* Provide more recent uio.h functionality to older kernels. *
+*********************************************************************************************************************************/
+#if RTLNX_VER_RANGE(2,6,19, 3,16,0)
+
+/**
+ * Detects the vector type.
+ */
+static int vbsf_iov_iter_detect_type(struct iovec const *paIov, size_t cSegs)
+{
+ /* Check the first segment with a non-zero length. */
+ while (cSegs-- > 0) {
+ if (paIov->iov_len > 0) {
+ if (access_ok(VERIFY_READ, paIov->iov_base, paIov->iov_len))
+#if RTLNX_VER_MIN(5,10,0)
+ return (uintptr_t)paIov->iov_base >= TASK_SIZE_MAX ? ITER_KVEC : 0;
+#else
+ return (uintptr_t)paIov->iov_base >= USER_DS.seg ? ITER_KVEC : 0;
+#endif
+ AssertMsgFailed(("%p LB %#zx\n", paIov->iov_base, paIov->iov_len));
+ break;
+ }
+ paIov++;
+ }
+ return 0;
+}
+
+
+# undef iov_iter_count
+# define iov_iter_count(a_pIter) vbsf_iov_iter_count(a_pIter)
+static size_t vbsf_iov_iter_count(struct vbsf_iov_iter const *iter)
+{
+ size_t cbRet = 0;
+ size_t cLeft = iter->nr_segs;
+ struct iovec const *iov = iter->iov;
+ while (cLeft-- > 0) {
+ cbRet += iov->iov_len;
+ iov++;
+ }
+ return cbRet - iter->iov_offset;
+}
+
+
+# undef iov_iter_single_seg_count
+# define iov_iter_single_seg_count(a_pIter) vbsf_iov_iter_single_seg_count(a_pIter)
+static size_t vbsf_iov_iter_single_seg_count(struct vbsf_iov_iter const *iter)
+{
+ if (iter->nr_segs > 0)
+ return iter->iov->iov_len - iter->iov_offset;
+ return 0;
+}
+
+
+# undef iov_iter_advance
+# define iov_iter_advance(a_pIter, a_cbSkip) vbsf_iov_iter_advance(a_pIter, a_cbSkip)
+static void vbsf_iov_iter_advance(struct vbsf_iov_iter *iter, size_t cbSkip)
+{
+ SFLOG2(("vbsf_iov_iter_advance: cbSkip=%#zx\n", cbSkip));
+ if (iter->nr_segs > 0) {
+ size_t const cbLeftCur = iter->iov->iov_len - iter->iov_offset;
+ Assert(iter->iov_offset <= iter->iov->iov_len);
+ if (cbLeftCur > cbSkip) {
+ iter->iov_offset += cbSkip;
+ } else {
+ cbSkip -= cbLeftCur;
+ iter->iov_offset = 0;
+ iter->iov++;
+ iter->nr_segs--;
+ while (iter->nr_segs > 0) {
+ size_t const cbSeg = iter->iov->iov_len;
+ if (cbSeg > cbSkip) {
+ iter->iov_offset = cbSkip;
+ break;
+ }
+ cbSkip -= cbSeg;
+ iter->iov++;
+ iter->nr_segs--;
+ }
+ }
+ }
+}
+
+
+# undef iov_iter_get_pages
+# define iov_iter_get_pages(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0) \
+ vbsf_iov_iter_get_pages(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0)
+static ssize_t vbsf_iov_iter_get_pages(struct vbsf_iov_iter *iter, struct page **papPages,
+ size_t cbMax, unsigned cMaxPages, size_t *poffPg0)
+{
+ while (iter->nr_segs > 0) {
+ size_t const cbLeft = iter->iov->iov_len - iter->iov_offset;
+ Assert(iter->iov->iov_len >= iter->iov_offset);
+ if (cbLeft > 0) {
+ uintptr_t uPtrFrom = (uintptr_t)iter->iov->iov_base + iter->iov_offset;
+ size_t offPg0 = *poffPg0 = uPtrFrom & PAGE_OFFSET_MASK;
+ size_t cPagesLeft = RT_ALIGN_Z(offPg0 + cbLeft, PAGE_SIZE) >> PAGE_SHIFT;
+ size_t cPages = RT_MIN(cPagesLeft, cMaxPages);
+ struct task_struct *pTask = current;
+ size_t cPagesLocked;
+
+ down_read(&pTask->mm->mmap_sem);
+ cPagesLocked = get_user_pages(pTask, pTask->mm, uPtrFrom, cPages, iter->v_write, 1 /*force*/, papPages, NULL);
+ up_read(&pTask->mm->mmap_sem);
+ if (cPagesLocked == cPages) {
+ size_t cbRet = (cPages << PAGE_SHIFT) - offPg0;
+ if (cPages == cPagesLeft) {
+ size_t offLastPg = (uPtrFrom + cbLeft) & PAGE_OFFSET_MASK;
+ if (offLastPg)
+ cbRet -= PAGE_SIZE - offLastPg;
+ }
+ Assert(cbRet <= cbLeft);
+ return cbRet;
+ }
+ if (cPagesLocked > 0)
+ vbsf_unlock_user_pages(papPages, cPagesLocked, false /*fSetDirty*/, false /*fLockPgHack*/);
+ return -EFAULT;
+ }
+ iter->iov_offset = 0;
+ iter->iov++;
+ iter->nr_segs--;
+ }
+ AssertFailed();
+ return 0;
+}
+
+
+# undef iov_iter_truncate
+# define iov_iter_truncate(iter, cbNew) vbsf_iov_iter_truncate(iter, cbNew)
+static void vbsf_iov_iter_truncate(struct vbsf_iov_iter *iter, size_t cbNew)
+{
+ /* we have no counter or stuff, so it's a no-op. */
+ RT_NOREF(iter, cbNew);
+}
+
+
+# undef iov_iter_revert
+# define iov_iter_revert(a_pIter, a_cbRewind) vbsf_iov_iter_revert(a_pIter, a_cbRewind)
+void vbsf_iov_iter_revert(struct vbsf_iov_iter *iter, size_t cbRewind)
+{
+ SFLOG2(("vbsf_iov_iter_revert: cbRewind=%#zx\n", cbRewind));
+ if (iter->iov_offset > 0) {
+ if (cbRewind <= iter->iov_offset) {
+ iter->iov_offset -= cbRewind;
+ return;
+ }
+ cbRewind -= iter->iov_offset;
+ iter->iov_offset = 0;
+ }
+
+ while (cbRewind > 0) {
+ struct iovec const *pIov = --iter->iov;
+ size_t const cbSeg = pIov->iov_len;
+ iter->nr_segs++;
+
+ Assert((uintptr_t)pIov >= (uintptr_t)iter->iov_org);
+ Assert(iter->nr_segs <= iter->nr_segs_org);
+
+ if (cbRewind <= cbSeg) {
+ iter->iov_offset = cbSeg - cbRewind;
+ break;
+ }
+ cbRewind -= cbSeg;
+ }
+}
+
+#endif /* 2.6.19 <= linux < 3.16.0 */
+#if RTLNX_VER_RANGE(3,16,0, 3,16,35)
+
+/** This is for implementing cMaxPage on 3.16 which doesn't have it. */
+static ssize_t vbsf_iov_iter_get_pages_3_16(struct iov_iter *iter, struct page **papPages,
+ size_t cbMax, unsigned cMaxPages, size_t *poffPg0)
+{
+ if (!(iter->type & ITER_BVEC)) {
+ size_t const offPg0 = iter->iov_offset & PAGE_OFFSET_MASK;
+ size_t const cbMaxPages = ((size_t)cMaxPages << PAGE_SHIFT) - offPg0;
+ if (cbMax > cbMaxPages)
+ cbMax = cbMaxPages;
+ }
+ /* else: BVEC works a page at a time and shouldn't have much of a problem here. */
+ return iov_iter_get_pages(iter, papPages, cbMax, poffPg0);
+}
+# undef iov_iter_get_pages
+# define iov_iter_get_pages(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0) \
+ vbsf_iov_iter_get_pages_3_16(a_pIter, a_papPages, a_cbMax, a_cMaxPages, a_poffPg0)
+
+#endif /* 3.16.0-3.16.34 */
+#if RTLNX_VER_RANGE(2,6,19, 3,18,0)
+
+static size_t copy_from_iter(uint8_t *pbDst, size_t cbToCopy, struct iov_iter *pSrcIter)
+{
+ size_t const cbTotal = cbToCopy;
+ Assert(iov_iter_count(pSrcIter) >= cbToCopy);
+# if RTLNX_VER_MIN(3,16,0)
+ if (pSrcIter->type & ITER_BVEC) {
+ while (cbToCopy > 0) {
+ size_t const offPage = (uintptr_t)pbDst & PAGE_OFFSET_MASK;
+ size_t const cbThisCopy = RT_MIN(PAGE_SIZE - offPage, cbToCopy);
+ struct page *pPage = rtR0MemObjLinuxVirtToPage(pbDst);
+ size_t cbCopied = copy_page_from_iter(pPage, offPage, cbThisCopy, pSrcIter);
+ AssertStmt(cbCopied <= cbThisCopy, cbCopied = cbThisCopy);
+ pbDst += cbCopied;
+ cbToCopy -= cbCopied;
+ if (cbCopied != cbToCopy)
+ break;
+ }
+ } else
+# endif
+ {
+ while (cbToCopy > 0) {
+ size_t cbThisCopy = iov_iter_single_seg_count(pSrcIter);
+ if (cbThisCopy > 0) {
+ if (cbThisCopy > cbToCopy)
+ cbThisCopy = cbToCopy;
+ if (pSrcIter->type & ITER_KVEC)
+ memcpy(pbDst, (void *)VBOX_ITER_IOV_ADDR(pSrcIter), cbThisCopy);
+ else if (copy_from_user(pbDst, VBOX_ITER_IOV_ADDR(pSrcIter), cbThisCopy) != 0)
+ break;
+ pbDst += cbThisCopy;
+ cbToCopy -= cbThisCopy;
+ }
+ iov_iter_advance(pSrcIter, cbThisCopy);
+ }
+ }
+ return cbTotal - cbToCopy;
+}
+
+
+static size_t copy_to_iter(uint8_t const *pbSrc, size_t cbToCopy, struct iov_iter *pDstIter)
+{
+ size_t const cbTotal = cbToCopy;
+ Assert(iov_iter_count(pDstIter) >= cbToCopy);
+# if RTLNX_VER_MIN(3,16,0)
+ if (pDstIter->type & ITER_BVEC) {
+ while (cbToCopy > 0) {
+ size_t const offPage = (uintptr_t)pbSrc & PAGE_OFFSET_MASK;
+ size_t const cbThisCopy = RT_MIN(PAGE_SIZE - offPage, cbToCopy);
+ struct page *pPage = rtR0MemObjLinuxVirtToPage((void *)pbSrc);
+ size_t cbCopied = copy_page_to_iter(pPage, offPage, cbThisCopy, pDstIter);
+ AssertStmt(cbCopied <= cbThisCopy, cbCopied = cbThisCopy);
+ pbSrc += cbCopied;
+ cbToCopy -= cbCopied;
+ if (cbCopied != cbToCopy)
+ break;
+ }
+ } else
+# endif
+ {
+ while (cbToCopy > 0) {
+ size_t cbThisCopy = iov_iter_single_seg_count(pDstIter);
+ if (cbThisCopy > 0) {
+ if (cbThisCopy > cbToCopy)
+ cbThisCopy = cbToCopy;
+ if (pDstIter->type & ITER_KVEC)
+ memcpy((void *)VBOX_ITER_IOV_ADDR(pDstIter), pbSrc, cbThisCopy);
+ else if (copy_to_user(VBOX_ITER_IOV_ADDR(pDstIter), pbSrc, cbThisCopy) != 0) {
+ break;
+ }
+ pbSrc += cbThisCopy;
+ cbToCopy -= cbThisCopy;
+ }
+ iov_iter_advance(pDstIter, cbThisCopy);
+ }
+ }
+ return cbTotal - cbToCopy;
+}
+
+#endif /* 3.16.0 <= linux < 3.18.0 */
+
+
+
+/*********************************************************************************************************************************
+* Handle management *
+*********************************************************************************************************************************/
+
+/**
+ * Called when an inode is released to unlink all handles that might impossibly
+ * still be associated with it.
+ *
+ * @param pInodeInfo The inode which handles to drop.
+ */
+void vbsf_handle_drop_chain(struct vbsf_inode_info *pInodeInfo)
+{
+ struct vbsf_handle *pCur, *pNext;
+ unsigned long fSavedFlags;
+ SFLOGFLOW(("vbsf_handle_drop_chain: %p\n", pInodeInfo));
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ RTListForEachSafe(&pInodeInfo->HandleList, pCur, pNext, struct vbsf_handle, Entry) {
+ AssertMsg( (pCur->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST))
+ == (VBSF_HANDLE_F_MAGIC | VBSF_HANDLE_F_ON_LIST), ("%p %#x\n", pCur, pCur->fFlags));
+ pCur->fFlags |= VBSF_HANDLE_F_ON_LIST;
+ RTListNodeRemove(&pCur->Entry);
+ }
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+}
+
+
+/**
+ * Locates a handle that matches all the flags in @a fFlags.
+ *
+ * @returns Pointer to handle on success (retained), use vbsf_handle_release() to
+ * release it. NULL if no suitable handle was found.
+ * @param pInodeInfo The inode info to search.
+ * @param fFlagsSet The flags that must be set.
+ * @param fFlagsClear The flags that must be clear.
+ */
+struct vbsf_handle *vbsf_handle_find(struct vbsf_inode_info *pInodeInfo, uint32_t fFlagsSet, uint32_t fFlagsClear)
+{
+ struct vbsf_handle *pCur;
+ unsigned long fSavedFlags;
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ RTListForEach(&pInodeInfo->HandleList, pCur, struct vbsf_handle, Entry) {
+ AssertMsg( (pCur->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST))
+ == (VBSF_HANDLE_F_MAGIC | VBSF_HANDLE_F_ON_LIST), ("%p %#x\n", pCur, pCur->fFlags));
+ if ((pCur->fFlags & (fFlagsSet | fFlagsClear)) == fFlagsSet) {
+ uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs);
+ if (cRefs > 1) {
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+ SFLOGFLOW(("vbsf_handle_find: returns %p\n", pCur));
+ return pCur;
+ }
+ /* Oops, already being closed (safe as it's only ever increased here). */
+ ASMAtomicDecU32(&pCur->cRefs);
+ }
+ }
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+ SFLOGFLOW(("vbsf_handle_find: returns NULL!\n"));
+ return NULL;
+}
+
+
+/**
+ * Slow worker for vbsf_handle_release() that does the freeing.
+ *
+ * @returns 0 (ref count).
+ * @param pHandle The handle to release.
+ * @param pSuperInfo The info structure for the shared folder associated with
+ * the handle.
+ * @param pszCaller The caller name (for logging failures).
+ */
+uint32_t vbsf_handle_release_slow(struct vbsf_handle *pHandle, struct vbsf_super_info *pSuperInfo, const char *pszCaller)
+{
+ int rc;
+ unsigned long fSavedFlags;
+
+ SFLOGFLOW(("vbsf_handle_release_slow: %p (%s)\n", pHandle, pszCaller));
+
+ /*
+ * Remove from the list.
+ */
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ AssertMsg((pHandle->fFlags & VBSF_HANDLE_F_MAGIC_MASK) == VBSF_HANDLE_F_MAGIC, ("%p %#x\n", pHandle, pHandle->fFlags));
+ Assert(pHandle->pInodeInfo);
+ Assert(pHandle->pInodeInfo && pHandle->pInodeInfo->u32Magic == SF_INODE_INFO_MAGIC);
+
+ if (pHandle->fFlags & VBSF_HANDLE_F_ON_LIST) {
+ pHandle->fFlags &= ~VBSF_HANDLE_F_ON_LIST;
+ RTListNodeRemove(&pHandle->Entry);
+ }
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+
+ /*
+ * Actually destroy it.
+ */
+ rc = VbglR0SfHostReqCloseSimple(pSuperInfo->map.root, pHandle->hHost);
+ if (RT_FAILURE(rc))
+ LogFunc(("Caller %s: VbglR0SfHostReqCloseSimple %#RX64 failed with rc=%Rrc\n", pszCaller, pHandle->hHost, rc));
+ pHandle->hHost = SHFL_HANDLE_NIL;
+ pHandle->fFlags = VBSF_HANDLE_F_MAGIC_DEAD;
+ kfree(pHandle);
+ return 0;
+}
+
+
+/**
+ * Appends a handle to a handle list.
+ *
+ * @param pInodeInfo The inode to add it to.
+ * @param pHandle The handle to add.
+ */
+void vbsf_handle_append(struct vbsf_inode_info *pInodeInfo, struct vbsf_handle *pHandle)
+{
+#ifdef VBOX_STRICT
+ struct vbsf_handle *pCur;
+#endif
+ unsigned long fSavedFlags;
+
+ SFLOGFLOW(("vbsf_handle_append: %p (to %p)\n", pHandle, pInodeInfo));
+ AssertMsg((pHandle->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST)) == VBSF_HANDLE_F_MAGIC,
+ ("%p %#x\n", pHandle, pHandle->fFlags));
+ Assert(pInodeInfo->u32Magic == SF_INODE_INFO_MAGIC);
+
+ spin_lock_irqsave(&g_SfHandleLock, fSavedFlags);
+
+ AssertMsg((pHandle->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST)) == VBSF_HANDLE_F_MAGIC,
+ ("%p %#x\n", pHandle, pHandle->fFlags));
+#ifdef VBOX_STRICT
+ RTListForEach(&pInodeInfo->HandleList, pCur, struct vbsf_handle, Entry) {
+ Assert(pCur != pHandle);
+ AssertMsg( (pCur->fFlags & (VBSF_HANDLE_F_MAGIC_MASK | VBSF_HANDLE_F_ON_LIST))
+ == (VBSF_HANDLE_F_MAGIC | VBSF_HANDLE_F_ON_LIST), ("%p %#x\n", pCur, pCur->fFlags));
+ }
+ pHandle->pInodeInfo = pInodeInfo;
+#endif
+
+ pHandle->fFlags |= VBSF_HANDLE_F_ON_LIST;
+ RTListAppend(&pInodeInfo->HandleList, &pHandle->Entry);
+
+ spin_unlock_irqrestore(&g_SfHandleLock, fSavedFlags);
+}
+
+
+
+/*********************************************************************************************************************************
+* Misc *
+*********************************************************************************************************************************/
+
+#if RTLNX_VER_MAX(2,6,6)
+/** Any writable mappings? */
+DECLINLINE(bool) mapping_writably_mapped(struct address_space const *mapping)
+{
+# if RTLNX_VER_MIN(2,5,6)
+ return !list_empty(&mapping->i_mmap_shared);
+# else
+ return mapping->i_mmap_shared != NULL;
+# endif
+}
+#endif
+
+
+#if RTLNX_VER_MAX(2,5,12)
+/** Missing in 2.4.x, so just stub it for now. */
+DECLINLINE(bool) PageWriteback(struct page const *page)
+{
+ return false;
+}
+#endif
+
+
+/**
+ * Helper for deciding wheter we should do a read via the page cache or not.
+ *
+ * By default we will only use the page cache if there is a writable memory
+ * mapping of the file with a chance that it may have modified any of the pages
+ * already.
+ */
+DECLINLINE(bool) vbsf_should_use_cached_read(struct file *file, struct address_space *mapping, struct vbsf_super_info *pSuperInfo)
+{
+ if ( (file->f_flags & O_DIRECT)
+ || pSuperInfo->enmCacheMode == kVbsfCacheMode_None)
+ return false;
+ if ( pSuperInfo->enmCacheMode == kVbsfCacheMode_Read
+ || pSuperInfo->enmCacheMode == kVbsfCacheMode_ReadWrite)
+ return true;
+ Assert(pSuperInfo->enmCacheMode == kVbsfCacheMode_Strict);
+ return mapping
+ && mapping->nrpages > 0
+ && mapping_writably_mapped(mapping);
+}
+
+
+
+/*********************************************************************************************************************************
+* Pipe / splice stuff mainly for 2.6.17 >= linux < 2.6.31 (where no fallbacks were available) *
+*********************************************************************************************************************************/
+
+#if RTLNX_VER_RANGE(2,6,17, 3,16,0)
+
+# if RTLNX_VER_MAX(2,6,30)
+# define LOCK_PIPE(a_pPipe) do { if ((a_pPipe)->inode) mutex_lock(&(a_pPipe)->inode->i_mutex); } while (0)
+# define UNLOCK_PIPE(a_pPipe) do { if ((a_pPipe)->inode) mutex_unlock(&(a_pPipe)->inode->i_mutex); } while (0)
+# else
+# define LOCK_PIPE(a_pPipe) pipe_lock(a_pPipe)
+# define UNLOCK_PIPE(a_pPipe) pipe_unlock(a_pPipe)
+# endif
+
+
+/** Waits for the pipe buffer status to change. */
+static void vbsf_wait_pipe(struct pipe_inode_info *pPipe)
+{
+ DEFINE_WAIT(WaitStuff);
+# ifdef TASK_NONINTERACTIVE
+ prepare_to_wait(&pPipe->wait, &WaitStuff, TASK_INTERRUPTIBLE | TASK_NONINTERACTIVE);
+# else
+ prepare_to_wait(&pPipe->wait, &WaitStuff, TASK_INTERRUPTIBLE);
+# endif
+ UNLOCK_PIPE(pPipe);
+
+ schedule();
+
+ finish_wait(&pPipe->wait, &WaitStuff);
+ LOCK_PIPE(pPipe);
+}
+
+
+/** Worker for vbsf_feed_pages_to_pipe that wakes up readers. */
+static void vbsf_wake_up_pipe(struct pipe_inode_info *pPipe, bool fReaders)
+{
+ smp_mb();
+ if (waitqueue_active(&pPipe->wait))
+ wake_up_interruptible_sync(&pPipe->wait);
+ if (fReaders)
+ kill_fasync(&pPipe->fasync_readers, SIGIO, POLL_IN);
+ else
+ kill_fasync(&pPipe->fasync_writers, SIGIO, POLL_OUT);
+}
+
+#endif
+#if RTLNX_VER_RANGE(2,6,17, 2,6,31)
+
+/** Verify pipe buffer content (needed for page-cache to ensure idle page). */
+static int vbsf_pipe_buf_confirm(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ /*SFLOG3(("vbsf_pipe_buf_confirm: %p\n", pPipeBuf));*/
+ return 0;
+}
+
+
+/** Maps the buffer page. */
+static void *vbsf_pipe_buf_map(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf, int atomic)
+{
+ void *pvRet;
+ if (!atomic)
+ pvRet = kmap(pPipeBuf->page);
+ else {
+ pPipeBuf->flags |= PIPE_BUF_FLAG_ATOMIC;
+ pvRet = kmap_atomic(pPipeBuf->page, KM_USER0);
+ }
+ /*SFLOG3(("vbsf_pipe_buf_map: %p -> %p\n", pPipeBuf, pvRet));*/
+ return pvRet;
+}
+
+
+/** Unmaps the buffer page. */
+static void vbsf_pipe_buf_unmap(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf, void *pvMapping)
+{
+ /*SFLOG3(("vbsf_pipe_buf_unmap: %p/%p\n", pPipeBuf, pvMapping)); */
+ if (!(pPipeBuf->flags & PIPE_BUF_FLAG_ATOMIC))
+ kunmap(pPipeBuf->page);
+ else {
+ pPipeBuf->flags &= ~PIPE_BUF_FLAG_ATOMIC;
+ kunmap_atomic(pvMapping, KM_USER0);
+ }
+}
+
+
+/** Gets a reference to the page. */
+static void vbsf_pipe_buf_get(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ page_cache_get(pPipeBuf->page);
+ /*SFLOG3(("vbsf_pipe_buf_get: %p (return count=%d)\n", pPipeBuf, page_count(pPipeBuf->page)));*/
+}
+
+
+/** Release the buffer page (counter to vbsf_pipe_buf_get). */
+static void vbsf_pipe_buf_release(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ /*SFLOG3(("vbsf_pipe_buf_release: %p (incoming count=%d)\n", pPipeBuf, page_count(pPipeBuf->page)));*/
+ page_cache_release(pPipeBuf->page);
+}
+
+
+/** Attempt to steal the page.
+ * @returns 0 success, 1 on failure. */
+static int vbsf_pipe_buf_steal(struct pipe_inode_info *pPipe, struct pipe_buffer *pPipeBuf)
+{
+ if (page_count(pPipeBuf->page) == 1) {
+ lock_page(pPipeBuf->page);
+ SFLOG3(("vbsf_pipe_buf_steal: %p -> 0\n", pPipeBuf));
+ return 0;
+ }
+ SFLOG3(("vbsf_pipe_buf_steal: %p -> 1\n", pPipeBuf));
+ return 1;
+}
+
+
+/**
+ * Pipe buffer operations for used by vbsf_feed_pages_to_pipe.
+ */
+static struct pipe_buf_operations vbsf_pipe_buf_ops = {
+ .can_merge = 0,
+# if RTLNX_VER_MIN(2,6,23)
+ .confirm = vbsf_pipe_buf_confirm,
+# else
+ .pin = vbsf_pipe_buf_confirm,
+# endif
+ .map = vbsf_pipe_buf_map,
+ .unmap = vbsf_pipe_buf_unmap,
+ .get = vbsf_pipe_buf_get,
+ .release = vbsf_pipe_buf_release,
+ .steal = vbsf_pipe_buf_steal,
+};
+
+
+/**
+ * Feeds the pages to the pipe.
+ *
+ * Pages given to the pipe are set to NULL in papPages.
+ */
+static ssize_t vbsf_feed_pages_to_pipe(struct pipe_inode_info *pPipe, struct page **papPages, size_t cPages, uint32_t offPg0,
+ uint32_t cbActual, unsigned fFlags)
+{
+ ssize_t cbRet = 0;
+ size_t iPage = 0;
+ bool fNeedWakeUp = false;
+
+ LOCK_PIPE(pPipe);
+ for (;;) {
+ if ( pPipe->readers > 0
+ && pPipe->nrbufs < PIPE_BUFFERS) {
+ struct pipe_buffer *pPipeBuf = &pPipe->bufs[(pPipe->curbuf + pPipe->nrbufs) % PIPE_BUFFERS];
+ uint32_t const cbThisPage = RT_MIN(cbActual, PAGE_SIZE - offPg0);
+ pPipeBuf->len = cbThisPage;
+ pPipeBuf->offset = offPg0;
+# if RTLNX_VER_MIN(2,6,23)
+ pPipeBuf->private = 0;
+# endif
+ pPipeBuf->ops = &vbsf_pipe_buf_ops;
+ pPipeBuf->flags = fFlags & SPLICE_F_GIFT ? PIPE_BUF_FLAG_GIFT : 0;
+ pPipeBuf->page = papPages[iPage];
+
+ papPages[iPage++] = NULL;
+ pPipe->nrbufs++;
+ fNeedWakeUp |= pPipe->inode != NULL;
+ offPg0 = 0;
+ cbRet += cbThisPage;
+
+ /* done? */
+ cbActual -= cbThisPage;
+ if (!cbActual)
+ break;
+ } else if (pPipe->readers == 0) {
+ SFLOGFLOW(("vbsf_feed_pages_to_pipe: no readers!\n"));
+ send_sig(SIGPIPE, current, 0);
+ if (cbRet == 0)
+ cbRet = -EPIPE;
+ break;
+ } else if (fFlags & SPLICE_F_NONBLOCK) {
+ if (cbRet == 0)
+ cbRet = -EAGAIN;
+ break;
+ } else if (signal_pending(current)) {
+ if (cbRet == 0)
+ cbRet = -ERESTARTSYS;
+ SFLOGFLOW(("vbsf_feed_pages_to_pipe: pending signal! (%zd)\n", cbRet));
+ break;
+ } else {
+ if (fNeedWakeUp) {
+ vbsf_wake_up_pipe(pPipe, true /*fReaders*/);
+ fNeedWakeUp = 0;
+ }
+ pPipe->waiting_writers++;
+ vbsf_wait_pipe(pPipe);
+ pPipe->waiting_writers--;
+ }
+ }
+ UNLOCK_PIPE(pPipe);
+
+ if (fNeedWakeUp)
+ vbsf_wake_up_pipe(pPipe, true /*fReaders*/);
+
+ return cbRet;
+}
+
+
+/**
+ * For splicing from a file to a pipe.
+ */
+static ssize_t vbsf_splice_read(struct file *file, loff_t *poffset, struct pipe_inode_info *pipe, size_t len, unsigned int flags)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ ssize_t cbRet;
+
+ SFLOGFLOW(("vbsf_splice_read: file=%p poffset=%p{%#RX64} pipe=%p len=%#zx flags=%#x\n", file, poffset, *poffset, pipe, len, flags));
+ if (vbsf_should_use_cached_read(file, inode->i_mapping, pSuperInfo)) {
+ cbRet = generic_file_splice_read(file, poffset, pipe, len, flags);
+ } else {
+ /*
+ * Create a read request.
+ */
+ loff_t offFile = *poffset;
+ size_t cPages = RT_MIN(RT_ALIGN_Z((offFile & ~PAGE_CACHE_MASK) + len, PAGE_CACHE_SIZE) >> PAGE_CACHE_SHIFT,
+ PIPE_BUFFERS);
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ,
+ PgLst.aPages[cPages]));
+ if (pReq) {
+ /*
+ * Allocate pages.
+ */
+ struct page *apPages[PIPE_BUFFERS];
+ size_t i;
+ pReq->PgLst.offFirstPage = (uint16_t)offFile & (uint16_t)PAGE_OFFSET_MASK;
+ cbRet = 0;
+ for (i = 0; i < cPages; i++) {
+ struct page *pPage;
+ apPages[i] = pPage = alloc_page(GFP_USER);
+ if (pPage) {
+ pReq->PgLst.aPages[i] = page_to_phys(pPage);
+# ifdef VBOX_STRICT
+ ASMMemFill32(kmap(pPage), PAGE_SIZE, UINT32_C(0xdeadbeef));
+ kunmap(pPage);
+# endif
+ } else {
+ cbRet = -ENOMEM;
+ break;
+ }
+ }
+ if (cbRet == 0) {
+ /*
+ * Do the reading.
+ */
+ uint32_t const cbToRead = RT_MIN((cPages << PAGE_SHIFT) - (offFile & PAGE_OFFSET_MASK), len);
+ struct vbsf_reg_info *sf_r = (struct vbsf_reg_info *)file->private_data;
+ int vrc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbToRead, cPages);
+ if (RT_SUCCESS(vrc)) {
+ /*
+ * Get the number of bytes read, jettison the request
+ * and, in case of EOF, any unnecessary pages.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbActual <= cbToRead, cbActual = cbToRead);
+ SFLOG2(("vbsf_splice_read: read -> %#x bytes @ %#RX64\n", cbActual, offFile));
+
+ VbglR0PhysHeapFree(pReq);
+ pReq = NULL;
+
+ /*
+ * Now, feed it to the pipe thingy.
+ * This will take ownership of the all pages no matter what happens.
+ */
+ cbRet = vbsf_feed_pages_to_pipe(pipe, apPages, cPages, offFile & PAGE_OFFSET_MASK, cbActual, flags);
+ if (cbRet > 0)
+ *poffset = offFile + cbRet;
+ } else {
+ cbRet = -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_splice_read: Read failed: %Rrc -> %zd\n", vrc, cbRet));
+ }
+ i = cPages;
+ }
+
+ while (i-- > 0)
+ if (apPages[i])
+ __free_pages(apPages[i], 0);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ cbRet = -ENOMEM;
+ }
+ }
+ SFLOGFLOW(("vbsf_splice_read: returns %zd (%#zx), *poffset=%#RX64\n", cbRet, cbRet, *poffset));
+ return cbRet;
+}
+
+#endif /* 2.6.17 <= LINUX_VERSION_CODE < 2.6.31 */
+#if RTLNX_VER_RANGE(2,6,17, 3,16,0)
+
+/**
+ * For splicing from a pipe to a file.
+ *
+ * Since we can combine buffers and request allocations, this should be faster
+ * than the default implementation.
+ */
+static ssize_t vbsf_splice_write(struct pipe_inode_info *pPipe, struct file *file, loff_t *poffset, size_t len, unsigned int flags)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ ssize_t cbRet;
+
+ SFLOGFLOW(("vbsf_splice_write: pPipe=%p file=%p poffset=%p{%#RX64} len=%#zx flags=%#x\n", pPipe, file, poffset, *poffset, len, flags));
+ /** @todo later if (false) {
+ cbRet = generic_file_splice_write(pPipe, file, poffset, len, flags);
+ } else */ {
+ /*
+ * Prepare a write request.
+ */
+# ifdef PIPE_BUFFERS
+ uint32_t const cMaxPages = RT_MIN(PIPE_BUFFERS, RT_ALIGN_Z(len, PAGE_SIZE) >> PAGE_SHIFT);
+# else
+ uint32_t const cMaxPages = RT_MIN(RT_MAX(RT_MIN(pPipe->buffers, 256), PIPE_DEF_BUFFERS),
+ RT_ALIGN_Z(len, PAGE_SIZE) >> PAGE_SHIFT);
+# endif
+ VBOXSFWRITEPGLSTREQ *pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ,
+ PgLst.aPages[cMaxPages]));
+ if (pReq) {
+ /*
+ * Feed from the pipe.
+ */
+ struct vbsf_reg_info *sf_r = (struct vbsf_reg_info *)file->private_data;
+ struct address_space *mapping = inode->i_mapping;
+ loff_t offFile = *poffset;
+ bool fNeedWakeUp = false;
+ cbRet = 0;
+
+ LOCK_PIPE(pPipe);
+
+ for (;;) {
+ unsigned cBufs = pPipe->nrbufs;
+ /*SFLOG2(("vbsf_splice_write: nrbufs=%#x curbuf=%#x\n", cBufs, pPipe->curbuf));*/
+ if (cBufs) {
+ /*
+ * There is data available. Write it to the file.
+ */
+ int vrc;
+ struct pipe_buffer *pPipeBuf = &pPipe->bufs[pPipe->curbuf];
+ uint32_t cPagesToWrite = 1;
+ uint32_t cbToWrite = pPipeBuf->len;
+
+ Assert(pPipeBuf->offset < PAGE_SIZE);
+ Assert(pPipeBuf->offset + pPipeBuf->len <= PAGE_SIZE);
+
+ pReq->PgLst.offFirstPage = pPipeBuf->offset & PAGE_OFFSET;
+ pReq->PgLst.aPages[0] = page_to_phys(pPipeBuf->page);
+
+ /* Add any adjacent page buffers: */
+ while ( cPagesToWrite < cBufs
+ && cPagesToWrite < cMaxPages
+ && ((pReq->PgLst.offFirstPage + cbToWrite) & PAGE_OFFSET_MASK) == 0) {
+# ifdef PIPE_BUFFERS
+ struct pipe_buffer *pPipeBuf2 = &pPipe->bufs[(pPipe->curbuf + cPagesToWrite) % PIPE_BUFFERS];
+# else
+ struct pipe_buffer *pPipeBuf2 = &pPipe->bufs[(pPipe->curbuf + cPagesToWrite) % pPipe->buffers];
+# endif
+ Assert(pPipeBuf2->len <= PAGE_SIZE);
+ Assert(pPipeBuf2->offset < PAGE_SIZE);
+ if (pPipeBuf2->offset != 0)
+ break;
+ pReq->PgLst.aPages[cPagesToWrite] = page_to_phys(pPipeBuf2->page);
+ cbToWrite += pPipeBuf2->len;
+ cPagesToWrite += 1;
+ }
+
+ /* Check that we don't have signals pending before we issue the write, as
+ we'll only end up having to cancel the HGCM request 99% of the time: */
+ if (!signal_pending(current)) {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ vrc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile,
+ cbToWrite, cPagesToWrite);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ } else
+ vrc = VERR_INTERRUPTED;
+ if (RT_SUCCESS(vrc)) {
+ /*
+ * Get the number of bytes actually written, update file position
+ * and return value, and advance the pipe buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbActual <= cbToWrite, cbActual = cbToWrite);
+ SFLOG2(("vbsf_splice_write: write -> %#x bytes @ %#RX64\n", cbActual, offFile));
+
+ cbRet += cbActual;
+
+ while (cbActual > 0) {
+ uint32_t cbAdvance = RT_MIN(pPipeBuf->len, cbActual);
+
+ vbsf_reg_write_sync_page_cache(mapping, offFile, cbAdvance, NULL,
+ &pPipeBuf->page, pPipeBuf->offset, 1);
+
+ offFile += cbAdvance;
+ cbActual -= cbAdvance;
+ pPipeBuf->offset += cbAdvance;
+ pPipeBuf->len -= cbAdvance;
+
+ if (!pPipeBuf->len) {
+ struct pipe_buf_operations const *pOps = pPipeBuf->ops;
+ pPipeBuf->ops = NULL;
+ pOps->release(pPipe, pPipeBuf);
+
+# ifdef PIPE_BUFFERS
+ pPipe->curbuf = (pPipe->curbuf + 1) % PIPE_BUFFERS;
+# else
+ pPipe->curbuf = (pPipe->curbuf + 1) % pPipe->buffers;
+# endif
+ pPipe->nrbufs -= 1;
+ pPipeBuf = &pPipe->bufs[pPipe->curbuf];
+
+# if RTLNX_VER_MAX(2,6,30)
+ fNeedWakeUp |= pPipe->inode != NULL;
+# else
+ fNeedWakeUp = true;
+# endif
+ } else {
+ Assert(cbActual == 0);
+ break;
+ }
+ }
+
+ *poffset = offFile;
+ } else {
+ if (cbRet == 0)
+ cbRet = vrc == VERR_INTERRUPTED ? -ERESTARTSYS : -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_splice_write: Write failed: %Rrc -> %zd (cbRet=%#zx)\n",
+ vrc, -RTErrConvertToErrno(vrc), cbRet));
+ break;
+ }
+ } else {
+ /*
+ * Wait for data to become available, if there is chance that'll happen.
+ */
+ /* Quit if there are no writers (think EOF): */
+ if (pPipe->writers == 0) {
+ SFLOGFLOW(("vbsf_splice_write: No buffers. No writers. The show is done!\n"));
+ break;
+ }
+
+ /* Quit if if we've written some and no writers waiting on the lock: */
+ if (cbRet > 0 && pPipe->waiting_writers == 0) {
+ SFLOGFLOW(("vbsf_splice_write: No waiting writers, returning what we've got.\n"));
+ break;
+ }
+
+ /* Quit with EAGAIN if non-blocking: */
+ if (flags & SPLICE_F_NONBLOCK) {
+ if (cbRet == 0)
+ cbRet = -EAGAIN;
+ break;
+ }
+
+ /* Quit if we've got pending signals: */
+ if (signal_pending(current)) {
+ if (cbRet == 0)
+ cbRet = -ERESTARTSYS;
+ SFLOGFLOW(("vbsf_splice_write: pending signal! (%zd)\n", cbRet));
+ break;
+ }
+
+ /* Wake up writers before we start waiting: */
+ if (fNeedWakeUp) {
+ vbsf_wake_up_pipe(pPipe, false /*fReaders*/);
+ fNeedWakeUp = false;
+ }
+ vbsf_wait_pipe(pPipe);
+ }
+ } /* feed loop */
+
+ if (fNeedWakeUp)
+ vbsf_wake_up_pipe(pPipe, false /*fReaders*/);
+
+ UNLOCK_PIPE(pPipe);
+
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ cbRet = -ENOMEM;
+ }
+ }
+ SFLOGFLOW(("vbsf_splice_write: returns %zd (%#zx), *poffset=%#RX64\n", cbRet, cbRet, *poffset));
+ return cbRet;
+}
+
+#endif /* 2.6.17 <= LINUX_VERSION_CODE < 3.16.0 */
+
+#if RTLNX_VER_RANGE(2,5,30, 2,6,23)
+/**
+ * Our own senfile implementation that does not go via the page cache like
+ * generic_file_sendfile() does.
+ */
+static ssize_t vbsf_reg_sendfile(struct file *pFile, loff_t *poffFile, size_t cbToSend, read_actor_t pfnActor,
+# if RTLNX_VER_MIN(2,6,8)
+ void *pvUser
+# else
+ void __user *pvUser
+# endif
+ )
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(pFile)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ ssize_t cbRet;
+ SFLOGFLOW(("vbsf_reg_sendfile: pFile=%p poffFile=%p{%#RX64} cbToSend=%#zx pfnActor=%p pvUser=%p\n",
+ pFile, poffFile, poffFile ? *poffFile : 0, cbToSend, pfnActor, pvUser));
+ Assert(pSuperInfo);
+
+ /*
+ * Return immediately if asked to send nothing.
+ */
+ if (cbToSend == 0)
+ return 0;
+
+ /*
+ * Like for vbsf_reg_read() and vbsf_reg_read_iter(), we allow going via
+ * the page cache in some cases or configs.
+ */
+ if (vbsf_should_use_cached_read(pFile, inode->i_mapping, pSuperInfo)) {
+ cbRet = generic_file_sendfile(pFile, poffFile, cbToSend, pfnActor, pvUser);
+ SFLOGFLOW(("vbsf_reg_sendfile: returns %#zx *poffFile=%#RX64 [generic_file_sendfile]\n", cbRet, poffFile ? *poffFile : UINT64_MAX));
+ } else {
+ /*
+ * Allocate a request and a bunch of pages for reading from the file.
+ */
+ struct page *apPages[16];
+ loff_t offFile = poffFile ? *poffFile : 0;
+ size_t const cPages = cbToSend + ((size_t)offFile & PAGE_OFFSET_MASK) >= RT_ELEMENTS(apPages) * PAGE_SIZE
+ ? RT_ELEMENTS(apPages)
+ : RT_ALIGN_Z(cbToSend + ((size_t)offFile & PAGE_OFFSET_MASK), PAGE_SIZE) >> PAGE_SHIFT;
+ size_t iPage;
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ,
+ PgLst.aPages[cPages]));
+ if (pReq) {
+ Assert(cPages > 0);
+ cbRet = 0;
+ for (iPage = 0; iPage < cPages; iPage++) {
+ struct page *pPage;
+ apPages[iPage] = pPage = alloc_page(GFP_USER);
+ if (pPage) {
+ Assert(page_count(pPage) == 1);
+ pReq->PgLst.aPages[iPage] = page_to_phys(pPage);
+ } else {
+ while (iPage-- > 0)
+ vbsf_put_page(apPages[iPage]);
+ cbRet = -ENOMEM;
+ break;
+ }
+ }
+ if (cbRet == 0) {
+ /*
+ * Do the job.
+ */
+ struct vbsf_reg_info *sf_r = (struct vbsf_reg_info *)pFile->private_data;
+ read_descriptor_t RdDesc;
+ RdDesc.count = cbToSend;
+# if RTLNX_VER_MIN(2,6,8)
+ RdDesc.arg.data = pvUser;
+# else
+ RdDesc.buf = pvUser;
+# endif
+ RdDesc.written = 0;
+ RdDesc.error = 0;
+
+ Assert(sf_r);
+ Assert((sf_r->Handle.fFlags & VBSF_HANDLE_F_MAGIC_MASK) == VBSF_HANDLE_F_MAGIC);
+
+ while (cbToSend > 0) {
+ /*
+ * Read another chunk. For paranoid reasons, we keep data where the page cache
+ * would keep it, i.e. page offset bits corresponds to the file offset bits.
+ */
+ uint32_t const offPg0 = (uint32_t)offFile & (uint32_t)PAGE_OFFSET_MASK;
+ uint32_t const cbToRead = RT_MIN((cPages << PAGE_SHIFT) - offPg0, cbToSend);
+ uint32_t const cPagesToRead = RT_ALIGN_Z(cbToRead + offPg0, PAGE_SIZE) >> PAGE_SHIFT;
+ int vrc;
+ pReq->PgLst.offFirstPage = (uint16_t)offPg0;
+ if (!signal_pending(current))
+ vrc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile,
+ cbToRead, cPagesToRead);
+ else
+ vrc = VERR_INTERRUPTED;
+ if (RT_SUCCESS(vrc)) {
+ /*
+ * Pass what we read to the actor.
+ */
+ uint32_t off = offPg0;
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ bool const fIsEof = cbActual < cbToRead;
+ AssertStmt(cbActual <= cbToRead, cbActual = cbToRead);
+ SFLOG3(("vbsf_reg_sendfile: Read %#x bytes (offPg0=%#x), wanted %#x ...\n", cbActual, offPg0, cbToRead));
+
+ iPage = 0;
+ while (cbActual > 0) {
+ uint32_t const cbPage = RT_MIN(cbActual, PAGE_SIZE - off);
+ int const cbRetActor = pfnActor(&RdDesc, apPages[iPage], off, cbPage);
+ Assert(cbRetActor >= 0); /* Returns zero on failure, with RdDesc.error holding the status code. */
+
+ AssertMsg(iPage < cPages && iPage < cPagesToRead, ("iPage=%#x cPages=%#x cPagesToRead=%#x\n", iPage, cPages, cPagesToRead));
+
+ offFile += cbRetActor;
+ if ((uint32_t)cbRetActor == cbPage && RdDesc.count > 0) {
+ cbActual -= cbPage;
+ cbToSend -= cbPage;
+ iPage++;
+ } else {
+ SFLOG3(("vbsf_reg_sendfile: cbRetActor=%#x (%d) cbPage=%#x RdDesc{count=%#lx error=%d} iPage=%#x/%#x/%#x cbToSend=%#zx\n",
+ cbRetActor, cbRetActor, cbPage, RdDesc.count, RdDesc.error, iPage, cPagesToRead, cPages, cbToSend));
+ vrc = VERR_CALLBACK_RETURN;
+ break;
+ }
+ off = 0;
+ }
+
+ /*
+ * Are we done yet?
+ */
+ if (RT_FAILURE_NP(vrc) || cbToSend == 0 || RdDesc.error != 0 || fIsEof) {
+ break;
+ }
+
+ /*
+ * Replace pages held by the actor.
+ */
+ vrc = VINF_SUCCESS;
+ for (iPage = 0; iPage < cPages; iPage++) {
+ struct page *pPage = apPages[iPage];
+ if (page_count(pPage) != 1) {
+ struct page *pNewPage = alloc_page(GFP_USER);
+ if (pNewPage) {
+ SFLOGFLOW(("vbsf_reg_sendfile: Replacing page #%x: %p -> %p\n", iPage, pPage, pNewPage));
+ vbsf_put_page(pPage);
+ apPages[iPage] = pNewPage;
+ } else {
+ SFLOGFLOW(("vbsf_reg_sendfile: Failed to allocate a replacement page.\n"));
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ }
+ if (RT_FAILURE(vrc))
+ break; /* RdDesc.written should be non-zero, so don't bother with setting error. */
+ } else {
+ RdDesc.error = vrc == VERR_INTERRUPTED ? -ERESTARTSYS : -RTErrConvertToErrno(vrc);
+ SFLOGFLOW(("vbsf_reg_sendfile: Read failed: %Rrc -> %zd (RdDesc.error=%#d)\n",
+ vrc, -RTErrConvertToErrno(vrc), RdDesc.error));
+ break;
+ }
+ }
+
+ /*
+ * Free memory.
+ */
+ for (iPage = 0; iPage < cPages; iPage++)
+ vbsf_put_page(apPages[iPage]);
+
+ /*
+ * Set the return values.
+ */
+ if (RdDesc.written) {
+ cbRet = RdDesc.written;
+ if (poffFile)
+ *poffFile = offFile;
+ } else {
+ cbRet = RdDesc.error;
+ }
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else {
+ cbRet = -ENOMEM;
+ }
+ SFLOGFLOW(("vbsf_reg_sendfile: returns %#zx offFile=%#RX64\n", cbRet, offFile));
+ }
+ return cbRet;
+}
+#endif /* 2.5.30 <= LINUX_VERSION_CODE < 2.6.23 */
+
+
+/*********************************************************************************************************************************
+* File operations on regular files *
+*********************************************************************************************************************************/
+
+/** Wrapper around put_page / page_cache_release. */
+DECLINLINE(void) vbsf_put_page(struct page *pPage)
+{
+#if RTLNX_VER_MIN(4,6,0)
+ put_page(pPage);
+#else
+ page_cache_release(pPage);
+#endif
+}
+
+
+/** Wrapper around get_page / page_cache_get. */
+DECLINLINE(void) vbsf_get_page(struct page *pPage)
+{
+#if RTLNX_VER_MIN(4,6,0)
+ get_page(pPage);
+#else
+ page_cache_get(pPage);
+#endif
+}
+
+
+/** Companion to vbsf_lock_user_pages(). */
+static void vbsf_unlock_user_pages(struct page **papPages, size_t cPages, bool fSetDirty, bool fLockPgHack)
+{
+ /* We don't mark kernel pages dirty: */
+ if (fLockPgHack)
+ fSetDirty = false;
+
+ while (cPages-- > 0)
+ {
+ struct page *pPage = papPages[cPages];
+ Assert((ssize_t)cPages >= 0);
+ if (fSetDirty && !PageReserved(pPage))
+ set_page_dirty(pPage);
+ vbsf_put_page(pPage);
+ }
+}
+
+
+/**
+ * Worker for vbsf_lock_user_pages_failed_check_kernel() and
+ * vbsf_iter_lock_pages().
+ */
+static int vbsf_lock_kernel_pages(uint8_t *pbStart, bool fWrite, size_t cPages, struct page **papPages)
+{
+ uintptr_t const uPtrFrom = (uintptr_t)pbStart;
+ uintptr_t const uPtrLast = (uPtrFrom & ~(uintptr_t)PAGE_OFFSET_MASK) + (cPages << PAGE_SHIFT) - 1;
+ uint8_t *pbPage = (uint8_t *)uPtrLast;
+ size_t iPage = cPages;
+
+ /*
+ * Touch the pages first (paranoia^2).
+ */
+ if (fWrite) {
+ uint8_t volatile *pbProbe = (uint8_t volatile *)uPtrFrom;
+ while (iPage-- > 0) {
+ *pbProbe = *pbProbe;
+ pbProbe += PAGE_SIZE;
+ }
+ } else {
+ uint8_t const *pbProbe = (uint8_t const *)uPtrFrom;
+ while (iPage-- > 0) {
+ ASMProbeReadByte(pbProbe);
+ pbProbe += PAGE_SIZE;
+ }
+ }
+
+ /*
+ * Get the pages.
+ * Note! Fixes here probably applies to rtR0MemObjNativeLockKernel as well.
+ */
+ iPage = cPages;
+ if ( uPtrFrom >= (unsigned long)__va(0)
+ && uPtrLast < (unsigned long)high_memory) {
+ /* The physical page mapping area: */
+ while (iPage-- > 0) {
+ struct page *pPage = papPages[iPage] = virt_to_page(pbPage);
+ vbsf_get_page(pPage);
+ pbPage -= PAGE_SIZE;
+ }
+ } else {
+ /* This is vmalloc or some such thing, so go thru page tables: */
+ while (iPage-- > 0) {
+ struct page *pPage = rtR0MemObjLinuxVirtToPage(pbPage);
+ if (pPage) {
+ papPages[iPage] = pPage;
+ vbsf_get_page(pPage);
+ pbPage -= PAGE_SIZE;
+ } else {
+ while (++iPage < cPages) {
+ pPage = papPages[iPage];
+ vbsf_put_page(pPage);
+ }
+ return -EFAULT;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Catches kernel_read() and kernel_write() calls and works around them.
+ *
+ * The file_operations::read and file_operations::write callbacks supposedly
+ * hands us the user buffers to read into and write out of. To allow the kernel
+ * to read and write without allocating buffers in userland, they kernel_read()
+ * and kernel_write() increases the user space address limit before calling us
+ * so that copyin/copyout won't reject it. Our problem is that get_user_pages()
+ * works on the userspace address space structures and will not be fooled by an
+ * increased addr_limit.
+ *
+ * This code tries to detect this situation and fake get_user_lock() for the
+ * kernel buffer.
+ */
+static int vbsf_lock_user_pages_failed_check_kernel(uintptr_t uPtrFrom, size_t cPages, bool fWrite, int rcFailed,
+ struct page **papPages, bool *pfLockPgHack)
+{
+ /*
+ * Check that this is valid user memory that is actually in the kernel range.
+ */
+#if RTLNX_VER_MIN(5,10,0)
+ if ( access_ok((void *)uPtrFrom, cPages << PAGE_SHIFT)
+ && uPtrFrom >= TASK_SIZE_MAX)
+#elif RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,1)
+ if ( access_ok((void *)uPtrFrom, cPages << PAGE_SHIFT)
+ && uPtrFrom >= USER_DS.seg)
+#else
+ if ( access_ok(fWrite ? VERIFY_WRITE : VERIFY_READ, (void *)uPtrFrom, cPages << PAGE_SHIFT)
+ && uPtrFrom >= USER_DS.seg)
+#endif
+ {
+ int rc = vbsf_lock_kernel_pages((uint8_t *)uPtrFrom, fWrite, cPages, papPages);
+ if (rc == 0) {
+ *pfLockPgHack = true;
+ return 0;
+ }
+ }
+
+ return rcFailed;
+}
+
+
+/** Wrapper around get_user_pages. */
+DECLINLINE(int) vbsf_lock_user_pages(uintptr_t uPtrFrom, size_t cPages, bool fWrite, struct page **papPages, bool *pfLockPgHack)
+{
+# if RTLNX_VER_MIN(4,9,0) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,73, 4,4,74) /** @todo Figure out when & what exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,75, 4,4,90) /** @todo Figure out when & what exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,92, 4,5,0) /** @todo Figure out when & what exactly. */)
+ ssize_t cPagesLocked = get_user_pages_unlocked(uPtrFrom, cPages, papPages,
+ fWrite ? FOLL_WRITE | FOLL_FORCE : FOLL_FORCE);
+# elif RTLNX_VER_MIN(4,6,0)
+ ssize_t cPagesLocked = get_user_pages_unlocked(uPtrFrom, cPages, fWrite, 1 /*force*/, papPages);
+# elif RTLNX_VER_RANGE(4,4,168, 4,5,0)
+ ssize_t cPagesLocked = get_user_pages_unlocked(current, current->mm, uPtrFrom, cPages, papPages,
+ fWrite ? FOLL_WRITE | FOLL_FORCE : FOLL_FORCE);
+# elif RTLNX_VER_MIN(4,0,0)
+ ssize_t cPagesLocked = get_user_pages_unlocked(current, current->mm, uPtrFrom, cPages, fWrite, 1 /*force*/, papPages);
+# else
+ struct task_struct *pTask = current;
+ ssize_t cPagesLocked;
+ down_read(&pTask->mm->mmap_sem);
+ cPagesLocked = get_user_pages(pTask, pTask->mm, uPtrFrom, cPages, fWrite, 1 /*force*/, papPages, NULL);
+ up_read(&pTask->mm->mmap_sem);
+# endif
+ *pfLockPgHack = false;
+ if (cPagesLocked == cPages)
+ return 0;
+
+ /*
+ * It failed.
+ */
+ if (cPagesLocked < 0)
+ return vbsf_lock_user_pages_failed_check_kernel(uPtrFrom, cPages, fWrite, (int)cPagesLocked, papPages, pfLockPgHack);
+
+ vbsf_unlock_user_pages(papPages, cPagesLocked, false /*fSetDirty*/, false /*fLockPgHack*/);
+
+ /* We could use uPtrFrom + cPagesLocked to get the correct status here... */
+ return -EFAULT;
+}
+
+#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */
+
+/**
+ * Read function used when accessing files that are memory mapped.
+ *
+ * We read from the page cache here to present the a cohertent picture of the
+ * the file content.
+ */
+static ssize_t vbsf_reg_read_mapped(struct file *file, char /*__user*/ *buf, size_t size, loff_t *off)
+{
+# if RTLNX_VER_MIN(3,16,0)
+ struct iovec iov = { .iov_base = buf, .iov_len = size };
+ struct iov_iter iter;
+ struct kiocb kiocb;
+ ssize_t cbRet;
+
+ init_sync_kiocb(&kiocb, file);
+ kiocb.ki_pos = *off;
+ iov_iter_init(&iter, READ, &iov, 1, size);
+
+ cbRet = generic_file_read_iter(&kiocb, &iter);
+
+ *off = kiocb.ki_pos;
+ return cbRet;
+
+# elif RTLNX_VER_MIN(2,6,19)
+ struct iovec iov = { .iov_base = buf, .iov_len = size };
+ struct kiocb kiocb;
+ ssize_t cbRet;
+
+ init_sync_kiocb(&kiocb, file);
+ kiocb.ki_pos = *off;
+
+ cbRet = generic_file_aio_read(&kiocb, &iov, 1, *off);
+ if (cbRet == -EIOCBQUEUED)
+ cbRet = wait_on_sync_kiocb(&kiocb);
+
+ *off = kiocb.ki_pos;
+ return cbRet;
+
+# else /* 2.6.18 or earlier: */
+ return generic_file_read(file, buf, size, off);
+# endif
+}
+
+
+/**
+ * Fallback case of vbsf_reg_read() that locks the user buffers and let the host
+ * write directly to them.
+ */
+static ssize_t vbsf_reg_read_locking(struct file *file, char /*__user*/ *buf, size_t size, loff_t *off,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r)
+{
+ /*
+ * Lock pages and execute the read, taking care not to pass the host
+ * more than it can handle in one go or more than we care to allocate
+ * page arrays for. The latter limit is set at just short of 32KB due
+ * to how the physical heap works.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFREADPGLSTREQ *pReq;
+ loff_t offFile = *off;
+ ssize_t cbRet = -ENOMEM;
+ size_t cPages = (((uintptr_t)buf & PAGE_OFFSET_MASK) + size + PAGE_OFFSET_MASK) >> PAGE_SHIFT;
+ size_t cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 1), cPages);
+ bool fLockPgHack;
+
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+ cbRet = 0;
+ for (;;) {
+ /*
+ * Figure out how much to process now and lock the user pages.
+ */
+ int rc;
+ size_t cbChunk = (uintptr_t)buf & PAGE_OFFSET_MASK;
+ pReq->PgLst.offFirstPage = (uint16_t)cbChunk;
+ cPages = RT_ALIGN_Z(cbChunk + size, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPages <= cMaxPages)
+ cbChunk = size;
+ else {
+ cPages = cMaxPages;
+ cbChunk = (cMaxPages << PAGE_SHIFT) - cbChunk;
+ }
+
+ rc = vbsf_lock_user_pages((uintptr_t)buf, cPages, true /*fWrite*/, papPages, &fLockPgHack);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbChunk, cPages);
+
+ Assert(cPages <= cMaxPages);
+ vbsf_unlock_user_pages(papPages, cPages, true /*fSetDirty*/, fLockPgHack);
+
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+ cbRet += cbActual;
+ offFile += cbActual;
+ buf = (uint8_t *)buf + cbActual;
+ size -= cbActual;
+
+ /*
+ * Are we done already? If so commit the new file offset.
+ */
+ if (!size || cbActual < cbChunk) {
+ *off = offFile;
+ break;
+ }
+ } else if (rc == VERR_NO_MEMORY && cMaxPages > 4) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully read stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet > 0) {
+ SFLOGFLOW(("vbsf_reg_read: read at %#RX64 -> %Rrc; got cbRet=%#zx already\n", offFile, rc, cbRet));
+ *off = offFile;
+ } else {
+ SFLOGFLOW(("vbsf_reg_read: read at %#RX64 -> %Rrc\n", offFile, rc));
+ cbRet = -EPROTO;
+ }
+ break;
+ }
+ }
+ }
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read: returns %zd (%#zx), *off=%RX64 [lock]\n", cbRet, cbRet, *off));
+ return cbRet;
+}
+
+
+/**
+ * Read from a regular file.
+ *
+ * @param file the file
+ * @param buf the buffer
+ * @param size length of the buffer
+ * @param off offset within the file (in/out).
+ * @returns the number of read bytes on success, Linux error code otherwise
+ */
+static ssize_t vbsf_reg_read(struct file *file, char /*__user*/ *buf, size_t size, loff_t *off)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_reg_info *sf_r = file->private_data;
+ struct address_space *mapping = inode->i_mapping;
+
+ SFLOGFLOW(("vbsf_reg_read: inode=%p file=%p buf=%p size=%#zx off=%#llx\n", inode, file, buf, size, *off));
+
+ if (!S_ISREG(inode->i_mode)) {
+ LogFunc(("read from non regular file %d\n", inode->i_mode));
+ return -EINVAL;
+ }
+
+ /** @todo XXX Check read permission according to inode->i_mode! */
+
+ if (!size)
+ return 0;
+
+ /*
+ * If there is a mapping and O_DIRECT isn't in effect, we must at a
+ * heed dirty pages in the mapping and read from them. For simplicity
+ * though, we just do page cache reading when there are writable
+ * mappings around with any kind of pages loaded.
+ */
+ if (vbsf_should_use_cached_read(file, mapping, pSuperInfo))
+ return vbsf_reg_read_mapped(file, buf, size, off);
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (size <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) + size;
+ VBOXSFREADEMBEDDEDREQ *pReq = (VBOXSFREADEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ if ((PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqReadEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, *off, (uint32_t)size);
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ if (copy_to_user(buf, pReq->abData, cbRet) == 0)
+ *off += cbRet;
+ else
+ cbRet = -EFAULT;
+ } else
+ cbRet = -EPROTO;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read: returns %zd (%#zx), *off=%RX64 [embed]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ }
+
+# if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */
+ /*
+ * For medium sized requests try use a bounce buffer.
+ */
+ if (size <= _64K /** @todo make this configurable? */) {
+ void *pvBounce = kmalloc(size, GFP_KERNEL);
+ if (pvBounce) {
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqReadContig(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, *off,
+ (uint32_t)size, pvBounce, virt_to_phys(pvBounce));
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ if (copy_to_user(buf, pvBounce, cbRet) == 0)
+ *off += cbRet;
+ else
+ cbRet = -EFAULT;
+ } else
+ cbRet = -EPROTO;
+ VbglR0PhysHeapFree(pReq);
+ kfree(pvBounce);
+ SFLOGFLOW(("vbsf_reg_read: returns %zd (%#zx), *off=%RX64 [bounce]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ kfree(pvBounce);
+ }
+ }
+# endif
+
+ return vbsf_reg_read_locking(file, buf, size, off, pSuperInfo, sf_r);
+}
+
+#endif /* < 5.10.0 */
+
+/**
+ * Helper the synchronizes the page cache content with something we just wrote
+ * to the host.
+ */
+static void vbsf_reg_write_sync_page_cache(struct address_space *mapping, loff_t offFile, uint32_t cbRange,
+ uint8_t const *pbSrcBuf, struct page **papSrcPages,
+ uint32_t offSrcPage, size_t cSrcPages)
+{
+ Assert(offSrcPage < PAGE_SIZE);
+ if (mapping && mapping->nrpages > 0) {
+ /*
+ * Work the pages in the write range.
+ */
+ while (cbRange > 0) {
+ /*
+ * Lookup the page at offFile. We're fine if there aren't
+ * any there. We're skip if it's dirty or is being written
+ * back, at least for now.
+ */
+ size_t const offDstPage = offFile & PAGE_OFFSET_MASK;
+ size_t const cbToCopy = RT_MIN(PAGE_SIZE - offDstPage, cbRange);
+ pgoff_t const idxPage = offFile >> PAGE_SHIFT;
+ struct page *pDstPage = find_lock_page(mapping, idxPage);
+ if (pDstPage) {
+ if ( pDstPage->mapping == mapping /* ignore if re-purposed (paranoia) */
+ && pDstPage->index == idxPage
+ && !PageDirty(pDstPage) /* ignore if dirty */
+ && !PageWriteback(pDstPage) /* ignore if being written back */ ) {
+ /*
+ * Map the page and do the copying.
+ */
+ uint8_t *pbDst = (uint8_t *)kmap(pDstPage);
+ if (pbSrcBuf)
+ memcpy(&pbDst[offDstPage], pbSrcBuf, cbToCopy);
+ else {
+ uint32_t const cbSrc0 = PAGE_SIZE - offSrcPage;
+ uint8_t const *pbSrc = (uint8_t const *)kmap(papSrcPages[0]);
+ AssertMsg(cSrcPages >= 1, ("offFile=%#llx cbRange=%#zx cbToCopy=%#zx\n", offFile, cbRange, cbToCopy));
+ memcpy(&pbDst[offDstPage], &pbSrc[offSrcPage], RT_MIN(cbToCopy, cbSrc0));
+ kunmap(papSrcPages[0]);
+ if (cbToCopy > cbSrc0) {
+ AssertMsg(cSrcPages >= 2, ("offFile=%#llx cbRange=%#zx cbToCopy=%#zx\n", offFile, cbRange, cbToCopy));
+ pbSrc = (uint8_t const *)kmap(papSrcPages[1]);
+ memcpy(&pbDst[offDstPage + cbSrc0], pbSrc, cbToCopy - cbSrc0);
+ kunmap(papSrcPages[1]);
+ }
+ }
+ kunmap(pDstPage);
+ flush_dcache_page(pDstPage);
+ if (cbToCopy == PAGE_SIZE)
+ SetPageUptodate(pDstPage);
+# if RTLNX_VER_MIN(2,4,10)
+ mark_page_accessed(pDstPage);
+# endif
+ } else
+ SFLOGFLOW(("vbsf_reg_write_sync_page_cache: Skipping page %p: mapping=%p (vs %p) writeback=%d offset=%#lx (vs%#lx)\n",
+ pDstPage, pDstPage->mapping, mapping, PageWriteback(pDstPage), pDstPage->index, idxPage));
+ unlock_page(pDstPage);
+ vbsf_put_page(pDstPage);
+ }
+
+ /*
+ * Advance.
+ */
+ if (pbSrcBuf)
+ pbSrcBuf += cbToCopy;
+ else
+ {
+ offSrcPage += cbToCopy;
+ Assert(offSrcPage < PAGE_SIZE * 2);
+ if (offSrcPage >= PAGE_SIZE) {
+ offSrcPage &= PAGE_OFFSET_MASK;
+ papSrcPages++;
+# ifdef VBOX_STRICT
+ Assert(cSrcPages > 0);
+ cSrcPages--;
+# endif
+ }
+ }
+ offFile += cbToCopy;
+ cbRange -= cbToCopy;
+ }
+ }
+ RT_NOREF(cSrcPages);
+}
+
+#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */
+
+/**
+ * Fallback case of vbsf_reg_write() that locks the user buffers and let the host
+ * write directly to them.
+ */
+static ssize_t vbsf_reg_write_locking(struct file *file, const char /*__user*/ *buf, size_t size, loff_t *off, loff_t offFile,
+ struct inode *inode, struct vbsf_inode_info *sf_i,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r)
+{
+ /*
+ * Lock pages and execute the write, taking care not to pass the host
+ * more than it can handle in one go or more than we care to allocate
+ * page arrays for. The latter limit is set at just short of 32KB due
+ * to how the physical heap works.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFWRITEPGLSTREQ *pReq;
+ ssize_t cbRet = -ENOMEM;
+ size_t cPages = (((uintptr_t)buf & PAGE_OFFSET_MASK) + size + PAGE_OFFSET_MASK) >> PAGE_SHIFT;
+ size_t cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 1), cPages);
+ bool fLockPgHack;
+
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+ cbRet = 0;
+ for (;;) {
+ /*
+ * Figure out how much to process now and lock the user pages.
+ */
+ int rc;
+ size_t cbChunk = (uintptr_t)buf & PAGE_OFFSET_MASK;
+ pReq->PgLst.offFirstPage = (uint16_t)cbChunk;
+ cPages = RT_ALIGN_Z(cbChunk + size, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPages <= cMaxPages)
+ cbChunk = size;
+ else {
+ cPages = cMaxPages;
+ cbChunk = (cMaxPages << PAGE_SHIFT) - cbChunk;
+ }
+
+ rc = vbsf_lock_user_pages((uintptr_t)buf, cPages, false /*fWrite*/, papPages, &fLockPgHack);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbChunk, cPages);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+
+ vbsf_reg_write_sync_page_cache(inode->i_mapping, offFile, cbActual, NULL /*pbKrnlBuf*/,
+ papPages, (uintptr_t)buf & PAGE_OFFSET_MASK, cPages);
+ Assert(cPages <= cMaxPages);
+ vbsf_unlock_user_pages(papPages, cPages, false /*fSetDirty*/, fLockPgHack);
+
+ cbRet += cbActual;
+ buf = (uint8_t *)buf + cbActual;
+ size -= cbActual;
+
+ offFile += cbActual;
+ if ((file->f_flags & O_APPEND) && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ offFile = pReq->Parms.off64Write.u.value64;
+ if (offFile > i_size_read(inode))
+ i_size_write(inode, offFile);
+
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+
+ /*
+ * Are we done already? If so commit the new file offset.
+ */
+ if (!size || cbActual < cbChunk) {
+ *off = offFile;
+ break;
+ }
+ } else {
+ vbsf_unlock_user_pages(papPages, cPages, false /*fSetDirty*/, fLockPgHack);
+ if (rc == VERR_NO_MEMORY && cMaxPages > 4) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully written stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet > 0) {
+ SFLOGFLOW(("vbsf_reg_write: write at %#RX64 -> %Rrc; got cbRet=%#zx already\n", offFile, rc, cbRet));
+ *off = offFile;
+ } else {
+ SFLOGFLOW(("vbsf_reg_write: write at %#RX64 -> %Rrc\n", offFile, rc));
+ cbRet = -EPROTO;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write: returns %zd (%#zx), *off=%RX64 [lock]\n", cbRet, cbRet, *off));
+ return cbRet;
+}
+
+
+/**
+ * Write to a regular file.
+ *
+ * @param file the file
+ * @param buf the buffer
+ * @param size length of the buffer
+ * @param off offset within the file
+ * @returns the number of written bytes on success, Linux error code otherwise
+ */
+static ssize_t vbsf_reg_write(struct file *file, const char *buf, size_t size, loff_t * off)
+{
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_reg_info *sf_r = file->private_data;
+ struct address_space *mapping = inode->i_mapping;
+ loff_t pos;
+
+ SFLOGFLOW(("vbsf_reg_write: inode=%p file=%p buf=%p size=%#zx off=%#llx\n", inode, file, buf, size, *off));
+ Assert(sf_i);
+ Assert(pSuperInfo);
+ Assert(sf_r);
+ AssertReturn(S_ISREG(inode->i_mode), -EINVAL);
+
+ pos = *off;
+ if (file->f_flags & O_APPEND)
+ pos = i_size_read(inode);
+
+ /** @todo XXX Check write permission according to inode->i_mode! */
+
+ if (!size) {
+ if (file->f_flags & O_APPEND) /** @todo check if this is the consensus behavior... */
+ *off = pos;
+ return 0;
+ }
+
+ /** @todo Implement the read-write caching mode. */
+
+ /*
+ * If there are active writable mappings, coordinate with any
+ * pending writes via those.
+ */
+ if ( mapping
+ && mapping->nrpages > 0
+ && mapping_writably_mapped(mapping)) {
+# if RTLNX_VER_MIN(2,6,32)
+ int err = filemap_fdatawait_range(mapping, pos, pos + size - 1);
+ if (err)
+ return err;
+# else
+ /** @todo ... */
+# endif
+ }
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (size <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) + size;
+ VBOXSFWRITEEMBEDDEDREQ *pReq = (VBOXSFWRITEEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if ( pReq
+ && (PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ if (copy_from_user(pReq->abData, buf, size) == 0) {
+ int vrc = VbglR0SfHostReqWriteEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost,
+ pos, (uint32_t)size);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ vbsf_reg_write_sync_page_cache(mapping, pos, (uint32_t)cbRet, pReq->abData,
+ NULL /*papSrcPages*/, 0 /*offSrcPage0*/, 0 /*cSrcPages*/);
+ pos += cbRet;
+ if ((file->f_flags & O_APPEND) && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ pos = pReq->Parms.off64Write.u.value64;
+ *off = pos;
+ if (pos > i_size_read(inode))
+ i_size_write(inode, pos);
+ } else
+ cbRet = -EPROTO;
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+ } else
+ cbRet = -EFAULT;
+
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write: returns %zd (%#zx), *off=%RX64 [embed]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ }
+
+# if 0 /* Turns out this is slightly slower than locking the pages even for 4KB reads (4.19/amd64). */
+ /*
+ * For medium sized requests try use a bounce buffer.
+ */
+ if (size <= _64K /** @todo make this configurable? */) {
+ void *pvBounce = kmalloc(size, GFP_KERNEL);
+ if (pvBounce) {
+ if (copy_from_user(pvBounce, buf, size) == 0) {
+ VBOXSFWRITEPGLSTREQ *pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqWriteContig(pSuperInfo->map.root, pReq, sf_r->handle, pos,
+ (uint32_t)size, pvBounce, virt_to_phys(pvBounce));
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbRet <= (ssize_t)size, cbRet = size);
+ vbsf_reg_write_sync_page_cache(mapping, pos, (uint32_t)cbRet, (uint8_t const *)pvBounce,
+ NULL /*papSrcPages*/, 0 /*offSrcPage0*/, 0 /*cSrcPages*/);
+ pos += cbRet;
+ *off = pos;
+ if (pos > i_size_read(inode))
+ i_size_write(inode, pos);
+ } else
+ cbRet = -EPROTO;
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+ VbglR0PhysHeapFree(pReq);
+ kfree(pvBounce);
+ SFLOGFLOW(("vbsf_reg_write: returns %zd (%#zx), *off=%RX64 [bounce]\n", cbRet, cbRet, *off));
+ return cbRet;
+ }
+ kfree(pvBounce);
+ } else {
+ kfree(pvBounce);
+ SFLOGFLOW(("vbsf_reg_write: returns -EFAULT, *off=%RX64 [bounce]\n", *off));
+ return -EFAULT;
+ }
+ }
+ }
+# endif
+
+ return vbsf_reg_write_locking(file, buf, size, off, pos, inode, sf_i, pSuperInfo, sf_r);
+}
+
+#endif /* < 5.10.0 */
+#if RTLNX_VER_MIN(2,6,19)
+/* See kernel 6.0.0 change eba2d3d798295dc43cae8fade102f9d083a2a741. */
+# if RTLNX_VER_MIN(6,0,0) || RTLNX_RHEL_RANGE(9,4, 9,99)
+# define VBOX_IOV_GET_PAGES iov_iter_get_pages2
+# else
+# define VBOX_IOV_GET_PAGES iov_iter_get_pages
+# endif
+
+/**
+ * Companion to vbsf_iter_lock_pages().
+ */
+DECLINLINE(void) vbsf_iter_unlock_pages(struct iov_iter *iter, struct page **papPages, size_t cPages, bool fSetDirty)
+{
+ /* We don't mark kernel pages dirty (KVECs, BVECs, PIPEs): */
+ if (!iter_is_iovec(iter))
+ fSetDirty = false;
+
+ while (cPages-- > 0)
+ {
+ struct page *pPage = papPages[cPages];
+ if (fSetDirty && !PageReserved(pPage))
+ set_page_dirty(pPage);
+ vbsf_put_page(pPage);
+ }
+}
+
+
+/**
+ * Locks up to @a cMaxPages from the I/O vector iterator, advancing the
+ * iterator.
+ *
+ * @returns 0 on success, negative errno value on failure.
+ * @param iter The iterator to lock pages from.
+ * @param fWrite Whether to write (true) or read (false) lock the pages.
+ * @param pStash Where we stash peek results.
+ * @param cMaxPages The maximum number of pages to get.
+ * @param papPages Where to return the locked pages.
+ * @param pcPages Where to return the number of pages.
+ * @param poffPage0 Where to return the offset into the first page.
+ * @param pcbChunk Where to return the number of bytes covered.
+ */
+static int vbsf_iter_lock_pages(struct iov_iter *iter, bool fWrite, struct vbsf_iter_stash *pStash, size_t cMaxPages,
+ struct page **papPages, size_t *pcPages, size_t *poffPage0, size_t *pcbChunk)
+{
+ size_t cbChunk = 0;
+ size_t cPages = 0;
+ size_t offPage0 = 0;
+ int rc = 0;
+
+ Assert(iov_iter_count(iter) + pStash->cb > 0);
+ if (!VBOX_IOV_ITER_IS_KVEC(iter))
+ {
+ /*
+ * Do we have a stashed page?
+ */
+ if (pStash->pPage) {
+ papPages[0] = pStash->pPage;
+ offPage0 = pStash->off;
+ cbChunk = pStash->cb;
+ cPages = 1;
+ pStash->pPage = NULL;
+ pStash->off = 0;
+ pStash->cb = 0;
+ if ( offPage0 + cbChunk < PAGE_SIZE
+ || iov_iter_count(iter) == 0) {
+ *poffPage0 = offPage0;
+ *pcbChunk = cbChunk;
+ *pcPages = cPages;
+ SFLOGFLOW(("vbsf_iter_lock_pages: returns %d - cPages=%#zx offPage0=%#zx cbChunk=%zx (stashed)\n",
+ rc, cPages, offPage0, cbChunk));
+ return 0;
+ }
+ cMaxPages -= 1;
+ SFLOG3(("vbsf_iter_lock_pages: Picked up stashed page: %#zx LB %#zx\n", offPage0, cbChunk));
+ } else {
+# if RTLNX_VER_MAX(4,11,0)
+ /*
+ * Copy out our starting point to assist rewinding.
+ */
+ pStash->offFromEnd = iov_iter_count(iter);
+ pStash->Copy = *iter;
+# endif
+ }
+
+ /*
+ * Get pages segment by segment.
+ */
+ do {
+ /*
+ * Make a special case of the first time thru here, since that's
+ * the most typical scenario.
+ */
+ ssize_t cbSegRet;
+ if (cPages == 0) {
+# if RTLNX_VER_MAX(3,19,0)
+ while (!iov_iter_single_seg_count(iter)) /* Old code didn't skip empty segments which caused EFAULTs. */
+ iov_iter_advance(iter, 0);
+# endif
+ cbSegRet = VBOX_IOV_GET_PAGES(iter, papPages, iov_iter_count(iter), cMaxPages, &offPage0);
+ if (cbSegRet > 0) {
+# if RTLNX_VER_MAX(6,0,0)
+ iov_iter_advance(iter, cbSegRet);
+#endif
+ cbChunk = (size_t)cbSegRet;
+ cPages = RT_ALIGN_Z(offPage0 + cbSegRet, PAGE_SIZE) >> PAGE_SHIFT;
+ cMaxPages -= cPages;
+ SFLOG3(("vbsf_iter_lock_pages: iov_iter_get_pages -> %#zx @ %#zx; %#zx pages [first]\n", cbSegRet, offPage0, cPages));
+ if ( cMaxPages == 0
+ || ((offPage0 + (size_t)cbSegRet) & PAGE_OFFSET_MASK))
+ break;
+ } else {
+ AssertStmt(cbSegRet < 0, cbSegRet = -EFAULT);
+ rc = (int)cbSegRet;
+ break;
+ }
+ } else {
+ /*
+ * Probe first page of new segment to check that we've got a zero offset and
+ * can continue on the current chunk. Stash the page if the offset isn't zero.
+ */
+ size_t offPgProbe;
+ size_t cbSeg = iov_iter_single_seg_count(iter);
+ while (!cbSeg) {
+ iov_iter_advance(iter, 0);
+ cbSeg = iov_iter_single_seg_count(iter);
+ }
+ cbSegRet = VBOX_IOV_GET_PAGES(iter, &papPages[cPages], iov_iter_count(iter), 1, &offPgProbe);
+ if (cbSegRet > 0) {
+# if RTLNX_VER_MAX(6,0,0)
+ iov_iter_advance(iter, cbSegRet); /** @todo maybe not do this if we stash the page? */
+#endif
+ Assert(offPgProbe + cbSegRet <= PAGE_SIZE);
+ if (offPgProbe == 0) {
+ cbChunk += cbSegRet;
+ cPages += 1;
+ cMaxPages -= 1;
+ SFLOG3(("vbsf_iter_lock_pages: iov_iter_get_pages(1) -> %#zx @ %#zx\n", cbSegRet, offPgProbe));
+ if ( cMaxPages == 0
+ || cbSegRet != PAGE_SIZE)
+ break;
+
+ /*
+ * Get the rest of the segment (if anything remaining).
+ */
+ cbSeg -= cbSegRet;
+ if (cbSeg > 0) {
+ cbSegRet = VBOX_IOV_GET_PAGES(iter, &papPages[cPages], iov_iter_count(iter), cMaxPages, &offPgProbe);
+ if (cbSegRet > 0) {
+ size_t const cPgRet = RT_ALIGN_Z((size_t)cbSegRet, PAGE_SIZE) >> PAGE_SHIFT;
+ Assert(offPgProbe == 0);
+# if RTLNX_VER_MAX(6,0,0)
+ iov_iter_advance(iter, cbSegRet);
+# endif
+ SFLOG3(("vbsf_iter_lock_pages: iov_iter_get_pages() -> %#zx; %#zx pages\n", cbSegRet, cPgRet));
+ cPages += cPgRet;
+ cMaxPages -= cPgRet;
+ cbChunk += cbSegRet;
+ if ( cMaxPages == 0
+ || ((size_t)cbSegRet & PAGE_OFFSET_MASK))
+ break;
+ } else {
+ AssertStmt(cbSegRet < 0, cbSegRet = -EFAULT);
+ rc = (int)cbSegRet;
+ break;
+ }
+ }
+ } else {
+ /* The segment didn't start at a page boundrary, so stash it for
+ the next round: */
+ SFLOGFLOW(("vbsf_iter_lock_pages: iov_iter_get_pages(1) -> %#zx @ %#zx; stashed\n", cbSegRet, offPgProbe));
+ Assert(papPages[cPages]);
+ pStash->pPage = papPages[cPages];
+ pStash->off = offPgProbe;
+ pStash->cb = cbSegRet;
+ break;
+ }
+ } else {
+ AssertStmt(cbSegRet < 0, cbSegRet = -EFAULT);
+ rc = (int)cbSegRet;
+ break;
+ }
+ }
+ Assert(cMaxPages > 0);
+ } while (iov_iter_count(iter) > 0);
+
+ } else {
+ /*
+ * The silly iov_iter_get_pages_alloc() function doesn't handle KVECs,
+ * so everyone needs to do that by themselves.
+ *
+ * Note! Fixes here may apply to rtR0MemObjNativeLockKernel()
+ * and vbsf_lock_user_pages_failed_check_kernel() as well.
+ */
+# if RTLNX_VER_MAX(4,11,0)
+ pStash->offFromEnd = iov_iter_count(iter);
+ pStash->Copy = *iter;
+# endif
+ do {
+ uint8_t *pbBuf;
+ size_t offStart;
+ size_t cPgSeg;
+
+ size_t cbSeg = iov_iter_single_seg_count(iter);
+ while (!cbSeg) {
+ iov_iter_advance(iter, 0);
+ cbSeg = iov_iter_single_seg_count(iter);
+ }
+
+ pbBuf = VBOX_ITER_IOV_ADDR(iter);
+ offStart = (uintptr_t)pbBuf & PAGE_OFFSET_MASK;
+ if (!cPages)
+ offPage0 = offStart;
+ else if (offStart)
+ break;
+
+ cPgSeg = RT_ALIGN_Z(cbSeg, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPgSeg > cMaxPages) {
+ cPgSeg = cMaxPages;
+ cbSeg = (cPgSeg << PAGE_SHIFT) - offStart;
+ }
+
+ rc = vbsf_lock_kernel_pages(pbBuf, fWrite, cPgSeg, &papPages[cPages]);
+ if (rc == 0) {
+ iov_iter_advance(iter, cbSeg);
+ cbChunk += cbSeg;
+ cPages += cPgSeg;
+ cMaxPages -= cPgSeg;
+ if ( cMaxPages == 0
+ || ((offStart + cbSeg) & PAGE_OFFSET_MASK) != 0)
+ break;
+ } else
+ break;
+ } while (iov_iter_count(iter) > 0);
+ }
+
+ /*
+ * Clean up if we failed; set return values.
+ */
+ if (rc == 0) {
+ /* likely */
+ } else {
+ if (cPages > 0)
+ vbsf_iter_unlock_pages(iter, papPages, cPages, false /*fSetDirty*/);
+ offPage0 = cbChunk = cPages = 0;
+ }
+ *poffPage0 = offPage0;
+ *pcbChunk = cbChunk;
+ *pcPages = cPages;
+ SFLOGFLOW(("vbsf_iter_lock_pages: returns %d - cPages=%#zx offPage0=%#zx cbChunk=%zx\n", rc, cPages, offPage0, cbChunk));
+ return rc;
+}
+
+
+/**
+ * Rewinds the I/O vector.
+ */
+static bool vbsf_iter_rewind(struct iov_iter *iter, struct vbsf_iter_stash *pStash, size_t cbToRewind, size_t cbChunk)
+{
+ size_t cbExtra;
+ if (!pStash->pPage) {
+ cbExtra = 0;
+ } else {
+ cbExtra = pStash->cb;
+ vbsf_put_page(pStash->pPage);
+ pStash->pPage = NULL;
+ pStash->cb = 0;
+ pStash->off = 0;
+ }
+
+# if RTLNX_VER_MIN(4,11,0) || RTLNX_VER_MAX(3,16,0)
+ iov_iter_revert(iter, cbToRewind + cbExtra);
+ return true;
+# else
+ /** @todo impl this */
+ return false;
+# endif
+}
+
+
+/**
+ * Cleans up the page locking stash.
+ */
+DECLINLINE(void) vbsf_iter_cleanup_stash(struct iov_iter *iter, struct vbsf_iter_stash *pStash)
+{
+ if (pStash->pPage)
+ vbsf_iter_rewind(iter, pStash, 0, 0);
+}
+
+
+/**
+ * Calculates the longest span of pages we could transfer to the host in a
+ * single request.
+ *
+ * @returns Page count, non-zero.
+ * @param iter The I/O vector iterator to inspect.
+ */
+static size_t vbsf_iter_max_span_of_pages(struct iov_iter *iter)
+{
+ size_t cPages;
+# if RTLNX_VER_MIN(3,16,0)
+ if (iter_is_iovec(iter) || (VBSF_GET_ITER_TYPE(iter) & ITER_KVEC)) {
+# endif
+ const struct iovec *pCurIov = VBSF_GET_ITER_IOV(iter);
+ size_t cLeft = iter->nr_segs;
+ size_t cPagesSpan = 0;
+
+ /* iovect and kvec are identical, except for the __user tagging of iov_base. */
+ AssertCompileMembersSameSizeAndOffset(struct iovec, iov_base, struct kvec, iov_base);
+ AssertCompileMembersSameSizeAndOffset(struct iovec, iov_len, struct kvec, iov_len);
+ AssertCompile(sizeof(struct iovec) == sizeof(struct kvec));
+
+ cPages = 1;
+ AssertReturn(cLeft > 0, cPages);
+
+ /* Special case: segment offset. */
+ if (iter->iov_offset > 0) {
+ if (iter->iov_offset < pCurIov->iov_len) {
+ size_t const cbSegLeft = pCurIov->iov_len - iter->iov_offset;
+ size_t const offPage0 = ((uintptr_t)pCurIov->iov_base + iter->iov_offset) & PAGE_OFFSET_MASK;
+ cPages = cPagesSpan = RT_ALIGN_Z(offPage0 + cbSegLeft, PAGE_SIZE) >> PAGE_SHIFT;
+ if ((offPage0 + cbSegLeft) & PAGE_OFFSET_MASK)
+ cPagesSpan = 0;
+ }
+ SFLOGFLOW(("vbsf_iter: seg[0]= %p LB %#zx\n", pCurIov->iov_base, pCurIov->iov_len));
+ pCurIov++;
+ cLeft--;
+ }
+
+ /* Full segments. */
+ while (cLeft-- > 0) {
+ if (pCurIov->iov_len > 0) {
+ size_t const offPage0 = (uintptr_t)pCurIov->iov_base & PAGE_OFFSET_MASK;
+ if (offPage0 == 0) {
+ if (!(pCurIov->iov_len & PAGE_OFFSET_MASK)) {
+ cPagesSpan += pCurIov->iov_len >> PAGE_SHIFT;
+ } else {
+ cPagesSpan += RT_ALIGN_Z(pCurIov->iov_len, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+ cPagesSpan = 0;
+ }
+ } else {
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+ if (!((offPage0 + pCurIov->iov_len) & PAGE_OFFSET_MASK)) {
+ cPagesSpan = pCurIov->iov_len >> PAGE_SHIFT;
+ } else {
+ cPagesSpan += RT_ALIGN_Z(offPage0 + pCurIov->iov_len, PAGE_SIZE) >> PAGE_SHIFT;
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+ cPagesSpan = 0;
+ }
+ }
+ }
+ SFLOGFLOW(("vbsf_iter: seg[%u]= %p LB %#zx\n", iter->nr_segs - cLeft, pCurIov->iov_base, pCurIov->iov_len));
+ pCurIov++;
+ }
+ if (cPagesSpan > cPages)
+ cPages = cPagesSpan;
+# if RTLNX_VER_MIN(3,16,0)
+ } else {
+ /* Won't bother with accurate counts for the next two types, just make
+ some rough estimates (does pipes have segments?): */
+ size_t cSegs = VBSF_GET_ITER_TYPE(iter) & ITER_BVEC ? RT_MAX(1, iter->nr_segs) : 1;
+ cPages = (iov_iter_count(iter) + (PAGE_SIZE * 2 - 2) * cSegs) >> PAGE_SHIFT;
+ }
+# endif
+ SFLOGFLOW(("vbsf_iter_max_span_of_pages: returns %#zx\n", cPages));
+ return cPages;
+}
+
+
+/**
+ * Worker for vbsf_reg_read_iter() that deals with larger reads using page
+ * locking.
+ */
+static ssize_t vbsf_reg_read_iter_locking(struct kiocb *kio, struct iov_iter *iter, size_t cbToRead,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r)
+{
+ /*
+ * Estimate how many pages we may possible submit in a single request so
+ * that we can allocate matching request buffer and page array.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFREADPGLSTREQ *pReq;
+ ssize_t cbRet = 0;
+ size_t cMaxPages = vbsf_iter_max_span_of_pages(iter);
+ cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 2), cMaxPages);
+
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFREADPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+
+ /*
+ * The read loop.
+ */
+ struct vbsf_iter_stash Stash = VBSF_ITER_STASH_INITIALIZER;
+ do {
+ /*
+ * Grab as many pages as we can. This means that if adjacent
+ * segments both starts and ends at a page boundrary, we can
+ * do them both in the same transfer from the host.
+ */
+ size_t cPages = 0;
+ size_t cbChunk = 0;
+ size_t offPage0 = 0;
+ int rc = vbsf_iter_lock_pages(iter, true /*fWrite*/, &Stash, cMaxPages, papPages, &cPages, &offPage0, &cbChunk);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ pReq->PgLst.offFirstPage = (uint16_t)offPage0;
+ AssertStmt(cbChunk <= cbToRead, cbChunk = cbToRead);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, kio->ki_pos, cbChunk, cPages);
+ SFLOGFLOW(("vbsf_reg_read_iter_locking: VbglR0SfHostReqReadPgLst -> %d (cbActual=%#x cbChunk=%#zx of %#zx cPages=%#zx offPage0=%#x\n",
+ rc, pReq->Parms.cb32Read.u.value32, cbChunk, cbToRead, cPages, offPage0));
+
+ vbsf_iter_unlock_pages(iter, papPages, cPages, true /*fSetDirty*/);
+
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+ cbRet += cbActual;
+ kio->ki_pos += cbActual;
+ cbToRead -= cbActual;
+
+ /*
+ * Are we done already?
+ */
+ if (!cbToRead)
+ break;
+ if (cbActual < cbChunk) { /* We ASSUME end-of-file here. */
+ if (vbsf_iter_rewind(iter, &Stash, cbChunk - cbActual, cbActual))
+ iov_iter_truncate(iter, 0);
+ break;
+ }
+ } else {
+ /*
+ * Try rewind the iter structure.
+ */
+ bool const fRewindOkay = vbsf_iter_rewind(iter, &Stash, cbChunk, cbChunk);
+ if (rc == VERR_NO_MEMORY && cMaxPages > 4 && fRewindOkay) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully read stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet <= 0)
+ cbRet = -EPROTO;
+ break;
+ }
+ }
+ } while (cbToRead > 0);
+
+ vbsf_iter_cleanup_stash(iter, &Stash);
+ }
+ else
+ cbRet = -ENOMEM;
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read_iter_locking: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+}
+
+
+/**
+ * Read into I/O vector iterator.
+ *
+ * @returns Number of bytes read on success, negative errno on error.
+ * @param kio The kernel I/O control block (or something like that).
+ * @param iter The I/O vector iterator describing the buffer.
+ */
+# if RTLNX_VER_MIN(3,16,0)
+static ssize_t vbsf_reg_read_iter(struct kiocb *kio, struct iov_iter *iter)
+# else
+static ssize_t vbsf_reg_aio_read(struct kiocb *kio, const struct iovec *iov, unsigned long cSegs, loff_t offFile)
+# endif
+{
+# if RTLNX_VER_MAX(3,16,0)
+ struct vbsf_iov_iter fake_iter = VBSF_IOV_ITER_INITIALIZER(cSegs, iov, 0 /*write*/);
+ struct vbsf_iov_iter *iter = &fake_iter;
+# endif
+ size_t cbToRead = iov_iter_count(iter);
+ struct inode *inode = VBSF_GET_F_DENTRY(kio->ki_filp)->d_inode;
+ struct address_space *mapping = inode->i_mapping;
+
+ struct vbsf_reg_info *sf_r = kio->ki_filp->private_data;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+
+ SFLOGFLOW(("vbsf_reg_read_iter: inode=%p file=%p size=%#zx off=%#llx type=%#x\n",
+ inode, kio->ki_filp, cbToRead, kio->ki_pos, VBSF_GET_ITER_TYPE(iter) ));
+ AssertReturn(S_ISREG(inode->i_mode), -EINVAL);
+
+ /*
+ * Do we have anything at all to do here?
+ */
+ if (!cbToRead)
+ return 0;
+
+ /*
+ * If there is a mapping and O_DIRECT isn't in effect, we must at a
+ * heed dirty pages in the mapping and read from them. For simplicity
+ * though, we just do page cache reading when there are writable
+ * mappings around with any kind of pages loaded.
+ */
+ if (vbsf_should_use_cached_read(kio->ki_filp, mapping, pSuperInfo)) {
+# if RTLNX_VER_MIN(3,16,0)
+ return generic_file_read_iter(kio, iter);
+# else
+ return generic_file_aio_read(kio, iov, cSegs, offFile);
+# endif
+ }
+
+ /*
+ * Now now we reject async I/O requests.
+ */
+ if (!is_sync_kiocb(kio)) {
+ SFLOGFLOW(("vbsf_reg_read_iter: async I/O not yet supported\n")); /** @todo extend FsPerf with AIO tests. */
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (cbToRead <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFREADEMBEDDEDREQ, abData[0]) + cbToRead;
+ VBOXSFREADEMBEDDEDREQ *pReq = (VBOXSFREADEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ if ((PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ int vrc = VbglR0SfHostReqReadEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost,
+ kio->ki_pos, (uint32_t)cbToRead);
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRet <= (ssize_t)cbToRead, cbRet = cbToRead);
+ if (copy_to_iter(pReq->abData, cbRet, iter) == cbRet) {
+ kio->ki_pos += cbRet;
+ if (cbRet < cbToRead)
+ iov_iter_truncate(iter, 0);
+ } else
+ cbRet = -EFAULT;
+ } else
+ cbRet = -EPROTO;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_read_iter: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ }
+
+ /*
+ * Otherwise do the page locking thing.
+ */
+ return vbsf_reg_read_iter_locking(kio, iter, cbToRead, pSuperInfo, sf_r);
+}
+
+
+/**
+ * Worker for vbsf_reg_write_iter() that deals with larger writes using page
+ * locking.
+ */
+static ssize_t vbsf_reg_write_iter_locking(struct kiocb *kio, struct iov_iter *iter, size_t cbToWrite, loff_t offFile,
+ struct vbsf_super_info *pSuperInfo, struct vbsf_reg_info *sf_r, struct inode *inode,
+ struct vbsf_inode_info *sf_i, struct address_space *mapping, bool fAppend)
+{
+ /*
+ * Estimate how many pages we may possible submit in a single request so
+ * that we can allocate matching request buffer and page array.
+ */
+ struct page *apPagesStack[16];
+ struct page **papPages = &apPagesStack[0];
+ struct page **papPagesFree = NULL;
+ VBOXSFWRITEPGLSTREQ *pReq;
+ ssize_t cbRet = 0;
+ size_t cMaxPages = vbsf_iter_max_span_of_pages(iter);
+ cMaxPages = RT_MIN(RT_MAX(pSuperInfo->cMaxIoPages, 2), cMaxPages);
+
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ while (!pReq && cMaxPages > 4) {
+ cMaxPages /= 2;
+ pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(RT_UOFFSETOF_DYN(VBOXSFWRITEPGLSTREQ, PgLst.aPages[cMaxPages]));
+ }
+ if (pReq && cMaxPages > RT_ELEMENTS(apPagesStack))
+ papPagesFree = papPages = kmalloc(cMaxPages * sizeof(sizeof(papPages[0])), GFP_KERNEL);
+ if (pReq && papPages) {
+
+ /*
+ * The write loop.
+ */
+ struct vbsf_iter_stash Stash = VBSF_ITER_STASH_INITIALIZER;
+ do {
+ /*
+ * Grab as many pages as we can. This means that if adjacent
+ * segments both starts and ends at a page boundrary, we can
+ * do them both in the same transfer from the host.
+ */
+ size_t cPages = 0;
+ size_t cbChunk = 0;
+ size_t offPage0 = 0;
+ int rc = vbsf_iter_lock_pages(iter, false /*fWrite*/, &Stash, cMaxPages, papPages, &cPages, &offPage0, &cbChunk);
+ if (rc == 0) {
+ size_t iPage = cPages;
+ while (iPage-- > 0)
+ pReq->PgLst.aPages[iPage] = page_to_phys(papPages[iPage]);
+ pReq->PgLst.offFirstPage = (uint16_t)offPage0;
+ AssertStmt(cbChunk <= cbToWrite, cbChunk = cbToWrite);
+ } else {
+ cbRet = rc;
+ break;
+ }
+
+ /*
+ * Issue the request and unlock the pages.
+ */
+ rc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root, pReq, sf_r->Handle.hHost, offFile, cbChunk, cPages);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ SFLOGFLOW(("vbsf_reg_write_iter_locking: VbglR0SfHostReqWritePgLst -> %d (cbActual=%#x cbChunk=%#zx of %#zx cPages=%#zx offPage0=%#x\n",
+ rc, pReq->Parms.cb32Write.u.value32, cbChunk, cbToWrite, cPages, offPage0));
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Success, advance position and buffer.
+ */
+ uint32_t cbActual = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbActual <= cbChunk, cbActual = cbChunk);
+
+ vbsf_reg_write_sync_page_cache(mapping, offFile, cbActual, NULL /*pbSrcBuf*/, papPages, offPage0, cPages);
+ vbsf_iter_unlock_pages(iter, papPages, cPages, false /*fSetDirty*/);
+
+ cbRet += cbActual;
+ cbToWrite -= cbActual;
+
+ offFile += cbActual;
+ if (fAppend && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ offFile = pReq->Parms.off64Write.u.value64;
+ kio->ki_pos = offFile;
+ if (offFile > i_size_read(inode))
+ i_size_write(inode, offFile);
+
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+
+ /*
+ * Are we done already?
+ */
+ if (!cbToWrite)
+ break;
+ if (cbActual < cbChunk) { /* We ASSUME end-of-file here. */
+ if (vbsf_iter_rewind(iter, &Stash, cbChunk - cbActual, cbActual))
+ iov_iter_truncate(iter, 0);
+ break;
+ }
+ } else {
+ /*
+ * Try rewind the iter structure.
+ */
+ bool fRewindOkay;
+ vbsf_iter_unlock_pages(iter, papPages, cPages, false /*fSetDirty*/);
+ fRewindOkay = vbsf_iter_rewind(iter, &Stash, cbChunk, cbChunk);
+ if (rc == VERR_NO_MEMORY && cMaxPages > 4 && fRewindOkay) {
+ /*
+ * The host probably doesn't have enough heap to handle the
+ * request, reduce the page count and retry.
+ */
+ cMaxPages /= 4;
+ Assert(cMaxPages > 0);
+ } else {
+ /*
+ * If we've successfully written stuff, return it rather than
+ * the error. (Not sure if this is such a great idea...)
+ */
+ if (cbRet <= 0)
+ cbRet = -EPROTO;
+ break;
+ }
+ }
+ } while (cbToWrite > 0);
+
+ vbsf_iter_cleanup_stash(iter, &Stash);
+ }
+ else
+ cbRet = -ENOMEM;
+ if (papPagesFree)
+ kfree(papPages);
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write_iter_locking: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+}
+
+
+/**
+ * Write from I/O vector iterator.
+ *
+ * @returns Number of bytes written on success, negative errno on error.
+ * @param kio The kernel I/O control block (or something like that).
+ * @param iter The I/O vector iterator describing the buffer.
+ */
+# if RTLNX_VER_MIN(3,16,0)
+static ssize_t vbsf_reg_write_iter(struct kiocb *kio, struct iov_iter *iter)
+# else
+static ssize_t vbsf_reg_aio_write(struct kiocb *kio, const struct iovec *iov, unsigned long cSegs, loff_t offFile)
+# endif
+{
+# if RTLNX_VER_MAX(3,16,0)
+ struct vbsf_iov_iter fake_iter = VBSF_IOV_ITER_INITIALIZER(cSegs, iov, 1 /*write*/);
+ struct vbsf_iov_iter *iter = &fake_iter;
+# endif
+ size_t cbToWrite = iov_iter_count(iter);
+ struct inode *inode = VBSF_GET_F_DENTRY(kio->ki_filp)->d_inode;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct address_space *mapping = inode->i_mapping;
+
+ struct vbsf_reg_info *sf_r = kio->ki_filp->private_data;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+# if RTLNX_VER_MIN(3,16,0)
+ loff_t offFile = kio->ki_pos;
+# endif
+# if RTLNX_VER_MIN(4,1,0)
+ bool const fAppend = RT_BOOL(kio->ki_flags & IOCB_APPEND);
+# else
+ bool const fAppend = RT_BOOL(kio->ki_filp->f_flags & O_APPEND);
+# endif
+
+
+ SFLOGFLOW(("vbsf_reg_write_iter: inode=%p file=%p size=%#zx off=%#llx type=%#x\n",
+ inode, kio->ki_filp, cbToWrite, offFile, VBSF_GET_ITER_TYPE(iter) ));
+ AssertReturn(S_ISREG(inode->i_mode), -EINVAL);
+
+ /*
+ * Enforce APPEND flag (more later).
+ */
+ if (fAppend)
+ kio->ki_pos = offFile = i_size_read(inode);
+
+ /*
+ * Do we have anything at all to do here?
+ */
+ if (!cbToWrite)
+ return 0;
+
+ /** @todo Implement the read-write caching mode. */
+
+ /*
+ * Now now we reject async I/O requests.
+ */
+ if (!is_sync_kiocb(kio)) {
+ SFLOGFLOW(("vbsf_reg_write_iter: async I/O not yet supported\n")); /** @todo extend FsPerf with AIO tests. */
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * If there are active writable mappings, coordinate with any
+ * pending writes via those.
+ */
+ if ( mapping
+ && mapping->nrpages > 0
+ && mapping_writably_mapped(mapping)) {
+# if RTLNX_VER_MIN(2,6,32)
+ int err = filemap_fdatawait_range(mapping, offFile, offFile + cbToWrite - 1);
+ if (err)
+ return err;
+# else
+ /** @todo ... */
+# endif
+ }
+
+ /*
+ * For small requests, try use an embedded buffer provided we get a heap block
+ * that does not cross page boundraries (see host code).
+ */
+ if (cbToWrite <= PAGE_SIZE / 4 * 3 - RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) /* see allocator */) {
+ uint32_t const cbReq = RT_UOFFSETOF(VBOXSFWRITEEMBEDDEDREQ, abData[0]) + cbToWrite;
+ VBOXSFWRITEEMBEDDEDREQ *pReq = (VBOXSFWRITEEMBEDDEDREQ *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ if ((PAGE_SIZE - ((uintptr_t)pReq & PAGE_OFFSET_MASK)) >= cbReq) {
+ ssize_t cbRet;
+ if (copy_from_iter(pReq->abData, cbToWrite, iter) == cbToWrite) {
+ int vrc = VbglR0SfHostReqWriteEmbedded(pSuperInfo->map.root, pReq, sf_r->Handle.hHost,
+ offFile, (uint32_t)cbToWrite);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ if (RT_SUCCESS(vrc)) {
+ cbRet = pReq->Parms.cb32Write.u.value32;
+ AssertStmt(cbRet <= (ssize_t)cbToWrite, cbRet = cbToWrite);
+ vbsf_reg_write_sync_page_cache(mapping, offFile, (uint32_t)cbRet, pReq->abData,
+ NULL /*papSrcPages*/, 0 /*offSrcPage0*/, 0 /*cSrcPages*/);
+
+ offFile += cbRet;
+ if (fAppend && (g_fSfFeatures & SHFL_FEATURE_WRITE_UPDATES_OFFSET))
+ offFile = pReq->Parms.off64Write.u.value64;
+ kio->ki_pos = offFile;
+ if (offFile > i_size_read(inode))
+ i_size_write(inode, offFile);
+
+# if RTLNX_VER_MIN(4,11,0)
+ if ((size_t)cbRet < cbToWrite)
+ iov_iter_revert(iter, cbToWrite - cbRet);
+# endif
+ } else
+ cbRet = -EPROTO;
+ sf_i->force_restat = 1; /* mtime (and size) may have changed */
+ } else
+ cbRet = -EFAULT;
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_write_iter: returns %#zx (%zd)\n", cbRet, cbRet));
+ return cbRet;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ }
+
+ /*
+ * Otherwise do the page locking thing.
+ */
+ return vbsf_reg_write_iter_locking(kio, iter, cbToWrite, offFile, pSuperInfo, sf_r, inode, sf_i, mapping, fAppend);
+}
+
+#endif /* >= 2.6.19 */
+
+/**
+ * Used by vbsf_reg_open() and vbsf_inode_atomic_open() to
+ *
+ * @returns shared folders create flags.
+ * @param fLnxOpen The linux O_XXX flags to convert.
+ * @param pfHandle Pointer to vbsf_handle::fFlags.
+ * @param pszCaller Caller, for logging purposes.
+ */
+uint32_t vbsf_linux_oflags_to_vbox(unsigned fLnxOpen, uint32_t *pfHandle, const char *pszCaller)
+{
+ uint32_t fVBoxFlags = SHFL_CF_ACCESS_DENYNONE;
+
+ /*
+ * Disposition.
+ */
+ if (fLnxOpen & O_CREAT) {
+ Log(("%s: O_CREAT set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_CREATE_IF_NEW;
+ if (fLnxOpen & O_EXCL) {
+ Log(("%s: O_EXCL set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_FAIL_IF_EXISTS;
+ } else if (fLnxOpen & O_TRUNC) {
+ Log(("%s: O_TRUNC set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
+ } else
+ fVBoxFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS;
+ } else {
+ fVBoxFlags |= SHFL_CF_ACT_FAIL_IF_NEW;
+ if (fLnxOpen & O_TRUNC) {
+ Log(("%s: O_TRUNC set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS;
+ }
+ }
+
+ /*
+ * Access.
+ */
+ switch (fLnxOpen & O_ACCMODE) {
+ case O_RDONLY:
+ fVBoxFlags |= SHFL_CF_ACCESS_READ;
+ *pfHandle |= VBSF_HANDLE_F_READ;
+ break;
+
+ case O_WRONLY:
+ fVBoxFlags |= SHFL_CF_ACCESS_WRITE;
+ *pfHandle |= VBSF_HANDLE_F_WRITE;
+ break;
+
+ case O_RDWR:
+ fVBoxFlags |= SHFL_CF_ACCESS_READWRITE;
+ *pfHandle |= VBSF_HANDLE_F_READ | VBSF_HANDLE_F_WRITE;
+ break;
+
+ default:
+ BUG();
+ }
+
+ if (fLnxOpen & O_APPEND) {
+ Log(("%s: O_APPEND set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_ACCESS_APPEND;
+ *pfHandle |= VBSF_HANDLE_F_APPEND;
+ }
+
+ /*
+ * Only directories?
+ */
+ if (fLnxOpen & O_DIRECTORY) {
+ Log(("%s: O_DIRECTORY set\n", pszCaller));
+ fVBoxFlags |= SHFL_CF_DIRECTORY;
+ }
+
+ return fVBoxFlags;
+}
+
+
+/**
+ * Open a regular file.
+ *
+ * @param inode the inode
+ * @param file the file
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_reg_open(struct inode *inode, struct file *file)
+{
+ int rc, rc_linux = 0;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct dentry *dentry = VBSF_GET_F_DENTRY(file);
+ struct vbsf_reg_info *sf_r;
+ VBOXSFCREATEREQ *pReq;
+
+ SFLOGFLOW(("vbsf_reg_open: inode=%p file=%p flags=%#x %s\n", inode, file, file->f_flags, sf_i ? sf_i->path->String.ach : NULL));
+ Assert(pSuperInfo);
+ Assert(sf_i);
+
+ sf_r = kmalloc(sizeof(*sf_r), GFP_KERNEL);
+ if (!sf_r) {
+ LogRelFunc(("could not allocate reg info\n"));
+ return -ENOMEM;
+ }
+
+ RTListInit(&sf_r->Handle.Entry);
+ sf_r->Handle.cRefs = 1;
+ sf_r->Handle.fFlags = VBSF_HANDLE_F_FILE | VBSF_HANDLE_F_MAGIC;
+ sf_r->Handle.hHost = SHFL_HANDLE_NIL;
+
+ /* Already open? */
+ if (sf_i->handle != SHFL_HANDLE_NIL) {
+ /*
+ * This inode was created with vbsf_create_worker(). Check the CreateFlags:
+ * O_CREAT, O_TRUNC: inherent true (file was just created). Not sure
+ * about the access flags (SHFL_CF_ACCESS_*).
+ */
+ sf_i->force_restat = 1;
+ sf_r->Handle.hHost = sf_i->handle;
+ sf_i->handle = SHFL_HANDLE_NIL;
+ file->private_data = sf_r;
+
+ sf_r->Handle.fFlags |= VBSF_HANDLE_F_READ | VBSF_HANDLE_F_WRITE; /** @todo fix */
+ vbsf_handle_append(sf_i, &sf_r->Handle);
+ SFLOGFLOW(("vbsf_reg_open: returns 0 (#1) - sf_i=%p hHost=%#llx\n", sf_i, sf_r->Handle.hHost));
+ return 0;
+ }
+
+ pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + sf_i->path->u16Size);
+ if (!pReq) {
+ kfree(sf_r);
+ LogRelFunc(("Failed to allocate a VBOXSFCREATEREQ buffer!\n"));
+ return -ENOMEM;
+ }
+ RT_BCOPY_UNFORTIFIED(&pReq->StrPath, sf_i->path, SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ RT_ZERO(pReq->CreateParms);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+
+ /* We check the value of pReq->CreateParms.Handle afterwards to
+ * find out if the call succeeded or failed, as the API does not seem
+ * to cleanly distinguish error and informational messages.
+ *
+ * Furthermore, we must set pReq->CreateParms.Handle to SHFL_HANDLE_NIL
+ * to make the shared folders host service use our fMode parameter */
+
+ /* We ignore O_EXCL, as the Linux kernel seems to call create
+ beforehand itself, so O_EXCL should always fail. */
+ pReq->CreateParms.CreateFlags = vbsf_linux_oflags_to_vbox(file->f_flags & ~O_EXCL, &sf_r->Handle.fFlags, __FUNCTION__);
+ pReq->CreateParms.Info.Attr.fMode = inode->i_mode;
+ LogFunc(("vbsf_reg_open: calling VbglR0SfHostReqCreate, file %s, flags=%#x, %#x\n",
+ sf_i->path->String.utf8, file->f_flags, pReq->CreateParms.CreateFlags));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_FAILURE(rc)) {
+ LogFunc(("VbglR0SfHostReqCreate failed flags=%d,%#x rc=%Rrc\n", file->f_flags, pReq->CreateParms.CreateFlags, rc));
+ kfree(sf_r);
+ VbglR0PhysHeapFree(pReq);
+ return -RTErrConvertToErrno(rc);
+ }
+
+ if (pReq->CreateParms.Handle != SHFL_HANDLE_NIL) {
+ vbsf_dentry_chain_increase_ttl(dentry);
+ vbsf_update_inode(inode, sf_i, &pReq->CreateParms.Info, pSuperInfo, false /*fInodeLocked*/, 0 /*fSetAttrs*/);
+ rc_linux = 0;
+ } else {
+ switch (pReq->CreateParms.Result) {
+ case SHFL_PATH_NOT_FOUND:
+ vbsf_dentry_invalidate_ttl(dentry);
+ rc_linux = -ENOENT;
+ break;
+ case SHFL_FILE_NOT_FOUND:
+ vbsf_dentry_invalidate_ttl(dentry);
+ /** @todo sf_dentry_increase_parent_ttl(file->f_dentry); if we can trust it. */
+ rc_linux = -ENOENT;
+ break;
+ case SHFL_FILE_EXISTS:
+ vbsf_dentry_chain_increase_ttl(dentry);
+ vbsf_update_inode(inode, sf_i, &pReq->CreateParms.Info, pSuperInfo, false /*fInodeLocked*/, 0 /*fSetAttrs*/);
+ rc_linux = -EEXIST;
+ break;
+ default:
+ vbsf_dentry_chain_increase_parent_ttl(dentry);
+ rc_linux = 0;
+ break;
+ }
+ }
+
+ sf_r->Handle.hHost = pReq->CreateParms.Handle;
+ file->private_data = sf_r;
+ vbsf_handle_append(sf_i, &sf_r->Handle);
+ VbglR0PhysHeapFree(pReq);
+ SFLOGFLOW(("vbsf_reg_open: returns 0 (#2) - sf_i=%p hHost=%#llx\n", sf_i, sf_r->Handle.hHost));
+ return rc_linux;
+}
+
+
+/**
+ * Close a regular file.
+ *
+ * @param inode the inode
+ * @param file the file
+ * @returns 0 on success, Linux error code otherwise
+ */
+static int vbsf_reg_release(struct inode *inode, struct file *file)
+{
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct vbsf_reg_info *sf_r = file->private_data;
+
+ SFLOGFLOW(("vbsf_reg_release: inode=%p file=%p\n", inode, file));
+ if (sf_r) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct address_space *mapping = inode->i_mapping;
+ Assert(pSuperInfo);
+
+ /* If we're closing the last handle for this inode, make sure the flush
+ the mapping or we'll end up in vbsf_writepage without a handle. */
+ if ( mapping
+ && mapping->nrpages > 0
+ /** @todo && last writable handle */ ) {
+#if RTLNX_VER_MIN(2,4,25)
+ if (filemap_fdatawrite(mapping) != -EIO)
+#else
+ if ( filemap_fdatasync(mapping) == 0
+ && fsync_inode_data_buffers(inode) == 0)
+#endif
+ filemap_fdatawait(inode->i_mapping);
+ }
+
+ /* Release sf_r, closing the handle if we're the last user. */
+ file->private_data = NULL;
+ vbsf_handle_release(&sf_r->Handle, pSuperInfo, "vbsf_reg_release");
+
+ sf_i->handle = SHFL_HANDLE_NIL;
+ }
+ return 0;
+}
+
+
+/**
+ * Wrapper around generic/default seek function that ensures that we've got
+ * the up-to-date file size when doing anything relative to EOF.
+ *
+ * The issue is that the host may extend the file while we weren't looking and
+ * if the caller wishes to append data, it may end up overwriting existing data
+ * if we operate with a stale size. So, we always retrieve the file size on EOF
+ * relative seeks.
+ */
+static loff_t vbsf_reg_llseek(struct file *file, loff_t off, int whence)
+{
+ SFLOGFLOW(("vbsf_reg_llseek: file=%p off=%lld whence=%d\n", file, off, whence));
+
+ switch (whence) {
+#ifdef SEEK_HOLE
+ case SEEK_HOLE:
+ case SEEK_DATA:
+#endif
+ case SEEK_END: {
+ struct vbsf_reg_info *sf_r = file->private_data;
+ int rc = vbsf_inode_revalidate_with_handle(VBSF_GET_F_DENTRY(file), sf_r->Handle.hHost,
+ true /*fForce*/, false /*fInodeLocked*/);
+ if (rc == 0)
+ break;
+ return rc;
+ }
+ }
+
+#if RTLNX_VER_MIN(2,4,8)
+ return generic_file_llseek(file, off, whence);
+#else
+ return default_llseek(file, off, whence);
+#endif
+}
+
+
+/**
+ * Flush region of file - chiefly mmap/msync.
+ *
+ * We cannot use the noop_fsync / simple_sync_file here as that means
+ * msync(,,MS_SYNC) will return before the data hits the host, thereby
+ * causing coherency issues with O_DIRECT access to the same file as
+ * well as any host interaction with the file.
+ */
+#if RTLNX_VER_MIN(3,1,0) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_MIN(3,0,101) /** @todo figure when exactly */)
+static int vbsf_reg_fsync(struct file *file, loff_t start, loff_t end, int datasync)
+{
+# if RTLNX_VER_MIN(3,16,0)
+ return __generic_file_fsync(file, start, end, datasync);
+# else
+ return generic_file_fsync(file, start, end, datasync);
+# endif
+}
+#elif RTLNX_VER_MIN(2,6,35)
+static int vbsf_reg_fsync(struct file *file, int datasync)
+{
+ return generic_file_fsync(file, datasync);
+}
+#else /* < 2.6.35 */
+static int vbsf_reg_fsync(struct file *file, struct dentry *dentry, int datasync)
+{
+# if RTLNX_VER_MIN(2,6,31)
+ return simple_fsync(file, dentry, datasync);
+# else
+ int rc;
+ struct inode *inode = dentry->d_inode;
+ AssertReturn(inode, -EINVAL);
+
+ /** @todo What about file_fsync()? (<= 2.5.11) */
+
+# if RTLNX_VER_MIN(2,5,12)
+ rc = sync_mapping_buffers(inode->i_mapping);
+ if ( rc == 0
+ && (inode->i_state & I_DIRTY)
+ && ((inode->i_state & I_DIRTY_DATASYNC) || !datasync)
+ ) {
+ struct writeback_control wbc = {
+ .sync_mode = WB_SYNC_ALL,
+ .nr_to_write = 0
+ };
+ rc = sync_inode(inode, &wbc);
+ }
+# else /* < 2.5.12 */
+ /** @todo
+ * Somethings is buggy here or in the 2.4.21-27.EL kernel I'm testing on.
+ *
+ * In theory we shouldn't need to do anything here, since msync will call
+ * writepage() on each dirty page and we write them out synchronously. So, the
+ * problem is elsewhere... Doesn't happen all the time either. Sigh.
+ */
+ rc = fsync_inode_buffers(inode);
+# if RTLNX_VER_MIN(2,4,10)
+ if (rc == 0 && datasync)
+ rc = fsync_inode_data_buffers(inode);
+# endif
+
+# endif /* < 2.5.12 */
+ return rc;
+# endif
+}
+#endif /* < 2.6.35 */
+
+
+#if RTLNX_VER_MIN(4,5,0)
+/**
+ * Copy a datablock from one file to another on the host side.
+ */
+static ssize_t vbsf_reg_copy_file_range(struct file *pFileSrc, loff_t offSrc, struct file *pFileDst, loff_t offDst,
+ size_t cbRange, unsigned int fFlags)
+{
+ ssize_t cbRet;
+ if (g_uSfLastFunction >= SHFL_FN_COPY_FILE_PART) {
+ struct inode *pInodeSrc = pFileSrc->f_inode;
+ struct vbsf_inode_info *pInodeInfoSrc = VBSF_GET_INODE_INFO(pInodeSrc);
+ struct vbsf_super_info *pSuperInfoSrc = VBSF_GET_SUPER_INFO(pInodeSrc->i_sb);
+ struct vbsf_reg_info *pFileInfoSrc = (struct vbsf_reg_info *)pFileSrc->private_data;
+ struct inode *pInodeDst = pInodeSrc;
+ struct vbsf_inode_info *pInodeInfoDst = VBSF_GET_INODE_INFO(pInodeDst);
+ struct vbsf_super_info *pSuperInfoDst = VBSF_GET_SUPER_INFO(pInodeDst->i_sb);
+ struct vbsf_reg_info *pFileInfoDst = (struct vbsf_reg_info *)pFileDst->private_data;
+ VBOXSFCOPYFILEPARTREQ *pReq;
+
+ /*
+ * Some extra validation.
+ */
+ AssertPtrReturn(pInodeInfoSrc, -EOPNOTSUPP);
+ Assert(pInodeInfoSrc->u32Magic == SF_INODE_INFO_MAGIC);
+ AssertPtrReturn(pInodeInfoDst, -EOPNOTSUPP);
+ Assert(pInodeInfoDst->u32Magic == SF_INODE_INFO_MAGIC);
+
+# if RTLNX_VER_MAX(4,11,0)
+ if (!S_ISREG(pInodeSrc->i_mode) || !S_ISREG(pInodeDst->i_mode))
+ return S_ISDIR(pInodeSrc->i_mode) || S_ISDIR(pInodeDst->i_mode) ? -EISDIR : -EINVAL;
+# endif
+
+ /*
+ * Allocate the request and issue it.
+ */
+ pReq = (VBOXSFCOPYFILEPARTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ int vrc = VbglR0SfHostReqCopyFilePart(pSuperInfoSrc->map.root, pFileInfoSrc->Handle.hHost, offSrc,
+ pSuperInfoDst->map.root, pFileInfoDst->Handle.hHost, offDst,
+ cbRange, 0 /*fFlags*/, pReq);
+ if (RT_SUCCESS(vrc))
+ cbRet = pReq->Parms.cb64ToCopy.u.value64;
+ else if (vrc == VERR_NOT_IMPLEMENTED)
+ cbRet = -EOPNOTSUPP;
+ else
+ cbRet = -RTErrConvertToErrno(vrc);
+
+ VbglR0PhysHeapFree(pReq);
+ } else
+ cbRet = -ENOMEM;
+ } else {
+ cbRet = -EOPNOTSUPP;
+ }
+ SFLOGFLOW(("vbsf_reg_copy_file_range: returns %zd\n", cbRet));
+ return cbRet;
+}
+#endif /* > 4.5 */
+
+
+#ifdef SFLOG_ENABLED
+/*
+ * This is just for logging page faults and such.
+ */
+
+/** Pointer to the ops generic_file_mmap returns the first time it's called. */
+static struct vm_operations_struct const *g_pGenericFileVmOps = NULL;
+/** Merge of g_LoggingVmOpsTemplate and g_pGenericFileVmOps. */
+static struct vm_operations_struct g_LoggingVmOps;
+
+
+/* Generic page fault callback: */
+# if RTLNX_VER_MIN(4,11,0)
+static vm_fault_t vbsf_vmlog_fault(struct vm_fault *vmf)
+{
+ vm_fault_t rc;
+ SFLOGFLOW(("vbsf_vmlog_fault: vmf=%p flags=%#x addr=%p\n", vmf, vmf->flags, vmf->address));
+ rc = g_pGenericFileVmOps->fault(vmf);
+ SFLOGFLOW(("vbsf_vmlog_fault: returns %d\n", rc));
+ return rc;
+}
+# elif RTLNX_VER_MIN(2,6,23)
+static int vbsf_vmlog_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ int rc;
+# if RTLNX_VER_MIN(4,10,0)
+ SFLOGFLOW(("vbsf_vmlog_fault: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->address));
+# else
+ SFLOGFLOW(("vbsf_vmlog_fault: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->virtual_address));
+# endif
+ rc = g_pGenericFileVmOps->fault(vma, vmf);
+ SFLOGFLOW(("vbsf_vmlog_fault: returns %d\n", rc));
+ return rc;
+}
+# endif
+
+
+/* Special/generic page fault handler: */
+# if RTLNX_VER_MIN(2,6,26)
+# elif RTLNX_VER_MIN(2,6,1)
+static struct page *vbsf_vmlog_nopage(struct vm_area_struct *vma, unsigned long address, int *type)
+{
+ struct page *page;
+ SFLOGFLOW(("vbsf_vmlog_nopage: vma=%p address=%p type=%p:{%#x}\n", vma, address, type, type ? *type : 0));
+ page = g_pGenericFileVmOps->nopage(vma, address, type);
+ SFLOGFLOW(("vbsf_vmlog_nopage: returns %p\n", page));
+ return page;
+}
+# else
+static struct page *vbsf_vmlog_nopage(struct vm_area_struct *vma, unsigned long address, int write_access_or_unused)
+{
+ struct page *page;
+ SFLOGFLOW(("vbsf_vmlog_nopage: vma=%p address=%p wau=%d\n", vma, address, write_access_or_unused));
+ page = g_pGenericFileVmOps->nopage(vma, address, write_access_or_unused);
+ SFLOGFLOW(("vbsf_vmlog_nopage: returns %p\n", page));
+ return page;
+}
+# endif /* < 2.6.26 */
+
+
+/* Special page fault callback for making something writable: */
+# if RTLNX_VER_MIN(4,11,0)
+static vm_fault_t vbsf_vmlog_page_mkwrite(struct vm_fault *vmf)
+{
+ vm_fault_t rc;
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vmf=%p flags=%#x addr=%p\n", vmf, vmf->flags, vmf->address));
+ rc = g_pGenericFileVmOps->page_mkwrite(vmf);
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: returns %d\n", rc));
+ return rc;
+}
+# elif RTLNX_VER_MIN(2,6,30)
+static int vbsf_vmlog_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ int rc;
+# if RTLNX_VER_MIN(4,10,0)
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->address));
+# else
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vma=%p vmf=%p flags=%#x addr=%p\n", vma, vmf, vmf->flags, vmf->virtual_address));
+# endif
+ rc = g_pGenericFileVmOps->page_mkwrite(vma, vmf);
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: returns %d\n", rc));
+ return rc;
+}
+# elif RTLNX_VER_MIN(2,6,18)
+static int vbsf_vmlog_page_mkwrite(struct vm_area_struct *vma, struct page *page)
+{
+ int rc;
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: vma=%p page=%p\n", vma, page));
+ rc = g_pGenericFileVmOps->page_mkwrite(vma, page);
+ SFLOGFLOW(("vbsf_vmlog_page_mkwrite: returns %d\n", rc));
+ return rc;
+}
+# endif
+
+
+/* Special page fault callback for mapping pages: */
+# if RTLNX_VER_MIN(5,12,0)
+static vm_fault_t vbsf_vmlog_map_pages(struct vm_fault *vmf, pgoff_t start, pgoff_t end)
+{
+ vm_fault_t rc;
+ SFLOGFLOW(("vbsf_vmlog_map_pages: vmf=%p (flags=%#x addr=%p) start=%p end=%p\n", vmf, vmf->flags, vmf->address, start, end));
+ rc = g_pGenericFileVmOps->map_pages(vmf, start, end);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+ return rc;
+}
+# elif RTLNX_VER_MIN(4,10,0)
+static void vbsf_vmlog_map_pages(struct vm_fault *vmf, pgoff_t start, pgoff_t end)
+{
+ SFLOGFLOW(("vbsf_vmlog_map_pages: vmf=%p (flags=%#x addr=%p) start=%p end=%p\n", vmf, vmf->flags, vmf->address, start, end));
+ g_pGenericFileVmOps->map_pages(vmf, start, end);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+}
+# elif RTLNX_VER_MIN(4,8,0)
+static void vbsf_vmlog_map_pages(struct fault_env *fenv, pgoff_t start, pgoff_t end)
+{
+ SFLOGFLOW(("vbsf_vmlog_map_pages: fenv=%p (flags=%#x addr=%p) start=%p end=%p\n", fenv, fenv->flags, fenv->address, start, end));
+ g_pGenericFileVmOps->map_pages(fenv, start, end);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+}
+# elif RTLNX_VER_MIN(3,15,0)
+static void vbsf_vmlog_map_pages(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ SFLOGFLOW(("vbsf_vmlog_map_pages: vma=%p vmf=%p (flags=%#x addr=%p)\n", vma, vmf, vmf->flags, vmf->virtual_address));
+ g_pGenericFileVmOps->map_pages(vma, vmf);
+ SFLOGFLOW(("vbsf_vmlog_map_pages: returns\n"));
+}
+# endif
+
+
+/** Overload template. */
+static struct vm_operations_struct const g_LoggingVmOpsTemplate = {
+# if RTLNX_VER_MIN(2,6,23)
+ .fault = vbsf_vmlog_fault,
+# endif
+# if RTLNX_VER_MAX(2,6,26)
+ .nopage = vbsf_vmlog_nopage,
+# endif
+# if RTLNX_VER_MIN(2,6,18)
+ .page_mkwrite = vbsf_vmlog_page_mkwrite,
+# endif
+# if RTLNX_VER_MIN(3,15,0)
+ .map_pages = vbsf_vmlog_map_pages,
+# endif
+};
+
+/** file_operations::mmap wrapper for logging purposes. */
+extern int vbsf_reg_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ int rc;
+ SFLOGFLOW(("vbsf_reg_mmap: file=%p vma=%p\n", file, vma));
+ rc = generic_file_mmap(file, vma);
+ if (rc == 0) {
+ /* Merge the ops and template the first time thru (there's a race here). */
+ if (g_pGenericFileVmOps == NULL) {
+ uintptr_t const *puSrc1 = (uintptr_t *)vma->vm_ops;
+ uintptr_t const *puSrc2 = (uintptr_t *)&g_LoggingVmOpsTemplate;
+ uintptr_t volatile *puDst = (uintptr_t *)&g_LoggingVmOps;
+ size_t cbLeft = sizeof(g_LoggingVmOps) / sizeof(*puDst);
+ while (cbLeft-- > 0) {
+ *puDst = *puSrc2 && *puSrc1 ? *puSrc2 : *puSrc1;
+ puSrc1++;
+ puSrc2++;
+ puDst++;
+ }
+ g_pGenericFileVmOps = vma->vm_ops;
+ vma->vm_ops = &g_LoggingVmOps;
+ } else if (g_pGenericFileVmOps == vma->vm_ops)
+ vma->vm_ops = &g_LoggingVmOps;
+ else
+ SFLOGFLOW(("vbsf_reg_mmap: Warning: vm_ops=%p, expected %p!\n", vma->vm_ops, g_pGenericFileVmOps));
+ }
+ SFLOGFLOW(("vbsf_reg_mmap: returns %d\n", rc));
+ return rc;
+}
+
+#endif /* SFLOG_ENABLED */
+
+
+/**
+ * File operations for regular files.
+ *
+ * Note on splice_read/splice_write/sendfile:
+ * - Splice was introduced in 2.6.17. The generic_file_splice_read/write
+ * methods go thru the page cache, which is undesirable and is why we
+ * need to cook our own versions of the code as long as we cannot track
+ * host-side writes and correctly invalidate the guest page-cache.
+ * - Sendfile reimplemented using splice in 2.6.23.
+ * - The default_file_splice_read/write no-page-cache fallback functions,
+ * were introduced in 2.6.31. The write one work in page units.
+ * - Since linux 3.16 there is iter_file_splice_write that uses iter_write.
+ * - Since linux 4.9 the generic_file_splice_read function started using
+ * read_iter.
+ */
+struct file_operations vbsf_reg_fops = {
+ .open = vbsf_reg_open,
+#if RTLNX_VER_MAX(5,10,0) /* No regular .read/.write for 5.10, only .read_iter/.write_iter or in-kernel reads/writes fail. */
+ .read = vbsf_reg_read,
+ .write = vbsf_reg_write,
+#endif
+#if RTLNX_VER_MIN(3,16,0)
+ .read_iter = vbsf_reg_read_iter,
+ .write_iter = vbsf_reg_write_iter,
+#elif RTLNX_VER_MIN(2,6,19)
+ .aio_read = vbsf_reg_aio_read,
+ .aio_write = vbsf_reg_aio_write,
+#endif
+ .release = vbsf_reg_release,
+#ifdef SFLOG_ENABLED
+ .mmap = vbsf_reg_mmap,
+#else
+ .mmap = generic_file_mmap,
+#endif
+#if RTLNX_VER_RANGE(2,6,17, 2,6,31)
+ .splice_read = vbsf_splice_read,
+#endif
+#if RTLNX_VER_MIN(3,16,0)
+ .splice_write = iter_file_splice_write,
+#elif RTLNX_VER_MIN(2,6,17)
+ .splice_write = vbsf_splice_write,
+#endif
+#if RTLNX_VER_RANGE(2,5,30, 2,6,23)
+ .sendfile = vbsf_reg_sendfile,
+#endif
+ .llseek = vbsf_reg_llseek,
+ .fsync = vbsf_reg_fsync,
+#if RTLNX_VER_MIN(4,5,0)
+ .copy_file_range = vbsf_reg_copy_file_range,
+#endif
+};
+
+
+/**
+ * Inodes operations for regular files.
+ */
+struct inode_operations vbsf_reg_iops = {
+#if RTLNX_VER_MIN(2,5,18)
+ .getattr = vbsf_inode_getattr,
+#else
+ .revalidate = vbsf_inode_revalidate,
+#endif
+ .setattr = vbsf_inode_setattr,
+};
+
+
+
+/*********************************************************************************************************************************
+* Address Space Operations on Regular Files (for mmap, sendfile, direct I/O) *
+*********************************************************************************************************************************/
+
+/**
+ * Used to read the content of a page into the page cache.
+ *
+ * Needed for mmap and reads+writes when the file is mmapped in a
+ * shared+writeable fashion.
+ */
+#if RTLNX_VER_MIN(5,19,0)|| RTLNX_RHEL_RANGE(9,3, 9,99)
+static int vbsf_read_folio(struct file *file, struct folio *folio)
+{
+ struct page *page = &folio->page;
+#else
+static int vbsf_readpage(struct file *file, struct page *page)
+{
+#endif
+ struct inode *inode = VBSF_GET_F_DENTRY(file)->d_inode;
+ int err;
+
+ SFLOGFLOW(("vbsf_readpage: inode=%p file=%p page=%p off=%#llx\n", inode, file, page, (uint64_t)page->index << PAGE_SHIFT));
+ Assert(PageLocked(page));
+
+ if (PageUptodate(page)) {
+ unlock_page(page);
+ return 0;
+ }
+
+ if (!is_bad_inode(inode)) {
+ VBOXSFREADPGLSTREQ *pReq = (VBOXSFREADPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ struct vbsf_reg_info *sf_r = file->private_data;
+ uint32_t cbRead;
+ int vrc;
+
+ pReq->PgLst.offFirstPage = 0;
+ pReq->PgLst.aPages[0] = page_to_phys(page);
+ vrc = VbglR0SfHostReqReadPgLst(pSuperInfo->map.root,
+ pReq,
+ sf_r->Handle.hHost,
+ (uint64_t)page->index << PAGE_SHIFT,
+ PAGE_SIZE,
+ 1 /*cPages*/);
+
+ cbRead = pReq->Parms.cb32Read.u.value32;
+ AssertStmt(cbRead <= PAGE_SIZE, cbRead = PAGE_SIZE);
+ VbglR0PhysHeapFree(pReq);
+
+ if (RT_SUCCESS(vrc)) {
+ if (cbRead == PAGE_SIZE) {
+ /* likely */
+ } else {
+ uint8_t *pbMapped = (uint8_t *)kmap(page);
+ RT_BZERO(&pbMapped[cbRead], PAGE_SIZE - cbRead);
+ kunmap(page);
+ /** @todo truncate the inode file size? */
+ }
+
+ flush_dcache_page(page);
+ SetPageUptodate(page);
+ unlock_page(page);
+ return 0;
+ }
+ err = -RTErrConvertToErrno(vrc);
+ } else
+ err = -ENOMEM;
+ } else
+ err = -EIO;
+ SetPageError(page);
+ unlock_page(page);
+ return err;
+}
+
+
+/**
+ * Used to write out the content of a dirty page cache page to the host file.
+ *
+ * Needed for mmap and writes when the file is mmapped in a shared+writeable
+ * fashion.
+ */
+#if RTLNX_VER_MIN(2,5,52)
+static int vbsf_writepage(struct page *page, struct writeback_control *wbc)
+#else
+static int vbsf_writepage(struct page *page)
+#endif
+{
+ struct address_space *mapping = page->mapping;
+ struct inode *inode = mapping->host;
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(inode);
+ struct vbsf_handle *pHandle = vbsf_handle_find(sf_i, VBSF_HANDLE_F_WRITE, VBSF_HANDLE_F_APPEND);
+ int err;
+
+ SFLOGFLOW(("vbsf_writepage: inode=%p page=%p off=%#llx pHandle=%p (%#llx)\n",
+ inode, page, (uint64_t)page->index << PAGE_SHIFT, pHandle, pHandle ? pHandle->hHost : 0));
+
+ if (pHandle) {
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(inode->i_sb);
+ VBOXSFWRITEPGLSTREQ *pReq = (VBOXSFWRITEPGLSTREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ uint64_t const cbFile = i_size_read(inode);
+ uint64_t const offInFile = (uint64_t)page->index << PAGE_SHIFT;
+ uint32_t const cbToWrite = page->index != (cbFile >> PAGE_SHIFT) ? PAGE_SIZE
+ : (uint32_t)cbFile & (uint32_t)PAGE_OFFSET_MASK;
+ int vrc;
+
+ pReq->PgLst.offFirstPage = 0;
+ pReq->PgLst.aPages[0] = page_to_phys(page);
+ vrc = VbglR0SfHostReqWritePgLst(pSuperInfo->map.root,
+ pReq,
+ pHandle->hHost,
+ offInFile,
+ cbToWrite,
+ 1 /*cPages*/);
+ sf_i->ModificationTimeAtOurLastWrite = sf_i->ModificationTime;
+ AssertMsgStmt(pReq->Parms.cb32Write.u.value32 == cbToWrite || RT_FAILURE(vrc), /* lazy bird */
+ ("%#x vs %#x\n", pReq->Parms.cb32Write, cbToWrite),
+ vrc = VERR_WRITE_ERROR);
+ VbglR0PhysHeapFree(pReq);
+
+ if (RT_SUCCESS(vrc)) {
+ /* Update the inode if we've extended the file. */
+ /** @todo is this necessary given the cbToWrite calc above? */
+ uint64_t const offEndOfWrite = offInFile + cbToWrite;
+ if ( offEndOfWrite > cbFile
+ && offEndOfWrite > i_size_read(inode))
+ i_size_write(inode, offEndOfWrite);
+
+ /* Update and unlock the page. */
+ if (PageError(page))
+ ClearPageError(page);
+ SetPageUptodate(page);
+ unlock_page(page);
+
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_writepage");
+ return 0;
+ }
+
+ /*
+ * We failed.
+ */
+ err = -EIO;
+ } else
+ err = -ENOMEM;
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_writepage");
+ } else {
+ /** @todo we could re-open the file here and deal with this... */
+ static uint64_t volatile s_cCalls = 0;
+ if (s_cCalls++ < 16)
+ printk("vbsf_writepage: no writable handle for %s..\n", sf_i->path->String.ach);
+ err = -EIO;
+ }
+ SetPageError(page);
+ unlock_page(page);
+ return err;
+}
+
+
+#if RTLNX_VER_MIN(2,6,24)
+/**
+ * Called when writing thru the page cache (which we shouldn't be doing).
+ */
+static inline void vbsf_write_begin_warn(loff_t pos, unsigned len, unsigned flags)
+{
+ /** @todo r=bird: We shouldn't ever get here, should we? Because we don't use
+ * the page cache for any writes AFAIK. We could just as well use
+ * simple_write_begin & simple_write_end here if we think we really
+ * need to have non-NULL function pointers in the table... */
+ static uint64_t volatile s_cCalls = 0;
+ if (s_cCalls++ < 16) {
+ printk("vboxsf: Unexpected call to vbsf_write_begin(pos=%#llx len=%#x flags=%#x)! Please report.\n",
+ (unsigned long long)pos, len, flags);
+ RTLogBackdoorPrintf("vboxsf: Unexpected call to vbsf_write_begin(pos=%#llx len=%#x flags=%#x)! Please report.\n",
+ (unsigned long long)pos, len, flags);
+# ifdef WARN_ON
+ WARN_ON(1);
+# endif
+ }
+}
+
+# if RTLNX_VER_MIN(5,19,0) || RTLNX_RHEL_RANGE(9,3, 9,99)
+int vbsf_write_begin(struct file *file, struct address_space *mapping, loff_t pos,
+ unsigned len, struct page **pagep, void **fsdata)
+{
+ vbsf_write_begin_warn(pos, len, 0);
+ return simple_write_begin(file, mapping, pos, len, pagep, fsdata);
+}
+# else
+int vbsf_write_begin(struct file *file, struct address_space *mapping, loff_t pos,
+ unsigned len, unsigned flags, struct page **pagep, void **fsdata)
+{
+ vbsf_write_begin_warn(pos, len, flags);
+ return simple_write_begin(file, mapping, pos, len, flags, pagep, fsdata);
+}
+# endif
+
+#endif /* KERNEL_VERSION >= 2.6.24 */
+
+#if RTLNX_VER_MIN(5,14,0)
+/**
+ * Companion to vbsf_write_begin (i.e. shouldn't be called).
+ */
+static int vbsf_write_end(struct file *file, struct address_space *mapping,
+ loff_t pos, unsigned int len, unsigned int copied,
+ struct page *page, void *fsdata)
+{
+ static uint64_t volatile s_cCalls = 0;
+ if (s_cCalls++ < 16)
+ {
+ printk("vboxsf: Unexpected call to vbsf_write_end(pos=%#llx len=%#x)! Please report.\n",
+ (unsigned long long)pos, len);
+ RTLogBackdoorPrintf("vboxsf: Unexpected call to vbsf_write_end(pos=%#llx len=%#x)! Please report.\n",
+ (unsigned long long)pos, len);
+# ifdef WARN_ON
+ WARN_ON(1);
+# endif
+ }
+ return -ENOTSUPP;
+}
+#endif /* KERNEL_VERSION >= 5.14.0 */
+
+
+#if RTLNX_VER_MIN(2,4,10)
+
+# ifdef VBOX_UEK
+# undef iov_iter /* HACK ALERT! Don't put anything needing vbsf_iov_iter after this fun! */
+# endif
+
+/**
+ * This is needed to make open accept O_DIRECT as well as dealing with direct
+ * I/O requests if we don't intercept them earlier.
+ */
+# if RTLNX_VER_MIN(4, 7, 0) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,73, 4,4,74) /** @todo Figure out when exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,75, 4,4,90) /** @todo Figure out when exactly. */) \
+ || (defined(CONFIG_SUSE_KERNEL) && RTLNX_VER_RANGE(4,4,92, 4,5,0) /** @todo Figure out when exactly. */)
+static ssize_t vbsf_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
+# elif RTLNX_VER_MIN(4, 1, 0)
+static ssize_t vbsf_direct_IO(struct kiocb *iocb, struct iov_iter *iter, loff_t offset)
+# elif RTLNX_VER_MIN(3, 16, 0) || defined(VBOX_UEK)
+static ssize_t vbsf_direct_IO(int rw, struct kiocb *iocb, struct iov_iter *iter, loff_t offset)
+# elif RTLNX_VER_MIN(2, 6, 6)
+static ssize_t vbsf_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 55)
+static int vbsf_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 41)
+static int vbsf_direct_IO(int rw, struct file *file, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 35)
+static int vbsf_direct_IO(int rw, struct inode *inode, const struct iovec *iov, loff_t offset, unsigned long nr_segs)
+# elif RTLNX_VER_MIN(2, 5, 26)
+static int vbsf_direct_IO(int rw, struct inode *inode, char *buf, loff_t offset, size_t count)
+# elif LINUX_VERSION_CODE == KERNEL_VERSION(2, 4, 21) && defined(I_NEW) /* RHEL3 Frankenkernel. */
+static int vbsf_direct_IO(int rw, struct file *file, struct kiobuf *buf, unsigned long whatever1, int whatever2)
+# else
+static int vbsf_direct_IO(int rw, struct inode *inode, struct kiobuf *buf, unsigned long whatever1, int whatever2)
+# endif
+{
+ TRACE();
+ return -EINVAL;
+}
+
+#endif
+
+/**
+ * Address space (for the page cache) operations for regular files.
+ *
+ * @todo the FsPerf touch/flush (mmap) test fails on 4.4.0 (ubuntu 16.04 lts).
+ */
+struct address_space_operations vbsf_reg_aops = {
+#if RTLNX_VER_MIN(5,19,0) || RTLNX_RHEL_RANGE(9,3, 9,99)
+ .read_folio = vbsf_read_folio,
+#else
+ .readpage = vbsf_readpage,
+#endif
+ .writepage = vbsf_writepage,
+ /** @todo Need .writepages if we want msync performance... */
+#if RTLNX_VER_MIN(5,18,0) || RTLNX_RHEL_RANGE(9,2, 9,99)
+ .dirty_folio = filemap_dirty_folio,
+#elif RTLNX_VER_MIN(2,5,12)
+ .set_page_dirty = __set_page_dirty_buffers,
+#endif
+#if RTLNX_VER_MIN(5,14,0)
+ .write_begin = vbsf_write_begin,
+ .write_end = vbsf_write_end,
+#elif RTLNX_VER_MIN(2,6,24)
+ .write_begin = vbsf_write_begin,
+ .write_end = simple_write_end,
+#elif RTLNX_VER_MIN(2,5,45)
+ .prepare_write = simple_prepare_write,
+ .commit_write = simple_commit_write,
+#endif
+#if RTLNX_VER_MIN(2,4,10)
+ .direct_IO = vbsf_direct_IO,
+#endif
+};
diff --git a/src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c b/src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c
new file mode 100644
index 00000000..9af2b4e4
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/testcase/tstmmap.c
@@ -0,0 +1,126 @@
+/* $Id: tstmmap.c $ */
+/** @file
+ * vboxsf - Simple writable mmap testcase.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+
+int main(int argc, char **argv)
+{
+ uint8_t abBuf[4096];
+ int fd;
+ size_t cErrors = 0;
+ size_t cbFile;
+ size_t offFile;
+ uint8_t *pbMapping;
+ const char *pszFile = "tstmmap-file1";
+ if (argc > 1)
+ pszFile = argv[1];
+
+ fd = open(pszFile, O_CREAT | O_TRUNC | O_RDWR, 0660);
+ if (fd < 0)
+ {
+ fprintf(stderr, "error creating file: %s\n", pszFile);
+ return 1;
+ }
+
+ /* write 64 KB to the file: */
+ memset(abBuf, 0xf6, sizeof(abBuf));
+ for (cbFile = 0; cbFile < 0x10000; cbFile += sizeof(abBuf))
+ if (write(fd, abBuf, sizeof(abBuf)) != sizeof(abBuf))
+ {
+ fprintf(stderr, "error writing file: %s\n", pszFile);
+ return 1;
+ }
+ fsync(fd);
+
+ /* Map the file: */
+ pbMapping = (uint8_t *)mmap(NULL, cbFile, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (pbMapping == (void *)-1)
+ {
+ fprintf(stderr, "error mapping file: %s\n", pszFile);
+ return 1;
+ }
+
+ /* Modify the mapping and sync it: */
+ memset(pbMapping, 0xf7, cbFile);
+ if (msync(pbMapping, cbFile, MS_SYNC) != 0)
+ {
+ fprintf(stderr, "error msync'ing file: %s\n", pszFile);
+ return 1;
+ }
+
+ /* Unmap and close it: */
+ if (munmap(pbMapping, cbFile) != 0)
+ fprintf(stderr, "error munmap'ing file: %s\n", pszFile);
+ close(fd);
+
+ /*
+ * Open it again and check the content.
+ */
+ fd = open(pszFile, O_RDWR, 0);
+ if (fd < 0)
+ {
+ fprintf(stderr, "error reopening file: %s\n", pszFile);
+ return 1;
+ }
+
+ while (offFile < cbFile && cErrors < 42)
+ {
+ size_t offBuf;
+ ssize_t cbRead = read(fd, abBuf, sizeof(abBuf));
+ if (cbRead != (ssize_t)sizeof(abBuf))
+ {
+ fprintf(stderr, "error reading file: %zd, off %#zx (%s)\n", cbRead, offFile, pszFile);
+ return 1;
+ }
+
+ for (offBuf = 0; offBuf < sizeof(abBuf); offBuf++)
+ if (abBuf[offBuf] != 0xf7)
+ {
+ fprintf(stderr, "mismatch at %#zx: %#x, expected %#x\n", offFile + offBuf, abBuf[offBuf], 0xf7);
+ cErrors++;
+ if (cErrors > 42)
+ break;
+ }
+
+ offFile += sizeof(abBuf);
+ }
+
+ close(fd);
+
+ return cErrors == 0 ? 0 : 1;
+}
+
diff --git a/src/VBox/Additions/linux/sharedfolders/utils.c b/src/VBox/Additions/linux/sharedfolders/utils.c
new file mode 100644
index 00000000..b75997a8
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/utils.c
@@ -0,0 +1,1288 @@
+/* $Id: utils.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, utility functions.
+ *
+ * Utility functions (mainly conversion from/to VirtualBox/Linux data structures).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "vfsmod.h"
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <linux/vfs.h>
+
+
+int vbsf_nlscpy(struct vbsf_super_info *pSuperInfo, char *name, size_t name_bound_len,
+ const unsigned char *utf8_name, size_t utf8_len)
+{
+ Assert(name_bound_len > 1);
+ Assert(RTStrNLen(utf8_name, utf8_len) == utf8_len);
+
+ if (pSuperInfo->nls) {
+ const char *in = utf8_name;
+ size_t in_bound_len = utf8_len;
+ char *out = name;
+ size_t out_bound_len = name_bound_len - 1;
+
+ while (in_bound_len) {
+#if RTLNX_VER_MIN(2,6,31)
+ unicode_t uni;
+ int cbInEnc = utf8_to_utf32(in, in_bound_len, &uni);
+#else
+ linux_wchar_t uni;
+ int cbInEnc = utf8_mbtowc(&uni, in, in_bound_len);
+#endif
+ if (cbInEnc >= 0) {
+ int cbOutEnc = pSuperInfo->nls->uni2char(uni, out, out_bound_len);
+ if (cbOutEnc >= 0) {
+ /*SFLOG3(("vbsf_nlscpy: cbOutEnc=%d cbInEnc=%d uni=%#x in_bound_len=%u\n", cbOutEnc, cbInEnc, uni, in_bound_len));*/
+ out += cbOutEnc;
+ out_bound_len -= cbOutEnc;
+
+ in += cbInEnc;
+ in_bound_len -= cbInEnc;
+ } else {
+ SFLOG(("vbsf_nlscpy: nls->uni2char failed with %d on %#x (pos %u in '%s'), out_bound_len=%u\n",
+ cbOutEnc, uni, in - (const char *)utf8_name, (const char *)utf8_name, (unsigned)out_bound_len));
+ return cbOutEnc;
+ }
+ } else {
+ SFLOG(("vbsf_nlscpy: utf8_to_utf32/utf8_mbtowc failed with %d on %x (pos %u in '%s'), in_bound_len=%u!\n",
+ cbInEnc, *in, in - (const char *)utf8_name, (const char *)utf8_name, (unsigned)in_bound_len));
+ return -EINVAL;
+ }
+ }
+
+ *out = '\0';
+ } else {
+ if (utf8_len + 1 > name_bound_len)
+ return -ENAMETOOLONG;
+
+ memcpy(name, utf8_name, utf8_len + 1);
+ }
+ return 0;
+}
+
+
+/**
+ * Converts the given NLS string to a host one, kmalloc'ing
+ * the output buffer (use kfree on result).
+ */
+int vbsf_nls_to_shflstring(struct vbsf_super_info *pSuperInfo, const char *pszNls, PSHFLSTRING *ppString)
+{
+ int rc;
+ size_t const cchNls = strlen(pszNls);
+ PSHFLSTRING pString = NULL;
+ if (pSuperInfo->nls) {
+ /*
+ * NLS -> UTF-8 w/ SHLF string header.
+ */
+ /* Calc length first: */
+ size_t cchUtf8 = 0;
+ size_t offNls = 0;
+ while (offNls < cchNls) {
+ linux_wchar_t uc; /* Note! We renamed the type due to clashes. */
+ int const cbNlsCodepoint = pSuperInfo->nls->char2uni(&pszNls[offNls], cchNls - offNls, &uc);
+ if (cbNlsCodepoint >= 0) {
+ char achTmp[16];
+#if RTLNX_VER_MIN(2,6,31)
+ int cbUtf8Codepoint = utf32_to_utf8(uc, achTmp, sizeof(achTmp));
+#else
+ int cbUtf8Codepoint = utf8_wctomb(achTmp, uc, sizeof(achTmp));
+#endif
+ if (cbUtf8Codepoint > 0) {
+ cchUtf8 += cbUtf8Codepoint;
+ offNls += cbNlsCodepoint;
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->uni2char(%#x) failed: %d\n", uc, cbUtf8Codepoint));
+ return -EINVAL;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->char2uni(%.*Rhxs) failed: %d\n",
+ RT_MIN(8, cchNls - offNls), &pszNls[offNls], cbNlsCodepoint));
+ return -EINVAL;
+ }
+ }
+ if (cchUtf8 + 1 < _64K) {
+ /* Allocate: */
+ pString = (PSHFLSTRING)kmalloc(SHFLSTRING_HEADER_SIZE + cchUtf8 + 1, GFP_KERNEL);
+ if (pString) {
+ char *pchDst = pString->String.ach;
+ pString->u16Length = (uint16_t)cchUtf8;
+ pString->u16Size = (uint16_t)(cchUtf8 + 1);
+
+ /* Do the conversion (cchUtf8 is counted down): */
+ rc = 0;
+ offNls = 0;
+ while (offNls < cchNls) {
+ linux_wchar_t uc; /* Note! We renamed the type due to clashes. */
+ int const cbNlsCodepoint = pSuperInfo->nls->char2uni(&pszNls[offNls], cchNls - offNls, &uc);
+ if (cbNlsCodepoint >= 0) {
+#if RTLNX_VER_MIN(2,6,31)
+ int cbUtf8Codepoint = utf32_to_utf8(uc, pchDst, cchUtf8);
+#else
+ int cbUtf8Codepoint = utf8_wctomb(pchDst, uc, cchUtf8);
+#endif
+ if (cbUtf8Codepoint > 0) {
+ AssertBreakStmt(cbUtf8Codepoint <= cchUtf8, rc = -EINVAL);
+ cchUtf8 -= cbUtf8Codepoint;
+ pchDst += cbUtf8Codepoint;
+ offNls += cbNlsCodepoint;
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->uni2char(%#x) failed! %d, cchUtf8=%zu\n",
+ uc, cbUtf8Codepoint, cchUtf8));
+ rc = -EINVAL;
+ break;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: nls->char2uni(%.*Rhxs) failed! %d\n",
+ RT_MIN(8, cchNls - offNls), &pszNls[offNls], cbNlsCodepoint));
+ rc = -EINVAL;
+ break;
+ }
+ }
+ if (rc == 0) {
+ /*
+ * Succeeded. Just terminate the string and we're good.
+ */
+ Assert(pchDst - pString->String.ach == pString->u16Length);
+ *pchDst = '\0';
+ } else {
+ kfree(pString);
+ pString = NULL;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: failed to allocate %u bytes\n", SHFLSTRING_HEADER_SIZE + cchUtf8 + 1));
+ rc = -ENOMEM;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: too long: %zu bytes (%zu nls bytes)\n", cchUtf8, cchNls));
+ rc = -ENAMETOOLONG;
+ }
+ } else {
+ /*
+ * UTF-8 -> UTF-8 w/ SHLF string header.
+ */
+ if (cchNls + 1 < _64K) {
+ pString = (PSHFLSTRING)kmalloc(SHFLSTRING_HEADER_SIZE + cchNls + 1, GFP_KERNEL);
+ if (pString) {
+ pString->u16Length = (uint16_t)cchNls;
+ pString->u16Size = (uint16_t)(cchNls + 1);
+ RT_BCOPY_UNFORTIFIED(pString->String.ach, pszNls, cchNls);
+ pString->String.ach[cchNls] = '\0';
+ rc = 0;
+ } else {
+ Log(("vbsf_nls_to_shflstring: failed to allocate %u bytes\n", SHFLSTRING_HEADER_SIZE + cchNls + 1));
+ rc = -ENOMEM;
+ }
+ } else {
+ Log(("vbsf_nls_to_shflstring: too long: %zu bytes\n", cchNls));
+ rc = -ENAMETOOLONG;
+ }
+ }
+ *ppString = pString;
+ return rc;
+}
+
+
+/**
+ * Convert from VBox to linux time.
+ */
+#if RTLNX_VER_MAX(2,6,0)
+DECLINLINE(void) vbsf_time_to_linux(time_t *pLinuxDst, PCRTTIMESPEC pVBoxSrc)
+{
+ int64_t t = RTTimeSpecGetNano(pVBoxSrc);
+ do_div(t, RT_NS_1SEC);
+ *pLinuxDst = t;
+}
+#else /* >= 2.6.0 */
+# if RTLNX_VER_MAX(4,18,0)
+DECLINLINE(void) vbsf_time_to_linux(struct timespec *pLinuxDst, PCRTTIMESPEC pVBoxSrc)
+# else
+DECLINLINE(void) vbsf_time_to_linux(struct timespec64 *pLinuxDst, PCRTTIMESPEC pVBoxSrc)
+# endif
+{
+ int64_t t = RTTimeSpecGetNano(pVBoxSrc);
+ pLinuxDst->tv_nsec = do_div(t, RT_NS_1SEC);
+ pLinuxDst->tv_sec = t;
+}
+#endif /* >= 2.6.0 */
+
+
+/**
+ * Convert from linux to VBox time.
+ */
+#if RTLNX_VER_MAX(2,6,0)
+DECLINLINE(void) vbsf_time_to_vbox(PRTTIMESPEC pVBoxDst, time_t *pLinuxSrc)
+{
+ RTTimeSpecSetNano(pVBoxDst, RT_NS_1SEC_64 * *pLinuxSrc);
+}
+#else /* >= 2.6.0 */
+# if RTLNX_VER_MAX(4,18,0)
+DECLINLINE(void) vbsf_time_to_vbox(PRTTIMESPEC pVBoxDst, struct timespec const *pLinuxSrc)
+# else
+DECLINLINE(void) vbsf_time_to_vbox(PRTTIMESPEC pVBoxDst, struct timespec64 const *pLinuxSrc)
+# endif
+{
+ RTTimeSpecSetNano(pVBoxDst, pLinuxSrc->tv_nsec + pLinuxSrc->tv_sec * (int64_t)RT_NS_1SEC);
+}
+#endif /* >= 2.6.0 */
+
+
+/**
+ * Converts VBox access permissions to Linux ones (mode & 0777).
+ *
+ * @note Currently identical.
+ * @sa sf_access_permissions_to_vbox
+ */
+DECLINLINE(int) sf_access_permissions_to_linux(uint32_t fAttr)
+{
+ /* Access bits should be the same: */
+ AssertCompile(RTFS_UNIX_IRUSR == S_IRUSR);
+ AssertCompile(RTFS_UNIX_IWUSR == S_IWUSR);
+ AssertCompile(RTFS_UNIX_IXUSR == S_IXUSR);
+ AssertCompile(RTFS_UNIX_IRGRP == S_IRGRP);
+ AssertCompile(RTFS_UNIX_IWGRP == S_IWGRP);
+ AssertCompile(RTFS_UNIX_IXGRP == S_IXGRP);
+ AssertCompile(RTFS_UNIX_IROTH == S_IROTH);
+ AssertCompile(RTFS_UNIX_IWOTH == S_IWOTH);
+ AssertCompile(RTFS_UNIX_IXOTH == S_IXOTH);
+
+ return fAttr & RTFS_UNIX_ALL_ACCESS_PERMS;
+}
+
+
+/**
+ * Produce the Linux mode mask, given VBox, mount options and file type.
+ */
+DECLINLINE(int) sf_file_mode_to_linux(uint32_t fVBoxMode, int fFixedMode, int fClearMask, int fType)
+{
+ int fLnxMode = sf_access_permissions_to_linux(fVBoxMode);
+ if (fFixedMode != ~0)
+ fLnxMode = fFixedMode & 0777;
+ fLnxMode &= ~fClearMask;
+ fLnxMode |= fType;
+ return fLnxMode;
+}
+
+/**
+ * Update inode timestamps.
+ *
+ * @param pInode Linux inode object.
+ * @param pObjInfo VBox vboxsf object.
+ */
+static void vbsf_update_inode_timestamps(struct inode *pInode, PSHFLFSOBJINFO pObjInfo)
+{
+#if RTLNX_VER_MIN(6,6,0)
+ struct timespec64 ts;
+ vbsf_time_to_linux(&ts, &pObjInfo->ChangeTime);
+ inode_set_ctime_to_ts(pInode, ts);
+#else
+ vbsf_time_to_linux(&pInode->i_atime, &pObjInfo->AccessTime);
+ vbsf_time_to_linux(&pInode->i_ctime, &pObjInfo->ChangeTime);
+ vbsf_time_to_linux(&pInode->i_mtime, &pObjInfo->ModificationTime);
+#endif
+}
+
+/**
+ * Initializes the @a inode attributes based on @a pObjInfo and @a pSuperInfo
+ * options.
+ */
+void vbsf_init_inode(struct inode *inode, struct vbsf_inode_info *sf_i, PSHFLFSOBJINFO pObjInfo,
+ struct vbsf_super_info *pSuperInfo)
+{
+ PCSHFLFSOBJATTR pAttr = &pObjInfo->Attr;
+
+ TRACE();
+
+ sf_i->ts_up_to_date = jiffies;
+ sf_i->force_restat = 0;
+
+ if (RTFS_IS_DIRECTORY(pAttr->fMode)) {
+ inode->i_mode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->dmode, pSuperInfo->dmask, S_IFDIR);
+ inode->i_op = &vbsf_dir_iops;
+ inode->i_fop = &vbsf_dir_fops;
+
+ /* XXX: this probably should be set to the number of entries
+ in the directory plus two (. ..) */
+ set_nlink(inode, 1);
+ }
+ else if (RTFS_IS_SYMLINK(pAttr->fMode)) {
+ /** @todo r=bird: Aren't System V symlinks w/o any mode mask? IIRC there is
+ * no lchmod on Linux. */
+ inode->i_mode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFLNK);
+ inode->i_op = &vbsf_lnk_iops;
+ set_nlink(inode, 1);
+ } else {
+ inode->i_mode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFREG);
+ inode->i_op = &vbsf_reg_iops;
+ inode->i_fop = &vbsf_reg_fops;
+ inode->i_mapping->a_ops = &vbsf_reg_aops;
+#if RTLNX_VER_RANGE(2,5,17, 4,0,0)
+ inode->i_mapping->backing_dev_info = &pSuperInfo->bdi; /* This is needed for mmap. */
+#endif
+ set_nlink(inode, 1);
+ }
+
+#if RTLNX_VER_MIN(3,5,0)
+ inode->i_uid = make_kuid(current_user_ns(), pSuperInfo->uid);
+ inode->i_gid = make_kgid(current_user_ns(), pSuperInfo->gid);
+#else
+ inode->i_uid = pSuperInfo->uid;
+ inode->i_gid = pSuperInfo->gid;
+#endif
+
+ inode->i_size = pObjInfo->cbObject;
+#if RTLNX_VER_MAX(2,6,19) && !defined(KERNEL_FC6)
+ inode->i_blksize = 4096;
+#endif
+#if RTLNX_VER_MIN(2,4,11)
+ inode->i_blkbits = 12;
+#endif
+ /* i_blocks always in units of 512 bytes! */
+ inode->i_blocks = (pObjInfo->cbAllocated + 511) / 512;
+
+ vbsf_update_inode_timestamps(inode, pObjInfo);
+
+ sf_i->BirthTime = pObjInfo->BirthTime;
+ sf_i->ModificationTime = pObjInfo->ModificationTime;
+ RTTimeSpecSetSeconds(&sf_i->ModificationTimeAtOurLastWrite, 0);
+}
+
+
+/**
+ * Update the inode with new object info from the host.
+ *
+ * Called by sf_inode_revalidate() and sf_inode_revalidate_with_handle().
+ */
+void vbsf_update_inode(struct inode *pInode, struct vbsf_inode_info *pInodeInfo, PSHFLFSOBJINFO pObjInfo,
+ struct vbsf_super_info *pSuperInfo, bool fInodeLocked, unsigned fSetAttrs)
+{
+ PCSHFLFSOBJATTR pAttr = &pObjInfo->Attr;
+ int fMode;
+
+ TRACE();
+
+#if RTLNX_VER_MIN(4,5,0)
+ if (!fInodeLocked)
+ inode_lock(pInode);
+#endif
+
+ /*
+ * Calc new mode mask and update it if it changed.
+ */
+ if (RTFS_IS_DIRECTORY(pAttr->fMode))
+ fMode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->dmode, pSuperInfo->dmask, S_IFDIR);
+ else if (RTFS_IS_SYMLINK(pAttr->fMode))
+ /** @todo r=bird: Aren't System V symlinks w/o any mode mask? IIRC there is
+ * no lchmod on Linux. */
+ fMode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFLNK);
+ else
+ fMode = sf_file_mode_to_linux(pAttr->fMode, pSuperInfo->fmode, pSuperInfo->fmask, S_IFREG);
+
+ if (fMode == pInode->i_mode) {
+ /* likely */
+ } else {
+ if ((fMode & S_IFMT) == (pInode->i_mode & S_IFMT))
+ pInode->i_mode = fMode;
+ else {
+ SFLOGFLOW(("vbsf_update_inode: Changed from %o to %o (%s)\n",
+ pInode->i_mode & S_IFMT, fMode & S_IFMT, pInodeInfo->path->String.ach));
+ /** @todo we probably need to be more drastic... */
+ vbsf_init_inode(pInode, pInodeInfo, pObjInfo, pSuperInfo);
+
+#if RTLNX_VER_MIN(4,5,0)
+ if (!fInodeLocked)
+ inode_unlock(pInode);
+#endif
+ return;
+ }
+ }
+
+ /*
+ * Update the sizes.
+ * Note! i_blocks is always in units of 512 bytes!
+ */
+ pInode->i_blocks = (pObjInfo->cbAllocated + 511) / 512;
+ i_size_write(pInode, pObjInfo->cbObject);
+
+ /*
+ * Update the timestamps.
+ */
+ vbsf_update_inode_timestamps(pInode, pObjInfo);
+ pInodeInfo->BirthTime = pObjInfo->BirthTime;
+
+ /*
+ * Mark it as up to date.
+ * Best to do this before we start with any expensive map invalidation.
+ */
+ pInodeInfo->ts_up_to_date = jiffies;
+ pInodeInfo->force_restat = 0;
+
+ /*
+ * If the modification time changed, we may have to invalidate the page
+ * cache pages associated with this inode if we suspect the change was
+ * made by the host. How supicious we are depends on the cache mode.
+ *
+ * Note! The invalidate_inode_pages() call is pretty weak. It will _not_
+ * touch pages that are already mapped into an address space, but it
+ * will help if the file isn't currently mmap'ed or if we're in read
+ * or read/write caching mode.
+ */
+ if (!RTTimeSpecIsEqual(&pInodeInfo->ModificationTime, &pObjInfo->ModificationTime)) {
+ if (RTFS_IS_FILE(pAttr->fMode)) {
+ if (!(fSetAttrs & (ATTR_MTIME | ATTR_SIZE))) {
+ bool fInvalidate;
+ if (pSuperInfo->enmCacheMode == kVbsfCacheMode_None) {
+ fInvalidate = true; /* No-caching: always invalidate. */
+ } else {
+ if (RTTimeSpecIsEqual(&pInodeInfo->ModificationTimeAtOurLastWrite, &pInodeInfo->ModificationTime)) {
+ fInvalidate = false; /* Could be our write, so don't invalidate anything */
+ RTTimeSpecSetSeconds(&pInodeInfo->ModificationTimeAtOurLastWrite, 0);
+ } else {
+ /*RTLogBackdoorPrintf("vbsf_update_inode: Invalidating the mapping %s - %RU64 vs %RU64 vs %RU64 - %#x\n",
+ pInodeInfo->path->String.ach,
+ RTTimeSpecGetNano(&pInodeInfo->ModificationTimeAtOurLastWrite),
+ RTTimeSpecGetNano(&pInodeInfo->ModificationTime),
+ RTTimeSpecGetNano(&pObjInfo->ModificationTime), fSetAttrs);*/
+ fInvalidate = true; /* We haven't modified the file recently, so probably a host update. */
+ }
+ }
+ pInodeInfo->ModificationTime = pObjInfo->ModificationTime;
+
+ if (fInvalidate) {
+ struct address_space *mapping = pInode->i_mapping;
+ if (mapping && mapping->nrpages > 0) {
+ SFLOGFLOW(("vbsf_update_inode: Invalidating the mapping %s (%#x)\n", pInodeInfo->path->String.ach, fSetAttrs));
+#if RTLNX_VER_MIN(2,6,34)
+ invalidate_mapping_pages(mapping, 0, ~(pgoff_t)0);
+#elif RTLNX_VER_MIN(2,5,41)
+ invalidate_inode_pages(mapping);
+#else
+ invalidate_inode_pages(pInode);
+#endif
+ }
+ }
+ } else {
+ RTTimeSpecSetSeconds(&pInodeInfo->ModificationTimeAtOurLastWrite, 0);
+ pInodeInfo->ModificationTime = pObjInfo->ModificationTime;
+ }
+ } else
+ pInodeInfo->ModificationTime = pObjInfo->ModificationTime;
+ }
+
+ /*
+ * Done.
+ */
+#if RTLNX_VER_MIN(4,5,0)
+ if (!fInodeLocked)
+ inode_unlock(pInode);
+#endif
+}
+
+
+/** @note Currently only used for the root directory during (re-)mount. */
+int vbsf_stat(const char *caller, struct vbsf_super_info *pSuperInfo, SHFLSTRING *path, PSHFLFSOBJINFO result, int ok_to_fail)
+{
+ int rc;
+ VBOXSFCREATEREQ *pReq;
+ NOREF(caller);
+
+ TRACE();
+
+ pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + path->u16Size);
+ if (pReq) {
+ RT_ZERO(*pReq);
+ RT_BCOPY_UNFORTIFIED(&pReq->StrPath, path, SHFLSTRING_HEADER_SIZE + path->u16Size);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ LogFunc(("Calling VbglR0SfHostReqCreate on %s\n", path->String.utf8));
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ *result = pReq->CreateParms.Info;
+ rc = 0;
+ } else {
+ if (!ok_to_fail)
+ LogFunc(("VbglR0SfHostReqCreate on %s: file does not exist: %d (caller=%s)\n",
+ path->String.utf8, pReq->CreateParms.Result, caller));
+ rc = -ENOENT;
+ }
+ } else if (rc == VERR_INVALID_NAME) {
+ rc = -ENOENT; /* this can happen for names like 'foo*' on a Windows host */
+ } else {
+ LogFunc(("VbglR0SfHostReqCreate failed on %s: %Rrc (caller=%s)\n", path->String.utf8, rc, caller));
+ rc = -EPROTO;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ else
+ rc = -ENOMEM;
+ return rc;
+}
+
+
+/**
+ * Revalidate an inode, inner worker.
+ *
+ * @sa sf_inode_revalidate()
+ */
+int vbsf_inode_revalidate_worker(struct dentry *dentry, bool fForced, bool fInodeLocked)
+{
+ int rc;
+ struct inode *pInode = dentry ? dentry->d_inode : NULL;
+ if (pInode) {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(pInode);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pInode->i_sb);
+ AssertReturn(sf_i, -EINVAL);
+ AssertReturn(pSuperInfo, -EINVAL);
+
+ /*
+ * Can we get away without any action here?
+ */
+ if ( !fForced
+ && !sf_i->force_restat
+ && jiffies - sf_i->ts_up_to_date < pSuperInfo->cJiffiesInodeTTL)
+ rc = 0;
+ else {
+ /*
+ * No, we have to query the file info from the host.
+ * Try get a handle we can query, any kind of handle will do here.
+ */
+ struct vbsf_handle *pHandle = vbsf_handle_find(sf_i, 0, 0);
+ if (pHandle) {
+ /* Query thru pHandle. */
+ VBOXSFOBJINFOREQ *pReq = (VBOXSFOBJINFOREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ RT_ZERO(*pReq);
+ rc = VbglR0SfHostReqQueryObjInfo(pSuperInfo->map.root, pReq, pHandle->hHost);
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Reset the TTL and copy the info over into the inode structure.
+ */
+ vbsf_update_inode(pInode, sf_i, &pReq->ObjInfo, pSuperInfo, fInodeLocked, 0 /*fSetAttrs*/);
+ } else if (rc == VERR_INVALID_HANDLE) {
+ rc = -ENOENT; /* Restore.*/
+ } else {
+ LogFunc(("VbglR0SfHostReqQueryObjInfo failed on %#RX64: %Rrc\n", pHandle->hHost, rc));
+ rc = -RTErrConvertToErrno(rc);
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_inode_revalidate_worker");
+
+ } else {
+ /* Query via path. */
+ SHFLSTRING *pPath = sf_i->path;
+ VBOXSFCREATEREQ *pReq = (VBOXSFCREATEREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq) + pPath->u16Size);
+ if (pReq) {
+ RT_ZERO(*pReq);
+ RT_BCOPY_UNFORTIFIED(&pReq->StrPath, pPath, SHFLSTRING_HEADER_SIZE + pPath->u16Size);
+ pReq->CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->CreateParms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ rc = VbglR0SfHostReqCreate(pSuperInfo->map.root, pReq);
+ if (RT_SUCCESS(rc)) {
+ if (pReq->CreateParms.Result == SHFL_FILE_EXISTS) {
+ /*
+ * Reset the TTL and copy the info over into the inode structure.
+ */
+ vbsf_update_inode(pInode, sf_i, &pReq->CreateParms.Info, pSuperInfo, fInodeLocked, 0 /*fSetAttrs*/);
+ rc = 0;
+ } else {
+ rc = -ENOENT;
+ }
+ } else if (rc == VERR_INVALID_NAME) {
+ rc = -ENOENT; /* this can happen for names like 'foo*' on a Windows host */
+ } else {
+ LogFunc(("VbglR0SfHostReqCreate failed on %s: %Rrc\n", pPath->String.ach, rc));
+ rc = -EPROTO;
+ }
+ VbglR0PhysHeapFree(pReq);
+ }
+ else
+ rc = -ENOMEM;
+ }
+ }
+ } else {
+ LogFunc(("no dentry(%p) or inode(%p)\n", dentry, pInode));
+ rc = -EINVAL;
+ }
+ return rc;
+}
+
+
+#if RTLNX_VER_MAX(2,5,18)
+/**
+ * Revalidate an inode for 2.4.
+ *
+ * This is called in the stat(), lstat() and readlink() code paths. In the stat
+ * cases the caller will use the result afterwards to produce the stat data.
+ *
+ * @note 2.4.x has a getattr() inode operation too, but it is not used.
+ */
+int vbsf_inode_revalidate(struct dentry *dentry)
+{
+ /*
+ * We pretend the inode is locked here, as 2.4.x does not have inode level locking.
+ */
+ return vbsf_inode_revalidate_worker(dentry, false /*fForced*/, true /*fInodeLocked*/);
+}
+#endif /* < 2.5.18 */
+
+
+/**
+ * Similar to sf_inode_revalidate, but uses associated host file handle as that
+ * is quite a bit faster.
+ */
+int vbsf_inode_revalidate_with_handle(struct dentry *dentry, SHFLHANDLE hHostFile, bool fForced, bool fInodeLocked)
+{
+ int err;
+ struct inode *pInode = dentry ? dentry->d_inode : NULL;
+ if (!pInode) {
+ LogFunc(("no dentry(%p) or inode(%p)\n", dentry, pInode));
+ err = -EINVAL;
+ } else {
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(pInode);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pInode->i_sb);
+ AssertReturn(sf_i, -EINVAL);
+ AssertReturn(pSuperInfo, -EINVAL);
+
+ /*
+ * Can we get away without any action here?
+ */
+ if ( !fForced
+ && !sf_i->force_restat
+ && jiffies - sf_i->ts_up_to_date < pSuperInfo->cJiffiesInodeTTL)
+ err = 0;
+ else {
+ /*
+ * No, we have to query the file info from the host.
+ */
+ VBOXSFOBJINFOREQ *pReq = (VBOXSFOBJINFOREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ RT_ZERO(*pReq);
+ err = VbglR0SfHostReqQueryObjInfo(pSuperInfo->map.root, pReq, hHostFile);
+ if (RT_SUCCESS(err)) {
+ /*
+ * Reset the TTL and copy the info over into the inode structure.
+ */
+ vbsf_update_inode(pInode, sf_i, &pReq->ObjInfo, pSuperInfo, fInodeLocked, 0 /*fSetAttrs*/);
+ } else {
+ LogFunc(("VbglR0SfHostReqQueryObjInfo failed on %#RX64: %Rrc\n", hHostFile, err));
+ err = -RTErrConvertToErrno(err);
+ }
+ VbglR0PhysHeapFree(pReq);
+ } else
+ err = -ENOMEM;
+ }
+ }
+ return err;
+}
+
+
+/* on 2.6 this is a proxy for [sf_inode_revalidate] which (as a side
+ effect) updates inode attributes for [dentry] (given that [dentry]
+ has inode at all) from these new attributes we derive [kstat] via
+ [generic_fillattr] */
+#if RTLNX_VER_MIN(2,5,18)
+# if RTLNX_VER_MIN(6,3,0)
+int vbsf_inode_getattr(struct mnt_idmap *idmap, const struct path *path,
+ struct kstat *kstat, u32 request_mask, unsigned int flags)
+# elif RTLNX_VER_MIN(5,12,0)
+int vbsf_inode_getattr(struct user_namespace *ns, const struct path *path,
+ struct kstat *kstat, u32 request_mask, unsigned int flags)
+# elif RTLNX_VER_MIN(4,11,0)
+int vbsf_inode_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int flags)
+# else
+int vbsf_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *kstat)
+# endif
+{
+ int rc;
+# if RTLNX_VER_MIN(4,11,0)
+ struct dentry *dentry = path->dentry;
+# endif
+
+# if RTLNX_VER_MIN(4,11,0)
+ SFLOGFLOW(("vbsf_inode_getattr: dentry=%p request_mask=%#x flags=%#x\n", dentry, request_mask, flags));
+# else
+ SFLOGFLOW(("vbsf_inode_getattr: dentry=%p\n", dentry));
+# endif
+
+# if RTLNX_VER_MIN(4,11,0)
+ /*
+ * With the introduction of statx() userland can control whether we
+ * update the inode information or not.
+ */
+ switch (flags & AT_STATX_SYNC_TYPE) {
+ default:
+ rc = vbsf_inode_revalidate_worker(dentry, false /*fForced*/, false /*fInodeLocked*/);
+ break;
+
+ case AT_STATX_FORCE_SYNC:
+ rc = vbsf_inode_revalidate_worker(dentry, true /*fForced*/, false /*fInodeLocked*/);
+ break;
+
+ case AT_STATX_DONT_SYNC:
+ rc = 0;
+ break;
+ }
+# else
+ rc = vbsf_inode_revalidate_worker(dentry, false /*fForced*/, false /*fInodeLocked*/);
+# endif
+ if (rc == 0) {
+ /* Do generic filling in of info. */
+# if RTLNX_VER_MIN(6,6,0)
+ generic_fillattr(idmap, request_mask, dentry->d_inode, kstat);
+# elif RTLNX_VER_MIN(6,3,0)
+ generic_fillattr(idmap, dentry->d_inode, kstat);
+# elif RTLNX_VER_MIN(5,12,0)
+ generic_fillattr(ns, dentry->d_inode, kstat);
+# else
+ generic_fillattr(dentry->d_inode, kstat);
+# endif
+
+ /* Add birth time. */
+# if RTLNX_VER_MIN(4,11,0)
+ if (dentry->d_inode) {
+ struct vbsf_inode_info *pInodeInfo = VBSF_GET_INODE_INFO(dentry->d_inode);
+ if (pInodeInfo) {
+ vbsf_time_to_linux(&kstat->btime, &pInodeInfo->BirthTime);
+ kstat->result_mask |= STATX_BTIME;
+ }
+ }
+# endif
+
+ /*
+ * FsPerf shows the following numbers for sequential file access against
+ * a tmpfs folder on an AMD 1950X host running debian buster/sid:
+ *
+ * block size = r128600 ----- r128755 -----
+ * reads reads writes
+ * 4096 KB = 2254 MB/s 4953 MB/s 3668 MB/s
+ * 2048 KB = 2368 MB/s 4908 MB/s 3541 MB/s
+ * 1024 KB = 2208 MB/s 4011 MB/s 3291 MB/s
+ * 512 KB = 1908 MB/s 3399 MB/s 2721 MB/s
+ * 256 KB = 1625 MB/s 2679 MB/s 2251 MB/s
+ * 128 KB = 1413 MB/s 1967 MB/s 1684 MB/s
+ * 64 KB = 1152 MB/s 1409 MB/s 1265 MB/s
+ * 32 KB = 726 MB/s 815 MB/s 783 MB/s
+ * 16 KB = 683 MB/s 475 MB/s
+ * 8 KB = 294 MB/s 286 MB/s
+ * 4 KB = 145 MB/s 156 MB/s 149 MB/s
+ *
+ */
+ if (S_ISREG(kstat->mode))
+ kstat->blksize = _1M;
+ else if (S_ISDIR(kstat->mode))
+ /** @todo this may need more tuning after we rewrite the directory handling. */
+ kstat->blksize = _16K;
+ }
+ return rc;
+}
+#endif /* >= 2.5.18 */
+
+
+/**
+ * Modify inode attributes.
+ */
+#if RTLNX_VER_MIN(6,3,0)
+int vbsf_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr)
+#elif RTLNX_VER_MIN(5,12,0)
+int vbsf_inode_setattr(struct user_namespace *ns, struct dentry *dentry, struct iattr *iattr)
+#else
+int vbsf_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+#endif
+{
+ struct inode *pInode = dentry->d_inode;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(pInode->i_sb);
+ struct vbsf_inode_info *sf_i = VBSF_GET_INODE_INFO(pInode);
+ int vrc;
+ int rc;
+
+ SFLOGFLOW(("vbsf_inode_setattr: dentry=%p inode=%p ia_valid=%#x %s\n",
+ dentry, pInode, iattr->ia_valid, sf_i ? sf_i->path->String.ach : NULL));
+ AssertReturn(sf_i, -EINVAL);
+
+ /*
+ * Do minimal attribute permission checks. We set ATTR_FORCE since we cannot
+ * preserve ownership and such and would end up with EPERM here more often than
+ * we would like. For instance it would cause 'cp' to complain about EPERM
+ * from futimes() when asked to preserve times, see ticketref:18569.
+ */
+ iattr->ia_valid |= ATTR_FORCE;
+#if (RTLNX_VER_RANGE(3,16,39, 3,17,0)) || RTLNX_VER_MIN(4,9,0) || (RTLNX_VER_RANGE(4,1,37, 4,2,0)) || RTLNX_UBUNTU_ABI_MIN(4,4,255,208)
+# if RTLNX_VER_MIN(6,3,0)
+ rc = setattr_prepare(idmap, dentry, iattr);
+# elif RTLNX_VER_MIN(5,12,0)
+ rc = setattr_prepare(ns, dentry, iattr);
+# else
+ rc = setattr_prepare(dentry, iattr);
+# endif
+#else
+ rc = inode_change_ok(pInode, iattr);
+#endif
+ if (rc == 0) {
+ /*
+ * Don't modify MTIME and CTIME for open(O_TRUNC) and ftruncate, those
+ * operations will set those timestamps automatically. Saves a host call.
+ */
+ unsigned fAttrs = iattr->ia_valid;
+#if RTLNX_VER_MIN(2,6,15)
+ fAttrs &= ~ATTR_FILE;
+#endif
+ if ( fAttrs == (ATTR_SIZE | ATTR_MTIME | ATTR_CTIME)
+#if RTLNX_VER_MIN(2,6,24)
+ || (fAttrs & (ATTR_OPEN | ATTR_SIZE)) == (ATTR_OPEN | ATTR_SIZE)
+#endif
+ )
+ fAttrs &= ~(ATTR_MTIME | ATTR_CTIME);
+
+ /*
+ * We only implement a handful of attributes, so ignore any attempts
+ * at setting bits we don't support.
+ */
+ if (fAttrs & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME | ATTR_CTIME | ATTR_SIZE)) {
+ /*
+ * Try find a handle which allows us to modify the attributes, otherwise
+ * open the file/dir/whatever.
+ */
+ union SetAttrReqs
+ {
+ VBOXSFCREATEREQ Create;
+ VBOXSFOBJINFOREQ Info;
+ VBOXSFSETFILESIZEREQ SetSize;
+ VBOXSFCLOSEREQ Close;
+ } *pReq;
+ size_t cbReq;
+ SHFLHANDLE hHostFile;
+ /** @todo ATTR_FILE (2.6.15+) could be helpful here if we like. */
+ struct vbsf_handle *pHandle = fAttrs & ATTR_SIZE
+ ? vbsf_handle_find(sf_i, VBSF_HANDLE_F_WRITE, 0)
+ : vbsf_handle_find(sf_i, 0, 0);
+ if (pHandle) {
+ hHostFile = pHandle->hHost;
+ cbReq = RT_MAX(sizeof(VBOXSFOBJINFOREQ), sizeof(VBOXSFSETFILESIZEREQ));
+ pReq = (union SetAttrReqs *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ /* likely */
+ } else
+ rc = -ENOMEM;
+ } else {
+ hHostFile = SHFL_HANDLE_NIL;
+ cbReq = RT_MAX(sizeof(pReq->Info), sizeof(pReq->Create) + SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ pReq = (union SetAttrReqs *)VbglR0PhysHeapAlloc(cbReq);
+ if (pReq) {
+ RT_ZERO(pReq->Create.CreateParms);
+ pReq->Create.CreateParms.Handle = SHFL_HANDLE_NIL;
+ pReq->Create.CreateParms.CreateFlags = SHFL_CF_ACT_OPEN_IF_EXISTS
+ | SHFL_CF_ACT_FAIL_IF_NEW
+ | SHFL_CF_ACCESS_ATTR_WRITE;
+ if (fAttrs & ATTR_SIZE)
+ pReq->Create.CreateParms.CreateFlags |= SHFL_CF_ACCESS_WRITE;
+ RT_BCOPY_UNFORTIFIED(&pReq->Create.StrPath, sf_i->path, SHFLSTRING_HEADER_SIZE + sf_i->path->u16Size);
+ vrc = VbglR0SfHostReqCreate(pSuperInfo->map.root, &pReq->Create);
+ if (RT_SUCCESS(vrc)) {
+ if (pReq->Create.CreateParms.Result == SHFL_FILE_EXISTS) {
+ hHostFile = pReq->Create.CreateParms.Handle;
+ Assert(hHostFile != SHFL_HANDLE_NIL);
+ vbsf_dentry_chain_increase_ttl(dentry);
+ } else {
+ LogFunc(("file %s does not exist\n", sf_i->path->String.utf8));
+ vbsf_dentry_invalidate_ttl(dentry);
+ sf_i->force_restat = true;
+ rc = -ENOENT;
+ }
+ } else {
+ rc = -RTErrConvertToErrno(vrc);
+ LogFunc(("VbglR0SfCreate(%s) failed vrc=%Rrc rc=%d\n", sf_i->path->String.ach, vrc, rc));
+ }
+ } else
+ rc = -ENOMEM;
+ }
+ if (rc == 0) {
+ /*
+ * Set mode and/or timestamps.
+ */
+ if (fAttrs & (ATTR_MODE | ATTR_ATIME | ATTR_MTIME | ATTR_CTIME)) {
+ /* Fill in the attributes. Start by setting all to zero
+ since the host will ignore zeroed fields. */
+ RT_ZERO(pReq->Info.ObjInfo);
+
+ if (fAttrs & ATTR_MODE) {
+ pReq->Info.ObjInfo.Attr.fMode = sf_access_permissions_to_vbox(iattr->ia_mode);
+ if (iattr->ia_mode & S_IFDIR)
+ pReq->Info.ObjInfo.Attr.fMode |= RTFS_TYPE_DIRECTORY;
+ else if (iattr->ia_mode & S_IFLNK)
+ pReq->Info.ObjInfo.Attr.fMode |= RTFS_TYPE_SYMLINK;
+ else
+ pReq->Info.ObjInfo.Attr.fMode |= RTFS_TYPE_FILE;
+ }
+ if (fAttrs & ATTR_ATIME)
+ vbsf_time_to_vbox(&pReq->Info.ObjInfo.AccessTime, &iattr->ia_atime);
+ if (fAttrs & ATTR_MTIME)
+ vbsf_time_to_vbox(&pReq->Info.ObjInfo.ModificationTime, &iattr->ia_mtime);
+ if (fAttrs & ATTR_CTIME)
+ vbsf_time_to_vbox(&pReq->Info.ObjInfo.ChangeTime, &iattr->ia_ctime);
+
+ /* Make the change. */
+ vrc = VbglR0SfHostReqSetObjInfo(pSuperInfo->map.root, &pReq->Info, hHostFile);
+ if (RT_SUCCESS(vrc)) {
+ vbsf_update_inode(pInode, sf_i, &pReq->Info.ObjInfo, pSuperInfo, true /*fLocked*/, fAttrs);
+ } else {
+ rc = -RTErrConvertToErrno(vrc);
+ LogFunc(("VbglR0SfHostReqSetObjInfo(%s) failed vrc=%Rrc rc=%d\n", sf_i->path->String.ach, vrc, rc));
+ }
+ }
+
+ /*
+ * Change the file size.
+ * Note! Old API is more convenient here as it gives us up to date
+ * inode info back.
+ */
+ if ((fAttrs & ATTR_SIZE) && rc == 0) {
+ /*vrc = VbglR0SfHostReqSetFileSize(pSuperInfo->map.root, &pReq->SetSize, hHostFile, iattr->ia_size);
+ if (RT_SUCCESS(vrc)) {
+ i_size_write(pInode, iattr->ia_size);
+ } else if (vrc == VERR_NOT_IMPLEMENTED)*/ {
+ /* Fallback for pre 6.0 hosts: */
+ RT_ZERO(pReq->Info.ObjInfo);
+ pReq->Info.ObjInfo.cbObject = iattr->ia_size;
+ vrc = VbglR0SfHostReqSetFileSizeOld(pSuperInfo->map.root, &pReq->Info, hHostFile);
+ if (RT_SUCCESS(vrc))
+ vbsf_update_inode(pInode, sf_i, &pReq->Info.ObjInfo, pSuperInfo, true /*fLocked*/, fAttrs);
+ }
+ if (RT_SUCCESS(vrc)) {
+ /** @todo there is potentially more to be done here if there are mappings of
+ * the lovely file. */
+ } else {
+ rc = -RTErrConvertToErrno(vrc);
+ LogFunc(("VbglR0SfHostReqSetFileSize(%s, %#llx) failed vrc=%Rrc rc=%d\n",
+ sf_i->path->String.ach, (unsigned long long)iattr->ia_size, vrc, rc));
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ if (!pHandle) {
+ vrc = VbglR0SfHostReqClose(pSuperInfo->map.root, &pReq->Close, hHostFile);
+ if (RT_FAILURE(vrc))
+ LogFunc(("VbglR0SfHostReqClose(%s [%#llx]) failed vrc=%Rrc\n", sf_i->path->String.utf8, hHostFile, vrc));
+ }
+ }
+ if (pReq)
+ VbglR0PhysHeapFree(pReq);
+ if (pHandle)
+ vbsf_handle_release(pHandle, pSuperInfo, "vbsf_inode_setattr");
+ } else
+ SFLOGFLOW(("vbsf_inode_setattr: Nothing to do here: %#x (was %#x).\n", fAttrs, iattr->ia_valid));
+ }
+ return rc;
+}
+
+
+static int vbsf_make_path(const char *caller, struct vbsf_inode_info *sf_i,
+ const char *d_name, size_t d_len, SHFLSTRING **result)
+{
+ size_t path_len, shflstring_len;
+ SHFLSTRING *tmp;
+ uint16_t p_len;
+ uint8_t *p_name;
+ int fRoot = 0;
+
+ TRACE();
+ p_len = sf_i->path->u16Length;
+ p_name = sf_i->path->String.utf8;
+
+ if (p_len == 1 && *p_name == '/') {
+ path_len = d_len + 1;
+ fRoot = 1;
+ } else {
+ /* lengths of constituents plus terminating zero plus slash */
+ path_len = p_len + d_len + 2;
+ if (path_len > 0xffff) {
+ LogFunc(("path too long. caller=%s, path_len=%zu\n",
+ caller, path_len));
+ return -ENAMETOOLONG;
+ }
+ }
+
+ shflstring_len = offsetof(SHFLSTRING, String.utf8) + path_len;
+ tmp = kmalloc(shflstring_len, GFP_KERNEL);
+ if (!tmp) {
+ LogRelFunc(("kmalloc failed, caller=%s\n", caller));
+ return -ENOMEM;
+ }
+ tmp->u16Length = path_len - 1;
+ tmp->u16Size = path_len;
+
+ if (fRoot)
+ RT_BCOPY_UNFORTIFIED(&tmp->String.utf8[0], d_name, d_len + 1);
+ else {
+ RT_BCOPY_UNFORTIFIED(&tmp->String.utf8[0], p_name, p_len);
+ tmp->String.utf8[p_len] = '/';
+ RT_BCOPY_UNFORTIFIED(&tmp->String.utf8[p_len + 1], d_name, d_len);
+ tmp->String.utf8[p_len + 1 + d_len] = '\0';
+ }
+
+ *result = tmp;
+ return 0;
+}
+
+
+/**
+ * [dentry] contains string encoded in coding system that corresponds
+ * to [pSuperInfo]->nls, we must convert it to UTF8 here and pass down to
+ * [vbsf_make_path] which will allocate SHFLSTRING and fill it in
+ */
+int vbsf_path_from_dentry(struct vbsf_super_info *pSuperInfo, struct vbsf_inode_info *sf_i, struct dentry *dentry,
+ SHFLSTRING **result, const char *caller)
+{
+ int err;
+ const char *d_name;
+ size_t d_len;
+ const char *name;
+ size_t len = 0;
+
+ TRACE();
+ d_name = dentry->d_name.name;
+ d_len = dentry->d_name.len;
+
+ if (pSuperInfo->nls) {
+ size_t in_len, i, out_bound_len;
+ const char *in;
+ char *out;
+
+ in = d_name;
+ in_len = d_len;
+
+ out_bound_len = PATH_MAX;
+ out = kmalloc(out_bound_len, GFP_KERNEL);
+ name = out;
+
+ for (i = 0; i < d_len; ++i) {
+ /* We renamed the linux kernel wchar_t type to linux_wchar_t in
+ the-linux-kernel.h, as it conflicts with the C++ type of that name. */
+ linux_wchar_t uni;
+ int nb;
+
+ nb = pSuperInfo->nls->char2uni(in, in_len, &uni);
+ if (nb < 0) {
+ LogFunc(("nls->char2uni failed %x %d\n",
+ *in, in_len));
+ err = -EINVAL;
+ goto fail1;
+ }
+ in_len -= nb;
+ in += nb;
+
+#if RTLNX_VER_MIN(2,6,31)
+ nb = utf32_to_utf8(uni, out, out_bound_len);
+#else
+ nb = utf8_wctomb(out, uni, out_bound_len);
+#endif
+ if (nb < 0) {
+ LogFunc(("nls->uni2char failed %x %d\n",
+ uni, out_bound_len));
+ err = -EINVAL;
+ goto fail1;
+ }
+ out_bound_len -= nb;
+ out += nb;
+ len += nb;
+ }
+ if (len >= PATH_MAX - 1) {
+ err = -ENAMETOOLONG;
+ goto fail1;
+ }
+
+ LogFunc(("result(%d) = %.*s\n", len, len, name));
+ *out = 0;
+ } else {
+ name = d_name;
+ len = d_len;
+ }
+
+ err = vbsf_make_path(caller, sf_i, name, len, result);
+ if (name != d_name)
+ kfree(name);
+
+ return err;
+
+ fail1:
+ kfree(name);
+ return err;
+}
+
+
+/**
+ * This is called during name resolution/lookup to check if the @a dentry in the
+ * cache is still valid. The actual validation is job is handled by
+ * vbsf_inode_revalidate_worker().
+ *
+ * @note Caller holds no relevant locks, just a dentry reference.
+ */
+#if RTLNX_VER_MIN(3,6,0)
+static int vbsf_dentry_revalidate(struct dentry *dentry, unsigned flags)
+#elif RTLNX_VER_MIN(2,6,0)
+static int vbsf_dentry_revalidate(struct dentry *dentry, struct nameidata *nd)
+#else
+static int vbsf_dentry_revalidate(struct dentry *dentry, int flags)
+#endif
+{
+#if RTLNX_VER_RANGE(2,6,0, 3,6,0)
+ int const flags = nd ? nd->flags : 0;
+#endif
+
+ int rc;
+
+ Assert(dentry);
+ SFLOGFLOW(("vbsf_dentry_revalidate: %p %#x %s\n", dentry, flags,
+ dentry->d_inode ? VBSF_GET_INODE_INFO(dentry->d_inode)->path->String.ach : "<negative>"));
+
+ /*
+ * See Documentation/filesystems/vfs.txt why we skip LOOKUP_RCU.
+ *
+ * Also recommended: https://lwn.net/Articles/649115/
+ * https://lwn.net/Articles/649729/
+ * https://lwn.net/Articles/650786/
+ *
+ */
+#if RTLNX_VER_MIN(2,6,38)
+ if (flags & LOOKUP_RCU) {
+ rc = -ECHILD;
+ SFLOGFLOW(("vbsf_dentry_revalidate: RCU -> -ECHILD\n"));
+ } else
+#endif
+ {
+ /*
+ * Do we have an inode or not? If not it's probably a negative cache
+ * entry, otherwise most likely a positive one.
+ */
+ struct inode *pInode = dentry->d_inode;
+ if (pInode) {
+ /*
+ * Positive entry.
+ *
+ * Note! We're more aggressive here than other remote file systems,
+ * current (4.19) CIFS will for instance revalidate the inode
+ * and ignore the dentry timestamp for positive entries.
+ */
+ unsigned long const cJiffiesAge = jiffies - vbsf_dentry_get_update_jiffies(dentry);
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(dentry->d_sb);
+ if (cJiffiesAge < pSuperInfo->cJiffiesDirCacheTTL) {
+ SFLOGFLOW(("vbsf_dentry_revalidate: age: %lu vs. TTL %lu -> 1\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 1;
+ } else if (!vbsf_inode_revalidate_worker(dentry, true /*fForced*/, false /*fInodeLocked*/)) {
+ vbsf_dentry_set_update_jiffies(dentry, jiffies);
+ SFLOGFLOW(("vbsf_dentry_revalidate: age: %lu vs. TTL %lu -> reval -> 1\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 1;
+ } else {
+ SFLOGFLOW(("vbsf_dentry_revalidate: age: %lu vs. TTL %lu -> reval -> 0\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 0;
+ }
+ } else {
+ /*
+ * Negative entry.
+ *
+ * Invalidate dentries for open and renames here as we'll revalidate
+ * these when taking the actual action (also good for case preservation
+ * if we do case-insensitive mounts against windows + mac hosts at some
+ * later point).
+ */
+#if RTLNX_VER_MIN(2,6,28)
+ if (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET))
+#elif RTLNX_VER_MIN(2,5,75)
+ if (flags & LOOKUP_CREATE)
+#else
+ if (0)
+#endif
+ {
+ SFLOGFLOW(("vbsf_dentry_revalidate: negative: create or rename target -> 0\n"));
+ rc = 0;
+ } else {
+ /* Can we skip revalidation based on TTL? */
+ unsigned long const cJiffiesAge = vbsf_dentry_get_update_jiffies(dentry) - jiffies;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(dentry->d_sb);
+ if (cJiffiesAge < pSuperInfo->cJiffiesDirCacheTTL) {
+ SFLOGFLOW(("vbsf_dentry_revalidate: negative: age: %lu vs. TTL %lu -> 1\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 1;
+ } else {
+ /* We could revalidate it here, but we could instead just
+ have the caller kick it out. */
+ /** @todo stat the direntry and see if it exists now. */
+ SFLOGFLOW(("vbsf_dentry_revalidate: negative: age: %lu vs. TTL %lu -> 0\n", cJiffiesAge, pSuperInfo->cJiffiesDirCacheTTL));
+ rc = 0;
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+#ifdef SFLOG_ENABLED
+
+/** For logging purposes only. */
+# if RTLNX_VER_MIN(2,6,38)
+static int vbsf_dentry_delete(const struct dentry *pDirEntry)
+# else
+static int vbsf_dentry_delete(struct dentry *pDirEntry)
+# endif
+{
+ SFLOGFLOW(("vbsf_dentry_delete: %p\n", pDirEntry));
+ return 0;
+}
+
+# if RTLNX_VER_MIN(4,8,0)
+/** For logging purposes only. */
+static int vbsf_dentry_init(struct dentry *pDirEntry)
+{
+ SFLOGFLOW(("vbsf_dentry_init: %p\n", pDirEntry));
+ return 0;
+}
+# endif
+
+#endif /* SFLOG_ENABLED */
+
+/**
+ * Directory entry operations.
+ *
+ * Since 2.6.38 this is used via the super_block::s_d_op member.
+ */
+struct dentry_operations vbsf_dentry_ops = {
+ .d_revalidate = vbsf_dentry_revalidate,
+#ifdef SFLOG_ENABLED
+ .d_delete = vbsf_dentry_delete,
+# if RTLNX_VER_MIN(4,8,0)
+ .d_init = vbsf_dentry_init,
+# endif
+#endif
+};
+
diff --git a/src/VBox/Additions/linux/sharedfolders/vbsfmount.c b/src/VBox/Additions/linux/sharedfolders/vbsfmount.c
new file mode 100644
index 00000000..72d9210e
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vbsfmount.c
@@ -0,0 +1,113 @@
+/* $Id: vbsfmount.c $ */
+/** @file
+ * vbsfmount - Commonly used code to mount shared folders on Linux-based
+ * systems. Currently used by mount.vboxsf and VBoxService.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+#include <assert.h>
+#include <ctype.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mount.h>
+
+#include "vbsfmount.h"
+
+
+/** @todo Use defines for return values! */
+int vbsfmount_complete(const char *pszSharedFolder, const char *pszMountPoint,
+ unsigned long fFlags, const char *pszOpts)
+{
+ /*
+ * Combine pszOpts and fFlags.
+ */
+ int rc;
+ size_t const cchFlags = (fFlags & MS_NOSUID ? strlen(MNTOPT_NOSUID) + 1 : 0)
+ + (fFlags & MS_RDONLY ? strlen(MNTOPT_RO) : strlen(MNTOPT_RW));
+ size_t const cchOpts = pszOpts ? 1 + strlen(pszOpts) : 0;
+ char *pszBuf = (char *)malloc(cchFlags + cchOpts + 8);
+ if (pszBuf)
+ {
+ char *psz = pszBuf;
+ FILE *pMTab;
+
+ strcpy(psz, fFlags & MS_RDONLY ? MNTOPT_RO : MNTOPT_RW);
+ psz += strlen(psz);
+
+ if (fFlags & MS_NOSUID)
+ {
+ *psz++ = ',';
+ strcpy(psz, MNTOPT_NOSUID);
+ psz += strlen(psz);
+ }
+
+ if (cchOpts)
+ {
+ *psz++ = ',';
+ strcpy(psz, pszOpts);
+ }
+
+ assert(strlen(pszBuf) <= cchFlags + cchOpts);
+
+ /*
+ * Open the mtab and update it:
+ */
+ pMTab = setmntent(MOUNTED, "a+");
+ if (pMTab)
+ {
+ struct mntent Entry;
+ Entry.mnt_fsname = (char*)pszSharedFolder;
+ Entry.mnt_dir = (char *)pszMountPoint;
+ Entry.mnt_type = "vboxsf";
+ Entry.mnt_opts = pszBuf;
+ Entry.mnt_freq = 0;
+ Entry.mnt_passno = 0;
+
+ if (!addmntent(pMTab, &Entry))
+ rc = 0; /* success. */
+ else
+ rc = 3; /* Could not add an entry to the mount table. */
+
+ endmntent(pMTab);
+ }
+ else
+ rc = 2; /* Could not open mount table for update. */
+
+ free(pszBuf);
+ }
+ else
+ rc = 1; /* allocation error */
+ return rc;
+}
+
diff --git a/src/VBox/Additions/linux/sharedfolders/vbsfmount.h b/src/VBox/Additions/linux/sharedfolders/vbsfmount.h
new file mode 100644
index 00000000..663cc8be
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vbsfmount.h
@@ -0,0 +1,142 @@
+/* $Id: vbsfmount.h $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, mount(2) parameter structure.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef GA_INCLUDED_SRC_linux_sharedfolders_vbsfmount_h
+#define GA_INCLUDED_SRC_linux_sharedfolders_vbsfmount_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/* Linux constrains the size of data mount argument to PAGE_SIZE - 1. */
+#define MAX_MNTOPT_STR PAGE_SIZE
+#define MAX_HOST_NAME 256
+#define MAX_NLS_NAME 32
+#define VBSF_DEFAULT_TTL_MS 200
+
+#define VBSF_MOUNT_SIGNATURE_BYTE_0 '\377'
+#define VBSF_MOUNT_SIGNATURE_BYTE_1 '\376'
+#define VBSF_MOUNT_SIGNATURE_BYTE_2 '\375'
+
+/**
+ * VBox Linux Shared Folders VFS caching mode.
+ */
+enum vbsf_cache_mode {
+ /** Use the kernel modules default caching mode (kVbsfCacheMode_Strict). */
+ kVbsfCacheMode_Default = 0,
+ /** No caching, go to the host for everything. This will have some minor
+ * coherency issues for memory mapping with unsynced dirty pages. */
+ kVbsfCacheMode_None,
+ /** No caching, except for files with writable memory mappings.
+ * (Note to future: if we do oplock like stuff, it goes in here.) */
+ kVbsfCacheMode_Strict,
+ /** Use page cache for reads.
+ * This improves guest performance for read intensive jobs, like compiling
+ * building. The flip side is that the guest may not see host modification in a
+ * timely manner and possibly update files with out-of-date cache information,
+ * as there exists no protocol for the host to notify the guest about file
+ * modifications. */
+ kVbsfCacheMode_Read,
+ /** Use page cache for both reads and writes as far as that's possible.
+ * This is good for guest performance, but the price is that the guest possibly
+ * ignoring host changes and the host not seeing guest changes in a timely
+ * manner. */
+ kVbsfCacheMode_ReadWrite,
+ /** End of valid values (exclusive). */
+ kVbsfCacheMode_End,
+ /** Make sure the enum is sizeof(int32_t). */
+ kVbsfCacheMode_32BitHack = 0x7fffffff
+};
+
+/**
+ * VBox Linux Shared Folders VFS mount options.
+ */
+struct vbsf_mount_info_new {
+ /**
+ * The old version of the mount_info struct started with a
+ * char name[MAX_HOST_NAME] field, where name cannot be '\0'.
+ * So the new version of the mount_info struct starts with a
+ * nullchar field which is always 0 so that we can detect and
+ * reject the old structure being passed.
+ */
+ char nullchar;
+ /** Signature */
+ char signature[3];
+ /** Length of the whole structure */
+ int length;
+ /** Share name */
+ char name[MAX_HOST_NAME];
+ /** Name of an I/O charset */
+ char nls_name[MAX_NLS_NAME];
+ /** User ID for all entries, default 0=root */
+ int uid;
+ /** Group ID for all entries, default 0=root */
+ int gid;
+ /** Directory entry and inode time to live in milliseconds.
+ * -1 for kernel default, 0 to disable caching.
+ * @sa vbsf_mount_info_new::msDirCacheTTL, vbsf_mount_info_new::msInodeTTL */
+ int ttl;
+ /** Mode for directories if != -1. */
+ int dmode;
+ /** Mode for regular files if != -1. */
+ int fmode;
+ /** umask applied to directories */
+ int dmask;
+ /** umask applied to regular files */
+ int fmask;
+ /** Mount tag for VBoxService automounter.
+ * @since 6.0.0 */
+ char szTag[32];
+ /** Max pages to read & write at a time.
+ * @since 6.0.6 */
+ uint32_t cMaxIoPages;
+ /** The directory content buffer size. Set to 0 for kernel module default.
+ * Larger value reduces the number of host calls on large directories. */
+ uint32_t cbDirBuf;
+ /** The time to live for directory entries (in milliseconds). @a ttl is used
+ * if negative.
+ * @since 6.0.6 */
+ int32_t msDirCacheTTL;
+ /** The time to live for inode information (in milliseconds). @a ttl is used
+ * if negative.
+ * @since 6.0.6 */
+ int32_t msInodeTTL;
+ /** The cache and coherency mode.
+ * @since 6.0.6 */
+ enum vbsf_cache_mode enmCacheMode;
+};
+#ifdef AssertCompileSize
+AssertCompileSize(struct vbsf_mount_info_new, 2*4 + MAX_HOST_NAME + MAX_NLS_NAME + 7*4 + 32 + 5*4);
+#endif
+
+/** Completes the mount operation by adding the new mount point to mtab if required. */
+int vbsfmount_complete(const char *pszSharedFolder, const char *pszMountPoint,
+ unsigned long fFlags, const char *pszOpts);
+
+#endif /* !GA_INCLUDED_SRC_linux_sharedfolders_vbsfmount_h */
diff --git a/src/VBox/Additions/linux/sharedfolders/vfsmod.c b/src/VBox/Additions/linux/sharedfolders/vfsmod.c
new file mode 100644
index 00000000..18324fe1
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.c
@@ -0,0 +1,1753 @@
+/* $Id: vfsmod.c $ */
+/** @file
+ * vboxsf - VBox Linux Shared Folders VFS, module init/term, super block management.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * @note Anyone wishing to make changes here might wish to take a look at
+ * https://github.com/torvalds/linux/blob/master/Documentation/filesystems/vfs.txt
+ * which seems to be the closest there is to official documentation on
+ * writing filesystem drivers for Linux.
+ *
+ * See also: http://us1.samba.org/samba/ftp/cifs-cvs/ols2006-fs-tutorial-smf.odp
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "vfsmod.h"
+#include "version-generated.h"
+#include "revision-generated.h"
+#include "product-generated.h"
+#if RTLNX_VER_MIN(5,0,0) || RTLNX_RHEL_MIN(8,4)
+# include <uapi/linux/mount.h> /* for MS_REMOUNT */
+#elif RTLNX_VER_MAX(3,3,0)
+# include <linux/mount.h>
+#endif
+#include <linux/seq_file.h>
+#include <linux/vfs.h>
+#if RTLNX_VER_RANGE(2,5,62, 5,8,0)
+# include <linux/vermagic.h>
+#endif
+#include <VBox/err.h>
+#include <iprt/path.h>
+#if RTLNX_VER_MIN(5,1,0)
+# include <linux/fs_context.h>
+# include <linux/fs_parser.h>
+#elif RTLNX_VER_MIN(2,6,0)
+# include <linux/parser.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VBSF_DEFAULT_MAX_IO_PAGES RT_MIN(_16K / sizeof(RTGCPHYS64) /* => 8MB buffer */, VMMDEV_MAX_HGCM_DATA_SIZE >> PAGE_SHIFT)
+#define VBSF_DEFAULT_DIR_BUF_SIZE _64K
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+VBGLSFCLIENT g_SfClient;
+uint32_t g_fHostFeatures = 0;
+/** Last valid shared folders function number. */
+uint32_t g_uSfLastFunction = SHFL_FN_SET_FILE_SIZE;
+/** Shared folders features (SHFL_FEATURE_XXX). */
+uint64_t g_fSfFeatures = 0;
+
+/** Protects all the vbsf_inode_info::HandleList lists. */
+spinlock_t g_SfHandleLock;
+
+/** The 'follow_symlinks' module parameter.
+ * @todo Figure out how do this for 2.4.x! */
+static int g_fFollowSymlinks = 0;
+
+/* forward declaration */
+static struct super_operations g_vbsf_super_ops;
+
+
+
+/**
+ * Copies options from the mount info structure into @a pSuperInfo.
+ *
+ * This is used both by vbsf_super_info_alloc_and_map_it() and
+ * vbsf_remount_fs().
+ */
+static void vbsf_super_info_copy_remount_options(struct vbsf_super_info *pSuperInfo, struct vbsf_mount_info_new *info)
+{
+ pSuperInfo->uid = info->uid;
+ pSuperInfo->gid = info->gid;
+
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, szTag)) {
+ /* new fields */
+ pSuperInfo->dmode = info->dmode;
+ pSuperInfo->fmode = info->fmode;
+ pSuperInfo->dmask = info->dmask;
+ pSuperInfo->fmask = info->fmask;
+ } else {
+ pSuperInfo->dmode = ~0;
+ pSuperInfo->fmode = ~0;
+ }
+
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, cMaxIoPages)) {
+ AssertCompile(sizeof(pSuperInfo->szTag) >= sizeof(info->szTag));
+ RT_BCOPY_UNFORTIFIED(pSuperInfo->szTag, info->szTag, sizeof(info->szTag));
+ pSuperInfo->szTag[sizeof(pSuperInfo->szTag) - 1] = '\0';
+ } else {
+ pSuperInfo->szTag[0] = '\0';
+ }
+
+ /* The max number of pages in an I/O request. This must take into
+ account that the physical heap generally grows in 64 KB chunks,
+ so we should not try push that limit. It also needs to take
+ into account that the host will allocate temporary heap buffers
+ for the I/O bytes we send/receive, so don't push the host heap
+ too hard as we'd have to retry with smaller requests when this
+ happens, which isn't too efficient. */
+ pSuperInfo->cMaxIoPages = VBSF_DEFAULT_MAX_IO_PAGES;
+ if ( (unsigned)info->length >= sizeof(struct vbsf_mount_info_new)
+ && info->cMaxIoPages > 0) {
+ if (info->cMaxIoPages <= VMMDEV_MAX_HGCM_DATA_SIZE >> PAGE_SHIFT)
+ pSuperInfo->cMaxIoPages = RT_MAX(info->cMaxIoPages, 2); /* read_iter/write_iter requires a minimum of 2. */
+ else
+ printk(KERN_WARNING "vboxsf: max I/O page count (%#x) is out of range, using default (%#x) instead.\n",
+ info->cMaxIoPages, pSuperInfo->cMaxIoPages);
+ }
+
+ pSuperInfo->cbDirBuf = VBSF_DEFAULT_DIR_BUF_SIZE;
+ if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, cbDirBuf)
+ && info->cbDirBuf > 0) {
+ if (info->cbDirBuf <= _16M)
+ pSuperInfo->cbDirBuf = RT_ALIGN_32(info->cbDirBuf, PAGE_SIZE);
+ else
+ printk(KERN_WARNING "vboxsf: max directory buffer size (%#x) is out of range, using default (%#x) instead.\n",
+ info->cMaxIoPages, pSuperInfo->cMaxIoPages);
+ }
+
+ /*
+ * TTLs.
+ */
+ pSuperInfo->msTTL = info->ttl;
+ if (info->ttl > 0)
+ pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(info->ttl);
+ else if (info->ttl == 0 || info->ttl != -1)
+ pSuperInfo->cJiffiesDirCacheTTL = pSuperInfo->msTTL = 0;
+ else
+ pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(VBSF_DEFAULT_TTL_MS);
+ pSuperInfo->cJiffiesInodeTTL = pSuperInfo->cJiffiesDirCacheTTL;
+
+ pSuperInfo->msDirCacheTTL = -1;
+ if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, msDirCacheTTL)
+ && info->msDirCacheTTL >= 0) {
+ if (info->msDirCacheTTL > 0) {
+ pSuperInfo->msDirCacheTTL = info->msDirCacheTTL;
+ pSuperInfo->cJiffiesDirCacheTTL = msecs_to_jiffies(info->msDirCacheTTL);
+ } else {
+ pSuperInfo->msDirCacheTTL = 0;
+ pSuperInfo->cJiffiesDirCacheTTL = 0;
+ }
+ }
+
+ pSuperInfo->msInodeTTL = -1;
+ if ( (unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, msInodeTTL)
+ && info->msInodeTTL >= 0) {
+ if (info->msInodeTTL > 0) {
+ pSuperInfo->msInodeTTL = info->msInodeTTL;
+ pSuperInfo->cJiffiesInodeTTL = msecs_to_jiffies(info->msInodeTTL);
+ } else {
+ pSuperInfo->msInodeTTL = 0;
+ pSuperInfo->cJiffiesInodeTTL = 0;
+ }
+ }
+
+ /*
+ * Caching.
+ */
+ pSuperInfo->enmCacheMode = kVbsfCacheMode_Strict;
+ if ((unsigned)info->length >= RT_UOFFSETOF(struct vbsf_mount_info_new, enmCacheMode)) {
+ switch (info->enmCacheMode) {
+ case kVbsfCacheMode_Default:
+ case kVbsfCacheMode_Strict:
+ break;
+ case kVbsfCacheMode_None:
+ case kVbsfCacheMode_Read:
+ case kVbsfCacheMode_ReadWrite:
+ pSuperInfo->enmCacheMode = info->enmCacheMode;
+ break;
+ default:
+ printk(KERN_WARNING "vboxsf: cache mode (%#x) is out of range, using default instead.\n", info->enmCacheMode);
+ break;
+ }
+ }
+}
+
+/**
+ * Allocate the super info structure and try map the host share.
+ */
+static int vbsf_super_info_alloc_and_map_it(struct vbsf_mount_info_new *info, struct vbsf_super_info **sf_gp)
+{
+ int rc;
+ SHFLSTRING *str_name;
+ size_t name_len, str_len;
+ struct vbsf_super_info *pSuperInfo;
+
+ TRACE();
+ *sf_gp = NULL; /* (old gcc maybe used initialized) */
+
+ name_len = RTStrNLen(info->name, sizeof(info->name));
+ if (name_len >= sizeof(info->name)) {
+ SFLOGRELBOTH(("vboxsf: Specified shared folder name is not zero terminated!\n"));
+ return -EINVAL;
+ }
+ if (RTStrNLen(info->nls_name, sizeof(info->nls_name)) >= sizeof(info->nls_name)) {
+ SFLOGRELBOTH(("vboxsf: Specified nls name is not zero terminated!\n"));
+ return -EINVAL;
+ }
+
+ /*
+ * Allocate memory.
+ */
+ str_len = offsetof(SHFLSTRING, String.utf8) + name_len + 1;
+ str_name = (PSHFLSTRING)kmalloc(str_len, GFP_KERNEL);
+ pSuperInfo = (struct vbsf_super_info *)kmalloc(sizeof(*pSuperInfo), GFP_KERNEL);
+ if (pSuperInfo && str_name) {
+ RT_ZERO(*pSuperInfo);
+
+ str_name->u16Length = name_len;
+ str_name->u16Size = name_len + 1;
+ RT_BCOPY_UNFORTIFIED(str_name->String.utf8, info->name, name_len + 1);
+
+ /*
+ * Init the NLS support, if needed.
+ */
+ rc = 0;
+#define _IS_UTF8(_str) (strcmp(_str, "utf8") == 0)
+#define _IS_EMPTY(_str) (strcmp(_str, "") == 0)
+
+ /* Check if NLS charset is valid and not points to UTF8 table */
+ pSuperInfo->fNlsIsUtf8 = true;
+ if (info->nls_name[0]) {
+ if (_IS_UTF8(info->nls_name)) {
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8\n"));
+ pSuperInfo->nls = NULL;
+ } else {
+ pSuperInfo->fNlsIsUtf8 = false;
+ pSuperInfo->nls = load_nls(info->nls_name);
+ if (pSuperInfo->nls) {
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=%s -> %p\n", info->nls_name, pSuperInfo->nls));
+ } else {
+ SFLOGRELBOTH(("vboxsf: Failed to load nls '%s'!\n", info->nls_name));
+ rc = -EINVAL;
+ }
+ }
+ } else {
+#ifdef CONFIG_NLS_DEFAULT
+ /* If no NLS charset specified, try to load the default
+ * one if it's not points to UTF8. */
+ if (!_IS_UTF8(CONFIG_NLS_DEFAULT)
+ && !_IS_EMPTY(CONFIG_NLS_DEFAULT)) {
+ pSuperInfo->fNlsIsUtf8 = false;
+ pSuperInfo->nls = load_nls_default();
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: CONFIG_NLS_DEFAULT=%s -> %p\n", CONFIG_NLS_DEFAULT, pSuperInfo->nls));
+ } else {
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8 (default %s)\n", CONFIG_NLS_DEFAULT));
+ pSuperInfo->nls = NULL;
+ }
+#else
+ SFLOGFLOW(("vbsf_super_info_alloc_and_map_it: nls=utf8 (no default)\n"));
+ pSuperInfo->nls = NULL;
+#endif
+ }
+#undef _IS_UTF8
+#undef _IS_EMPTY
+ if (rc == 0) {
+ /*
+ * Try mount it.
+ */
+ rc = VbglR0SfHostReqMapFolderWithContigSimple(str_name, virt_to_phys(str_name), RTPATH_DELIMITER,
+ true /*fCaseSensitive*/, &pSuperInfo->map.root);
+ if (RT_SUCCESS(rc)) {
+ kfree(str_name);
+
+ /* The rest is shared with remount. */
+ vbsf_super_info_copy_remount_options(pSuperInfo, info);
+
+ *sf_gp = pSuperInfo;
+ return 0;
+ }
+
+ /*
+ * bail out:
+ */
+ if (rc == VERR_FILE_NOT_FOUND) {
+ LogRel(("vboxsf: SHFL_FN_MAP_FOLDER failed for '%s': share not found\n", info->name));
+ rc = -ENXIO;
+ } else {
+ LogRel(("vboxsf: SHFL_FN_MAP_FOLDER failed for '%s': %Rrc\n", info->name, rc));
+ rc = -EPROTO;
+ }
+ if (pSuperInfo->nls)
+ unload_nls(pSuperInfo->nls);
+ }
+ } else {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for super info!\n"));
+ rc = -ENOMEM;
+ }
+ if (str_name)
+ kfree(str_name);
+ if (pSuperInfo)
+ kfree(pSuperInfo);
+ return rc;
+}
+
+/* unmap the share and free super info [pSuperInfo] */
+static void vbsf_super_info_free(struct vbsf_super_info *pSuperInfo)
+{
+ int rc;
+
+ TRACE();
+ rc = VbglR0SfHostReqUnmapFolderSimple(pSuperInfo->map.root);
+ if (RT_FAILURE(rc))
+ LogFunc(("VbglR0SfHostReqUnmapFolderSimple failed rc=%Rrc\n", rc));
+
+ if (pSuperInfo->nls)
+ unload_nls(pSuperInfo->nls);
+
+ kfree(pSuperInfo);
+}
+
+
+/**
+ * Initialize backing device related matters.
+ */
+static int vbsf_init_backing_dev(struct super_block *sb, struct vbsf_super_info *pSuperInfo)
+{
+ int rc = 0;
+#if RTLNX_VER_MIN(2,6,0)
+ /* Each new shared folder map gets a new uint64_t identifier,
+ * allocated in sequence. We ASSUME the sequence will not wrap. */
+# if RTLNX_VER_MIN(2,6,26)
+ static uint64_t s_u64Sequence = 0;
+ uint64_t idSeqMine = ASMAtomicIncU64(&s_u64Sequence);
+# endif
+ struct backing_dev_info *bdi;
+
+# if RTLNX_VER_RANGE(4,0,0, 4,2,0)
+ pSuperInfo->bdi_org = sb->s_bdi;
+# endif
+
+# if RTLNX_VER_MIN(4,12,0)
+ rc = super_setup_bdi_name(sb, "vboxsf-%llu", (unsigned long long)idSeqMine);
+ if (!rc)
+ bdi = sb->s_bdi;
+ else
+ return rc;
+# else
+ bdi = &pSuperInfo->bdi;
+# endif
+
+ bdi->ra_pages = 0; /* No readahead */
+
+# if RTLNX_VER_MIN(2,6,12)
+ bdi->capabilities = 0
+# ifdef BDI_CAP_MAP_DIRECT
+ | BDI_CAP_MAP_DIRECT /* MAP_SHARED */
+# endif
+# ifdef BDI_CAP_MAP_COPY
+ | BDI_CAP_MAP_COPY /* MAP_PRIVATE */
+# endif
+# ifdef BDI_CAP_READ_MAP
+ | BDI_CAP_READ_MAP /* can be mapped for reading */
+# endif
+# ifdef BDI_CAP_WRITE_MAP
+ | BDI_CAP_WRITE_MAP /* can be mapped for writing */
+# endif
+# ifdef BDI_CAP_EXEC_MAP
+ | BDI_CAP_EXEC_MAP /* can be mapped for execution */
+# endif
+# ifdef BDI_CAP_STRICTLIMIT
+# if RTLNX_VER_MIN(4,19,0) /* Trouble with 3.16.x/debian8. Process stops after dirty page throttling.
+ * Only tested successfully with 4.19. Maybe skip altogether? */
+ | BDI_CAP_STRICTLIMIT;
+# endif
+# endif
+ ;
+# ifdef BDI_CAP_STRICTLIMIT
+ /* Smalles possible amount of dirty pages: %1 of RAM. We set this to
+ try reduce amount of data that's out of sync with the host side.
+ Besides, writepages isn't implemented, so flushing is extremely slow.
+ Note! Extremely slow linux 3.0.0 msync doesn't seem to be related to this setting. */
+ bdi_set_max_ratio(bdi, 1);
+# endif
+# endif /* >= 2.6.12 */
+
+# if RTLNX_VER_RANGE(2,6,24, 4,12,0)
+ rc = bdi_init(&pSuperInfo->bdi);
+# if RTLNX_VER_MIN(2,6,26)
+ if (!rc)
+ rc = bdi_register(&pSuperInfo->bdi, NULL, "vboxsf-%llu", (unsigned long long)idSeqMine);
+# endif /* >= 2.6.26 */
+# endif /* 4.11.0 > version >= 2.6.24 */
+
+# if RTLNX_VER_RANGE(2,6,34, 4,12,0)
+ if (!rc)
+ sb->s_bdi = bdi;
+# endif
+
+#endif /* >= 2.6.0 */
+ return rc;
+}
+
+
+/**
+ * Undoes what vbsf_init_backing_dev did.
+ */
+static void vbsf_done_backing_dev(struct super_block *sb, struct vbsf_super_info *pSuperInfo)
+{
+#if RTLNX_VER_RANGE(2,6,24, 4,12,0)
+ bdi_destroy(&pSuperInfo->bdi); /* includes bdi_unregister() */
+
+ /* Paranoia: Make sb->s_bdi not point at pSuperInfo->bdi, in case someone
+ trouches it after this point (we may screw up something). */
+# if RTLNX_VER_RANGE(4,0,0, 4,2,0)
+ sb->s_bdi = pSuperInfo->bdi_org; /* (noop_backing_dev_info is not exported) */
+# elif RTLNX_VER_RANGE(2,6,34, 4,10,0)
+ sb->s_bdi = &noop_backing_dev_info;
+# endif
+#endif
+}
+
+
+/**
+ * Creates the root inode and attaches it to the super block.
+ *
+ * @returns 0 on success, negative errno on failure.
+ * @param sb The super block.
+ * @param pSuperInfo Our super block info.
+ */
+static int vbsf_create_root_inode(struct super_block *sb, struct vbsf_super_info *pSuperInfo)
+{
+ SHFLFSOBJINFO fsinfo;
+ int rc;
+
+ /*
+ * Allocate and initialize the memory for our inode info structure.
+ */
+ struct vbsf_inode_info *sf_i = kmalloc(sizeof(*sf_i), GFP_KERNEL);
+ SHFLSTRING *path = kmalloc(sizeof(SHFLSTRING) + 1, GFP_KERNEL);
+ if (sf_i && path) {
+ sf_i->handle = SHFL_HANDLE_NIL;
+ sf_i->force_restat = false;
+ RTListInit(&sf_i->HandleList);
+#ifdef VBOX_STRICT
+ sf_i->u32Magic = SF_INODE_INFO_MAGIC;
+#endif
+ sf_i->path = path;
+
+ path->u16Length = 1;
+ path->u16Size = 2;
+ path->String.utf8[0] = '/';
+ path->String.utf8[1] = 0;
+
+ /*
+ * Stat the root directory (for inode info).
+ */
+ rc = vbsf_stat(__func__, pSuperInfo, sf_i->path, &fsinfo, 0);
+ if (rc == 0) {
+ /*
+ * Create the actual inode structure.
+ * Note! ls -la does display '.' and '..' entries with st_ino == 0, so root is #1.
+ */
+#if RTLNX_VER_MIN(2,4,25)
+ struct inode *iroot = iget_locked(sb, 1);
+#else
+ struct inode *iroot = iget(sb, 1);
+#endif
+ if (iroot) {
+ vbsf_init_inode(iroot, sf_i, &fsinfo, pSuperInfo);
+ VBSF_SET_INODE_INFO(iroot, sf_i);
+
+#if RTLNX_VER_MIN(2,4,25)
+ unlock_new_inode(iroot);
+#endif
+
+ /*
+ * Now make it a root inode.
+ */
+#if RTLNX_VER_MIN(3,4,0)
+ sb->s_root = d_make_root(iroot);
+#else
+ sb->s_root = d_alloc_root(iroot);
+#endif
+ if (sb->s_root) {
+
+ return 0;
+ }
+
+ SFLOGRELBOTH(("vboxsf: d_make_root failed!\n"));
+#if RTLNX_VER_MAX(3,4,0) /* d_make_root calls iput */
+ iput(iroot);
+#endif
+ /* iput() will call vbsf_evict_inode()/vbsf_clear_inode(). */
+ sf_i = NULL;
+ path = NULL;
+
+ rc = -ENOMEM;
+ } else {
+ SFLOGRELBOTH(("vboxsf: failed to allocate root inode!\n"));
+ rc = -ENOMEM;
+ }
+ } else
+ SFLOGRELBOTH(("vboxsf: could not stat root of share: %d\n", rc));
+ } else {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for root inode info!\n"));
+ rc = -ENOMEM;
+ }
+ if (sf_i)
+ kfree(sf_i);
+ if (path)
+ kfree(path);
+ return rc;
+}
+
+
+#if RTLNX_VER_MAX(5,1,0)
+static void vbsf_init_mount_info(struct vbsf_mount_info_new *mount_info,
+ const char *sf_name)
+{
+ mount_info->ttl = mount_info->msDirCacheTTL = mount_info->msInodeTTL = -1;
+ mount_info->dmode = mount_info->fmode = ~0U;
+ mount_info->enmCacheMode = kVbsfCacheMode_Strict;
+ mount_info->length = sizeof(struct vbsf_mount_info_new);
+ if (sf_name) {
+# if RTLNX_VER_MAX(2,5,69)
+ strncpy(mount_info->name, sf_name, sizeof(mount_info->name));
+ mount_info->name[sizeof(mount_info->name)-1] = 0;
+# else
+ strlcpy(mount_info->name, sf_name, sizeof(mount_info->name));
+# endif
+ }
+}
+#endif
+
+#if RTLNX_VER_RANGE(2,6,0, 5,1,0)
+/**
+ * The following section of code uses the Linux match_token() family of
+ * routines to parse string-based mount options.
+ */
+enum {
+ Opt_iocharset, /* nls_name[] */
+ Opt_nls, /* alias for iocharset */
+ Opt_uid,
+ Opt_gid,
+ Opt_ttl,
+ Opt_dmode,
+ Opt_fmode,
+ Opt_dmask,
+ Opt_fmask,
+ Opt_umask,
+ Opt_maxiopages,
+ Opt_dirbuf,
+ Opt_dcachettl,
+ Opt_inodettl,
+ Opt_cachemode, /* enum vbsf_cache_mode */
+ Opt_tag,
+ Opt_err
+};
+
+# if RTLNX_VER_MAX(2,6,28)
+static match_table_t vbsf_tokens = {
+# else
+static const match_table_t vbsf_tokens = {
+# endif
+ { Opt_iocharset, "iocharset=%s" },
+ { Opt_nls, "nls=%s" },
+ { Opt_uid, "uid=%u" },
+ { Opt_gid, "gid=%u" },
+ { Opt_ttl, "ttl=%u" },
+ { Opt_dmode, "dmode=%o" },
+ { Opt_fmode, "fmode=%o" },
+ { Opt_dmask, "dmask=%o" },
+ { Opt_fmask, "fmask=%o" },
+ { Opt_umask, "umask=%o" },
+ { Opt_maxiopages, "maxiopages=%u" },
+ { Opt_dirbuf, "dirbuf=%u" },
+ { Opt_dcachettl, "dcachettl=%u" },
+ { Opt_inodettl, "inodettl=%u" },
+ { Opt_cachemode, "cache=%s" },
+ { Opt_tag, "tag=%s" }, /* private option for automounter */
+ { Opt_err, NULL }
+};
+
+static int vbsf_parse_mount_options(char *options,
+ struct vbsf_mount_info_new *mount_info)
+{
+ substring_t args[MAX_OPT_ARGS];
+ int option;
+ int token;
+ char *p;
+ char *iocharset;
+ char *cachemode;
+ char *tag;
+
+ if (!options)
+ return -EINVAL;
+
+ while ((p = strsep(&options, ",")) != NULL) {
+ if (!*p)
+ continue;
+
+ token = match_token(p, vbsf_tokens, args);
+ switch (token) {
+ case Opt_iocharset:
+ case Opt_nls:
+ iocharset = match_strdup(&args[0]);
+ if (!iocharset) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for iocharset!\n"));
+ return -ENOMEM;
+ }
+ strlcpy(mount_info->nls_name, iocharset,
+ sizeof(mount_info->nls_name));
+ kfree(iocharset);
+ break;
+ case Opt_uid:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->uid = option;
+ break;
+ case Opt_gid:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->gid = option;
+ break;
+ case Opt_ttl:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->ttl = option;
+ break;
+ case Opt_dmode:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->dmode = option;
+ break;
+ case Opt_fmode:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->fmode = option;
+ break;
+ case Opt_dmask:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->dmask = option;
+ break;
+ case Opt_fmask:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->fmask = option;
+ break;
+ case Opt_umask:
+ if (match_octal(&args[0], &option))
+ return -EINVAL;
+ mount_info->dmask = mount_info->fmask = option;
+ break;
+ case Opt_maxiopages:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->cMaxIoPages = option;
+ break;
+ case Opt_dirbuf:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->cbDirBuf = option;
+ break;
+ case Opt_dcachettl:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->msDirCacheTTL = option;
+ break;
+ case Opt_inodettl:
+ if (match_int(&args[0], &option))
+ return -EINVAL;
+ mount_info->msInodeTTL = option;
+ break;
+ case Opt_cachemode: {
+ cachemode = match_strdup(&args[0]);
+ if (!cachemode) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for cachemode!\n"));
+ return -ENOMEM;
+ }
+ if (!strcmp(cachemode, "default") || !strcmp(cachemode, "strict"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Strict;
+ else if (!strcmp(cachemode, "none"))
+ mount_info->enmCacheMode = kVbsfCacheMode_None;
+ else if (!strcmp(cachemode, "read"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Read;
+ else if (!strcmp(cachemode, "readwrite"))
+ mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite;
+ else
+ printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", cachemode);
+ kfree(cachemode);
+ break;
+ }
+ case Opt_tag:
+ tag = match_strdup(&args[0]);
+ if (!tag) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for automount tag!\n"));
+ return -ENOMEM;
+ }
+ strlcpy(mount_info->szTag, tag, sizeof(mount_info->szTag));
+ kfree(tag);
+ break;
+ default:
+ printk(KERN_ERR "unrecognised mount option \"%s\"", p);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+#endif /* 5.1.0 > version >= 2.6.0 */
+
+
+#if RTLNX_VER_MAX(2,6,0)
+/**
+ * Linux kernel versions older than 2.6.0 don't have the match_token() routines
+ * so we parse the string-based mount options manually here.
+ */
+static int vbsf_parse_mount_options(char *options,
+ struct vbsf_mount_info_new *mount_info)
+{
+ char *value;
+ char *option;
+
+ if (!options)
+ return -EINVAL;
+
+# if RTLNX_VER_MIN(2,3,9)
+ while ((option = strsep(&options, ",")) != NULL) {
+# else
+ for (option = strtok(options, ","); option; option = strtok(NULL, ",")) {
+# endif
+ if (!*option)
+ continue;
+
+ value = strchr(option, '=');
+ if (value)
+ *value++ = '\0';
+
+ if (!strcmp(option, "iocharset") || !strcmp(option, "nls")) {
+ if (!value || !*value)
+ return -EINVAL;
+ strncpy(mount_info->nls_name, value, sizeof(mount_info->nls_name));
+ mount_info->nls_name[sizeof(mount_info->nls_name)-1] = 0;
+ } else if (!strcmp(option, "uid")) {
+ mount_info->uid = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "gid")) {
+ mount_info->gid = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "ttl")) {
+ mount_info->ttl = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dmode")) {
+ mount_info->dmode = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "fmode")) {
+ mount_info->fmode = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dmask")) {
+ mount_info->dmask = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "fmask")) {
+ mount_info->fmask = simple_strtoul(value, &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "umask")) {
+ mount_info->dmask = mount_info->fmask = simple_strtoul(value,
+ &value, 8);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "maxiopages")) {
+ mount_info->cMaxIoPages = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dirbuf")) {
+ mount_info->cbDirBuf = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "dcachettl")) {
+ mount_info->msDirCacheTTL = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "inodettl")) {
+ mount_info->msInodeTTL = simple_strtoul(value, &value, 0);
+ if (*value)
+ return -EINVAL;
+ } else if (!strcmp(option, "cache")) {
+ if (!value || !*value)
+ return -EINVAL;
+ if (!strcmp(value, "default") || !strcmp(value, "strict"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Strict;
+ else if (!strcmp(value, "none"))
+ mount_info->enmCacheMode = kVbsfCacheMode_None;
+ else if (!strcmp(value, "read"))
+ mount_info->enmCacheMode = kVbsfCacheMode_Read;
+ else if (!strcmp(value, "readwrite"))
+ mount_info->enmCacheMode = kVbsfCacheMode_ReadWrite;
+ else
+ printk(KERN_WARNING "vboxsf: cache mode (%s) is out of range, using default instead.\n", value);
+ } else if (!strcmp(option, "tag")) {
+ if (!value || !*value)
+ return -EINVAL;
+ strncpy(mount_info->szTag, value, sizeof(mount_info->szTag));
+ mount_info->szTag[sizeof(mount_info->szTag)-1] = 0;
+ } else if (!strcmp(option, "sf_name")) {
+ if (!value || !*value)
+ return -EINVAL;
+ strncpy(mount_info->name, value, sizeof(mount_info->name));
+ mount_info->name[sizeof(mount_info->name)-1] = 0;
+ } else {
+ printk(KERN_ERR "unrecognised mount option \"%s\"", option);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+
+/**
+ * This is called by vbsf_read_super_24(), vbsf_read_super_26(), and
+ * vbsf_get_tree() when vfs mounts the fs and wants to read the super_block.
+ *
+ * Calls vbsf_super_info_alloc_and_map_it() to map the folder and allocate super
+ * information structure.
+ *
+ * Initializes @a sb, initializes root inode and dentry.
+ *
+ * Should respect @a flags.
+ */
+#if RTLNX_VER_MIN(5,1,0)
+static int vbsf_read_super_aux(struct super_block *sb, struct fs_context *fc)
+#else
+static int vbsf_read_super_aux(struct super_block *sb, void *data, int flags)
+#endif
+{
+ int rc;
+ struct vbsf_super_info *pSuperInfo;
+
+ TRACE();
+#if RTLNX_VER_MAX(5,1,0)
+ if (!data) {
+ SFLOGRELBOTH(("vboxsf: No mount data. Is mount.vboxsf installed (typically in /sbin)?\n"));
+ return -EINVAL;
+ }
+
+ if (flags & MS_REMOUNT) {
+ SFLOGRELBOTH(("vboxsf: Remounting is not supported!\n"));
+ return -ENOSYS;
+ }
+#endif
+
+ /*
+ * Create our super info structure and map the shared folder.
+ */
+#if RTLNX_VER_MIN(5,1,0)
+ struct vbsf_mount_info_new *info = fc->fs_private;
+ rc = vbsf_super_info_alloc_and_map_it(info, &pSuperInfo);
+#else
+ rc = vbsf_super_info_alloc_and_map_it((struct vbsf_mount_info_new *)data, &pSuperInfo);
+#endif
+ if (rc == 0) {
+ /*
+ * Initialize the super block structure (must be done before
+ * root inode creation).
+ */
+ sb->s_magic = 0xface;
+ sb->s_blocksize = 1024;
+#if RTLNX_VER_MIN(2,4,3)
+ /* Required for seek/sendfile (see 'loff_t max' in fs/read_write.c / do_sendfile()). */
+# if defined MAX_LFS_FILESIZE
+ sb->s_maxbytes = MAX_LFS_FILESIZE;
+# elif BITS_PER_LONG == 32
+ sb->s_maxbytes = (loff_t)ULONG_MAX << PAGE_SHIFT;
+# else
+ sb->s_maxbytes = INT64_MAX;
+# endif
+#endif
+#if RTLNX_VER_MIN(2,6,11)
+ sb->s_time_gran = 1; /* This might be a little optimistic for windows hosts, where it should be 100. */
+#endif
+ sb->s_op = &g_vbsf_super_ops;
+#if RTLNX_VER_MIN(2,6,38)
+ sb->s_d_op = &vbsf_dentry_ops;
+#endif
+
+ /*
+ * Initialize the backing device. This is important for memory mapped
+ * files among other things.
+ */
+ rc = vbsf_init_backing_dev(sb, pSuperInfo);
+ if (rc == 0) {
+ /*
+ * Create the root inode and we're done.
+ */
+ rc = vbsf_create_root_inode(sb, pSuperInfo);
+ if (rc == 0) {
+ VBSF_SET_SUPER_INFO(sb, pSuperInfo);
+ SFLOGFLOW(("vbsf_read_super_aux: returns successfully\n"));
+ return 0;
+ }
+ vbsf_done_backing_dev(sb, pSuperInfo);
+ } else
+ SFLOGRELBOTH(("vboxsf: backing device information initialization failed: %d\n", rc));
+ vbsf_super_info_free(pSuperInfo);
+ }
+ return rc;
+}
+
+
+/**
+ * This is called when vfs is about to destroy the @a inode.
+ *
+ * We must free the inode info structure here.
+ */
+#if RTLNX_VER_MIN(2,6,36)
+static void vbsf_evict_inode(struct inode *inode)
+#else
+static void vbsf_clear_inode(struct inode *inode)
+#endif
+{
+ struct vbsf_inode_info *sf_i;
+
+ TRACE();
+
+ /*
+ * Flush stuff.
+ */
+#if RTLNX_VER_MIN(2,6,36)
+ truncate_inode_pages(&inode->i_data, 0);
+# if RTLNX_VER_MIN(3,5,0)
+ clear_inode(inode);
+# else
+ end_writeback(inode);
+# endif
+#endif
+ /*
+ * Clean up our inode info.
+ */
+ sf_i = VBSF_GET_INODE_INFO(inode);
+ if (sf_i) {
+ VBSF_SET_INODE_INFO(inode, NULL);
+
+ Assert(sf_i->u32Magic == SF_INODE_INFO_MAGIC);
+ BUG_ON(!sf_i->path);
+ kfree(sf_i->path);
+ vbsf_handle_drop_chain(sf_i);
+# ifdef VBOX_STRICT
+ sf_i->u32Magic = SF_INODE_INFO_MAGIC_DEAD;
+# endif
+ kfree(sf_i);
+ }
+}
+
+
+/* this is called by vfs when it wants to populate [inode] with data.
+ the only thing that is known about inode at this point is its index
+ hence we can't do anything here, and let lookup/whatever with the
+ job to properly fill then [inode] */
+#if RTLNX_VER_MAX(2,6,25)
+static void vbsf_read_inode(struct inode *inode)
+{
+}
+#endif
+
+
+/* vfs is done with [sb] (umount called) call [vbsf_super_info_free] to unmap
+ the folder and free [pSuperInfo] */
+static void vbsf_put_super(struct super_block *sb)
+{
+ struct vbsf_super_info *pSuperInfo;
+
+ pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ BUG_ON(!pSuperInfo);
+ vbsf_done_backing_dev(sb, pSuperInfo);
+ vbsf_super_info_free(pSuperInfo);
+}
+
+
+/**
+ * Get file system statistics.
+ */
+#if RTLNX_VER_MIN(2,6,18)
+static int vbsf_statfs(struct dentry *dentry, struct kstatfs *stat)
+#elif RTLNX_VER_MIN(2,5,73)
+static int vbsf_statfs(struct super_block *sb, struct kstatfs *stat)
+#else
+static int vbsf_statfs(struct super_block *sb, struct statfs *stat)
+#endif
+{
+#if RTLNX_VER_MIN(2,6,18)
+ struct super_block *sb = dentry->d_inode->i_sb;
+#endif
+ int rc;
+ VBOXSFVOLINFOREQ *pReq = (VBOXSFVOLINFOREQ *)VbglR0PhysHeapAlloc(sizeof(*pReq));
+ if (pReq) {
+ SHFLVOLINFO *pVolInfo = &pReq->VolInfo;
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ rc = VbglR0SfHostReqQueryVolInfo(pSuperInfo->map.root, pReq, SHFL_HANDLE_ROOT);
+ if (RT_SUCCESS(rc)) {
+ stat->f_type = UINT32_C(0x786f4256); /* 'VBox' little endian */
+ stat->f_bsize = pVolInfo->ulBytesPerAllocationUnit;
+#if RTLNX_VER_MIN(2,5,73)
+ stat->f_frsize = pVolInfo->ulBytesPerAllocationUnit;
+#endif
+ stat->f_blocks = pVolInfo->ullTotalAllocationBytes
+ / pVolInfo->ulBytesPerAllocationUnit;
+ stat->f_bfree = pVolInfo->ullAvailableAllocationBytes
+ / pVolInfo->ulBytesPerAllocationUnit;
+ stat->f_bavail = pVolInfo->ullAvailableAllocationBytes
+ / pVolInfo->ulBytesPerAllocationUnit;
+ stat->f_files = 1000;
+ stat->f_ffree = 1000000; /* don't return 0 here since the guest may think
+ * that it is not possible to create any more files */
+ stat->f_fsid.val[0] = 0;
+ stat->f_fsid.val[1] = 0;
+ stat->f_namelen = 255;
+#if RTLNX_VER_MIN(2,6,36)
+ stat->f_flags = 0; /* not valid */
+#endif
+ RT_ZERO(stat->f_spare);
+ rc = 0;
+ } else
+ rc = -RTErrConvertToErrno(rc);
+ VbglR0PhysHeapFree(pReq);
+ } else
+ rc = -ENOMEM;
+ return rc;
+}
+
+#if RTLNX_VER_MIN(5,1,0)
+static int vbsf_remount_fs(struct super_block *sb,
+ struct vbsf_mount_info_new *info)
+#else
+static int vbsf_remount_fs(struct super_block *sb, int *flags, char *data)
+#endif
+{
+#if RTLNX_VER_MIN(2,4,23)
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ struct vbsf_inode_info *sf_i;
+ struct inode *iroot;
+ SHFLFSOBJINFO fsinfo;
+ int err;
+ Assert(pSuperInfo);
+
+# if RTLNX_VER_MIN(5,1,0)
+ vbsf_super_info_copy_remount_options(pSuperInfo, info);
+# else
+ if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ vbsf_super_info_copy_remount_options(pSuperInfo, (struct vbsf_mount_info_new *)data);
+ } else {
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+ vbsf_init_mount_info(&mount_opts, NULL);
+ err = vbsf_parse_mount_options(data, &mount_opts);
+ if (err)
+ return err;
+ vbsf_super_info_copy_remount_options(pSuperInfo, &mount_opts);
+ }
+# endif
+
+ /* '.' and '..' entries are st_ino == 0 so root is #1 */
+ iroot = ilookup(sb, 1);
+ if (!iroot)
+ return -ENOSYS;
+
+ sf_i = VBSF_GET_INODE_INFO(iroot);
+ err = vbsf_stat(__func__, pSuperInfo, sf_i->path, &fsinfo, 0);
+ BUG_ON(err != 0);
+ vbsf_init_inode(iroot, sf_i, &fsinfo, pSuperInfo);
+ iput(iroot);
+ return 0;
+#else /* < 2.4.23 */
+ return -ENOSYS;
+#endif /* < 2.4.23 */
+}
+
+
+/**
+ * Show mount options.
+ *
+ * This is needed by the VBoxService automounter in order for it to pick up
+ * the the 'szTag' option value it sets on its mount.
+ */
+#if RTLNX_VER_MAX(3,3,0)
+static int vbsf_show_options(struct seq_file *m, struct vfsmount *mnt)
+#else
+static int vbsf_show_options(struct seq_file *m, struct dentry *root)
+#endif
+{
+#if RTLNX_VER_MAX(3,3,0)
+ struct super_block *sb = mnt->mnt_sb;
+#else
+ struct super_block *sb = root->d_sb;
+#endif
+ struct vbsf_super_info *pSuperInfo = VBSF_GET_SUPER_INFO(sb);
+ if (pSuperInfo) {
+ /* Performance related options: */
+ if (pSuperInfo->msTTL != -1)
+ seq_printf(m, ",ttl=%d", pSuperInfo->msTTL);
+ if (pSuperInfo->msDirCacheTTL >= 0)
+ seq_printf(m, ",dcachettl=%d", pSuperInfo->msDirCacheTTL);
+ if (pSuperInfo->msInodeTTL >= 0)
+ seq_printf(m, ",inodettl=%d", pSuperInfo->msInodeTTL);
+ if (pSuperInfo->cMaxIoPages != VBSF_DEFAULT_MAX_IO_PAGES)
+ seq_printf(m, ",maxiopages=%u", pSuperInfo->cMaxIoPages);
+ if (pSuperInfo->cbDirBuf != VBSF_DEFAULT_DIR_BUF_SIZE)
+ seq_printf(m, ",dirbuf=%u", pSuperInfo->cbDirBuf);
+ switch (pSuperInfo->enmCacheMode) {
+ default: AssertFailed(); RT_FALL_THRU();
+ case kVbsfCacheMode_Strict:
+ break;
+ case kVbsfCacheMode_None: seq_puts(m, ",cache=none"); break;
+ case kVbsfCacheMode_Read: seq_puts(m, ",cache=read"); break;
+ case kVbsfCacheMode_ReadWrite: seq_puts(m, ",cache=readwrite"); break;
+ }
+
+ /* Attributes and NLS: */
+ seq_printf(m, ",iocharset=%s", pSuperInfo->nls ? pSuperInfo->nls->charset : "utf8");
+ seq_printf(m, ",uid=%u,gid=%u", pSuperInfo->uid, pSuperInfo->gid);
+ if (pSuperInfo->dmode != ~0)
+ seq_printf(m, ",dmode=0%o", pSuperInfo->dmode);
+ if (pSuperInfo->fmode != ~0)
+ seq_printf(m, ",fmode=0%o", pSuperInfo->fmode);
+ if (pSuperInfo->dmask != 0)
+ seq_printf(m, ",dmask=0%o", pSuperInfo->dmask);
+ if (pSuperInfo->fmask != 0)
+ seq_printf(m, ",fmask=0%o", pSuperInfo->fmask);
+
+ /* Misc: */
+ if (pSuperInfo->szTag[0] != '\0') {
+ seq_puts(m, ",tag=");
+ seq_escape(m, pSuperInfo->szTag, " \t\n\\");
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Super block operations.
+ */
+static struct super_operations g_vbsf_super_ops = {
+#if RTLNX_VER_MAX(2,6,36)
+ .clear_inode = vbsf_clear_inode,
+#else
+ .evict_inode = vbsf_evict_inode,
+#endif
+#if RTLNX_VER_MAX(2,6,25)
+ .read_inode = vbsf_read_inode,
+#endif
+ .put_super = vbsf_put_super,
+ .statfs = vbsf_statfs,
+#if RTLNX_VER_MAX(5,1,0)
+ .remount_fs = vbsf_remount_fs,
+#endif
+ .show_options = vbsf_show_options
+};
+
+
+
+/*********************************************************************************************************************************
+* File system type related stuff. *
+*********************************************************************************************************************************/
+
+#if RTLNX_VER_RANGE(2,5,4, 5,1,0)
+
+static int vbsf_read_super_26(struct super_block *sb, void *data, int flags)
+{
+ int err;
+
+ TRACE();
+ err = vbsf_read_super_aux(sb, data, flags);
+ if (err)
+ printk(KERN_DEBUG "vbsf_read_super_aux err=%d\n", err);
+
+ return err;
+}
+
+# if RTLNX_VER_MIN(2,6,39)
+static struct dentry *sf_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
+{
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, dev_name);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return ERR_PTR(rc);
+ return mount_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26);
+ } else {
+ return mount_nodev(fs_type, flags, data, vbsf_read_super_26);
+ }
+}
+# elif RTLNX_VER_MIN(2,6,18)
+static int vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data, struct vfsmount *mnt)
+{
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, dev_name);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return rc;
+ return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26,
+ mnt);
+ } else {
+ return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26, mnt);
+ }
+}
+# else /* 2.6.18 > version >= 2.5.4 */
+static struct super_block *vbsf_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data)
+{
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, dev_name);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return ERR_PTR(rc);
+ return get_sb_nodev(fs_type, flags, &mount_opts, vbsf_read_super_26);
+ } else {
+ return get_sb_nodev(fs_type, flags, data, vbsf_read_super_26);
+ }
+}
+# endif
+#endif /* 5.1.0 > version >= 2.5.4 */
+
+#if RTLNX_VER_MAX(2,5,4) /* < 2.5.4 */
+
+static struct super_block *vbsf_read_super_24(struct super_block *sb, void *data, int flags)
+{
+ int err;
+
+ TRACE();
+
+ if (!VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ int rc;
+ struct vbsf_mount_info_new mount_opts = { '\0' };
+
+ vbsf_init_mount_info(&mount_opts, NULL);
+ rc = vbsf_parse_mount_options(data, &mount_opts);
+ if (rc)
+ return ERR_PTR(rc);
+ err = vbsf_read_super_aux(sb, &mount_opts, flags);
+ } else {
+ err = vbsf_read_super_aux(sb, data, flags);
+ }
+ if (err) {
+ printk(KERN_DEBUG "vbsf_read_super_aux err=%d\n", err);
+ return NULL;
+ }
+
+ return sb;
+}
+
+static DECLARE_FSTYPE(g_vboxsf_fs_type, "vboxsf", vbsf_read_super_24, 0);
+
+#endif /* < 2.5.4 */
+
+#if RTLNX_VER_MIN(5,1,0)
+
+/**
+ * The following section of code uses the Linux filesystem mount API (also
+ * known as the "filesystem context API") to parse string-based mount options.
+ * The API is described here:
+ * https://www.kernel.org/doc/Documentation/filesystems/mount_api.txt
+ */
+enum vbsf_cache_modes {
+ VBSF_CACHE_DEFAULT,
+ VBSF_CACHE_NONE,
+ VBSF_CACHE_STRICT,
+ VBSF_CACHE_READ,
+ VBSF_CACHE_RW
+};
+
+static const struct constant_table vbsf_param_cache_mode[] = {
+ { "default", VBSF_CACHE_DEFAULT },
+ { "none", VBSF_CACHE_NONE },
+ { "strict", VBSF_CACHE_STRICT },
+ { "read", VBSF_CACHE_READ },
+ { "readwrite", VBSF_CACHE_RW },
+ {}
+};
+
+enum {
+ Opt_iocharset, /* nls_name[] */
+ Opt_nls, /* alias for iocharset */
+ Opt_uid,
+ Opt_gid,
+ Opt_ttl,
+ Opt_dmode,
+ Opt_fmode,
+ Opt_dmask,
+ Opt_fmask,
+ Opt_umask,
+ Opt_maxiopages,
+ Opt_dirbuf,
+ Opt_dcachettl,
+ Opt_inodettl,
+ Opt_cachemode, /* enum vbsf_cache_mode */
+ Opt_tag
+};
+
+# if RTLNX_VER_MAX(5,6,0)
+static const struct fs_parameter_spec vbsf_fs_specs[] = {
+# else
+static const struct fs_parameter_spec vbsf_fs_parameters[] = {
+# endif
+ fsparam_string("iocharset", Opt_iocharset),
+ fsparam_string("nls", Opt_nls),
+ fsparam_u32 ("uid", Opt_uid),
+ fsparam_u32 ("gid", Opt_gid),
+ fsparam_u32 ("ttl", Opt_ttl),
+ fsparam_u32oct("dmode", Opt_dmode),
+ fsparam_u32oct("fmode", Opt_fmode),
+ fsparam_u32oct("dmask", Opt_dmask),
+ fsparam_u32oct("fmask", Opt_fmask),
+ fsparam_u32oct("umask", Opt_umask),
+ fsparam_u32 ("maxiopages", Opt_maxiopages),
+ fsparam_u32 ("dirbuf", Opt_dirbuf),
+ fsparam_u32 ("dcachettl", Opt_dcachettl),
+ fsparam_u32 ("inodettl", Opt_inodettl),
+# if RTLNX_VER_MAX(5,6,0)
+ fsparam_enum ("cache", Opt_cachemode),
+# else
+ fsparam_enum ("cache", Opt_cachemode, vbsf_param_cache_mode),
+# endif
+ fsparam_string("tag", Opt_tag),
+ {}
+};
+
+# if RTLNX_VER_MAX(5,6,0)
+static const struct fs_parameter_enum vbsf_fs_enums[] = {
+ { Opt_cachemode, "default", VBSF_CACHE_DEFAULT },
+ { Opt_cachemode, "none", VBSF_CACHE_NONE },
+ { Opt_cachemode, "strict", VBSF_CACHE_STRICT },
+ { Opt_cachemode, "read", VBSF_CACHE_READ },
+ { Opt_cachemode, "readwrite", VBSF_CACHE_RW },
+ {}
+};
+
+static const struct fs_parameter_description vbsf_fs_parameters = {
+ .name = "vboxsf",
+ .specs = vbsf_fs_specs,
+ .enums = vbsf_fs_enums
+};
+# endif
+
+/**
+ * Parse the (string-based) mount options passed in as -o foo,bar=123,etc.
+ */
+static int vbsf_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+ struct fs_parse_result result;
+ struct vbsf_mount_info_new *info = fc->fs_private;
+ int opt;
+
+# if RTLNX_VER_MAX(5,6,0)
+ opt = fs_parse(fc, &vbsf_fs_parameters, param, &result);
+# else
+ opt = fs_parse(fc, vbsf_fs_parameters, param, &result);
+# endif
+ if (opt < 0)
+ return opt;
+
+ switch (opt) {
+ case Opt_iocharset:
+ case Opt_nls:
+ strlcpy(info->nls_name, param->string, sizeof(info->nls_name));
+ break;
+ case Opt_uid:
+ info->uid = result.uint_32;
+ break;
+ case Opt_gid:
+ info->gid = result.uint_32;
+ break;
+ case Opt_ttl:
+ info->ttl = result.uint_32;
+ break;
+ case Opt_dmode:
+ if (result.uint_32 & ~0777)
+ return invalf(fc, "Invalid dmode specified: '%o'", result.uint_32);
+ info->dmode = result.uint_32;
+ break;
+ case Opt_fmode:
+ if (result.uint_32 & ~0777)
+ return invalf(fc, "Invalid fmode specified: '%o'", result.uint_32);
+ info->fmode = result.uint_32;
+ break;
+ case Opt_dmask:
+ if (result.uint_32 & ~07777)
+ return invalf(fc, "Invalid dmask specified: '%o'", result.uint_32);
+ info->dmask = result.uint_32;
+ break;
+ case Opt_fmask:
+ if (result.uint_32 & ~07777)
+ return invalf(fc, "Invalid fmask specified: '%o'", result.uint_32);
+ info->fmask = result.uint_32;
+ break;
+ case Opt_umask:
+ if (result.uint_32 & ~07777)
+ return invalf(fc, "Invalid umask specified: '%o'", result.uint_32);
+ info->dmask = info->fmask = result.uint_32;
+ break;
+ case Opt_maxiopages:
+ info->cMaxIoPages = result.uint_32;
+ break;
+ case Opt_dirbuf:
+ info->cbDirBuf = result.uint_32;
+ break;
+ case Opt_dcachettl:
+ info->msDirCacheTTL = result.uint_32;
+ break;
+ case Opt_inodettl:
+ info->msInodeTTL = result.uint_32;
+ break;
+ case Opt_cachemode:
+ if (result.uint_32 == VBSF_CACHE_DEFAULT || result.uint_32 == VBSF_CACHE_STRICT)
+ info->enmCacheMode = kVbsfCacheMode_Strict;
+ else if (result.uint_32 == VBSF_CACHE_NONE)
+ info->enmCacheMode = kVbsfCacheMode_None;
+ else if (result.uint_32 == VBSF_CACHE_READ)
+ info->enmCacheMode = kVbsfCacheMode_Read;
+ else if (result.uint_32 == VBSF_CACHE_RW)
+ info->enmCacheMode = kVbsfCacheMode_ReadWrite;
+ else
+ printk(KERN_WARNING "vboxsf: cache mode (%u) is out of range, using default instead.\n", result.uint_32);
+ break;
+ case Opt_tag:
+ strlcpy(info->szTag, param->string, sizeof(info->szTag));
+ break;
+ default:
+ return invalf(fc, "Invalid mount option: '%s'", param->key);
+ }
+
+ return 0;
+}
+
+/**
+ * Parse the mount options provided whether by the mount.vboxsf utility
+ * which supplies the mount information as a page of data or else as a
+ * string in the following format: key[=val][,key[=val]]*.
+ */
+static int vbsf_parse_monolithic(struct fs_context *fc, void *data)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ if (data) {
+ if (VBSF_IS_MOUNT_VBOXSF_DATA(data)) {
+ RT_BCOPY_UNFORTIFIED(info, data, sizeof(struct vbsf_mount_info_new));
+ } else {
+ /* this will call vbsf_parse_param() */
+ return generic_parse_monolithic(fc, data);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Clean up the filesystem-specific part of the filesystem context.
+ */
+static void vbsf_free_ctx(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ if (info) {
+ kfree(info);
+ fc->fs_private = NULL;
+ }
+}
+
+/**
+ * Create the mountable root and superblock which can then be used later for
+ * mounting the shared folder. The superblock is populated by
+ * vbsf_read_super_aux() which also sets up the shared folder mapping and the
+ * related paperwork in preparation for mounting the shared folder.
+ */
+static int vbsf_get_tree(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ if (!fc->source) {
+ SFLOGRELBOTH(("vboxsf: No shared folder specified\n"));
+ return invalf(fc, "vboxsf: No shared folder specified");
+ }
+
+ /* fc->source (the shared folder name) is set after vbsf_init_fs_ctx() */
+ strlcpy(info->name, fc->source, sizeof(info->name));
+
+# if RTLNX_VER_MAX(5,3,0)
+ return vfs_get_super(fc, vfs_get_independent_super, vbsf_read_super_aux);
+# else
+ return get_tree_nodev(fc, vbsf_read_super_aux);
+# endif
+}
+
+/**
+ * Reconfigures the superblock based on the mount information stored in the
+ * filesystem context. Called via '-o remount' (aka mount(2) with MS_REMOUNT)
+ * and is the equivalent of .fs_remount.
+ */
+static int vbsf_reconfigure(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+ struct super_block *sb = fc->root->d_sb;
+
+ return vbsf_remount_fs(sb, info);
+}
+
+static const struct fs_context_operations vbsf_context_ops = {
+ .parse_param = vbsf_parse_param,
+ .parse_monolithic = vbsf_parse_monolithic,
+ .free = vbsf_free_ctx,
+ .get_tree = vbsf_get_tree,
+ .reconfigure = vbsf_reconfigure
+};
+
+/**
+ * Set up the filesystem mount context.
+ */
+static int vbsf_init_fs_context(struct fs_context *fc)
+{
+ struct vbsf_mount_info_new *info = fc->fs_private;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ SFLOGRELBOTH(("vboxsf: Could not allocate memory for mount options\n"));
+ return -ENOMEM;
+ }
+
+ /* set default values for the mount information structure */
+ info->ttl = info->msDirCacheTTL = info->msInodeTTL = -1;
+ info->dmode = info->fmode = ~0U;
+ info->enmCacheMode = kVbsfCacheMode_Strict;
+ info->length = sizeof(struct vbsf_mount_info_new);
+
+ fc->fs_private = info;
+ fc->ops = &vbsf_context_ops;
+
+ return 0;
+}
+#endif /* >= 5.1.0 */
+
+
+#if RTLNX_VER_MIN(2,5,4)
+/**
+ * File system registration structure.
+ */
+static struct file_system_type g_vboxsf_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "vboxsf",
+# if RTLNX_VER_MIN(5,1,0)
+ .init_fs_context = vbsf_init_fs_context,
+# if RTLNX_VER_MAX(5,6,0)
+ .parameters = &vbsf_fs_parameters,
+# else
+ .parameters = vbsf_fs_parameters,
+# endif
+# elif RTLNX_VER_MIN(2,6,39)
+ .mount = sf_mount,
+# else
+ .get_sb = vbsf_get_sb,
+# endif
+ .kill_sb = kill_anon_super
+};
+#endif /* >= 2.5.4 */
+
+
+/*********************************************************************************************************************************
+* Module stuff *
+*********************************************************************************************************************************/
+
+/**
+ * Called on module initialization.
+ */
+static int __init init(void)
+{
+ int rc;
+ SFLOGFLOW(("vboxsf: init\n"));
+
+ /*
+ * Must be paranoid about the vbsf_mount_info_new size.
+ */
+ AssertCompile(sizeof(struct vbsf_mount_info_new) <= PAGE_SIZE);
+ if (sizeof(struct vbsf_mount_info_new) > PAGE_SIZE) {
+ printk(KERN_ERR
+ "vboxsf: Mount information structure is too large %lu\n"
+ "vboxsf: Must be less than or equal to %lu\n",
+ (unsigned long)sizeof(struct vbsf_mount_info_new),
+ (unsigned long)PAGE_SIZE);
+ return -EINVAL;
+ }
+
+ /*
+ * Initialize stuff.
+ */
+ spin_lock_init(&g_SfHandleLock);
+ rc = VbglR0SfInit();
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Try connect to the shared folder HGCM service.
+ * It is possible it is not there.
+ */
+ rc = VbglR0SfConnect(&g_SfClient);
+ if (RT_SUCCESS(rc)) {
+ /*
+ * Query host HGCM features and afterwards (must be last) shared folder features.
+ */
+ rc = VbglR0QueryHostFeatures(&g_fHostFeatures);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("vboxsf: VbglR0QueryHostFeatures failed: rc=%Rrc (ignored)\n", rc));
+ g_fHostFeatures = 0;
+ }
+ VbglR0SfHostReqQueryFeaturesSimple(&g_fSfFeatures, &g_uSfLastFunction);
+ LogRel(("vboxsf: g_fHostFeatures=%#x g_fSfFeatures=%#RX64 g_uSfLastFunction=%u\n",
+ g_fHostFeatures, g_fSfFeatures, g_uSfLastFunction));
+
+ /*
+ * Tell the shared folder service about our expectations:
+ * - UTF-8 strings (rather than UTF-16)
+ * - Wheter to return or follow (default) symbolic links.
+ */
+ rc = VbglR0SfHostReqSetUtf8Simple();
+ if (RT_SUCCESS(rc)) {
+ if (!g_fFollowSymlinks) {
+ rc = VbglR0SfHostReqSetSymlinksSimple();
+ if (RT_FAILURE(rc))
+ printk(KERN_WARNING "vboxsf: Host unable to enable showing symlinks, rc=%d\n", rc);
+ }
+ /*
+ * Now that we're ready for action, try register the
+ * file system with the kernel.
+ */
+ rc = register_filesystem(&g_vboxsf_fs_type);
+ if (rc == 0) {
+ printk(KERN_INFO "vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) "\n");
+#ifdef VERMAGIC_STRING
+ LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n",
+ VERMAGIC_STRING, LINUX_VERSION_CODE));
+#elif defined(UTS_RELEASE)
+ LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " on %s (LINUX_VERSION_CODE=%#x)\n",
+ UTS_RELEASE, LINUX_VERSION_CODE));
+#else
+ LogRel(("vboxsf: Successfully loaded version " VBOX_VERSION_STRING " r" __stringify(VBOX_SVN_REV) " (LINUX_VERSION_CODE=%#x)\n", LINUX_VERSION_CODE));
+#endif
+ return 0;
+ }
+
+ /*
+ * Failed. Bail out.
+ */
+ LogRel(("vboxsf: register_filesystem failed: rc=%d\n", rc));
+ } else {
+ LogRel(("vboxsf: VbglR0SfSetUtf8 failed, rc=%Rrc\n", rc));
+ rc = -EPROTO;
+ }
+ VbglR0SfDisconnect(&g_SfClient);
+ } else {
+ LogRel(("vboxsf: VbglR0SfConnect failed, rc=%Rrc\n", rc));
+ rc = rc == VERR_HGCM_SERVICE_NOT_FOUND ? -EHOSTDOWN : -ECONNREFUSED;
+ }
+ VbglR0SfTerm();
+ } else {
+ LogRel(("vboxsf: VbglR0SfInit failed, rc=%Rrc\n", rc));
+ rc = -EPROTO;
+ }
+ return rc;
+}
+
+
+/**
+ * Called on module finalization.
+ */
+static void __exit fini(void)
+{
+ SFLOGFLOW(("vboxsf: fini\n"));
+
+ unregister_filesystem(&g_vboxsf_fs_type);
+ VbglR0SfDisconnect(&g_SfClient);
+ VbglR0SfTerm();
+}
+
+
+/*
+ * Module parameters.
+ */
+#if RTLNX_VER_MIN(2,5,52)
+module_param_named(follow_symlinks, g_fFollowSymlinks, int, 0);
+MODULE_PARM_DESC(follow_symlinks,
+ "Let host resolve symlinks rather than showing them");
+#endif
+
+
+/*
+ * Module declaration related bits.
+ */
+module_init(init);
+module_exit(fini);
+
+MODULE_DESCRIPTION(VBOX_PRODUCT " VFS Module for Host File System Access");
+MODULE_AUTHOR(VBOX_VENDOR);
+MODULE_LICENSE("GPL and additional rights");
+#ifdef MODULE_ALIAS_FS
+MODULE_ALIAS_FS("vboxsf");
+#endif
+#ifdef MODULE_VERSION
+MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV));
+#endif
+
diff --git a/src/VBox/Additions/linux/sharedfolders/vfsmod.h b/src/VBox/Additions/linux/sharedfolders/vfsmod.h
new file mode 100644
index 00000000..b39db721
--- /dev/null
+++ b/src/VBox/Additions/linux/sharedfolders/vfsmod.h
@@ -0,0 +1,483 @@
+/* $Id: vfsmod.h $ */
+/** @file
+ * vboxsf - Linux Shared Folders VFS, internal header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef GA_INCLUDED_SRC_linux_sharedfolders_vfsmod_h
+#define GA_INCLUDED_SRC_linux_sharedfolders_vfsmod_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#if 0 /* Enables strict checks. */
+# define RT_STRICT
+# define VBOX_STRICT
+#endif
+
+#define LOG_GROUP LOG_GROUP_SHARED_FOLDERS
+#include "the-linux-kernel.h"
+#include <iprt/list.h>
+#include <iprt/asm.h>
+#include <VBox/log.h>
+
+#if RTLNX_VER_MIN(2,6,0)
+# include <linux/backing-dev.h>
+#endif
+
+#include <VBox/VBoxGuestLibSharedFolders.h>
+#include <VBox/VBoxGuestLibSharedFoldersInline.h>
+#include <iprt/asm.h>
+#include "vbsfmount.h"
+
+
+/*
+ * Logging wrappers.
+ */
+#if 1
+# define TRACE() LogFunc(("tracepoint\n"))
+# define SFLOG(aArgs) Log(aArgs)
+# define SFLOGFLOW(aArgs) LogFlow(aArgs)
+# define SFLOG2(aArgs) Log2(aArgs)
+# define SFLOG3(aArgs) Log3(aArgs)
+# define SFLOGRELBOTH(aArgs) LogRel(aArgs)
+# ifdef LOG_ENABLED
+# define SFLOG_ENABLED 1
+# endif
+#else
+# define TRACE() RTLogBackdoorPrintf("%s: tracepoint\n", __FUNCTION__)
+# define SFLOG(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOGFLOW(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOG2(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOG3(aArgs) RTLogBackdoorPrintf aArgs
+# define SFLOG_ENABLED 1
+# define SFLOGRELBOTH(aArgs) do { RTLogBackdoorPrintf aArgs; printk aArgs; } while (0)
+#endif
+
+
+/*
+ * inode compatibility glue.
+ */
+#if RTLNX_VER_MAX(2,6,0)
+
+DECLINLINE(loff_t) i_size_read(struct inode *pInode)
+{
+ AssertCompile(sizeof(loff_t) == sizeof(uint64_t));
+ return ASMAtomicReadU64((uint64_t volatile *)&pInode->i_size);
+}
+
+DECLINLINE(void) i_size_write(struct inode *pInode, loff_t cbNew)
+{
+ AssertCompile(sizeof(pInode->i_size) == sizeof(uint64_t));
+ ASMAtomicWriteU64((uint64_t volatile *)&pInode->i_size, cbNew);
+}
+
+#endif /* < 2.6.0 */
+
+#if RTLNX_VER_MAX(3,2,0) && !RTLNX_RHEL_MIN(6, 10)
+DECLINLINE(void) set_nlink(struct inode *pInode, unsigned int cLinks)
+{
+ pInode->i_nlink = cLinks;
+}
+#endif
+
+
+/* global variables */
+extern VBGLSFCLIENT g_SfClient;
+extern spinlock_t g_SfHandleLock;
+extern uint32_t g_uSfLastFunction;
+extern uint64_t g_fSfFeatures;
+
+extern struct inode_operations vbsf_dir_iops;
+extern struct inode_operations vbsf_lnk_iops;
+extern struct inode_operations vbsf_reg_iops;
+extern struct file_operations vbsf_dir_fops;
+extern struct file_operations vbsf_reg_fops;
+extern struct dentry_operations vbsf_dentry_ops;
+extern struct address_space_operations vbsf_reg_aops;
+
+
+/**
+ * VBox specific per-mount (shared folder) information.
+ */
+struct vbsf_super_info {
+ VBGLSFMAP map;
+ struct nls_table *nls;
+ /** Set if the NLS table is UTF-8. */
+ bool fNlsIsUtf8;
+ int uid;
+ int gid;
+ int dmode;
+ int fmode;
+ int dmask;
+ int fmask;
+ /** Maximum number of pages to allow in an I/O buffer with the host.
+ * This applies to read and write operations. */
+ uint32_t cMaxIoPages;
+ /** The default directory buffer size. */
+ uint32_t cbDirBuf;
+ /** The time to live for directory entries in jiffies, zero if disabled. */
+ uint32_t cJiffiesDirCacheTTL;
+ /** The time to live for inode information in jiffies, zero if disabled. */
+ uint32_t cJiffiesInodeTTL;
+ /** The cache and coherency mode. */
+ enum vbsf_cache_mode enmCacheMode;
+ /** Mount tag for VBoxService automounter. @since 6.0 */
+ char szTag[32];
+#if RTLNX_VER_RANGE(2,6,0, 4,12,0)
+ /** The backing device info structure. */
+ struct backing_dev_info bdi;
+#endif
+ /** The mount option value for /proc/mounts. */
+ int32_t msTTL;
+ /** The time to live for directory entries in milliseconds, for /proc/mounts. */
+ int32_t msDirCacheTTL;
+ /** The time to live for inode information in milliseconds, for /proc/mounts. */
+ int32_t msInodeTTL;
+#if RTLNX_VER_RANGE(4,0,0, 4,2,0)
+ /** 4.0 and 4.1 are missing noop_backing_dev_info export, so take down the
+ * initial value so we can restore it in vbsf_done_backing_dev(). (paranoia) */
+ struct backing_dev_info *bdi_org;
+#endif
+};
+
+/* Following casts are here to prevent assignment of void * to
+ pointers of arbitrary type */
+#if RTLNX_VER_MAX(2,6,0)
+# define VBSF_GET_SUPER_INFO(sb) ((struct vbsf_super_info *)(sb)->u.generic_sbp)
+# define VBSF_SET_SUPER_INFO(sb, a_pSuperInfo) do { (sb)->u.generic_sbp = a_pSuperInfo; } while (0)
+#else
+# define VBSF_GET_SUPER_INFO(sb) ((struct vbsf_super_info *)(sb)->s_fs_info)
+# define VBSF_SET_SUPER_INFO(sb, a_pSuperInfo) do { (sb)->s_fs_info = a_pSuperInfo;} while (0)
+#endif
+
+
+/**
+ * For associating inodes with host handles.
+ *
+ * This is necessary for address_space_operations::vbsf_writepage and allows
+ * optimizing stat, lookups and other operations on open files and directories.
+ */
+struct vbsf_handle {
+ /** List entry (head vbsf_inode_info::HandleList). */
+ RTLISTNODE Entry;
+ /** Host file/whatever handle. */
+ SHFLHANDLE hHost;
+ /** VBSF_HANDLE_F_XXX */
+ uint32_t fFlags;
+ /** Reference counter.
+ * Close the handle and free the structure when it reaches zero. */
+ uint32_t volatile cRefs;
+#ifdef VBOX_STRICT
+ /** For strictness checks. */
+ struct vbsf_inode_info *pInodeInfo;
+#endif
+};
+
+/** @name VBSF_HANDLE_F_XXX - Handle summary flags (vbsf_handle::fFlags).
+ * @{ */
+#define VBSF_HANDLE_F_READ UINT32_C(0x00000001)
+#define VBSF_HANDLE_F_WRITE UINT32_C(0x00000002)
+#define VBSF_HANDLE_F_APPEND UINT32_C(0x00000004)
+#define VBSF_HANDLE_F_FILE UINT32_C(0x00000010)
+#define VBSF_HANDLE_F_DIR UINT32_C(0x00000020)
+#define VBSF_HANDLE_F_ON_LIST UINT32_C(0x00000080)
+#define VBSF_HANDLE_F_MAGIC_MASK UINT32_C(0xffffff00)
+#define VBSF_HANDLE_F_MAGIC UINT32_C(0x75030700) /**< Maurice Ravel (1875-03-07). */
+#define VBSF_HANDLE_F_MAGIC_DEAD UINT32_C(0x19371228)
+/** @} */
+
+
+/**
+ * VBox specific per-inode information.
+ */
+struct vbsf_inode_info {
+ /** Which file */
+ SHFLSTRING *path;
+ /** Some information was changed, update data on next revalidate */
+ bool force_restat;
+ /** The timestamp (jiffies) where the inode info was last updated. */
+ unsigned long ts_up_to_date;
+ /** The birth time. */
+ RTTIMESPEC BirthTime;
+
+ /** @name Host modification detection stats.
+ * @{ */
+ /** The raw modification time, for mapping invalidation purposes. */
+ RTTIMESPEC ModificationTime;
+ /** Copy of ModificationTime from the last time we wrote to the the file. */
+ RTTIMESPEC ModificationTimeAtOurLastWrite;
+ /** @} */
+
+ /** handle valid if a file was created with vbsf_create_worker until it will
+ * be opened with vbsf_reg_open()
+ * @todo r=bird: figure this one out... */
+ SHFLHANDLE handle;
+
+ /** List of open handles (struct vbsf_handle), protected by g_SfHandleLock. */
+ RTLISTANCHOR HandleList;
+#ifdef VBOX_STRICT
+ uint32_t u32Magic;
+# define SF_INODE_INFO_MAGIC UINT32_C(0x18620822) /**< Claude Debussy */
+# define SF_INODE_INFO_MAGIC_DEAD UINT32_C(0x19180325)
+#endif
+};
+
+#if RTLNX_VER_MIN(2,6,19) || defined(KERNEL_FC6)
+/* FC6 kernel 2.6.18, vanilla kernel 2.6.19+ */
+# define VBSF_GET_INODE_INFO(i) ((struct vbsf_inode_info *) (i)->i_private)
+# define VBSF_SET_INODE_INFO(i, sf_i) (i)->i_private = sf_i
+#else
+/* vanilla kernel up to 2.6.18 */
+# define VBSF_GET_INODE_INFO(i) ((struct vbsf_inode_info *) (i)->u.generic_ip)
+# define VBSF_SET_INODE_INFO(i, sf_i) (i)->u.generic_ip = sf_i
+#endif
+
+extern void vbsf_init_inode(struct inode *inode, struct vbsf_inode_info *sf_i, PSHFLFSOBJINFO info,
+ struct vbsf_super_info *pSuperInfo);
+extern void vbsf_update_inode(struct inode *pInode, struct vbsf_inode_info *pInodeInfo, PSHFLFSOBJINFO pObjInfo,
+ struct vbsf_super_info *pSuperInfo, bool fInodeLocked, unsigned fSetAttrs);
+extern int vbsf_inode_revalidate_worker(struct dentry *dentry, bool fForced, bool fInodeLocked);
+extern int vbsf_inode_revalidate_with_handle(struct dentry *dentry, SHFLHANDLE hHostFile, bool fForced, bool fInodeLocked);
+#if RTLNX_VER_MIN(2,5,18)
+# if RTLNX_VER_MIN(6,3,0)
+extern int vbsf_inode_getattr(struct mnt_idmap *idmap, const struct path *path,
+ struct kstat *kstat, u32 request_mask, unsigned int query_flags);
+# elif RTLNX_VER_MIN(5,12,0)
+extern int vbsf_inode_getattr(struct user_namespace *ns, const struct path *path,
+ struct kstat *kstat, u32 request_mask, unsigned int query_flags);
+# elif RTLNX_VER_MIN(4,11,0)
+extern int vbsf_inode_getattr(const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int query_flags);
+# else
+extern int vbsf_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *kstat);
+# endif
+#else /* < 2.5.44 */
+extern int vbsf_inode_revalidate(struct dentry *dentry);
+#endif /* < 2.5.44 */
+#if RTLNX_VER_MIN(6,3,0)
+extern int vbsf_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr);
+#elif RTLNX_VER_MIN(5,12,0)
+extern int vbsf_inode_setattr(struct user_namespace *ns, struct dentry *dentry, struct iattr *iattr);
+#else
+extern int vbsf_inode_setattr(struct dentry *dentry, struct iattr *iattr);
+#endif
+
+
+extern void vbsf_handle_drop_chain(struct vbsf_inode_info *pInodeInfo);
+extern struct vbsf_handle *vbsf_handle_find(struct vbsf_inode_info *pInodeInfo, uint32_t fFlagsSet, uint32_t fFlagsClear);
+extern uint32_t vbsf_handle_release_slow(struct vbsf_handle *pHandle, struct vbsf_super_info *pSuperInfo,
+ const char *pszCaller);
+extern void vbsf_handle_append(struct vbsf_inode_info *pInodeInfo, struct vbsf_handle *pHandle);
+
+/**
+ * Releases a handle.
+ *
+ * @returns New reference count.
+ * @param pHandle The handle to release.
+ * @param pSuperInfo The info structure for the shared folder associated
+ * with the handle.
+ * @param pszCaller The caller name (for logging failures).
+ */
+DECLINLINE(uint32_t) vbsf_handle_release(struct vbsf_handle *pHandle, struct vbsf_super_info *pSuperInfo, const char *pszCaller)
+{
+ uint32_t cRefs;
+
+ Assert((pHandle->fFlags & VBSF_HANDLE_F_MAGIC_MASK) == VBSF_HANDLE_F_MAGIC);
+ Assert(pHandle->pInodeInfo);
+ Assert(pHandle->pInodeInfo && pHandle->pInodeInfo->u32Magic == SF_INODE_INFO_MAGIC);
+
+ cRefs = ASMAtomicDecU32(&pHandle->cRefs);
+ Assert(cRefs < _64M);
+ if (cRefs)
+ return cRefs;
+ return vbsf_handle_release_slow(pHandle, pSuperInfo, pszCaller);
+}
+
+
+/**
+ * VBox specific information for a regular file.
+ */
+struct vbsf_reg_info {
+ /** Handle tracking structure.
+ * @note Must be first! */
+ struct vbsf_handle Handle;
+};
+
+uint32_t vbsf_linux_oflags_to_vbox(unsigned fLnxOpen, uint32_t *pfHandle, const char *pszCaller);
+
+
+/**
+ * VBox specific information for an open directory.
+ */
+struct vbsf_dir_info {
+ /** Handle tracking structure.
+ * @note Must be first! */
+ struct vbsf_handle Handle;
+ /** Semaphore protecting everything below. */
+ struct semaphore Lock;
+ /** A magic number (VBSF_DIR_INFO_MAGIC). */
+ uint32_t u32Magic;
+ /** Size of the buffer for directory entries. */
+ uint32_t cbBuf;
+ /** Buffer for directory entries on the physical heap. */
+ PSHFLDIRINFO pBuf;
+ /** Number of valid bytes in the buffer. */
+ uint32_t cbValid;
+ /** Number of entries left in the buffer. */
+ uint32_t cEntriesLeft;
+ /** The position of the next entry. Incremented by one for each entry. */
+ loff_t offPos;
+ /** The next entry. */
+ PSHFLDIRINFO pEntry;
+ /** Set if there are no more files. */
+ bool fNoMoreFiles;
+};
+
+/** Magic number for vbsf_dir_info::u32Magic (Robert Anson Heinlein). */
+#define VBSF_DIR_INFO_MAGIC UINT32_C(0x19070707)
+/** Value of vbsf_dir_info::u32Magic when freed. */
+#define VBSF_DIR_INFO_MAGIC_DEAD UINT32_C(0x19880508)
+
+
+/**
+ * Sets the update-jiffies value for a dentry.
+ *
+ * This is used together with vbsf_super_info::cJiffiesDirCacheTTL to reduce
+ * re-validation of dentry structures while walking.
+ *
+ * This used to be living in d_time, but since 4.9.0 that seems to have become
+ * unfashionable and d_fsdata is now used to for this purpose. We do this all
+ * the way back, since d_time seems only to have been used by the file system
+ * specific code (at least going back to 2.4.0).
+ */
+DECLINLINE(void) vbsf_dentry_set_update_jiffies(struct dentry *pDirEntry, unsigned long uToSet)
+{
+ /*SFLOG3(("vbsf_dentry_set_update_jiffies: %p: %lx -> %#lx\n", pDirEntry, (unsigned long)pDirEntry->d_fsdata, uToSet));*/
+ pDirEntry->d_fsdata = (void *)uToSet;
+}
+
+/**
+ * Get the update-jiffies value for a dentry.
+ */
+DECLINLINE(unsigned long) vbsf_dentry_get_update_jiffies(struct dentry *pDirEntry)
+{
+ return (unsigned long)pDirEntry->d_fsdata;
+}
+
+/**
+ * Invalidates the update TTL for the given directory entry so that it is
+ * revalidate the next time it is used.
+ * @param pDirEntry The directory entry cache entry to invalidate.
+ */
+DECLINLINE(void) vbsf_dentry_invalidate_ttl(struct dentry *pDirEntry)
+{
+ vbsf_dentry_set_update_jiffies(pDirEntry, jiffies - INT32_MAX / 2);
+}
+
+/**
+ * Increase the time-to-live of @a pDirEntry and all ancestors.
+ * @param pDirEntry The directory entry cache entry which ancestors
+ * we should increase the TTL for.
+ */
+DECLINLINE(void) vbsf_dentry_chain_increase_ttl(struct dentry *pDirEntry)
+{
+#ifdef VBOX_STRICT
+ struct super_block * const pSuper = pDirEntry->d_sb;
+#endif
+ unsigned long const uToSet = jiffies;
+ do {
+ Assert(pDirEntry->d_sb == pSuper);
+ vbsf_dentry_set_update_jiffies(pDirEntry, uToSet);
+ pDirEntry = pDirEntry->d_parent;
+ } while (!IS_ROOT(pDirEntry));
+}
+
+/**
+ * Increase the time-to-live of all ancestors.
+ * @param pDirEntry The directory entry cache entry which ancestors
+ * we should increase the TTL for.
+ */
+DECLINLINE(void) vbsf_dentry_chain_increase_parent_ttl(struct dentry *pDirEntry)
+{
+ Assert(!pDirEntry->d_parent || pDirEntry->d_parent->d_sb == pDirEntry->d_sb);
+ pDirEntry = pDirEntry->d_parent;
+ if (pDirEntry)
+ vbsf_dentry_chain_increase_ttl(pDirEntry);
+}
+
+/** Macro for getting the dentry for a struct file. */
+#if RTLNX_VER_MIN(4,6,0)
+# define VBSF_GET_F_DENTRY(f) file_dentry(f)
+#elif RTLNX_VER_MIN(2,6,20)
+# define VBSF_GET_F_DENTRY(f) (f->f_path.dentry)
+#else
+# define VBSF_GET_F_DENTRY(f) (f->f_dentry)
+#endif
+
+/**
+ * Macro for checking if the 'data' argument passed in via mount(2) was supplied
+ * by the mount.vboxsf command line utility as a page of data containing the
+ * vbsf_mount_info_new structure.
+ */
+#define VBSF_IS_MOUNT_VBOXSF_DATA(data) \
+ (((struct vbsf_mount_info_new *)data)->nullchar == '\0' && \
+ ((struct vbsf_mount_info_new *)data)->signature[0] == VBSF_MOUNT_SIGNATURE_BYTE_0 && \
+ ((struct vbsf_mount_info_new *)data)->signature[1] == VBSF_MOUNT_SIGNATURE_BYTE_1 && \
+ ((struct vbsf_mount_info_new *)data)->signature[2] == VBSF_MOUNT_SIGNATURE_BYTE_2)
+
+extern int vbsf_stat(const char *caller, struct vbsf_super_info *pSuperInfo, SHFLSTRING * path, PSHFLFSOBJINFO result,
+ int ok_to_fail);
+extern int vbsf_path_from_dentry(struct vbsf_super_info *pSuperInfo, struct vbsf_inode_info *sf_i, struct dentry *dentry,
+ SHFLSTRING ** result, const char *caller);
+extern int vbsf_nlscpy(struct vbsf_super_info *pSuperInfo, char *name, size_t name_bound_len,
+ const unsigned char *utf8_name, size_t utf8_len);
+extern int vbsf_nls_to_shflstring(struct vbsf_super_info *pSuperInfo, const char *pszNls, PSHFLSTRING *ppString);
+
+
+/**
+ * Converts Linux access permissions to VBox ones (mode & 0777).
+ *
+ * @note Currently identical.
+ * @sa sf_access_permissions_to_linux
+ */
+DECLINLINE(uint32_t) sf_access_permissions_to_vbox(int fAttr)
+{
+ /* Access bits should be the same: */
+ AssertCompile(RTFS_UNIX_IRUSR == S_IRUSR);
+ AssertCompile(RTFS_UNIX_IWUSR == S_IWUSR);
+ AssertCompile(RTFS_UNIX_IXUSR == S_IXUSR);
+ AssertCompile(RTFS_UNIX_IRGRP == S_IRGRP);
+ AssertCompile(RTFS_UNIX_IWGRP == S_IWGRP);
+ AssertCompile(RTFS_UNIX_IXGRP == S_IXGRP);
+ AssertCompile(RTFS_UNIX_IROTH == S_IROTH);
+ AssertCompile(RTFS_UNIX_IWOTH == S_IWOTH);
+ AssertCompile(RTFS_UNIX_IXOTH == S_IXOTH);
+
+ return fAttr & RTFS_UNIX_ALL_ACCESS_PERMS;
+}
+
+#endif /* !GA_INCLUDED_SRC_linux_sharedfolders_vfsmod_h */
diff --git a/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c b/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c
new file mode 100644
index 00000000..6f8d6eed
--- /dev/null
+++ b/src/VBox/Additions/linux/testcase/TimesyncBackdoor.c
@@ -0,0 +1,103 @@
+/** @file
+ *
+ * VirtualBox Timesync using temporary Backdoor
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <unistd.h>
+#include <asm/io.h>
+#include <sys/time.h>
+#include <time.h>
+
+void usage()
+{
+ printf("TimesyncBackdoor [-interval <seconds>]"
+ " [-daemonize]"
+ "\n");
+}
+
+int main(int argc, char *argv[])
+{
+ int secInterval = 10;
+ int fDaemonize = 0;
+ int i;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp(argv[i], "-interval") == 0)
+ {
+ if (argc <= i)
+ {
+ usage();
+ return 1;
+ }
+ secInterval = atoi(argv[i + 1]);
+ i++;
+ }
+ else if (strcmp(argv[i], "-daemonize") == 0)
+ {
+ fDaemonize = 1;
+ }
+ else
+ {
+ usage();
+ return 1;
+ }
+ }
+
+ /* get port IO permission */
+ if (iopl(3))
+ {
+ printf("Error: could not set IOPL to 3!\n");
+ return 1;
+ }
+
+ printf("VirtualBox timesync tool. Sync interval: %d seconds.\n", secInterval);
+
+ if (fDaemonize)
+ daemon(1, 0);
+
+ do
+ {
+ unsigned long long time;
+ /* get the high 32bit, this _must_ be done first */
+ outl(0, 0x505);
+ time = (unsigned long long)inl(0x505) << 32;
+ /* get the low 32bit */
+ outl(1, 0x505);
+ time |= inl(0x505);
+
+ /* set the system time */
+ struct timeval tv;
+ tv.tv_sec = time / (unsigned long long)1000;
+ tv.tv_usec = (time % (unsigned long long)1000) * 1000;
+ settimeofday(&tv, NULL);
+
+ /* wait for the next run */
+ sleep(secInterval);
+
+ } while (1);
+
+ return 0;
+}
diff --git a/src/VBox/Additions/solaris/.scm-settings b/src/VBox/Additions/solaris/.scm-settings
new file mode 100644
index 00000000..d395b87c
--- /dev/null
+++ b/src/VBox/Additions/solaris/.scm-settings
@@ -0,0 +1,54 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the Solaris guest additions.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# The whole lot is dual licensed.
+--license-ose-dual
+
+--filter-out-dirs "/DRM/include/."
+
+--filter-out-files /Installer/vboxguest.depend
+--filter-out-files /Installer/vboxguest.pkginfo
+--filter-out-files /Installer/vboxguest.space
+/Installer/postinstall.sh: --no-convert-tabs --license-ose-dual
+/Installer/vbox_vendor_select: --treat-as .sh --license-based-on-mit --no-convert-tabs
+
+/Mouse/vboxms.conf: --treat-as .sh
+
+--filter-out-dirs "/SharedFolders/solaris10/sys/."
+/SharedFolders/*: --no-convert-tabs
+
+/DRM/vboxvideo_drm.c: --no-convert-tabs
+
diff --git a/src/VBox/Additions/solaris/DRM/Makefile.kmk b/src/VBox/Additions/solaris/DRM/Makefile.kmk
new file mode 100644
index 00000000..3d6561e8
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/Makefile.kmk
@@ -0,0 +1,71 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the vboxvideo DRM module (Solaris kernel OpenGL module).
+#
+
+#
+# Copyright (C) 2009-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#ifneq ($(KBUILD_HOST),solaris)
+#$(error "The Solaris guest additions can only be built on Solaris!")
+#endif
+
+#
+# vboxvideo - The Video DRM (Direct Rendering Module) kernel module
+#
+SYSMODS.solaris += vboxvideo
+vboxvideo_TEMPLATE = VBoxGuestR0Drv
+vboxvideo_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV)
+vboxvideo_DEPS += $(VBOX_SVN_REV_KMK)
+if ($(VBOX_SOLARIS_11_UPDATE_VERSION) > 3)
+ vboxvideo_DEFS += VBOX_WITH_SYSTEM_QUEUE_H
+endif
+vboxvideo_INCS := \
+ include/
+vboxvideo_SOURCES = \
+ vboxvideo_drm.c
+vboxvideo_LIBS = \
+ $(VBOX_LIB_VBGL_R0) \
+ $(VBOX_LIB_IPRT_GUEST_R0)
+ifeq ($(KBUILD_HOST),solaris)
+ vboxvideo_LDFLAGS += -N misc/drm
+else
+ vboxvideo_SOURCES += deps.asm
+ vboxvideo_deps.asm_ASFLAGS = -f bin -g null
+endif
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/solaris/DRM/deps.asm b/src/VBox/Additions/solaris/DRM/deps.asm
new file mode 100644
index 00000000..e191b02c
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/deps.asm
@@ -0,0 +1,47 @@
+; $Id: deps.asm $
+;; @file
+; Solaris kernel module dependency
+;
+
+;
+; Copyright (C) 2012-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/solaris/kmoddeps.mac"
+
+kmoddeps_header ; ELF header, section table and shared string table
+
+kmoddeps_dynstr_start ; ELF .dynstr section
+kmoddeps_dynstr_string str_misc_ctf, "misc/ctf"
+kmoddeps_dynstr_end
+
+kmoddeps_dynamic_start ; ELF .dynamic section
+kmoddeps_dynamic_needed str_misc_ctf
+kmoddeps_dynamic_end
diff --git a/src/VBox/Additions/solaris/DRM/include/drm.h b/src/VBox/Additions/solaris/DRM/include/drm.h
new file mode 100644
index 00000000..5bff1e1e
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/include/drm.h
@@ -0,0 +1,820 @@
+/* BEGIN CSTYLED */
+
+/**
+ * \file drm.h
+ * Header for the Direct Rendering Manager
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ *
+ * \par Acknowledgments:
+ * Dec 1999, Richard Henderson <rth@twiddle.net>, move to generic \c cmpxchg.
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * 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 (including the next
+ * paragraph) 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
+ * VA LINUX SYSTEMS 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.
+ */
+
+/**
+ * \mainpage
+ *
+ * The Direct Rendering Manager (DRM) is a device-independent kernel-level
+ * device driver that provides support for the XFree86 Direct Rendering
+ * Infrastructure (DRI).
+ *
+ * The DRM supports the Direct Rendering Infrastructure (DRI) in four major
+ * ways:
+ * -# The DRM provides synchronized access to the graphics hardware via
+ * the use of an optimized two-tiered lock.
+ * -# The DRM enforces the DRI security policy for access to the graphics
+ * hardware by only allowing authenticated X11 clients access to
+ * restricted regions of memory.
+ * -# The DRM provides a generic DMA engine, complete with multiple
+ * queues and the ability to detect the need for an OpenGL context
+ * switch.
+ * -# The DRM is extensible via the use of small device-specific modules
+ * that rely extensively on the API exported by the DRM module.
+ *
+ */
+
+/*
+ * Copyright 2009-2010 Oracle Corporation
+ * All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _DRM_H_
+#define _DRM_H_
+
+#include <sys/types32.h>
+
+#ifndef __user
+#define __user
+#endif
+
+#ifdef __GNUC__
+# define DEPRECATED __attribute__ ((deprecated))
+#else
+# define DEPRECATED
+# define __volatile__ volatile
+#endif
+
+#if defined(__linux__)
+#include <asm/ioctl.h> /* For _IO* macros */
+#define DRM_IOCTL_NR(n) _IOC_NR(n)
+#define DRM_IOC_VOID _IOC_NONE
+#define DRM_IOC_READ _IOC_READ
+#define DRM_IOC_WRITE _IOC_WRITE
+#define DRM_IOC_READWRITE _IOC_READ|_IOC_WRITE
+#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size)
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
+#if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) && defined(IN_MODULE)
+/* Prevent name collision when including sys/ioccom.h */
+#undef ioctl
+#include <sys/ioccom.h>
+#define ioctl(a,b,c) xf86ioctl(a,b,c)
+#else
+#include <sys/ioccom.h>
+#endif /* __FreeBSD__ && xf86ioctl */
+#define DRM_IOCTL_NR(n) ((n) & 0xff)
+#define DRM_IOC_VOID IOC_VOID
+#define DRM_IOC_READ IOC_OUT
+#define DRM_IOC_WRITE IOC_IN
+#define DRM_IOC_READWRITE IOC_INOUT
+#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size)
+#endif
+
+/* Solaris-specific. */
+#if defined(__SOLARIS__) || defined(sun)
+#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
+
+#define _IOC_NRBITS 8
+#define _IOC_TYPEBITS 8
+#define _IOC_SIZEBITS 14
+#define _IOC_DIRBITS 2
+
+#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
+#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
+#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
+#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
+
+#define _IOC_NRSHIFT 0
+#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
+#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
+#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
+
+#define _IOC_NONE 0U
+#define _IOC_WRITE 1U
+#define _IOC_READ 2U
+
+#define _IOC(dir, type, nr, size) \
+ (((dir) << _IOC_DIRSHIFT) | \
+ ((type) << _IOC_TYPESHIFT) | \
+ ((nr) << _IOC_NRSHIFT) | \
+ ((size) << _IOC_SIZESHIFT))
+
+/* used for X server compile */
+#if !defined(_KERNEL)
+#define _IO(type, nr) _IOC(_IOC_NONE, (type), (nr), 0)
+#define _IOR(type, nr, size) _IOC(_IOC_READ, (type), (nr), sizeof (size))
+#define _IOW(type, nr, size) _IOC(_IOC_WRITE, (type), (nr), sizeof (size))
+#define _IOWR(type, nr, size) _IOC(_IOC_READ|_IOC_WRITE, \
+ (type), (nr), sizeof (size))
+
+#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
+#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
+#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
+#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
+
+#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
+#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
+#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
+#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
+#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
+#endif /* _KERNEL */
+
+#define DRM_IOCTL_NR(n) _IOC_NR(n)
+#define DRM_IOC_VOID IOC_VOID
+#define DRM_IOC_READ IOC_OUT
+#define DRM_IOC_WRITE IOC_IN
+#define DRM_IOC_READWRITE IOC_INOUT
+#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size)
+
+#endif /* __Solaris__ or sun */
+#define XFREE86_VERSION(major,minor,patch,snap) \
+ ((major << 16) | (minor << 8) | patch)
+
+#ifndef CONFIG_XFREE86_VERSION
+#define CONFIG_XFREE86_VERSION XFREE86_VERSION(4,1,0,0)
+#endif
+
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+#define DRM_PROC_DEVICES "/proc/devices"
+#define DRM_PROC_MISC "/proc/misc"
+#define DRM_PROC_DRM "/proc/drm"
+#define DRM_DEV_DRM "/dev/drm"
+#define DRM_DEV_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
+#define DRM_DEV_UID 0
+#define DRM_DEV_GID 0
+#endif
+
+#if CONFIG_XFREE86_VERSION >= XFREE86_VERSION(4,1,0,0)
+#ifdef __OpenBSD__
+#define DRM_MAJOR 81
+#endif
+#if defined(__linux__) || defined(__NetBSD__)
+#define DRM_MAJOR 226
+#endif
+#define DRM_MAX_MINOR 15
+#endif
+#define DRM_NAME "drm" /**< Name in kernel, /dev, and /proc */
+#define DRM_MIN_ORDER 5 /**< At least 2^5 bytes = 32 bytes */
+#define DRM_MAX_ORDER 22 /**< Up to 2^22 bytes = 4MB */
+#define DRM_RAM_PERCENT 10 /**< How much system ram can we lock? */
+
+#define _DRM_LOCK_HELD 0x80000000U /**< Hardware lock is held */
+#define _DRM_LOCK_CONT 0x40000000U /**< Hardware lock is contended */
+#define _DRM_LOCK_IS_HELD(lock) ((lock) & _DRM_LOCK_HELD)
+#define _DRM_LOCK_IS_CONT(lock) ((lock) & _DRM_LOCK_CONT)
+#define _DRM_LOCKING_CONTEXT(lock) ((lock) & ~(_DRM_LOCK_HELD|_DRM_LOCK_CONT))
+
+#if defined(__linux__)
+#if defined(__KERNEL__)
+typedef __u64 drm_u64_t;
+#else
+typedef unsigned long long drm_u64_t;
+#endif
+
+typedef unsigned int drm_handle_t;
+#else
+#include <sys/types.h>
+typedef uint64_t drm_u64_t;
+typedef unsigned long long drm_handle_t; /**< To mapped regions */
+#endif
+typedef unsigned int drm_context_t; /**< GLXContext handle */
+typedef unsigned int drm_drawable_t;
+typedef unsigned int drm_magic_t; /**< Magic for authentication */
+
+/**
+ * Cliprect.
+ *
+ * \warning If you change this structure, make sure you change
+ * XF86DRIClipRectRec in the server as well
+ *
+ * \note KW: Actually it's illegal to change either for
+ * backwards-compatibility reasons.
+ */
+typedef struct drm_clip_rect {
+ unsigned short x1;
+ unsigned short y1;
+ unsigned short x2;
+ unsigned short y2;
+} drm_clip_rect_t;
+
+/**
+ * Drawable information.
+ */
+typedef struct drm_drawable_info {
+ unsigned int num_rects;
+ drm_clip_rect_t *rects;
+} drm_drawable_info_t;
+
+/**
+ * Texture region,
+ */
+typedef struct drm_tex_region {
+ unsigned char next;
+ unsigned char prev;
+ unsigned char in_use;
+ unsigned char padding;
+ unsigned int age;
+} drm_tex_region_t;
+
+/**
+ * Hardware lock.
+ *
+ * The lock structure is a simple cache-line aligned integer. To avoid
+ * processor bus contention on a multiprocessor system, there should not be any
+ * other data stored in the same cache line.
+ */
+typedef struct drm_hw_lock {
+ __volatile__ unsigned int lock; /**< lock variable */
+ char padding[60]; /**< Pad to cache line */
+} drm_hw_lock_t;
+
+/* This is beyond ugly, and only works on GCC. However, it allows me to use
+ * drm.h in places (i.e., in the X-server) where I can't use size_t. The real
+ * fix is to use uint32_t instead of size_t, but that fix will break existing
+ * LP64 (i.e., PowerPC64, SPARC64, IA-64, Alpha, etc.) systems. That *will*
+ * eventually happen, though. I chose 'unsigned long' to be the fallback type
+ * because that works on all the platforms I know about. Hopefully, the
+ * real fix will happen before that bites us.
+ */
+
+#ifdef __SIZE_TYPE__
+# define DRM_SIZE_T __SIZE_TYPE__
+#else
+#if !defined(__SOLARIS__) && !defined(sun)
+# warning "__SIZE_TYPE__ not defined. Assuming sizeof(size_t) == sizeof(unsigned long)!"
+#endif
+# define DRM_SIZE_T unsigned long
+#endif
+
+/**
+ * DRM_IOCTL_VERSION ioctl argument type.
+ *
+ * \sa drmGetVersion().
+ */
+typedef struct drm_version {
+ int version_major; /**< Major version */
+ int version_minor; /**< Minor version */
+ int version_patchlevel; /**< Patch level */
+ DRM_SIZE_T name_len; /**< Length of name buffer */
+ char __user *name; /**< Name of driver */
+ DRM_SIZE_T date_len; /**< Length of date buffer */
+ char __user *date; /**< User-space buffer to hold date */
+ DRM_SIZE_T desc_len; /**< Length of desc buffer */
+ char __user *desc; /**< User-space buffer to hold desc */
+} drm_version_t;
+
+/**
+ * DRM_IOCTL_GET_UNIQUE ioctl argument type.
+ *
+ * \sa drmGetBusid() and drmSetBusId().
+ */
+typedef struct drm_unique {
+ DRM_SIZE_T unique_len; /**< Length of unique */
+ char __user *unique; /**< Unique name for driver instantiation */
+} drm_unique_t;
+
+#undef DRM_SIZE_T
+
+typedef struct drm_list {
+ int count; /**< Length of user-space structures */
+ drm_version_t __user *version;
+} drm_list_t;
+
+typedef struct drm_block {
+ int unused;
+} drm_block_t;
+
+/**
+ * DRM_IOCTL_CONTROL ioctl argument type.
+ *
+ * \sa drmCtlInstHandler() and drmCtlUninstHandler().
+ */
+typedef struct drm_control {
+ enum {
+ DRM_ADD_COMMAND,
+ DRM_RM_COMMAND,
+ DRM_INST_HANDLER,
+ DRM_UNINST_HANDLER
+ } func;
+ int irq;
+} drm_control_t;
+
+/**
+ * Type of memory to map.
+ */
+typedef enum drm_map_type {
+ _DRM_FRAME_BUFFER = 0, /**< WC (no caching), no core dump */
+ _DRM_REGISTERS = 1, /**< no caching, no core dump */
+ _DRM_SHM = 2, /**< shared, cached */
+ _DRM_AGP = 3, /**< AGP/GART */
+ _DRM_SCATTER_GATHER = 4, /**< Scatter/gather memory for PCI DMA */
+ _DRM_CONSISTENT = 5, /**< Consistent memory for PCI DMA */
+ _DRM_TTM = 6
+} drm_map_type_t;
+
+/**
+ * Memory mapping flags.
+ */
+typedef enum drm_map_flags {
+ _DRM_RESTRICTED = 0x01, /**< Cannot be mapped to user-virtual */
+ _DRM_READ_ONLY = 0x02,
+ _DRM_LOCKED = 0x04, /**< shared, cached, locked */
+ _DRM_KERNEL = 0x08, /**< kernel requires access */
+ _DRM_WRITE_COMBINING = 0x10, /**< use write-combining if available */
+ _DRM_CONTAINS_LOCK = 0x20, /**< SHM page that contains lock */
+ _DRM_REMOVABLE = 0x40, /**< Removable mapping */
+ _DRM_DRIVER = 0x80 /**< Managed by driver */
+} drm_map_flags_t;
+
+typedef struct drm_ctx_priv_map {
+ unsigned int ctx_id; /**< Context requesting private mapping */
+ void *handle; /**< Handle of map */
+} drm_ctx_priv_map_t;
+
+/**
+ * DRM_IOCTL_GET_MAP, DRM_IOCTL_ADD_MAP and DRM_IOCTL_RM_MAP ioctls
+ * argument type.
+ *
+ * \sa drmAddMap().
+ */
+typedef struct drm_map {
+ unsigned long long offset; /**< Requested physical address (0 for SAREA)*/
+ unsigned long long handle;
+ /**< User-space: "Handle" to pass to mmap() */
+ /**< Kernel-space: kernel-virtual address */
+ unsigned long size; /**< Requested physical size (bytes) */
+ drm_map_type_t type; /**< Type of memory to map */
+ drm_map_flags_t flags; /**< Flags */
+ int mtrr; /**< MTRR slot used */
+ /* Private data */
+} drm_map_t;
+
+/**
+ * DRM_IOCTL_GET_CLIENT ioctl argument type.
+ */
+typedef struct drm_client {
+ int idx; /**< Which client desired? */
+ int auth; /**< Is client authenticated? */
+ unsigned long pid; /**< Process ID */
+ unsigned long uid; /**< User ID */
+ unsigned long magic; /**< Magic */
+ unsigned long iocs; /**< Ioctl count */
+} drm_client_t;
+
+typedef enum {
+ _DRM_STAT_LOCK,
+ _DRM_STAT_OPENS,
+ _DRM_STAT_CLOSES,
+ _DRM_STAT_IOCTLS,
+ _DRM_STAT_LOCKS,
+ _DRM_STAT_UNLOCKS,
+ _DRM_STAT_VALUE, /**< Generic value */
+ _DRM_STAT_BYTE, /**< Generic byte counter (1024bytes/K) */
+ _DRM_STAT_COUNT, /**< Generic non-byte counter (1000/k) */
+
+ _DRM_STAT_IRQ, /**< IRQ */
+ _DRM_STAT_PRIMARY, /**< Primary DMA bytes */
+ _DRM_STAT_SECONDARY, /**< Secondary DMA bytes */
+ _DRM_STAT_DMA, /**< DMA */
+ _DRM_STAT_SPECIAL, /**< Special DMA (e.g., priority or polled) */
+ _DRM_STAT_MISSED /**< Missed DMA opportunity */
+ /* Add to the *END* of the list */
+} drm_stat_type_t;
+
+/**
+ * DRM_IOCTL_GET_STATS ioctl argument type.
+ */
+typedef struct drm_stats {
+ unsigned long count;
+ struct {
+ unsigned long value;
+ drm_stat_type_t type;
+ } data[15];
+} drm_stats_t;
+
+/**
+ * Hardware locking flags.
+ */
+typedef enum drm_lock_flags {
+ _DRM_LOCK_READY = 0x01, /**< Wait until hardware is ready for DMA */
+ _DRM_LOCK_QUIESCENT = 0x02, /**< Wait until hardware quiescent */
+ _DRM_LOCK_FLUSH = 0x04, /**< Flush this context's DMA queue first */
+ _DRM_LOCK_FLUSH_ALL = 0x08, /**< Flush all DMA queues first */
+ /* These *HALT* flags aren't supported yet
+ -- they will be used to support the
+ full-screen DGA-like mode. */
+ _DRM_HALT_ALL_QUEUES = 0x10, /**< Halt all current and future queues */
+ _DRM_HALT_CUR_QUEUES = 0x20 /**< Halt all current queues */
+} drm_lock_flags_t;
+
+/**
+ * DRM_IOCTL_LOCK, DRM_IOCTL_UNLOCK and DRM_IOCTL_FINISH ioctl argument type.
+ *
+ * \sa drmGetLock() and drmUnlock().
+ */
+typedef struct drm_lock {
+ int context;
+ drm_lock_flags_t flags;
+} drm_lock_t;
+
+/**
+ * DMA flags
+ *
+ * \warning
+ * These values \e must match xf86drm.h.
+ *
+ * \sa drm_dma.
+ */
+typedef enum drm_dma_flags {
+ /* Flags for DMA buffer dispatch */
+ _DRM_DMA_BLOCK = 0x01, /**<
+ * Block until buffer dispatched.
+ *
+ * \note The buffer may not yet have
+ * been processed by the hardware --
+ * getting a hardware lock with the
+ * hardware quiescent will ensure
+ * that the buffer has been
+ * processed.
+ */
+ _DRM_DMA_WHILE_LOCKED = 0x02, /**< Dispatch while lock held */
+ _DRM_DMA_PRIORITY = 0x04, /**< High priority dispatch */
+
+ /* Flags for DMA buffer request */
+ _DRM_DMA_WAIT = 0x10, /**< Wait for free buffers */
+ _DRM_DMA_SMALLER_OK = 0x20, /**< Smaller-than-requested buffers OK */
+ _DRM_DMA_LARGER_OK = 0x40 /**< Larger-than-requested buffers OK */
+} drm_dma_flags_t;
+
+/**
+ * DRM_IOCTL_ADD_BUFS and DRM_IOCTL_MARK_BUFS ioctl argument type.
+ *
+ * \sa drmAddBufs().
+ */
+typedef enum {
+ _DRM_PAGE_ALIGN = 0x01, /**< Align on page boundaries for DMA */
+ _DRM_AGP_BUFFER = 0x02, /**< Buffer is in AGP space */
+ _DRM_SG_BUFFER = 0x04, /**< Scatter/gather memory buffer */
+ _DRM_FB_BUFFER = 0x08, /**< Buffer is in frame buffer */
+ _DRM_PCI_BUFFER_RO = 0x10 /**< Map PCI DMA buffer read-only */
+} drm_buf_flag;
+typedef struct drm_buf_desc {
+ int count; /**< Number of buffers of this size */
+ int size; /**< Size in bytes */
+ int low_mark; /**< Low water mark */
+ int high_mark; /**< High water mark */
+ drm_buf_flag flags;
+ unsigned long agp_start; /**<
+ * Start address of where the AGP buffers are
+ * in the AGP aperture
+ */
+} drm_buf_desc_t;
+
+/**
+ * DRM_IOCTL_INFO_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_info {
+ int count; /**< Number of buffers described in list */
+ drm_buf_desc_t __user *list; /**< List of buffer descriptions */
+} drm_buf_info_t;
+
+/**
+ * DRM_IOCTL_FREE_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_free {
+ int count;
+ int __user *list;
+} drm_buf_free_t;
+
+/**
+ * Buffer information
+ *
+ * \sa drm_buf_map.
+ */
+typedef struct drm_buf_pub {
+ int idx; /**< Index into the master buffer list */
+ int total; /**< Buffer size */
+ int used; /**< Amount of buffer in use (for DMA) */
+ void __user *address; /**< Address of buffer */
+} drm_buf_pub_t;
+
+/**
+ * DRM_IOCTL_MAP_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_map {
+ int count; /**< Length of the buffer list */
+#if defined(__cplusplus)
+ void __user *c_virtual;
+#else
+ void __user *virtual; /**< Mmap'd area in user-virtual */
+#endif
+ drm_buf_pub_t __user *list; /**< Buffer information */
+ int fd;
+} drm_buf_map_t;
+
+/**
+ * DRM_IOCTL_DMA ioctl argument type.
+ *
+ * Indices here refer to the offset into the buffer list in drm_buf_get.
+ *
+ * \sa drmDMA().
+ */
+typedef struct drm_dma {
+ int context; /**< Context handle */
+ int send_count; /**< Number of buffers to send */
+ int __user *send_indices; /**< List of handles to buffers */
+ int __user *send_sizes; /**< Lengths of data to send */
+ drm_dma_flags_t flags; /**< Flags */
+ int request_count; /**< Number of buffers requested */
+ int request_size; /**< Desired size for buffers */
+ int __user *request_indices; /**< Buffer information */
+ int __user *request_sizes;
+ int granted_count; /**< Number of buffers granted */
+} drm_dma_t;
+
+typedef enum {
+ _DRM_CONTEXT_PRESERVED = 0x01,
+ _DRM_CONTEXT_2DONLY = 0x02
+} drm_ctx_flags_t;
+
+/**
+ * DRM_IOCTL_ADD_CTX ioctl argument type.
+ *
+ * \sa drmCreateContext() and drmDestroyContext().
+ */
+typedef struct drm_ctx {
+ drm_context_t handle;
+ drm_ctx_flags_t flags;
+} drm_ctx_t;
+
+/**
+ * DRM_IOCTL_RES_CTX ioctl argument type.
+ */
+typedef struct drm_ctx_res {
+ int count;
+ drm_ctx_t __user *contexts;
+} drm_ctx_res_t;
+
+
+/**
+ * DRM_IOCTL_ADD_DRAW and DRM_IOCTL_RM_DRAW ioctl argument type.
+ */
+typedef struct drm_draw {
+ drm_drawable_t handle;
+} drm_draw_t;
+
+/**
+ * DRM_IOCTL_UPDATE_DRAW ioctl argument type.
+ */
+typedef enum {
+ DRM_DRAWABLE_CLIPRECTS,
+} drm_drawable_info_type_t;
+
+typedef struct drm_update_draw {
+ drm_drawable_t handle;
+ unsigned int type;
+ unsigned int num;
+ unsigned long long data;
+} drm_update_draw_t;
+
+/**
+ * DRM_IOCTL_GET_MAGIC and DRM_IOCTL_AUTH_MAGIC ioctl argument type.
+ */
+typedef struct drm_auth {
+ drm_magic_t magic;
+} drm_auth_t;
+
+/**
+ * DRM_IOCTL_IRQ_BUSID ioctl argument type.
+ *
+ * \sa drmGetInterruptFromBusID().
+ */
+typedef struct drm_irq_busid {
+ int irq; /**< IRQ number */
+ int busnum; /**< bus number */
+ int devnum; /**< device number */
+ int funcnum; /**< function number */
+} drm_irq_busid_t;
+
+typedef enum {
+ _DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */
+ _DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */
+ _DRM_VBLANK_FLIP = 0x8000000, /**< Scheduled buffer swap should flip */
+ _DRM_VBLANK_NEXTONMISS = 0x10000000, /**< If missed, wait for next vblank */
+ _DRM_VBLANK_SECONDARY = 0x20000000, /**< Secondary display controller */
+ _DRM_VBLANK_SIGNAL = 0x40000000 /**< Send signal instead of blocking */
+} drm_vblank_seq_type_t;
+
+#define _DRM_VBLANK_TYPES_MASK (_DRM_VBLANK_ABSOLUTE | _DRM_VBLANK_RELATIVE)
+#define _DRM_VBLANK_FLAGS_MASK (_DRM_VBLANK_SIGNAL | _DRM_VBLANK_SECONDARY | \
+ _DRM_VBLANK_NEXTONMISS)
+
+struct drm_wait_vblank_request {
+ drm_vblank_seq_type_t type;
+ unsigned int sequence;
+ unsigned long signal;
+};
+
+struct drm_wait_vblank_reply {
+ drm_vblank_seq_type_t type;
+ unsigned int sequence;
+ long tval_sec;
+ long tval_usec;
+};
+
+/**
+ * DRM_IOCTL_WAIT_VBLANK ioctl argument type.
+ *
+ * \sa drmWaitVBlank().
+ */
+typedef union drm_wait_vblank {
+ struct drm_wait_vblank_request request;
+ struct drm_wait_vblank_reply reply;
+} drm_wait_vblank_t;
+
+/**
+ * DRM_IOCTL_AGP_ENABLE ioctl argument type.
+ *
+ * \sa drmAgpEnable().
+ */
+typedef struct drm_agp_mode {
+ unsigned long mode; /**< AGP mode */
+} drm_agp_mode_t;
+
+/**
+ * DRM_IOCTL_AGP_ALLOC and DRM_IOCTL_AGP_FREE ioctls argument type.
+ *
+ * \sa drmAgpAlloc() and drmAgpFree().
+ */
+typedef struct drm_agp_buffer {
+ unsigned long size; /**< In bytes -- will round to page boundary */
+ unsigned long handle; /**< Used for binding / unbinding */
+ unsigned long type; /**< Type of memory to allocate */
+ unsigned long physical; /**< Physical used by i810 */
+} drm_agp_buffer_t;
+
+/**
+ * DRM_IOCTL_AGP_BIND and DRM_IOCTL_AGP_UNBIND ioctls argument type.
+ *
+ * \sa drmAgpBind() and drmAgpUnbind().
+ */
+typedef struct drm_agp_binding {
+ unsigned long handle; /**< From drm_agp_buffer */
+ unsigned long offset; /**< In bytes -- will round to page boundary */
+} drm_agp_binding_t;
+
+/**
+ * DRM_IOCTL_AGP_INFO ioctl argument type.
+ *
+ * \sa drmAgpVersionMajor(), drmAgpVersionMinor(), drmAgpGetMode(),
+ * drmAgpBase(), drmAgpSize(), drmAgpMemoryUsed(), drmAgpMemoryAvail(),
+ * drmAgpVendorId() and drmAgpDeviceId().
+ */
+typedef struct drm_agp_info {
+ int agp_version_major;
+ int agp_version_minor;
+ unsigned long mode;
+ unsigned long aperture_base; /**< physical address */
+ unsigned long aperture_size; /**< bytes */
+ unsigned long memory_allowed; /**< bytes */
+ unsigned long memory_used;
+
+ /** \name PCI information */
+ /*@{ */
+ unsigned short id_vendor;
+ unsigned short id_device;
+ /*@} */
+} drm_agp_info_t;
+
+/**
+ * DRM_IOCTL_SG_ALLOC ioctl argument type.
+ */
+typedef struct drm_scatter_gather {
+ unsigned long size; /**< In bytes -- will round to page boundary */
+ unsigned long handle; /**< Used for mapping / unmapping */
+} drm_scatter_gather_t;
+
+/**
+ * DRM_IOCTL_SET_VERSION ioctl argument type.
+ */
+typedef struct drm_set_version {
+ int drm_di_major;
+ int drm_di_minor;
+ int drm_dd_major;
+ int drm_dd_minor;
+} drm_set_version_t;
+
+/**
+ * \name Ioctls Definitions
+ */
+/*@{*/
+
+#define DRM_IOCTL_BASE 'd'
+#define DRM_IO(nr) _IO(DRM_IOCTL_BASE,nr)
+#define DRM_IOR(nr,type) _IOR(DRM_IOCTL_BASE,nr,type)
+#define DRM_IOW(nr,type) _IOW(DRM_IOCTL_BASE,nr,type)
+#define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type)
+
+#define DRM_IOCTL_VERSION DRM_IOWR(0x00, drm_version_t)
+#define DRM_IOCTL_GET_UNIQUE DRM_IOWR(0x01, drm_unique_t)
+#define DRM_IOCTL_GET_MAGIC DRM_IOR( 0x02, drm_auth_t)
+#define DRM_IOCTL_IRQ_BUSID DRM_IOWR(0x03, drm_irq_busid_t)
+#define DRM_IOCTL_GET_MAP DRM_IOWR(0x04, drm_map_t)
+#define DRM_IOCTL_GET_CLIENT DRM_IOWR(0x05, drm_client_t)
+#define DRM_IOCTL_GET_STATS DRM_IOR( 0x06, drm_stats_t)
+#define DRM_IOCTL_SET_VERSION DRM_IOWR(0x07, drm_set_version_t)
+
+#define DRM_IOCTL_SET_UNIQUE DRM_IOW( 0x10, drm_unique_t)
+#define DRM_IOCTL_AUTH_MAGIC DRM_IOW( 0x11, drm_auth_t)
+#define DRM_IOCTL_BLOCK DRM_IOWR(0x12, drm_block_t)
+#define DRM_IOCTL_UNBLOCK DRM_IOWR(0x13, drm_block_t)
+#define DRM_IOCTL_CONTROL DRM_IOW( 0x14, drm_control_t)
+#define DRM_IOCTL_ADD_MAP DRM_IOWR(0x15, drm_map_t)
+#define DRM_IOCTL_ADD_BUFS DRM_IOWR(0x16, drm_buf_desc_t)
+#define DRM_IOCTL_MARK_BUFS DRM_IOW( 0x17, drm_buf_desc_t)
+#define DRM_IOCTL_INFO_BUFS DRM_IOWR(0x18, drm_buf_info_t)
+#define DRM_IOCTL_MAP_BUFS DRM_IOWR(0x19, drm_buf_map_t)
+#define DRM_IOCTL_FREE_BUFS DRM_IOW( 0x1a, drm_buf_free_t)
+
+#define DRM_IOCTL_RM_MAP DRM_IOW( 0x1b, drm_map_t)
+
+#define DRM_IOCTL_SET_SAREA_CTX DRM_IOW( 0x1c, drm_ctx_priv_map_t)
+#define DRM_IOCTL_GET_SAREA_CTX DRM_IOWR(0x1d, drm_ctx_priv_map_t)
+
+#define DRM_IOCTL_ADD_CTX DRM_IOWR(0x20, drm_ctx_t)
+#define DRM_IOCTL_RM_CTX DRM_IOWR(0x21, drm_ctx_t)
+#define DRM_IOCTL_MOD_CTX DRM_IOW( 0x22, drm_ctx_t)
+#define DRM_IOCTL_GET_CTX DRM_IOWR(0x23, drm_ctx_t)
+#define DRM_IOCTL_SWITCH_CTX DRM_IOW( 0x24, drm_ctx_t)
+#define DRM_IOCTL_NEW_CTX DRM_IOW( 0x25, drm_ctx_t)
+#define DRM_IOCTL_RES_CTX DRM_IOWR(0x26, drm_ctx_res_t)
+#define DRM_IOCTL_ADD_DRAW DRM_IOWR(0x27, drm_draw_t)
+#define DRM_IOCTL_RM_DRAW DRM_IOWR(0x28, drm_draw_t)
+#define DRM_IOCTL_DMA DRM_IOWR(0x29, drm_dma_t)
+#define DRM_IOCTL_LOCK DRM_IOW( 0x2a, drm_lock_t)
+#define DRM_IOCTL_UNLOCK DRM_IOW( 0x2b, drm_lock_t)
+#define DRM_IOCTL_FINISH DRM_IOW( 0x2c, drm_lock_t)
+
+#define DRM_IOCTL_AGP_ACQUIRE DRM_IO( 0x30)
+#define DRM_IOCTL_AGP_RELEASE DRM_IO( 0x31)
+#define DRM_IOCTL_AGP_ENABLE DRM_IOW( 0x32, drm_agp_mode_t)
+#define DRM_IOCTL_AGP_INFO DRM_IOR( 0x33, drm_agp_info_t)
+#define DRM_IOCTL_AGP_ALLOC DRM_IOWR(0x34, drm_agp_buffer_t)
+#define DRM_IOCTL_AGP_FREE DRM_IOW( 0x35, drm_agp_buffer_t)
+#define DRM_IOCTL_AGP_BIND DRM_IOW( 0x36, drm_agp_binding_t)
+#define DRM_IOCTL_AGP_UNBIND DRM_IOW( 0x37, drm_agp_binding_t)
+
+#define DRM_IOCTL_SG_ALLOC DRM_IOW( 0x38, drm_scatter_gather_t)
+#define DRM_IOCTL_SG_FREE DRM_IOW( 0x39, drm_scatter_gather_t)
+
+#define DRM_IOCTL_WAIT_VBLANK DRM_IOWR(0x3a, drm_wait_vblank_t)
+
+#define DRM_IOCTL_UPDATE_DRAW DRM_IOW(0x3f, drm_update_draw_t)
+/*@}*/
+
+/**
+ * Device specific ioctls should only be in their respective headers
+ * The device specific ioctl range is from 0x40 to 0x99.
+ * Generic IOCTLS restart at 0xA0.
+ *
+ * \sa drmCommandNone(), drmCommandRead(), drmCommandWrite(), and
+ * drmCommandReadWrite().
+ */
+#define DRM_COMMAND_BASE 0x40
+#define DRM_COMMAND_END 0xA0
+
+#endif /* _DRM_H_ */
diff --git a/src/VBox/Additions/solaris/DRM/include/drmP.h b/src/VBox/Additions/solaris/DRM/include/drmP.h
new file mode 100644
index 00000000..0763e708
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/include/drmP.h
@@ -0,0 +1,908 @@
+/*
+ * drmP.h -- Private header for Direct Rendering Manager -*- linux-c -*-
+ * Created: Mon Jan 4 10:05:05 1999 by faith@precisioninsight.com
+ */
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * 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 (including the next
+ * paragraph) 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
+ * VA LINUX SYSTEMS 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.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ *
+ */
+
+/*
+ * Copyright 2009-2010 Oracle Corporation
+ * All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _DRMP_H
+#define _DRMP_H
+
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/modctl.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/cmn_err.h>
+#include <sys/varargs.h>
+#include <sys/pci.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sunldi.h>
+#include <sys/pmem.h>
+#include <sys/agpgart.h>
+#include <sys/time.h>
+#include "drm_atomic.h"
+#include "drm.h"
+#if !defined(VBOX_WITH_SYSTEM_QUEUE_H)
+# include "queue.h"
+#endif
+#include "drm_linux_list.h"
+
+#ifndef __inline__
+#define __inline__ inline
+#endif
+
+#if !defined(__FUNCTION__)
+#if defined(C99)
+#define __FUNCTION__ __func__
+#else
+#define __FUNCTION__ " "
+#endif
+#endif
+
+/* DRM space units */
+#define DRM_PAGE_SHIFT PAGESHIFT
+#define DRM_PAGE_SIZE (1 << DRM_PAGE_SHIFT)
+#define DRM_PAGE_OFFSET (DRM_PAGE_SIZE - 1)
+#define DRM_PAGE_MASK ~(DRM_PAGE_SIZE - 1)
+#define DRM_MB2PAGES(x) ((x) << 8)
+#define DRM_PAGES2BYTES(x) ((x) << DRM_PAGE_SHIFT)
+#define DRM_BYTES2PAGES(x) ((x) >> DRM_PAGE_SHIFT)
+#define DRM_PAGES2KB(x) ((x) << 2)
+#define DRM_ALIGNED(offset) (((offset) & DRM_PAGE_OFFSET) == 0)
+
+#define PAGE_SHIFT DRM_PAGE_SHIFT
+#define PAGE_SIZE DRM_PAGE_SIZE
+
+#define DRM_MAX_INSTANCES 8
+#define DRM_DEVNODE "drm"
+#define DRM_UNOPENED 0
+#define DRM_OPENED 1
+
+#define DRM_HASH_SIZE 16 /* Size of key hash table */
+#define DRM_KERNEL_CONTEXT 0 /* Change drm_resctx if changed */
+#define DRM_RESERVED_CONTEXTS 1 /* Change drm_resctx if changed */
+
+#define DRM_MEM_DMA 0
+#define DRM_MEM_SAREA 1
+#define DRM_MEM_DRIVER 2
+#define DRM_MEM_MAGIC 3
+#define DRM_MEM_IOCTLS 4
+#define DRM_MEM_MAPS 5
+#define DRM_MEM_BUFS 6
+#define DRM_MEM_SEGS 7
+#define DRM_MEM_PAGES 8
+#define DRM_MEM_FILES 9
+#define DRM_MEM_QUEUES 10
+#define DRM_MEM_CMDS 11
+#define DRM_MEM_MAPPINGS 12
+#define DRM_MEM_BUFLISTS 13
+#define DRM_MEM_DRMLISTS 14
+#define DRM_MEM_TOTALDRM 15
+#define DRM_MEM_BOUNDDRM 16
+#define DRM_MEM_CTXBITMAP 17
+#define DRM_MEM_STUB 18
+#define DRM_MEM_SGLISTS 19
+#define DRM_MEM_AGPLISTS 20
+#define DRM_MEM_CTXLIST 21
+#define DRM_MEM_MM 22
+#define DRM_MEM_HASHTAB 23
+#define DRM_MEM_OBJECTS 24
+
+#define DRM_MAX_CTXBITMAP (PAGE_SIZE * 8)
+#define DRM_MAP_HASH_OFFSET 0x10000000
+#define DRM_MAP_HASH_ORDER 12
+#define DRM_OBJECT_HASH_ORDER 12
+#define DRM_FILE_PAGE_OFFSET_START ((0xFFFFFFFFUL >> PAGE_SHIFT) + 1)
+#define DRM_FILE_PAGE_OFFSET_SIZE ((0xFFFFFFFFUL >> PAGE_SHIFT) * 16)
+#define DRM_MM_INIT_MAX_PAGES 256
+
+
+/* Internal types and structures */
+#define DRM_ARRAY_SIZE(x) (sizeof (x) / sizeof (x[0]))
+#define DRM_MIN(a, b) ((a) < (b) ? (a) : (b))
+#define DRM_MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#define DRM_IF_VERSION(maj, min) (maj << 16 | min)
+
+#define __OS_HAS_AGP 1
+
+#define DRM_DEV_MOD (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
+#define DRM_DEV_UID 0
+#define DRM_DEV_GID 0
+
+#define DRM_CURRENTPID ddi_get_pid()
+#define DRM_SPINLOCK(l) mutex_enter(l)
+#define DRM_SPINUNLOCK(u) mutex_exit(u)
+#define DRM_SPINLOCK_ASSERT(l)
+#define DRM_LOCK() mutex_enter(&dev->dev_lock)
+#define DRM_UNLOCK() mutex_exit(&dev->dev_lock)
+#define DRM_LOCK_OWNED() ASSERT(mutex_owned(&dev->dev_lock))
+#define spin_lock_irqsave(l, flag) mutex_enter(l)
+#define spin_unlock_irqrestore(u, flag) mutex_exit(u)
+#define spin_lock(l) mutex_enter(l)
+#define spin_unlock(u) mutex_exit(u)
+
+#define DRM_UDELAY(sec) delay(drv_usectohz(sec *1000))
+#define DRM_MEMORYBARRIER()
+
+typedef struct drm_file drm_file_t;
+typedef struct drm_device drm_device_t;
+typedef struct drm_driver_info drm_driver_t;
+
+#define DRM_DEVICE drm_device_t *dev = dev1
+#define DRM_IOCTL_ARGS \
+ drm_device_t *dev1, intptr_t data, drm_file_t *fpriv, int mode
+
+#define DRM_COPYFROM_WITH_RETURN(dest, src, size) \
+ if (ddi_copyin(src, dest, size, 0)) \
+ return (EFAULT)
+
+#define DRM_COPYTO_WITH_RETURN(dest, src, size) \
+ if (ddi_copyout((src), (dest), (size), 0)) \
+ return (EFAULT)
+
+#define DRM_COPY_FROM_USER(dest, src, size) \
+ ddi_copyin((src), (dest), (size), 0) /* flag for src */
+
+#define DRM_COPY_TO_USER(dest, src, size) \
+ ddi_copyout((src), (dest), (size), 0) /* flags for dest */
+
+#define DRM_COPY_FROM_USER_UNCHECKED(arg1, arg2, arg3) \
+ ddi_copyin((arg2), (arg1), (arg3), 0)
+
+#define DRM_COPY_TO_USER_UNCHECKED(arg1, arg2, arg3) \
+ ddi_copyout((arg2), arg1, arg3, 0)
+
+#define DRM_READ8(map, offset) \
+ *(uint8_t *)((uintptr_t)((map)->dev_addr) + (offset))
+#define DRM_READ16(map, offset) \
+ *(uint16_t *)((uintptr_t)((map)->dev_addr) + (offset))
+#define DRM_READ32(map, offset) \
+ *(uint32_t *)((uintptr_t)((map)->dev_addr) + (offset))
+#define DRM_WRITE8(map, offset, val) \
+ *(uint8_t *)((uintptr_t)((map)->dev_addr) + (offset)) = (val)
+#define DRM_WRITE16(map, offset, val) \
+ *(uint16_t *)((uintptr_t)((map)->dev_addr) + (offset)) = (val)
+#define DRM_WRITE32(map, offset, val) \
+ *(uint32_t *)((uintptr_t)((map)->dev_addr) + (offset)) = (val)
+
+typedef struct drm_wait_queue {
+ kcondvar_t cv;
+ kmutex_t lock;
+}wait_queue_head_t;
+
+#define DRM_INIT_WAITQUEUE(q, pri) \
+{ \
+ mutex_init(&(q)->lock, NULL, MUTEX_DRIVER, pri); \
+ cv_init(&(q)->cv, NULL, CV_DRIVER, NULL); \
+}
+
+#define DRM_FINI_WAITQUEUE(q) \
+{ \
+ mutex_destroy(&(q)->lock); \
+ cv_destroy(&(q)->cv); \
+}
+
+#define DRM_WAKEUP(q) \
+{ \
+ mutex_enter(&(q)->lock); \
+ cv_broadcast(&(q)->cv); \
+ mutex_exit(&(q)->lock); \
+}
+
+#define jiffies ddi_get_lbolt()
+#define DRM_WAIT_ON(ret, q, timeout, condition) \
+mutex_enter(&(q)->lock); \
+while (!(condition)) { \
+ ret = cv_timedwait_sig(&(q)->cv, &(q)->lock, jiffies + timeout); \
+ if (ret == -1) { \
+ ret = EBUSY; \
+ break; \
+ } else if (ret == 0) { \
+ ret = EINTR; \
+ break; \
+ } else { \
+ ret = 0; \
+ } \
+} \
+mutex_exit(&(q)->lock);
+
+#define DRM_GETSAREA() \
+{ \
+ drm_local_map_t *map; \
+ DRM_SPINLOCK_ASSERT(&dev->dev_lock); \
+ TAILQ_FOREACH(map, &dev->maplist, link) { \
+ if (map->type == _DRM_SHM && \
+ map->flags & _DRM_CONTAINS_LOCK) { \
+ dev_priv->sarea = map; \
+ break; \
+ } \
+ } \
+}
+
+#define LOCK_TEST_WITH_RETURN(dev, fpriv) \
+ if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock) || \
+ dev->lock.filp != fpriv) { \
+ DRM_ERROR("called without lock held"); \
+ return (EINVAL); \
+ }
+
+#define DRM_IRQ_ARGS caddr_t arg
+#define IRQ_HANDLED DDI_INTR_CLAIMED
+#define IRQ_NONE DDI_INTR_UNCLAIMED
+
+enum {
+ DRM_IS_NOT_AGP,
+ DRM_IS_AGP,
+ DRM_MIGHT_BE_AGP
+};
+
+/* Capabilities taken from src/sys/dev/pci/pcireg.h. */
+#ifndef PCIY_AGP
+#define PCIY_AGP 0x02
+#endif
+
+#ifndef PCIY_EXPRESS
+#define PCIY_EXPRESS 0x10
+#endif
+
+#define PAGE_ALIGN(addr) (((addr) + DRM_PAGE_SIZE - 1) & DRM_PAGE_MASK)
+#define DRM_SUSER(p) (crgetgid(p) == 0 || crgetuid(p) == 0)
+
+/*
+ * wait for 400 milliseconds
+ */
+#define DRM_HZ drv_usectohz(400000)
+
+typedef unsigned long dma_addr_t;
+typedef uint64_t u64;
+typedef uint32_t u32;
+typedef uint16_t u16;
+typedef uint8_t u8;
+typedef uint_t irqreturn_t;
+
+#define DRM_SUPPORT 1
+#define DRM_UNSUPPORT 0
+
+#define __OS_HAS_AGP 1
+
+#define __offsetof(type, field) ((size_t)(&((type *)0)->field))
+#define offsetof(type, field) __offsetof(type, field)
+
+typedef struct drm_pci_id_list
+{
+ int vendor;
+ int device;
+ long driver_private;
+ char *name;
+} drm_pci_id_list_t;
+
+#define DRM_AUTH 0x1
+#define DRM_MASTER 0x2
+#define DRM_ROOT_ONLY 0x4
+typedef int drm_ioctl_t(DRM_IOCTL_ARGS);
+typedef struct drm_ioctl_desc {
+ int (*func)(DRM_IOCTL_ARGS);
+ int flags;
+} drm_ioctl_desc_t;
+
+typedef struct drm_magic_entry {
+ drm_magic_t magic;
+ struct drm_file *priv;
+ struct drm_magic_entry *next;
+} drm_magic_entry_t;
+
+typedef struct drm_magic_head {
+ struct drm_magic_entry *head;
+ struct drm_magic_entry *tail;
+} drm_magic_head_t;
+
+typedef struct drm_buf {
+ int idx; /* Index into master buflist */
+ int total; /* Buffer size */
+ int order; /* log-base-2(total) */
+ int used; /* Amount of buffer in use (for DMA) */
+ unsigned long offset; /* Byte offset (used internally) */
+ void *address; /* Address of buffer */
+ unsigned long bus_address; /* Bus address of buffer */
+ struct drm_buf *next; /* Kernel-only: used for free list */
+ volatile int pending; /* On hardware DMA queue */
+ drm_file_t *filp;
+ /* Uniq. identifier of holding process */
+ int context; /* Kernel queue for this buffer */
+ enum {
+ DRM_LIST_NONE = 0,
+ DRM_LIST_FREE = 1,
+ DRM_LIST_WAIT = 2,
+ DRM_LIST_PEND = 3,
+ DRM_LIST_PRIO = 4,
+ DRM_LIST_RECLAIM = 5
+ } list; /* Which list we're on */
+
+ int dev_priv_size; /* Size of buffer private storage */
+ void *dev_private; /* Per-buffer private storage */
+} drm_buf_t;
+
+typedef struct drm_freelist {
+ int initialized; /* Freelist in use */
+ uint32_t count; /* Number of free buffers */
+ drm_buf_t *next; /* End pointer */
+
+ int low_mark; /* Low water mark */
+ int high_mark; /* High water mark */
+} drm_freelist_t;
+
+typedef struct drm_buf_entry {
+ int buf_size;
+ int buf_count;
+ drm_buf_t *buflist;
+ int seg_count;
+ int page_order;
+
+ uint32_t *seglist;
+ unsigned long *seglist_bus;
+
+ drm_freelist_t freelist;
+} drm_buf_entry_t;
+
+typedef TAILQ_HEAD(drm_file_list, drm_file) drm_file_list_t;
+struct drm_file {
+ TAILQ_ENTRY(drm_file) link;
+ int authenticated;
+ int master;
+ int minor;
+ pid_t pid;
+ uid_t uid;
+ int refs;
+ drm_magic_t magic;
+ unsigned long ioctl_count;
+ void *driver_priv;
+};
+
+typedef struct drm_lock_data {
+ drm_hw_lock_t *hw_lock; /* Hardware lock */
+ drm_file_t *filp;
+ /* Uniq. identifier of holding process */
+ kcondvar_t lock_cv; /* lock queue - SOLARIS Specific */
+ kmutex_t lock_mutex; /* lock - SOLARIS Specific */
+ unsigned long lock_time; /* Time of last lock in jiffies */
+} drm_lock_data_t;
+
+/*
+ * This structure, in drm_device_t, is always initialized while the device
+ * is open. dev->dma_lock protects the incrementing of dev->buf_use, which
+ * when set marks that no further bufs may be allocated until device teardown
+ * occurs (when the last open of the device has closed). The high/low
+ * watermarks of bufs are only touched by the X Server, and thus not
+ * concurrently accessed, so no locking is needed.
+ */
+typedef struct drm_device_dma {
+ drm_buf_entry_t bufs[DRM_MAX_ORDER+1];
+ int buf_count;
+ drm_buf_t **buflist; /* Vector of pointers info bufs */
+ int seg_count;
+ int page_count;
+ unsigned long *pagelist;
+ unsigned long byte_count;
+ enum {
+ _DRM_DMA_USE_AGP = 0x01,
+ _DRM_DMA_USE_SG = 0x02
+ } flags;
+} drm_device_dma_t;
+
+typedef struct drm_agp_mem {
+ void *handle;
+ unsigned long bound; /* address */
+ int pages;
+ caddr_t phys_addr;
+ struct drm_agp_mem *prev;
+ struct drm_agp_mem *next;
+} drm_agp_mem_t;
+
+typedef struct drm_agp_head {
+ agp_info_t agp_info;
+ const char *chipset;
+ drm_agp_mem_t *memory;
+ unsigned long mode;
+ int enabled;
+ int acquired;
+ unsigned long base;
+ int mtrr;
+ int cant_use_aperture;
+ unsigned long page_mask;
+ ldi_ident_t agpgart_li;
+ ldi_handle_t agpgart_lh;
+} drm_agp_head_t;
+
+
+typedef struct drm_dma_handle {
+ ddi_dma_handle_t dma_hdl;
+ ddi_acc_handle_t acc_hdl;
+ ddi_dma_cookie_t cookie;
+ uint_t cookie_num;
+ uintptr_t vaddr; /* virtual addr */
+ uintptr_t paddr; /* physical addr */
+ size_t real_sz; /* real size of memory */
+} drm_dma_handle_t;
+
+typedef struct drm_sg_mem {
+ unsigned long handle;
+ void *virtual;
+ int pages;
+ dma_addr_t *busaddr;
+ ddi_umem_cookie_t *umem_cookie;
+ drm_dma_handle_t *dmah_sg;
+ drm_dma_handle_t *dmah_gart; /* Handle to PCI memory */
+} drm_sg_mem_t;
+
+typedef TAILQ_HEAD(drm_map_list, drm_local_map) drm_map_list_t;
+
+/* BEGIN CSTYLED */
+typedef struct drm_local_map {
+ unsigned long offset; /* Physical address (0 for SAREA) */
+ unsigned long size; /* Physical size (bytes) */
+ drm_map_type_t type; /* Type of memory mapped */
+ drm_map_flags_t flags; /* Flags */
+ void *handle; /* User-space: "Handle" to pass to mmap */
+ /* Kernel-space: kernel-virtual address */
+ int mtrr; /* Boolean: MTRR used */
+ /* Private data */
+ int rid; /* PCI resource ID for bus_space */
+ int kernel_owned; /* Boolean: 1= initmapped, 0= addmapped */
+ caddr_t dev_addr; /* base device address */
+ ddi_acc_handle_t dev_handle; /* The data access handle */
+ ddi_umem_cookie_t drm_umem_cookie; /* For SAREA alloc and free */
+ TAILQ_ENTRY(drm_local_map) link;
+} drm_local_map_t;
+/* END CSTYLED */
+
+typedef TAILQ_HEAD(drm_vbl_sig_list, drm_vbl_sig) drm_vbl_sig_list_t;
+typedef struct drm_vbl_sig {
+ TAILQ_ENTRY(drm_vbl_sig) link;
+ unsigned int sequence;
+ int signo;
+ int pid;
+} drm_vbl_sig_t;
+
+
+/* used for clone device */
+typedef TAILQ_HEAD(drm_cminor_list, drm_cminor) drm_cminor_list_t;
+typedef struct drm_cminor {
+ TAILQ_ENTRY(drm_cminor) link;
+ drm_file_t *fpriv;
+ int minor;
+} drm_cminor_t;
+
+/* location of GART table */
+#define DRM_ATI_GART_MAIN 1
+#define DRM_ATI_GART_FB 2
+
+typedef struct ati_pcigart_info {
+ int gart_table_location;
+ int is_pcie;
+ void *addr;
+ dma_addr_t bus_addr;
+ drm_local_map_t mapping;
+} drm_ati_pcigart_info;
+
+/* DRM device structure */
+struct drm_device;
+struct drm_driver_info {
+ int (*load)(struct drm_device *, unsigned long);
+ int (*firstopen)(struct drm_device *);
+ int (*open)(struct drm_device *, drm_file_t *);
+ void (*preclose)(struct drm_device *, drm_file_t *);
+ void (*postclose)(struct drm_device *, drm_file_t *);
+ void (*lastclose)(struct drm_device *);
+ int (*unload)(struct drm_device *);
+ void (*reclaim_buffers_locked)(struct drm_device *, drm_file_t *);
+ int (*presetup)(struct drm_device *);
+ int (*postsetup)(struct drm_device *);
+ int (*open_helper)(struct drm_device *, drm_file_t *);
+ void (*free_filp_priv)(struct drm_device *, drm_file_t *);
+ void (*release)(struct drm_device *, void *);
+ int (*dma_ioctl)(DRM_IOCTL_ARGS);
+ void (*dma_ready)(struct drm_device *);
+ int (*dma_quiescent)(struct drm_device *);
+ int (*dma_flush_block_and_flush)(struct drm_device *,
+ int, drm_lock_flags_t);
+ int (*dma_flush_unblock)(struct drm_device *, int,
+ drm_lock_flags_t);
+ int (*context_ctor)(struct drm_device *, int);
+ int (*context_dtor)(struct drm_device *, int);
+ int (*kernel_context_switch)(struct drm_device *, int, int);
+ int (*kernel_context_switch_unlock)(struct drm_device *);
+ int (*device_is_agp) (struct drm_device *);
+ int (*irq_preinstall)(struct drm_device *);
+ void (*irq_postinstall)(struct drm_device *);
+ void (*irq_uninstall)(struct drm_device *dev);
+ uint_t (*irq_handler)(DRM_IRQ_ARGS);
+ int (*vblank_wait)(struct drm_device *, unsigned int *);
+ int (*vblank_wait2)(struct drm_device *, unsigned int *);
+ /* added for intel minimized vblank */
+ u32 (*get_vblank_counter)(struct drm_device *dev, int crtc);
+ int (*enable_vblank)(struct drm_device *dev, int crtc);
+ void (*disable_vblank)(struct drm_device *dev, int crtc);
+
+ drm_ioctl_desc_t *driver_ioctls;
+ int max_driver_ioctl;
+
+ int buf_priv_size;
+ int driver_major;
+ int driver_minor;
+ int driver_patchlevel;
+ const char *driver_name; /* Simple driver name */
+ const char *driver_desc; /* Longer driver name */
+ const char *driver_date; /* Date of last major changes. */
+
+ unsigned use_agp :1;
+ unsigned require_agp :1;
+ unsigned use_sg :1;
+ unsigned use_dma :1;
+ unsigned use_pci_dma :1;
+ unsigned use_dma_queue :1;
+ unsigned use_irq :1;
+ unsigned use_vbl_irq :1;
+ unsigned use_vbl_irq2 :1;
+ unsigned use_mtrr :1;
+};
+
+/*
+ * hardware-specific code needs to initialize mutexes which
+ * can be used in interrupt context, so they need to know
+ * the interrupt priority. Interrupt cookie in drm_device
+ * structure is the intr_block field.
+ */
+#define DRM_INTR_PRI(dev) \
+ DDI_INTR_PRI((dev)->intr_block)
+
+struct drm_device {
+ drm_driver_t *driver;
+ drm_cminor_list_t minordevs;
+ dev_info_t *dip;
+ void *drm_handle;
+ int drm_supported;
+ const char *desc; /* current driver description */
+ kmutex_t *irq_mutex;
+ kcondvar_t *irq_cv;
+
+ ddi_iblock_cookie_t intr_block;
+ uint32_t pci_device; /* PCI device id */
+ uint32_t pci_vendor;
+ char *unique; /* Unique identifier: e.g., busid */
+ int unique_len; /* Length of unique field */
+ int if_version; /* Highest interface version set */
+ int flags; /* Flags to open(2) */
+
+ /* Locks */
+ kmutex_t vbl_lock; /* protects vblank operations */
+ kmutex_t dma_lock; /* protects dev->dma */
+ kmutex_t irq_lock; /* protects irq condition checks */
+ kmutex_t dev_lock; /* protects everything else */
+ drm_lock_data_t lock; /* Information on hardware lock */
+
+ /* Usage Counters */
+ int open_count; /* Outstanding files open */
+ int buf_use; /* Buffers in use -- cannot alloc */
+
+ /* Performance counters */
+ unsigned long counters;
+ drm_stat_type_t types[15];
+ uint32_t counts[15];
+
+ /* Authentication */
+ drm_file_list_t files;
+ drm_magic_head_t magiclist[DRM_HASH_SIZE];
+
+ /* Linked list of mappable regions. Protected by dev_lock */
+ drm_map_list_t maplist;
+
+ drm_local_map_t **context_sareas;
+ int max_context;
+
+ /* DMA queues (contexts) */
+ drm_device_dma_t *dma; /* Optional pointer for DMA support */
+
+ /* Context support */
+ int irq; /* Interrupt used by board */
+ int irq_enabled; /* True if the irq handler is enabled */
+ int pci_domain;
+ int pci_bus;
+ int pci_slot;
+ int pci_func;
+ atomic_t context_flag; /* Context swapping flag */
+ int last_context; /* Last current context */
+
+ /* Only used for Radeon */
+ atomic_t vbl_received;
+ atomic_t vbl_received2;
+
+ drm_vbl_sig_list_t vbl_sig_list;
+ drm_vbl_sig_list_t vbl_sig_list2;
+
+ wait_queue_head_t vbl_queue; /* vbl wait channel */
+ /* vbl wait channel array */
+ wait_queue_head_t *vbl_queues;
+
+ /* number of VBLANK interrupts */
+ /* (driver must alloc the right number of counters) */
+ atomic_t *_vblank_count;
+ /* signal list to send on VBLANK */
+ struct drm_vbl_sig_list *vbl_sigs;
+
+ /* number of signals pending on all crtcs */
+ atomic_t vbl_signal_pending;
+ /* number of users of vblank interrupts per crtc */
+ atomic_t *vblank_refcount;
+ /* protected by dev->vbl_lock, used for wraparound handling */
+ u32 *last_vblank;
+ /* so we don't call enable more than */
+ atomic_t *vblank_enabled;
+ /* for compensation of spurious wraparounds */
+ u32 *vblank_premodeset;
+ /* Don't wait while crtc is likely disabled */
+ int *vblank_suspend;
+ /* size of vblank counter register */
+ u32 max_vblank_count;
+ int num_crtcs;
+ kmutex_t tasklet_lock;
+ void (*locked_tasklet_func)(struct drm_device *dev);
+
+ pid_t buf_pgid;
+ drm_agp_head_t *agp;
+ drm_sg_mem_t *sg; /* Scatter gather memory */
+ uint32_t *ctx_bitmap;
+ void *dev_private;
+ unsigned int agp_buffer_token;
+ drm_local_map_t *agp_buffer_map;
+
+ kstat_t *asoft_ksp; /* kstat support */
+
+ /* name Drawable information */
+ kmutex_t drw_lock;
+ unsigned int drw_bitfield_length;
+ u32 *drw_bitfield;
+ unsigned int drw_info_length;
+ drm_drawable_info_t **drw_info;
+ /*
+ * Saving S3 context
+ */
+ void *s3_private;
+};
+
+/* Memory management support (drm_memory.c) */
+void drm_mem_init(void);
+void drm_mem_uninit(void);
+void *drm_alloc(size_t, int);
+void *drm_calloc(size_t, size_t, int);
+void *drm_realloc(void *, size_t, size_t, int);
+void drm_free(void *, size_t, int);
+int drm_ioremap(drm_device_t *, drm_local_map_t *);
+void drm_ioremapfree(drm_local_map_t *);
+
+void drm_core_ioremap(struct drm_local_map *, struct drm_device *);
+void drm_core_ioremapfree(struct drm_local_map *, struct drm_device *);
+
+void drm_pci_free(drm_device_t *, drm_dma_handle_t *);
+void *drm_pci_alloc(drm_device_t *, size_t, size_t, dma_addr_t, int);
+
+struct drm_local_map *drm_core_findmap(struct drm_device *, unsigned long);
+
+int drm_context_switch(drm_device_t *, int, int);
+int drm_context_switch_complete(drm_device_t *, int);
+int drm_ctxbitmap_init(drm_device_t *);
+void drm_ctxbitmap_cleanup(drm_device_t *);
+void drm_ctxbitmap_free(drm_device_t *, int);
+int drm_ctxbitmap_next(drm_device_t *);
+
+/* Locking IOCTL support (drm_lock.c) */
+int drm_lock_take(drm_lock_data_t *, unsigned int);
+int drm_lock_transfer(drm_device_t *,
+ drm_lock_data_t *, unsigned int);
+int drm_lock_free(drm_device_t *,
+ volatile unsigned int *, unsigned int);
+
+/* Buffer management support (drm_bufs.c) */
+unsigned long drm_get_resource_start(drm_device_t *, unsigned int);
+unsigned long drm_get_resource_len(drm_device_t *, unsigned int);
+int drm_initmap(drm_device_t *, unsigned long, unsigned long,
+ unsigned int, int, int);
+void drm_rmmap(drm_device_t *, drm_local_map_t *);
+int drm_addmap(drm_device_t *, unsigned long, unsigned long,
+ drm_map_type_t, drm_map_flags_t, drm_local_map_t **);
+int drm_order(unsigned long);
+
+/* DMA support (drm_dma.c) */
+int drm_dma_setup(drm_device_t *);
+void drm_dma_takedown(drm_device_t *);
+void drm_free_buffer(drm_device_t *, drm_buf_t *);
+void drm_reclaim_buffers(drm_device_t *, drm_file_t *);
+#define drm_core_reclaim_buffers drm_reclaim_buffers
+
+/* IRQ support (drm_irq.c) */
+int drm_irq_install(drm_device_t *);
+int drm_irq_uninstall(drm_device_t *);
+uint_t drm_irq_handler(DRM_IRQ_ARGS);
+void drm_driver_irq_preinstall(drm_device_t *);
+void drm_driver_irq_postinstall(drm_device_t *);
+void drm_driver_irq_uninstall(drm_device_t *);
+int drm_vblank_wait(drm_device_t *, unsigned int *);
+void drm_vbl_send_signals(drm_device_t *);
+void drm_handle_vblank(struct drm_device *dev, int crtc);
+u32 drm_vblank_count(struct drm_device *dev, int crtc);
+int drm_vblank_get(struct drm_device *dev, int crtc);
+void drm_vblank_put(struct drm_device *dev, int crtc);
+int drm_vblank_init(struct drm_device *dev, int num_crtcs);
+void drm_locked_tasklet(drm_device_t *, void(*func)(drm_device_t *));
+
+/* AGP/GART support (drm_agpsupport.c) */
+int drm_device_is_agp(drm_device_t *);
+int drm_device_is_pcie(drm_device_t *);
+drm_agp_head_t *drm_agp_init(drm_device_t *);
+void drm_agp_fini(drm_device_t *);
+int drm_agp_do_release(drm_device_t *);
+void *drm_agp_allocate_memory(size_t, uint32_t);
+int drm_agp_free_memory(void *);
+int drm_agp_bind_memory(unsigned int, uint32_t, drm_device_t *);
+int drm_agp_unbind_memory(unsigned long, drm_device_t *);
+
+/* kstat support (drm_kstats.c) */
+int drm_init_kstats(drm_device_t *);
+void drm_fini_kstats(drm_device_t *);
+
+/* Scatter Gather Support (drm_scatter.c) */
+void drm_sg_cleanup(drm_device_t *, drm_sg_mem_t *);
+
+/* ATI PCIGART support (ati_pcigart.c) */
+int drm_ati_pcigart_init(drm_device_t *, drm_ati_pcigart_info *);
+int drm_ati_pcigart_cleanup(drm_device_t *, drm_ati_pcigart_info *);
+
+/* Locking IOCTL support (drm_drv.c) */
+int drm_lock(DRM_IOCTL_ARGS);
+int drm_unlock(DRM_IOCTL_ARGS);
+int drm_version(DRM_IOCTL_ARGS);
+int drm_setversion(DRM_IOCTL_ARGS);
+
+/* Misc. IOCTL support (drm_ioctl.c) */
+int drm_irq_by_busid(DRM_IOCTL_ARGS);
+int drm_getunique(DRM_IOCTL_ARGS);
+int drm_setunique(DRM_IOCTL_ARGS);
+int drm_getmap(DRM_IOCTL_ARGS);
+int drm_getclient(DRM_IOCTL_ARGS);
+int drm_getstats(DRM_IOCTL_ARGS);
+int drm_noop(DRM_IOCTL_ARGS);
+
+/* Context IOCTL support (drm_context.c) */
+int drm_resctx(DRM_IOCTL_ARGS);
+int drm_addctx(DRM_IOCTL_ARGS);
+int drm_modctx(DRM_IOCTL_ARGS);
+int drm_getctx(DRM_IOCTL_ARGS);
+int drm_switchctx(DRM_IOCTL_ARGS);
+int drm_newctx(DRM_IOCTL_ARGS);
+int drm_rmctx(DRM_IOCTL_ARGS);
+int drm_setsareactx(DRM_IOCTL_ARGS);
+int drm_getsareactx(DRM_IOCTL_ARGS);
+
+/* Drawable IOCTL support (drm_drawable.c) */
+int drm_adddraw(DRM_IOCTL_ARGS);
+int drm_rmdraw(DRM_IOCTL_ARGS);
+int drm_update_draw(DRM_IOCTL_ARGS);
+
+/* Authentication IOCTL support (drm_auth.c) */
+int drm_getmagic(DRM_IOCTL_ARGS);
+int drm_authmagic(DRM_IOCTL_ARGS);
+int drm_remove_magic(drm_device_t *, drm_magic_t);
+drm_file_t *drm_find_file(drm_device_t *, drm_magic_t);
+/* Buffer management support (drm_bufs.c) */
+int drm_addmap_ioctl(DRM_IOCTL_ARGS);
+int drm_rmmap_ioctl(DRM_IOCTL_ARGS);
+int drm_addbufs_ioctl(DRM_IOCTL_ARGS);
+int drm_infobufs(DRM_IOCTL_ARGS);
+int drm_markbufs(DRM_IOCTL_ARGS);
+int drm_freebufs(DRM_IOCTL_ARGS);
+int drm_mapbufs(DRM_IOCTL_ARGS);
+
+/* DMA support (drm_dma.c) */
+int drm_dma(DRM_IOCTL_ARGS);
+
+/* IRQ support (drm_irq.c) */
+int drm_control(DRM_IOCTL_ARGS);
+int drm_wait_vblank(DRM_IOCTL_ARGS);
+
+/* AGP/GART support (drm_agpsupport.c) */
+int drm_agp_acquire(DRM_IOCTL_ARGS);
+int drm_agp_release(DRM_IOCTL_ARGS);
+int drm_agp_enable(DRM_IOCTL_ARGS);
+int drm_agp_info(DRM_IOCTL_ARGS);
+int drm_agp_alloc(DRM_IOCTL_ARGS);
+int drm_agp_free(DRM_IOCTL_ARGS);
+int drm_agp_unbind(DRM_IOCTL_ARGS);
+int drm_agp_bind(DRM_IOCTL_ARGS);
+
+/* Scatter Gather Support (drm_scatter.c) */
+int drm_sg_alloc(DRM_IOCTL_ARGS);
+int drm_sg_free(DRM_IOCTL_ARGS);
+
+extern int drm_debug_flag;
+
+
+/* We add function to support DRM_DEBUG,DRM_ERROR,DRM_INFO */
+extern void drm_debug(const char *fmt, ...);
+extern void drm_error(const char *fmt, ...);
+extern void drm_info(const char *fmt, ...);
+
+#ifdef DEBUG
+#define DRM_DEBUG if (drm_debug_flag >= 2) drm_debug
+#define DRM_INFO if (drm_debug_flag >= 1) drm_info
+#else
+#define DRM_DEBUG(...)
+#define DRM_INFO(...)
+#endif
+
+#define DRM_ERROR drm_error
+
+
+#define MAX_INSTNUMS 16
+
+extern int drm_dev_to_instance(dev_t);
+extern int drm_dev_to_minor(dev_t);
+extern void *drm_supp_register(dev_info_t *, drm_device_t *);
+extern int drm_supp_unregister(void *);
+
+extern int drm_open(drm_device_t *, drm_cminor_t *, int, int, cred_t *);
+extern int drm_close(drm_device_t *, int, int, int, cred_t *);
+extern int drm_attach(drm_device_t *);
+extern int drm_detach(drm_device_t *);
+extern int drm_probe(drm_device_t *, drm_pci_id_list_t *);
+
+extern int drm_pci_init(drm_device_t *);
+extern void drm_pci_end(drm_device_t *);
+extern int pci_get_info(drm_device_t *, int *, int *, int *);
+extern int pci_get_irq(drm_device_t *);
+extern int pci_get_vendor(drm_device_t *);
+extern int pci_get_device(drm_device_t *);
+
+extern struct drm_drawable_info *drm_get_drawable_info(drm_device_t *,
+ drm_drawable_t);
+/* File Operations helpers (drm_fops.c) */
+extern drm_file_t *drm_find_file_by_proc(drm_device_t *, cred_t *);
+extern drm_cminor_t *drm_find_file_by_minor(drm_device_t *, int);
+extern int drm_open_helper(drm_device_t *, drm_cminor_t *, int, int,
+ cred_t *);
+
+#endif /* _DRMP_H */
diff --git a/src/VBox/Additions/solaris/DRM/include/drm_atomic.h b/src/VBox/Additions/solaris/DRM/include/drm_atomic.h
new file mode 100644
index 00000000..05401cc4
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/include/drm_atomic.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2009-2010 Oracle Corporation
+ * All rights reserved.
+ * Use is subject to license terms.
+ */
+/*
+ * \file drm_atomic.h
+ * Atomic operations used in the DRM which may or may not be provided by the OS.
+ *
+ * \author Eric Anholt <anholt@FreeBSD.org>
+ */
+
+/*
+ * Copyright 2004 Eric Anholt
+ * All Rights Reserved.
+ *
+ * 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 (including the next
+ * paragraph) 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
+ * VA LINUX SYSTEMS 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.
+ */
+
+/* Many of these implementations are rather fake, but good enough. */
+
+
+
+#ifndef _SYS_DRM_ATOMIC_H_
+#define _SYS_DRM_ATOMIC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/atomic.h>
+
+#ifdef __LINT__
+#undef inline
+#define inline
+#endif
+typedef uint32_t atomic_t;
+
+#define atomic_set(p, v) (*(p) = (v))
+#define atomic_read(p) (*(p))
+#define atomic_inc(p) atomic_add_int(p, 1)
+#define atomic_dec(p) atomic_dec_uint(p)
+#define atomic_add(n, p) atomic_add_int(p, n)
+#define atomic_sub(n, p) atomic_dec_uint(p, n)
+#define atomic_set_int(p, bits) atomic_or_uint(p, bits)
+#define atomic_clear_int(p, bits) atomic_and_uint(p, ~(bits))
+#define atomic_cmpset_int(p, c, n) \
+ ((c == atomic_cas_uint(p, c, n)) ? 1 : 0)
+
+#define set_bit(b, p) \
+ atomic_set_int(((volatile uint_t *)(void *)p) + (b >> 5), \
+ 1 << (b & 0x1f))
+
+#define clear_bit(b, p) \
+ atomic_clear_int(((volatile uint_t *)(void *)p) + (b >> 5), \
+ 1 << (b & 0x1f))
+
+#define test_bit(b, p) \
+ (((volatile uint_t *)(void *)p)[b >> 5] & (1 << (b & 0x1f)))
+
+/*
+ * Note: this routine doesn't return old value. It return
+ * 0 when succeeds, or -1 when fails.
+ */
+#ifdef _LP64
+#define test_and_set_bit(b, p) \
+ atomic_set_long_excl(((ulong_t *)(void *)p) + (b >> 6), (b & 0x3f))
+#else
+#define test_and_set_bit(b, p) \
+ atomic_set_long_excl(((ulong_t *)(void *)p) + (b >> 5), (b & 0x1f))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_DRM_ATOMIC_H_ */
diff --git a/src/VBox/Additions/solaris/DRM/include/drm_linux_list.h b/src/VBox/Additions/solaris/DRM/include/drm_linux_list.h
new file mode 100644
index 00000000..e811c9fe
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/include/drm_linux_list.h
@@ -0,0 +1,71 @@
+/*
+ * drm_linux_list.h -- linux list functions for the BSDs.
+ * Created: Mon Apr 7 14:30:16 1999 by anholt@FreeBSD.org
+ */
+/*
+ * -
+ * Copyright 2003 Eric Anholt
+ * All Rights Reserved.
+ *
+ * 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 (including the next
+ * paragraph) 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
+ * VA LINUX SYSTEMS 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.
+ *
+ * Authors:
+ * Eric Anholt <anholt@FreeBSD.org>
+ *
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifndef _DRM_LINUX_LIST_H_
+#define _DRM_LINUX_LIST_H_
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+/* Cheat, assume the list_head is at the start of the struct */
+#define list_entry(entry, type, member) (type *)(entry)
+
+#define INIT_LIST_HEAD(head) { \
+ (head)->next = head; \
+ (head)->prev = head; \
+}
+
+#define list_add_tail(entry, head) { \
+ (entry)->prev = (head)->prev; \
+ (entry)->next = head; \
+ (head)->prev->next = entry; \
+ (head)->prev = entry; \
+}
+
+#define list_del(entry) { \
+ (entry)->next->prev = (entry)->prev; \
+ (entry)->prev->next = (entry)->next; \
+}
+
+#define list_for_each(entry, head) \
+ for (entry = (head)->next; entry != head; entry = (entry)->next)
+
+#define list_for_each_safe(entry, temp, head) \
+ for (entry = (head)->next, temp = (entry)->next; \
+ temp != head; \
+ entry = temp, temp = temp->next)
+
+#endif /* _DRM_LINUX_LIST_H_ */
diff --git a/src/VBox/Additions/solaris/DRM/include/queue.h b/src/VBox/Additions/solaris/DRM/include/queue.h
new file mode 100644
index 00000000..4994209e
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/include/queue.h
@@ -0,0 +1,585 @@
+/* BEGIN CSTYLED */
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ * $FreeBSD: /repoman/r/ncvs/src/sys/sys/queue.h,v 1.66 2006/05/26 18:17:53 emaste Exp $
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * This file defines four types of data structures: singly-linked lists,
+ * singly-linked tail queues, lists and tail queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A singly-linked tail queue is headed by a pair of pointers, one to the
+ * head of the list and the other to the tail of the list. The elements are
+ * singly linked for minimum space and pointer manipulation overhead at the
+ * expense of O(n) removal for arbitrary elements. New elements can be added
+ * to the list after an existing element, at the head of the list, or at the
+ * end of the list. Elements being removed from the head of the tail queue
+ * should use the explicit macro for this purpose for optimum efficiency.
+ * A singly-linked tail queue may only be traversed in the forward direction.
+ * Singly-linked tail queues are ideal for applications with large datasets
+ * and few or no removals or for implementing a FIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ *
+ *
+ * SLIST LIST STAILQ TAILQ
+ * _HEAD + + + +
+ * _HEAD_INITIALIZER + + + +
+ * _ENTRY + + + +
+ * _INIT + + + +
+ * _EMPTY + + + +
+ * _FIRST + + + +
+ * _NEXT + + + +
+ * _PREV - - - +
+ * _LAST - - + +
+ * _FOREACH + + + +
+ * _FOREACH_SAFE + + + +
+ * _FOREACH_REVERSE - - - +
+ * _FOREACH_REVERSE_SAFE - - - +
+ * _INSERT_HEAD + + + +
+ * _INSERT_BEFORE - + - +
+ * _INSERT_AFTER + + + +
+ * _INSERT_TAIL - - + +
+ * _CONCAT - - + +
+ * _REMOVE_HEAD + - + -
+ * _REMOVE + + + +
+ *
+ */
+#ifdef QUEUE_MACRO_DEBUG
+/* Store the last 2 places the queue element or head was altered */
+struct qm_trace {
+ char * lastfile;
+ int lastline;
+ char * prevfile;
+ int prevline;
+};
+
+#define TRACEBUF struct qm_trace trace;
+#define TRASHIT(x) do {(x) = (void *)-1;} while (*"\0")
+
+#define QMD_TRACE_HEAD(head) do { \
+ (head)->trace.prevline = (head)->trace.lastline; \
+ (head)->trace.prevfile = (head)->trace.lastfile; \
+ (head)->trace.lastline = __LINE__; \
+ (head)->trace.lastfile = __FILE__; \
+} while (*"\0")
+
+#define QMD_TRACE_ELEM(elem) do { \
+ (elem)->trace.prevline = (elem)->trace.lastline; \
+ (elem)->trace.prevfile = (elem)->trace.lastfile; \
+ (elem)->trace.lastline = __LINE__; \
+ (elem)->trace.lastfile = __FILE__; \
+} while (*"\0")
+
+#else
+#define QMD_TRACE_ELEM(elem)
+#define QMD_TRACE_HEAD(head)
+#define TRACEBUF
+#define TRASHIT(x)
+#endif /* QUEUE_MACRO_DEBUG */
+
+/*
+ * Singly-linked List declarations.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+
+#define SLIST_FIRST(head) ((head)->slh_first)
+
+#define SLIST_FOREACH(var, head, field) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var); \
+ (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
+ for ((varp) = &SLIST_FIRST((head)); \
+ ((var) = *(varp)) != NULL; \
+ (varp) = &SLIST_NEXT((var), field))
+
+#define SLIST_INIT(head) do { \
+ SLIST_FIRST((head)) = NULL; \
+} while (*"\0")
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
+ SLIST_NEXT((slistelm), field) = (elm); \
+} while (*"\0")
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
+ SLIST_FIRST((head)) = (elm); \
+} while (*"\0")
+
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if (SLIST_FIRST((head)) == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = SLIST_FIRST((head)); \
+ while (SLIST_NEXT(curelm, field) != (elm)) \
+ curelm = SLIST_NEXT(curelm, field); \
+ SLIST_NEXT(curelm, field) = \
+ SLIST_NEXT(SLIST_NEXT(curelm, field), field); \
+ } \
+ TRASHIT((elm)->field.sle_next); \
+} while (*"\0")
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
+} while (*"\0")
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type) \
+struct name { \
+ struct type *stqh_first;/* first element */ \
+ struct type **stqh_last;/* addr of last next element */ \
+}
+
+#define STAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).stqh_first }
+
+#define STAILQ_ENTRY(type) \
+struct { \
+ struct type *stqe_next; /* next element */ \
+}
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_CONCAT(head1, head2) do { \
+ if (!STAILQ_EMPTY((head2))) { \
+ *(head1)->stqh_last = (head2)->stqh_first; \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_INIT((head2)); \
+ } \
+} while (*"\0")
+
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+
+#define STAILQ_FOREACH(var, head, field) \
+ for((var) = STAILQ_FIRST((head)); \
+ (var); \
+ (var) = STAILQ_NEXT((var), field))
+
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = STAILQ_FIRST((head)); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define STAILQ_INIT(head) do { \
+ STAILQ_FIRST((head)) = NULL; \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (*"\0")
+
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_NEXT((tqelm), field) = (elm); \
+} while (*"\0")
+
+#define STAILQ_INSERT_HEAD(head, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_FIRST((head)) = (elm); \
+} while (*"\0")
+
+#define STAILQ_INSERT_TAIL(head, elm, field) do { \
+ STAILQ_NEXT((elm), field) = NULL; \
+ *(head)->stqh_last = (elm); \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+} while (*"\0")
+
+#define STAILQ_LAST(head, type, field) \
+ (STAILQ_EMPTY((head)) ? \
+ NULL : \
+ ((struct type *)(void *) \
+ ((char *)((head)->stqh_last) - __offsetof(struct type, field))))
+
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_REMOVE(head, elm, type, field) do { \
+ if (STAILQ_FIRST((head)) == (elm)) { \
+ STAILQ_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = STAILQ_FIRST((head)); \
+ while (STAILQ_NEXT(curelm, field) != (elm)) \
+ curelm = STAILQ_NEXT(curelm, field); \
+ if ((STAILQ_NEXT(curelm, field) = \
+ STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((curelm), field);\
+ } \
+ TRASHIT((elm)->field.stqe_next); \
+} while (*"\0")
+
+#define STAILQ_REMOVE_HEAD(head, field) do { \
+ if ((STAILQ_FIRST((head)) = \
+ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (*"\0")
+
+#define STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do { \
+ if ((STAILQ_FIRST((head)) = STAILQ_NEXT((elm), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (*"\0")
+
+/*
+ * List declarations.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List functions.
+ */
+
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_LIST_CHECK_HEAD(head, field) do { \
+ if (LIST_FIRST((head)) != NULL && \
+ LIST_FIRST((head))->field.le_prev != \
+ &LIST_FIRST((head))) \
+ panic("Bad list head %p first->prev != head", (head)); \
+} while (*"\0")
+
+#define QMD_LIST_CHECK_NEXT(elm, field) do { \
+ if (LIST_NEXT((elm), field) != NULL && \
+ LIST_NEXT((elm), field)->field.le_prev != \
+ &((elm)->field.le_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+} while (*"\0")
+
+#define QMD_LIST_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.le_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+} while (*"\0")
+#else
+#define QMD_LIST_CHECK_HEAD(head, field)
+#define QMD_LIST_CHECK_NEXT(elm, field)
+#define QMD_LIST_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+
+#define LIST_FIRST(head) ((head)->lh_first)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST((head)); \
+ (var); \
+ (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST((head)); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define LIST_INIT(head) do { \
+ LIST_FIRST((head)) = NULL; \
+} while (*"\0")
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ QMD_LIST_CHECK_NEXT(listelm, field); \
+ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
+ LIST_NEXT((listelm), field)->field.le_prev = \
+ &LIST_NEXT((elm), field); \
+ LIST_NEXT((listelm), field) = (elm); \
+ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \
+} while (*"\0")
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_LIST_CHECK_PREV(listelm, field); \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ LIST_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \
+} while (*"\0")
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ QMD_LIST_CHECK_HEAD((head), field); \
+ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
+ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
+ LIST_FIRST((head)) = (elm); \
+ (elm)->field.le_prev = &LIST_FIRST((head)); \
+} while (*"\0")
+
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_REMOVE(elm, field) do { \
+ QMD_LIST_CHECK_NEXT(elm, field); \
+ QMD_LIST_CHECK_PREV(elm, field); \
+ if (LIST_NEXT((elm), field) != NULL) \
+ LIST_NEXT((elm), field)->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = LIST_NEXT((elm), field); \
+ TRASHIT((elm)->field.le_next); \
+ TRASHIT((elm)->field.le_prev); \
+} while (*"\0")
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+ TRACEBUF \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+ TRACEBUF \
+}
+
+/*
+ * Tail queue functions.
+ */
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
+ if (!TAILQ_EMPTY(head) && \
+ TAILQ_FIRST((head))->field.tqe_prev != \
+ &TAILQ_FIRST((head))) \
+ panic("Bad tailq head %p first->prev != head", (head)); \
+} while (*"\0")
+
+#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
+ if (*(head)->tqh_last != NULL) \
+ panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
+} while (*"\0")
+
+#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
+ if (TAILQ_NEXT((elm), field) != NULL && \
+ TAILQ_NEXT((elm), field)->field.tqe_prev != \
+ &((elm)->field.tqe_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+} while (*"\0")
+
+#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.tqe_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+} while (*"\0")
+#else
+#define QMD_TAILQ_CHECK_HEAD(head, field)
+#define QMD_TAILQ_CHECK_TAIL(head, headname)
+#define QMD_TAILQ_CHECK_NEXT(elm, field)
+#define QMD_TAILQ_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ QMD_TRACE_HEAD(head1); \
+ QMD_TRACE_HEAD(head2); \
+ } \
+} while (*"\0")
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var); \
+ (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var); \
+ (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_INIT(head) do { \
+ TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+} while (*"\0")
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_NEXT(listelm, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else { \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ } \
+ TAILQ_NEXT((listelm), field) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (*"\0")
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_PREV(listelm, field); \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ TAILQ_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (*"\0")
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ QMD_TAILQ_CHECK_HEAD(head, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
+ TAILQ_FIRST((head))->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (*"\0")
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ QMD_TAILQ_CHECK_TAIL(head, field); \
+ TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (*"\0")
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ QMD_TAILQ_CHECK_NEXT(elm, field); \
+ QMD_TAILQ_CHECK_PREV(elm, field); \
+ if ((TAILQ_NEXT((elm), field)) != NULL) \
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else { \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ QMD_TRACE_HEAD(head); \
+ } \
+ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
+ TRASHIT((elm)->field.tqe_next); \
+ TRASHIT((elm)->field.tqe_prev); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (*"\0")
+
+
+#ifdef _KERNEL
+
+#endif /* _KERNEL */
+
+#endif /* !_SYS_QUEUE_H_ */
+
+/* END CSTYLED */
diff --git a/src/VBox/Additions/solaris/DRM/vboxvideo_drm.c b/src/VBox/Additions/solaris/DRM/vboxvideo_drm.c
new file mode 100644
index 00000000..c63deb33
--- /dev/null
+++ b/src/VBox/Additions/solaris/DRM/vboxvideo_drm.c
@@ -0,0 +1,409 @@
+/* $Id: vboxvideo_drm.c $ */
+/** @file
+ * vboxvideo_drm - Direct Rendering Module, Solaris Specific Code.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#undef offsetof /* This gets redefined in drmP.h */
+#include "include/drmP.h"
+#include "include/drm.h"
+
+#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define VBOXSOLQUOTE2(x) #x
+#define VBOXSOLQUOTE(x) VBOXSOLQUOTE2(x)
+/** The module name. */
+#define DEVICE_NAME "vboxvideo"
+/** The module description as seen in 'modinfo'. */
+#define DEVICE_DESC_DRV "VirtualBox DRM"
+
+/** DRM Specific defines */
+#define DRIVER_AUTHOR "Oracle Corporation"
+#define DRIVER_NAME DEVICE_NAME
+#define DRIVER_DESC DEVICE_DESC_DRV
+#define DRIVER_DATE "20090317"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+#define vboxvideo_PCI_IDS { 0x80ee, 0xbeef, 0, "VirtualBox Video" }, \
+ { 0, 0, 0, NULL }
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int VBoxVideoSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd);
+static int VBoxVideoSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd);
+static int VBoxVideoSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult);
+
+static void vboxVideoSolarisConfigure(drm_driver_t *pDriver);
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+extern struct cb_ops drm_cb_ops;
+
+/**
+ * dev_ops: for driver device operations
+ */
+static struct dev_ops g_VBoxVideoSolarisDevOps =
+{
+ DEVO_REV, /* driver build revision */
+ 0, /* ref count */
+ VBoxVideoSolarisGetInfo,
+ nulldev, /* identify */
+ nulldev, /* probe */
+ VBoxVideoSolarisAttach,
+ VBoxVideoSolarisDetach,
+ nodev, /* reset */
+ &drm_cb_ops,
+ NULL, /* dev bus ops*/
+ NULL /* power */
+};
+
+/**
+ * modldrv: export driver specifics to the kernel
+ */
+static struct modldrv g_VBoxVideoSolarisModule =
+{
+ &mod_driverops, /* extern from kernel */
+ DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" VBOXSOLQUOTE(VBOX_SVN_REV),
+ &g_VBoxVideoSolarisDevOps
+};
+
+/**
+ * modlinkage: export install/remove/info to the kernel
+ */
+static struct modlinkage g_VBoxVideoSolarisModLinkage =
+{
+ MODREV_1, /* loadable module system revision */
+ &g_VBoxVideoSolarisModule,
+ NULL /* terminate array of linkage structures */
+};
+
+/* VBoxVideo device PCI ID */
+static drm_pci_id_list_t vboxvideo_pciidlist[] = {
+ vboxvideo_PCI_IDS
+};
+
+
+/** DRM Driver */
+static drm_driver_t g_VBoxVideoSolarisDRMDriver = { 0 };
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Soft state. */
+static void *g_pVBoxVideoSolarisState;
+
+
+/**
+ * Kernel entry points
+ */
+int _init(void)
+{
+ LogFlow((DEVICE_NAME ":_init flow\n"));
+ cmn_err(CE_NOTE, DEVICE_NAME ":_init\n");
+
+ vboxVideoSolarisConfigure(&g_VBoxVideoSolarisDRMDriver);
+ int rc = ddi_soft_state_init(&g_pVBoxVideoSolarisState, sizeof(drm_device_t), DRM_MAX_INSTANCES);
+ if (!rc)
+ return mod_install(&g_VBoxVideoSolarisModLinkage);
+ else
+ LogRel((DEVICE_NAME ":_init: ddi_soft_state_init failed. rc=%d\n", rc));
+}
+
+
+int _fini(void)
+{
+ LogFlow((DEVICE_NAME ":_fini flow\n"));
+ cmn_err(CE_NOTE, DEVICE_NAME ":_fini\n");
+ int rc = mod_remove(&g_VBoxVideoSolarisModLinkage);
+ if (!rc)
+ ddi_soft_state_fini(&g_pVBoxVideoSolarisState);
+ return rc;
+}
+
+
+int _info(struct modinfo *pModInfo)
+{
+ LogFlow((DEVICE_NAME ":_info flow\n"));
+ cmn_err(CE_NOTE, DEVICE_NAME ":_info\n");
+ return mod_info(&g_VBoxVideoSolarisModLinkage, pModInfo);
+}
+
+
+/**
+ * Attach entry point, to attach a device to the system or resume it.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Operation type (attach/resume).
+ *
+ * @returns corresponding solaris error code.
+ */
+static int VBoxVideoSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd)
+{
+ LogFlow((DEVICE_NAME ":VBoxVideoSolarisAttach pDip=%p enmCmd=%d\n", pDip, enmCmd));
+ cmn_err(CE_NOTE, DEVICE_NAME ":attach\n");
+
+ switch (enmCmd)
+ {
+ case DDI_ATTACH:
+ {
+ drm_device_t *pState;
+ int Instance = ddi_get_instance(pDip);
+ int rc = ddi_soft_state_zalloc(g_pVBoxVideoSolarisState, Instance);
+ if (rc == DDI_SUCCESS)
+ {
+ pState = ddi_get_soft_state(g_pVBoxVideoSolarisState, Instance);
+ pState->dip = pDip;
+ pState->driver = &g_VBoxVideoSolarisDRMDriver;
+
+ /*
+ * Register using the DRM module which will create the minor nodes
+ */
+ void *pDRMHandle = drm_supp_register(pDip, pState);
+ if (pDRMHandle)
+ {
+ pState->drm_handle = pDRMHandle;
+
+ /*
+ * Probe with our pci-id.
+ * -XXX- is probing really required???
+ */
+ pState->drm_supported = DRM_UNSUPPORT;
+ rc = drm_probe(pState, vboxvideo_pciidlist);
+ if (rc == DDI_SUCCESS)
+ {
+ pState->drm_supported = DRM_SUPPORT;
+
+ /*
+ * Call the common attach DRM routine.
+ */
+ rc = drm_attach(pState);
+ if (rc == DDI_SUCCESS)
+ {
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((DEVICE_NAME ":VBoxVideoSolarisAttach drm_attach failed.rc=%d\n", rc));
+ }
+ else
+ LogRel((DEVICE_NAME ":VBoxVideoSolarisAttach drm_probe failed.rc=%d\n", rc));
+
+ drm_supp_unregister(pDRMHandle);
+ }
+ else
+ LogRel((DEVICE_NAME ":VBoxVideoSolarisAttach drm_supp_register failed.\n"));
+
+ ddi_soft_state_free(g_pVBoxVideoSolarisState, Instance);
+ }
+ else
+ LogRel((DEVICE_NAME ":VBoxVideoSolarisAttach failed to alloc memory for soft state.rc=%d\n", rc));
+ return DDI_FAILURE;
+ }
+
+ case DDI_RESUME:
+ {
+ /* Nothing to do here... */
+ return DDI_SUCCESS;
+ }
+ }
+ return DDI_FAILURE;
+}
+
+
+/**
+ * Detach entry point, to detach a device to the system or suspend it.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Operation type (detach/suspend).
+ *
+ * @returns corresponding solaris error code.
+ */
+static int VBoxVideoSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd)
+{
+ LogFlow((DEVICE_NAME ":VBoxVideoSolarisDetach pDip=%p enmCmd=%d\n", pDip, enmCmd));
+
+ switch (enmCmd)
+ {
+ case DDI_DETACH:
+ {
+ int Instance = ddi_get_instance(pDip);
+ drm_device_t *pState = ddi_get_soft_state(g_pVBoxVideoSolarisState, Instance);
+ if (pState)
+ {
+ drm_detach(pState);
+ drm_supp_unregister(pState->drm_handle);
+ ddi_soft_state_free(g_pVBoxVideoSolarisState, Instance);
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((DEVICE_NAME ":VBoxVideoSolarisDetach failed to get soft state.\n"));
+
+ return DDI_FAILURE;
+ }
+
+ case DDI_RESUME:
+ {
+ /* Nothing to do here... */
+ return DDI_SUCCESS;
+ }
+ }
+ return DDI_FAILURE;
+}
+
+
+/*
+ * Info entry point, called by solaris kernel for obtaining driver info.
+ *
+ * @param pDip The module structure instance (do not use).
+ * @param enmCmd Information request type.
+ * @param pvArg Type specific argument.
+ * @param ppvResult Where to store the requested info.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VBoxVideoSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult)
+{
+ LogFlow((DEVICE_NAME ":VBoxGuestSolarisGetInfo\n"));
+
+ int rc = DDI_FAILURE;
+ int Instance = drm_dev_to_instance((dev_t)pvArg);
+ drm_device_t *pState = NULL;
+ switch (enmCmd)
+ {
+ case DDI_INFO_DEVT2DEVINFO:
+ {
+ pState = ddi_get_soft_state(g_pVBoxVideoSolarisState, Instance);
+ if ( pState
+ && pState->dip)
+ {
+ *ppvResult = (void *)pState->dip;
+ rc = DDI_SUCCESS;
+ }
+ else
+ {
+ LogRel((DEVICE_NAME ":VBoxGuestSolarisGetInfo state or state's devinfo invalid.\n"));
+ rc = DDI_FAILURE;
+ }
+ break;
+ }
+
+ case DDI_INFO_DEVT2INSTANCE:
+ {
+ *ppvResult = (void *)(uintptr_t)Instance;
+ rc = DDI_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ rc = DDI_FAILURE;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+static int vboxVideoSolarisLoad(drm_device_t *pDevice, unsigned long fFlag)
+{
+ return 0;
+}
+
+static int vboxVideoSolarisUnload(drm_device_t *pDevice)
+{
+ return 0;
+}
+
+static void vboxVideoSolarisLastClose(drm_device_t *pDevice)
+{
+}
+
+static void vboxVideoSolarisPreClose(drm_device_t *pDevice, drm_file_t *pFile)
+{
+}
+
+
+static void vboxVideoSolarisConfigure(drm_driver_t *pDriver)
+{
+ /*
+ * DRM entry points, use the common DRM extension wherever possible.
+ */
+ pDriver->buf_priv_size = 1;
+ pDriver->load = vboxVideoSolarisLoad;
+ pDriver->unload = vboxVideoSolarisUnload;
+ pDriver->preclose = vboxVideoSolarisPreClose;
+ pDriver->lastclose = vboxVideoSolarisLastClose;
+ pDriver->device_is_agp = drm_device_is_agp;
+#if 0
+ pDriver->get_vblank_counter = drm_vblank_count;
+ pDriver->enable_vblank = NULL;
+ pDriver->disable_vblank = NULL;
+ pDriver->irq_install = drm_driver_irq_install;
+ pDriver->irq_preinstall = drm_driver_irq_preinstall;
+ pDriver->irq_postinstall = drm_driver_irq_postinstall;
+ pDirver->irq_uninstall = drm_driver_irq_uninstall;
+ pDriver->irq_handler = drm_driver_irq_handler;
+
+ pDriver->driver_ioctls =
+ pDriver->max_driver_ioctls =
+#endif
+
+ pDriver->driver_name = DRIVER_NAME;
+ pDriver->driver_desc = DRIVER_DESC;
+ pDriver->driver_date = DRIVER_DATE;
+ pDriver->driver_major = DRIVER_MAJOR;
+ pDriver->driver_minor = DRIVER_MINOR;
+ pDriver->driver_patchlevel = DRIVER_PATCHLEVEL;
+
+ pDriver->use_agp = 1;
+ pDriver->require_agp = 1;
+ pDriver->use_irq = 1;
+}
+
diff --git a/src/VBox/Additions/solaris/Installer/VBox.sh b/src/VBox/Additions/solaris/Installer/VBox.sh
new file mode 100755
index 00000000..c2935a56
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/VBox.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+# $Id: VBox.sh $
+## @file
+# VirtualBox startup script for Solaris Guests Additions
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+CURRENT_ISA=`isainfo -k`
+if test "$CURRENT_ISA" = "amd64"; then
+ INSTALL_DIR="/opt/VirtualBoxAdditions/amd64"
+else
+ INSTALL_DIR="/opt/VirtualBoxAdditions"
+fi
+
+APP=`basename $0`
+case "$APP" in
+ VBoxClient)
+ exec "$INSTALL_DIR/VBoxClient" "$@"
+ ;;
+ VBoxService)
+ exec "$INSTALL_DIR/VBoxService" "$@"
+ ;;
+ VBoxControl)
+ exec "$INSTALL_DIR/VBoxControl" "$@"
+ ;;
+ *)
+ echo "Unknown application - $APP"
+ exit 1
+ ;;
+esac
+exit 0
diff --git a/src/VBox/Additions/solaris/Installer/makepackage.sh b/src/VBox/Additions/solaris/Installer/makepackage.sh
new file mode 100755
index 00000000..abe33065
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/makepackage.sh
@@ -0,0 +1,160 @@
+#!/bin/sh
+# $Id: makepackage.sh $
+## @file
+# VirtualBox Solaris Guest Additions package creation script.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+#
+# Usage:
+# makespackage.sh $(PATH_TARGET)/install packagename svnrev
+
+if test -z "$3"; then
+ echo "Usage: $0 installdir packagename svnrev"
+ exit 1
+fi
+ostype=`uname -s`
+if test "$ostype" != "Linux" && test "$ostype" != "SunOS" ; then
+ echo "Linux/Solaris not detected."
+ exit 1
+fi
+
+VBOX_BASEPKG_DIR=$1
+VBOX_INSTALLED_DIR="$VBOX_BASEPKG_DIR"/opt/VirtualBoxAdditions
+VBOX_PKGFILENAME=$2
+VBOX_SVN_REV=$3
+
+VBOX_PKGNAME=SUNWvboxguest
+VBOX_AWK=/usr/bin/awk
+case "$ostype" in
+"SunOS")
+ VBOX_GGREP=/usr/sfw/bin/ggrep
+ VBOX_SOL_PKG_DEV=/var/spool/pkg
+ ;;
+*)
+ VBOX_GGREP=`which grep`
+ VBOX_SOL_PKG_DEV=$4
+ ;;
+esac
+VBOX_AWK=/usr/bin/awk
+
+# check for GNU grep we use which might not ship with all Solaris
+if test ! -f "$VBOX_GGREP" && test ! -h "$VBOX_GGREP"; then
+ echo "## GNU grep not found in $VBOX_GGREP."
+ exit 1
+fi
+
+# bail out on non-zero exit status
+set -e
+
+# Fixup filelist using awk, the parameters must be in awk syntax
+# params: filename condition action
+filelist_fixup()
+{
+ "$VBOX_AWK" 'NF == 6 && '"$2"' { '"$3"' } { print }' "$1" > "tmp-$1"
+ mv -f "tmp-$1" "$1"
+}
+
+dirlist_fixup()
+{
+ "$VBOX_AWK" 'NF == 6 && $1 == "d" && '"$2"' { '"$3"' } { print }' "$1" > "tmp-$1"
+ mv -f "tmp-$1" "$1"
+}
+
+# Create relative hardlinks
+cd "$VBOX_INSTALLED_DIR"
+ln -f ./VBoxISAExec $VBOX_INSTALLED_DIR/VBoxService
+ln -f ./VBoxISAExec $VBOX_INSTALLED_DIR/VBoxClient
+ln -f ./VBoxISAExec $VBOX_INSTALLED_DIR/VBoxControl
+ln -f ./VBoxISAExec $VBOX_INSTALLED_DIR/vboxmslnk
+
+# prepare file list
+cd "$VBOX_BASEPKG_DIR"
+echo 'i pkginfo=./vboxguest.pkginfo' > prototype
+echo 'i postinstall=./postinstall.sh' >> prototype
+echo 'i preremove=./preremove.sh' >> prototype
+echo 'i space=./vboxguest.space' >> prototype
+echo 'i depend=./vboxguest.depend' >> prototype
+if test -f "./vboxguest.copyright"; then
+ echo 'i copyright=./vboxguest.copyright' >> prototype
+fi
+
+# Exclude directory entries to not cause conflicts (owner,group) with existing directories in the system
+find . ! -type d | $VBOX_GGREP -v -E 'prototype|makepackage.sh|vboxguest.pkginfo|postinstall.sh|preremove.sh|vboxguest.space|vboxguest.depend|vboxguest.copyright' | pkgproto >> prototype
+
+# Include opt/VirtualBoxAdditions and subdirectories as we want uninstall to clean up directory structure as well
+find . -type d | $VBOX_GGREP -E 'opt/VirtualBoxAdditions|var/svc/manifest/application/virtualbox' | pkgproto >> prototype
+
+# Include /etc/fs/vboxfs (as we need to create the subdirectory)
+find . -type d | $VBOX_GGREP -E 'etc/fs/vboxfs' | pkgproto >> prototype
+
+
+# don't grok for the class files
+filelist_fixup prototype '$2 == "none"' '$5 = "root"; $6 = "bin"'
+
+# VBoxService requires suid
+filelist_fixup prototype '$3 == "opt/VirtualBoxAdditions/VBoxService"' '$4 = "4755"'
+filelist_fixup prototype '$3 == "opt/VirtualBoxAdditions/amd64/VBoxService"' '$4 = "4755"'
+
+# Manifest class action scripts
+filelist_fixup prototype '$3 == "var/svc/manifest/application/virtualbox/vboxservice.xml"' '$2 = "manifest";$6 = "sys"'
+filelist_fixup prototype '$3 == "var/svc/manifest/application/virtualbox/vboxmslnk.xml"' '$2 = "manifest";$6 = "sys"'
+
+# vboxguest
+filelist_fixup prototype '$3 == "usr/kernel/drv/vboxguest"' '$6="sys"'
+filelist_fixup prototype '$3 == "usr/kernel/drv/amd64/vboxguest"' '$6="sys"'
+
+# vboxms
+filelist_fixup prototype '$3 == "usr/kernel/drv/vboxms"' '$6="sys"'
+filelist_fixup prototype '$3 == "usr/kernel/drv/amd64/vboxms"' '$6="sys"'
+
+# Use 'root' as group so as to match attributes with the previous installation and prevent a conflict. Otherwise pkgadd bails out thinking
+# we're violating directory attributes of another (non existing) package
+dirlist_fixup prototype '$3 == "var/svc/manifest/application/virtualbox"' '$6 = "root"'
+
+echo " --- start of prototype ---"
+cat prototype
+echo " --- end of prototype --- "
+
+# explicitly set timestamp to shutup warning
+VBOXPKG_TIMESTAMP=vboxguest`date '+%Y%m%d%H%M%S'`_r$VBOX_SVN_REV
+
+# create the package instance
+pkgmk -d $VBOX_SOL_PKG_DEV -p $VBOXPKG_TIMESTAMP -o -r .
+
+# translate into package datastream
+pkgtrans -s -o "$VBOX_SOL_PKG_DEV" `pwd`/$VBOX_PKGFILENAME "$VBOX_PKGNAME"
+
+rm -rf "$VBOX_SOL_PKG_DEV/$VBOX_PKGNAME"
+exit $?
+
diff --git a/src/VBox/Additions/solaris/Installer/postinstall.sh b/src/VBox/Additions/solaris/Installer/postinstall.sh
new file mode 100755
index 00000000..4bfdfc78
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/postinstall.sh
@@ -0,0 +1,448 @@
+#!/bin/sh
+# $Id: postinstall.sh $
+## @file
+# VirtualBox postinstall script for Solaris Guest Additions.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# LC_ALL should take precedence over LC_* and LANG but whatever...
+LC_ALL=C
+export LC_ALL
+
+LANG=C
+export LANG
+
+# "Remote" installs ('pkgadd -R') can skip many of the steps below.
+REMOTE_INST=0
+BASEDIR_OPT=""
+if test "x${PKG_INSTALL_ROOT:-/}" != "x/"; then
+ BASEDIR_OPT="-R $PKG_INSTALL_ROOT"
+ REMOTE_INST=1
+fi
+export REMOTE_INST
+export BASEDIR_OPT
+
+# uncompress(directory, file)
+# Updates package metadata and uncompresses the file.
+uncompress_file()
+{
+ if test -z "$1" || test -z "$2"; then
+ echo "missing argument to uncompress_file()"
+ return 1
+ fi
+
+ # Remove compressed path from the pkg
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST "$1/$2.Z" 1>/dev/null
+
+ # Add uncompressed path to the pkg
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST "$1/$2" f
+
+ # Uncompress the file (removes compressed file when done)
+ uncompress -f "$1/$2.Z" > /dev/null 2>&1
+}
+
+# uncompress_files(directory_with_*.Z_files)
+uncompress_files()
+{
+ for i in "${1}/"*.Z; do
+ uncompress_file "${1}" "`basename \"${i}\" .Z`"
+ done
+}
+
+solaris64dir="amd64"
+solaris32dir="i386"
+vboxadditions_path="${PKG_INSTALL_ROOT}/opt/VirtualBoxAdditions"
+vboxadditions32_path=$vboxadditions_path/$solaris32dir
+vboxadditions64_path=$vboxadditions_path/$solaris64dir
+
+# get the current zone
+currentzone=`zonename`
+# get what ISA the guest is running
+cputype=`isainfo -k`
+if test "$cputype" = "amd64"; then
+ isadir=$solaris64dir
+else
+ isadir=""
+fi
+
+vboxadditionsisa_path=$vboxadditions_path/$isadir
+
+
+# uncompress if necessary
+if test -f "$vboxadditions32_path/VBoxClient.Z" || test -f "$vboxadditions64_path/VBoxClient.Z"; then
+ echo "Uncompressing files..."
+ if test -f "$vboxadditions32_path/VBoxClient.Z"; then
+ uncompress_files "$vboxadditions32_path"
+ fi
+ if test -f "$vboxadditions64_path/VBoxClient.Z"; then
+ uncompress_files "$vboxadditions64_path"
+ fi
+fi
+
+
+if test "$currentzone" = "global"; then
+ # vboxguest.sh would've been installed, we just need to call it.
+ echo "Configuring VirtualBox guest kernel module..."
+ # stop all previous modules (vboxguest, vboxfs) and load vboxguest
+ # ('vboxguest.sh start' only starts vboxguest).
+ if test "$REMOTE_INST" -eq 0; then
+ $vboxadditions_path/vboxguest.sh stopall silentunload
+ fi
+ $vboxadditions_path/vboxguest.sh start
+
+ # Figure out group to use for /etc/devlink.tab (before Solaris 11 SRU6
+ # it was always using group sys)
+ group=sys
+ if [ -f /etc/dev/reserved_devnames ]; then
+ # Solaris 11 SRU6 and later use group root (check a file which isn't
+ # tainted by VirtualBox install scripts and allow no other group)
+ refgroup=`LC_ALL=C /usr/bin/ls -lL /etc/dev/reserved_devnames | awk '{ print $4 }' 2>/dev/null`
+ if [ $? -eq 0 -a "x$refgroup" = "xroot" ]; then
+ group=root
+ fi
+ unset refgroup
+ fi
+
+ sed -e '/name=vboxguest/d' ${PKG_INSTALL_ROOT}/etc/devlink.tab > ${PKG_INSTALL_ROOT}/etc/devlink.vbox
+ echo "type=ddi_pseudo;name=vboxguest \D" >> ${PKG_INSTALL_ROOT}/etc/devlink.vbox
+ chmod 0644 ${PKG_INSTALL_ROOT}/etc/devlink.vbox
+ chown root:$group ${PKG_INSTALL_ROOT}/etc/devlink.vbox
+ mv -f ${PKG_INSTALL_ROOT}/etc/devlink.vbox ${PKG_INSTALL_ROOT}/etc/devlink.tab
+
+ # create the device link
+ /usr/sbin/devfsadm $BASEDIR_OPT -i vboxguest
+fi
+
+# create links
+echo "Creating links..."
+if test "$currentzone" = "global"; then
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST /dev/vboxguest=../devices/pci@0,0/pci80ee,cafe@4:vboxguest s
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST /dev/vboxms=../devices/pseudo/vboxms@0:vboxms s
+fi
+
+# check if X.Org exists (snv_130 and higher have /usr/X11/* as /usr/*)
+if test -f "${PKG_INSTALL_ROOT}/usr/bin/Xorg"; then
+ xorgbin="${PKG_INSTALL_ROOT}/usr/bin/Xorg"
+elif test -f "${PKG_INSTALL_ROOT}/usr/X11/bin/Xorg"; then
+ xorgbin="${PKG_INSTALL_ROOT}/usr/X11/bin/Xorg"
+else
+ xorgbin=""
+ retval=0
+fi
+
+# Install Xorg components to the required places
+if test ! -z "$xorgbin"; then
+ xorgversion_long=`$xorgbin -version 2>&1 | grep "X Window System Version"`
+ xorgversion=`/usr/bin/expr "${xorgversion_long}" : 'X Window System Version \([^ ]*\)'`
+ if test -z "$xorgversion_long"; then
+ xorgversion_long=`$xorgbin -version 2>&1 | grep "X.Org X Server"`
+ xorgversion=`/usr/bin/expr "${xorgversion_long}" : 'X.Org X Server \([^ ]*\)'`
+ fi
+
+ # "X.Y.Z" - strip off all numerics after the 2nd '.' character, e.g. "1.11.3" -> "1.11"
+ # The second sed invocation removes all '.' characters, e.g. "1.11" -> "111".
+ fileversion=`echo $xorgversion | sed "s/\.[0-9]*//2" | sed "s/\.//"`
+ vboxvideo_src="vboxvideo_drv_$fileversion.so"
+
+ # Handle exceptions now where the X.org version does not exactly match the file-version.
+ case "$xorgversion" in
+ 1.5.99 )
+ vboxvideo_src="vboxvideo_drv_16.so"
+ ;;
+ 7.2.* )
+ vboxvideo_src="vboxvideo_drv_71.so"
+ ;;
+ 6.9.* )
+ vboxvideo_src="vboxvideo_drv_70.so"
+ ;;
+ esac
+
+ retval=0
+ if test -z "$vboxvideo_src"; then
+ echo "*** Unknown version of the X Window System installed."
+ echo "*** Failed to install the VirtualBox X Window System drivers."
+
+ # Exit as partially failed installation
+ retval=2
+ elif test ! -f "$vboxadditions32_path/$vboxvideo_src" && test ! -f "$vboxadditions64_path/$vboxvideo_src"; then
+ if test ! -f "${PKG_INSTALL_ROOT}/usr/lib/xorg/modules/drivers/vboxvideo_drv.so"; then
+ # Xorg 1.19 and later (delivered first in st_006) already contain a driver
+ # for vboxvideo so advise users to install the required package if it isn't
+ # already present.
+ echo "As of X.Org Server 1.19, the VirtualBox graphics driver (vboxvideo) is part"
+ echo "of Solaris. Please install the package pkg:/x11/server/xorg/driver/xorg-video-vboxvideo"
+ echo "from the package repository for the vboxvideo_drv.so graphics driver."
+ fi
+ else
+ echo "Installing video driver for X.Org $xorgversion..."
+
+ # Determine destination paths (snv_130 and above use "/usr/lib/xorg", older use "/usr/X11/lib"
+ vboxvideo32_dest_base="${PKG_INSTALL_ROOT}/usr/lib/xorg/modules/drivers"
+ if test ! -d $vboxvideo32_dest_base; then
+ vboxvideo32_dest_base="${PKG_INSTALL_ROOT}/usr/X11/lib/modules/drivers"
+ fi
+
+ vboxvideo64_dest_base=$vboxvideo32_dest_base/$solaris64dir
+
+ # snv_163 drops 32-bit support completely, and uses 32-bit locations for the 64-bit stuff. Ugly.
+ # We try to detect this by looking at bitness of "vesa_drv.so", and adjust our destination paths accordingly.
+ # We do not rely on using Xorg -version's ABI output because some builds (snv_162 iirc) have 64-bit ABI with
+ # 32-bit file locations.
+ if test -f "$vboxvideo32_dest_base/vesa_drv.so"; then
+ bitsize=`file "$vboxvideo32_dest_base/vesa_drv.so" | grep -i "32-bit"`
+ skip32="no"
+ else
+ echo "* Warning vesa_drv.so missing. Assuming Xorg ABI is 64-bit..."
+ fi
+
+ if test -z "$bitsize"; then
+ skip32="yes"
+ vboxvideo64_dest_base=$vboxvideo32_dest_base
+ fi
+
+ # Make sure destination path exists
+ if test ! -d $vboxvideo64_dest_base; then
+ echo "*** Missing destination paths for video module. Aborting."
+ echo "*** Failed to install the VirtualBox X Window System driver."
+
+ # Exit as partially failed installation
+ retval=2
+ else
+ # 32-bit x11 drivers
+ if test "$skip32" = "no" && test -f "$vboxadditions32_path/$vboxvideo_src"; then
+ vboxvideo_dest="$vboxvideo32_dest_base/vboxvideo_drv.so"
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST "$vboxvideo_dest" f
+ cp "$vboxadditions32_path/$vboxvideo_src" "$vboxvideo_dest"
+
+ # Removing redundant names from pkg and files from disk
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions32_path/vboxvideo_drv_* 1>/dev/null
+ rm -f $vboxadditions32_path/vboxvideo_drv_*
+ fi
+
+ # 64-bit x11 drivers
+ if test -f "$vboxadditions64_path/$vboxvideo_src"; then
+ vboxvideo_dest="$vboxvideo64_dest_base/vboxvideo_drv.so"
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST "$vboxvideo_dest" f
+ cp "$vboxadditions64_path/$vboxvideo_src" "$vboxvideo_dest"
+
+ # Removing redundant names from pkg and files from disk
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions64_path/vboxvideo_drv_* 1>/dev/null
+ rm -f $vboxadditions64_path/vboxvideo_drv_*
+ fi
+
+ # Some distros like Indiana have no xorg.conf, deal with this
+ if test ! -f '${PKG_INSTALL_ROOT}/etc/X11/xorg.conf' && \
+ test ! -f '${PKG_INSTALL_ROOT}/etc/X11/.xorg.conf'; then
+
+ # Xorg 1.3.x+ should use the modeline-less Xorg confs while older should
+ # use ones with all the video modelines in place. Argh.
+ xorgconf_file="solaris_xorg_modeless.conf"
+ xorgconf_unfit="solaris_xorg.conf"
+ case "$xorgversion" in
+ 7.1.* | 7.2.* | 6.9.* | 7.0.* )
+ xorgconf_file="solaris_xorg.conf"
+ xorgconf_unfit="solaris_xorg_modeless.conf"
+ ;;
+ esac
+
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions_path/$xorgconf_file 1>/dev/null
+ mv -f $vboxadditions_path/$xorgconf_file ${PKG_INSTALL_ROOT}/etc/X11/.xorg.conf
+
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions_path/$xorgconf_unfit 1>/dev/null
+ rm -f $vboxadditions_path/$xorgconf_unfit
+ fi
+
+ # Check for VirtualBox graphics card
+ # S10u10's prtconf doesn't support the '-d' option, so let's use -v even though it's slower.
+ is_vboxgraphics=`${PKG_INSTALL_ROOT}/usr/sbin/prtconf -v | grep -i pci80ee,beef`
+ if test "$?" -eq 0; then
+ drivername="vboxvideo"
+ else
+ # Check for VMware graphics card
+ is_vmwaregraphics=`${PKG_INSTALL_ROOT}/usr/sbin/prtconf -v | grep -i pci15ad,405`
+ if test "$?" -eq 0; then
+ echo "Configuring X.Org to use VMware SVGA graphics driver..."
+ drivername="vmware"
+ fi
+ fi
+
+ # Adjust xorg.conf with video driver sections if a supported graphics card is found
+ if test ! -z "$drivername"; then
+ $vboxadditions_path/x11config15sol.pl "$drivername"
+ else
+ # No supported graphics card found, do nothing.
+ echo "## No supported graphics card found. Skipped configuring of X.org drivers."
+ fi
+ fi
+ fi
+
+
+ # Setup our VBoxClient
+ echo "Configuring client..."
+ vboxclient_src=$vboxadditions_path
+ vboxclient_dest="${PKG_INSTALL_ROOT}/usr/share/gnome/autostart"
+ clientinstalled=0
+ if test -d "$vboxclient_dest"; then
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST $vboxclient_dest/vboxclient.desktop=$vboxadditions_path/vboxclient.desktop s
+ clientinstalled=1
+ fi
+ vboxclient_dest="${PKG_INSTALL_ROOT}/usr/dt/config/Xsession.d"
+ if test -d "$vboxclient_dest"; then
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST $vboxclient_dest/1099.vboxclient=$vboxadditions_path/1099.vboxclient s
+ clientinstalled=1
+ fi
+
+ # Try other autostart locations if none of the above ones work
+ if test $clientinstalled -eq 0; then
+ vboxclient_dest="${PKG_INSTALL_ROOT}/etc/xdg/autostart"
+ if test -d "$vboxclient_dest"; then
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST $vboxclient_dest/1099.vboxclient=$vboxadditions_path/1099.vboxclient s
+ clientinstalled=1
+ else
+ echo "*** Failed to configure client, couldn't find any autostart directory!"
+ # Exit as partially failed installation
+ retval=2
+ fi
+ fi
+else
+ echo "(*) X.Org not found, skipped configuring X.Org guest additions."
+fi
+
+
+# Shared Folder kernel module (different for S10 & Nevada)
+osverstr=`uname -r`
+vboxfsmod="vboxfs"
+vboxfsunused="vboxfs_s10"
+if test "$osverstr" = "5.10"; then
+ vboxfsmod="vboxfs_s10"
+ vboxfsunused="vboxfs"
+fi
+
+# Move the appropriate module to kernel/fs & remove the unused module name from pkg and file from disk
+# 64-bit shared folder module
+if test -f "$vboxadditions64_path/$vboxfsmod"; then
+ echo "Installing 64-bit shared folders module..."
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST "/usr/kernel/fs/$solaris64dir/vboxfs" f
+ mv -f $vboxadditions64_path/$vboxfsmod ${PKG_INSTALL_ROOT}/usr/kernel/fs/$solaris64dir/vboxfs
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions64_path/$vboxfsmod 1>/dev/null
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions64_path/$vboxfsunused 1>/dev/null
+ rm -f $vboxadditions64_path/$vboxfsunused
+fi
+
+# 32-bit shared folder module
+if test -f "$vboxadditions32_path/$vboxfsmod"; then
+ echo "Installing 32-bit shared folders module..."
+ /usr/sbin/installf $BASEDIR_OPT -c none $PKGINST "/usr/kernel/fs/vboxfs" f
+ mv -f $vboxadditions32_path/$vboxfsmod ${PKG_INSTALL_ROOT}/usr/kernel/fs/vboxfs
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions32_path/$vboxfsmod 1>/dev/null
+ /usr/sbin/removef $BASEDIR_OPT $PKGINST $vboxadditions32_path/$vboxfsunused 1>/dev/null
+ rm -f $vboxadditions32_path/$vboxfsunused
+fi
+
+# 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.
+if test "$REMOTE_INST" -eq 0; then
+ groupadd vboxsf >/dev/null 2>&1
+fi
+
+# Move the pointer integration module to kernel/drv & remove the unused module name from pkg and file from disk
+
+# Finalize
+/usr/sbin/removef $BASEDIR_OPT -f $PKGINST
+/usr/sbin/installf $BASEDIR_OPT -f $PKGINST
+
+
+if test "$currentzone" = "global"; then
+ /usr/sbin/devfsadm $BASEDIR_OPT -i vboxguest
+
+ # Setup VBoxService and vboxmslnk and start the services automatically
+ echo "Configuring VBoxService and vboxmslnk services (this might take a while)..."
+ cmax=32
+ cslept=0
+ success=0
+ sync
+
+ if test "$REMOTE_INST" -eq 0; then
+ # Since S11 the way to import a manifest is via restarting manifest-import which is asynchronous and can
+ # take a while to complete, using disable/enable -s doesn't work either. So we restart it, and poll in
+ # 1 second intervals to see if our service has been successfully imported and timeout after 'cmax' seconds.
+ /usr/sbin/svcadm restart svc:system/manifest-import:default
+ /usr/bin/svcs virtualbox/vboxservice >/dev/null 2>&1 && /usr/bin/svcs virtualbox/vboxmslnk >/dev/null 2>&1
+ while test "$?" -ne 0;
+ do
+ sleep 1
+ cslept=`expr $cslept + 1`
+ if test "$cslept" -eq "$cmax"; then
+ success=1
+ break
+ fi
+ /usr/bin/svcs virtualbox/vboxservice >/dev/null 2>&1 && /usr/bin/svcs virtualbox/vboxmslnk >/dev/null 2>&1
+ done
+ if test "$success" -eq 0; then
+ echo "Enabling services..."
+ /usr/sbin/svcadm enable -s virtualbox/vboxservice
+ /usr/sbin/svcadm enable -s virtualbox/vboxmslnk
+ else
+ echo "## Service import failed."
+ echo "## See /var/svc/log/system-manifest-import:default.log for details."
+ # Exit as partially failed installation
+ retval=2
+ fi
+ fi
+
+ # Update boot archive
+ BOOTADMBIN=/sbin/bootadm
+ if test -x "$BOOTADMBIN"; then
+ if test -h "${PKG_INSTALL_ROOT}/dev/vboxguest"; then
+ echo "Updating boot archive..."
+ $BOOTADMBIN update-archive $BASEDIR_OPT > /dev/null
+ else
+ echo "## Guest kernel module doesn't seem to be up. Skipped explicit boot-archive update."
+ fi
+ else
+ echo "## $BOOTADMBIN not found/executable. Skipped explicit boot-archive update."
+ fi
+fi
+
+echo "Done."
+if test "$REMOTE_INST" -eq 0; then
+ if test $retval -eq 0; then
+ if test ! -z "$xorgbin"; then
+ echo "Please re-login to activate the X11 guest additions."
+ fi
+ echo "If you have just un-installed the previous guest additions a REBOOT is required."
+ fi
+fi
+exit $retval
+
diff --git a/src/VBox/Additions/solaris/Installer/preremove.sh b/src/VBox/Additions/solaris/Installer/preremove.sh
new file mode 100755
index 00000000..16fb8070
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/preremove.sh
@@ -0,0 +1,98 @@
+#!/bin/sh
+# $Id: preremove.sh $
+## @file
+# VirtualBox preremove script for Solaris Guest Additions.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+LC_ALL=C
+export LC_ALL
+
+LANG=C
+export LANG
+
+echo "Removing VirtualBox service..."
+
+# stop and unregister VBoxService
+if [ "${PKG_INSTALL_ROOT:-/}" = "/" ] ; then
+ /usr/sbin/svcadm disable -s svc:/application/virtualbox/vboxservice:default
+ /usr/sbin/svcadm disable -s svc:/application/virtualbox/vboxmslnk:default
+ # Don't need to delete, taken care of by the manifest action
+ #/usr/sbin/svccfg delete svc:/application/virtualbox/vboxservice:default
+ #/usr/sbin/svccfg delete svc:/application/virtualbox/vboxmslnk:default
+ /usr/sbin/svcadm restart -s svc:/system/manifest-import:default
+
+ # stop VBoxClient
+ pkill -INT VBoxClient
+fi
+
+echo "Removing VirtualBox kernel modules..."
+
+# vboxguest.sh would've been installed, we just need to call it.
+
+${PKG_INSTALL_ROOT}/opt/VirtualBoxAdditions/vboxguest.sh stopall silentunload
+
+# Figure out group to use for /etc/devlink.tab (before Solaris 11 SRU6
+# it was always using group sys)
+group=sys
+if [ -f /etc/dev/reserved_devnames ]; then
+ # Solaris 11 SRU6 and later use group root (check a file which isn't
+ # tainted by VirtualBox install scripts and allow no other group)
+ refgroup=`LC_ALL=C /usr/bin/ls -lL /etc/dev/reserved_devnames | awk '{ print $4 }' 2>/dev/null`
+ if [ $? -eq 0 -a "x$refgroup" = "xroot" ]; then
+ group=root
+ fi
+ unset refgroup
+fi
+
+# remove devlink.tab entry for vboxguest
+sed -e '/name=vboxguest/d' ${PKG_INSTALL_ROOT}/etc/devlink.tab > ${PKG_INSTALL_ROOT}/etc/devlink.vbox
+chmod 0644 ${PKG_INSTALL_ROOT}/etc/devlink.vbox
+chown root:$group ${PKG_INSTALL_ROOT}/etc/devlink.vbox
+mv -f ${PKG_INSTALL_ROOT}/etc/devlink.vbox ${PKG_INSTALL_ROOT}/etc/devlink.tab
+
+# remove the link
+if test -h "${PKG_INSTALL_ROOT}/dev/vboxguest" || test -f "${PKG_INSTALL_ROOT}/dev/vboxguest"; then
+ rm -f ${PKG_INSTALL_ROOT}/dev/vboxdrv
+fi
+if test -h "${PKG_INSTALL_ROOT}/dev/vboxms" || test -f "${PKG_INSTALL_ROOT}/dev/vboxms"; then
+ rm -f ${PKG_INSTALL_ROOT}/dev/vboxms
+fi
+
+# Try and restore xorg.conf!
+echo "Restoring X.Org..."
+${PKG_INSTALL_ROOT}/opt/VirtualBoxAdditions/x11restore.pl
+
+
+echo "Done."
+
diff --git a/src/VBox/Additions/solaris/Installer/vbox_vendor_select b/src/VBox/Additions/solaris/Installer/vbox_vendor_select
new file mode 100755
index 00000000..3e823acd
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/vbox_vendor_select
@@ -0,0 +1,88 @@
+#!/bin/ksh93
+# $Id: vbox_vendor_select $
+## @file
+# ???
+#
+
+#
+# Copyright (C) 2013-2023 Oracle and/or its affiliates.
+# This file is based on mesa_vendor_select from Solaris 11.3 with the following copyright:
+#
+# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
+#
+# 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 (including the next
+# paragraph) 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.
+#
+
+LINKDIR=/system/volatile/opengl
+MESA_SELECT=/lib/opengl/ogl_select/mesa_vendor_select
+
+PATH=/usr/bin:/usr/sbin
+
+ARCH="$(uname -p)"
+
+case "${ARCH}" in
+ i386) ;;
+ *) exit 1 ;; # Unsupported architecture
+esac
+
+# We need Mesa for the parts we do not supply.
+if [[ ! -x "${MESA_SELECT}" ]]; then
+ exit 0
+fi
+
+if [[ $# -eq 1 ]] && [[ $1 == "identify" ]] ; then
+ # Probe time. Check whether this system supports pass-through.
+ # If so, emit an identity string attaching us to the current
+ # console identifier.
+ if /usr/bin/VBoxClient --check3d ; then
+ print "$(constype) vbox"
+ fi
+ return 0
+fi
+
+# Make a file link. $1 is the source path, $2 is the target path
+function make_link {
+ if [[ $# != 2 ]]; then
+ return
+ fi
+ if [[ -h $2 ]]; then
+ rm -f $2
+ fi
+ ln -sf $1 $2
+}
+
+# Start by setting up Mesa, as we use that for everything except the user
+# libraries.
+${MESA_SELECT}
+
+# User libraries
+if [[ -f ${LINKDIR}/lib/libGL.so.1 ]] && [[ -f /usr/lib/VBoxOGL.so ]] ; then
+ make_link /usr/lib/VBoxOGL.so ${LINKDIR}/lib/libGL.so.1
+fi
+if [[ -f ${LINKDIR}/lib/i386/libGL.so.1 ]] && \
+ [[ -f /usr/lib/i386/VBoxOGL.so ]] ; then
+ make_link /usr/lib/i386/VBoxOGL.so ${LINKDIR}/lib/i386/libGL.so.1
+fi
+if [[ -f ${LINKDIR}/lib/amd64/libGL.so.1 ]] && \
+ [[ -f /usr/lib/amd64/VBoxOGL.so ]] ; then
+ make_link /usr/lib/amd64/VBoxOGL.so ${LINKDIR}/lib/amd64/libGL.so.1
+fi
+
+return 0
diff --git a/src/VBox/Additions/solaris/Installer/vboxguest.depend b/src/VBox/Additions/solaris/Installer/vboxguest.depend
new file mode 100644
index 00000000..68196355
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/vboxguest.depend
@@ -0,0 +1 @@
+P SUNWuiu8 Iconv modules for UTF-8 Locale
diff --git a/src/VBox/Additions/solaris/Installer/vboxguest.pkginfo b/src/VBox/Additions/solaris/Installer/vboxguest.pkginfo
new file mode 100644
index 00000000..9456d0d4
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/vboxguest.pkginfo
@@ -0,0 +1,15 @@
+PKG="SUNWvboxguest"
+NAME="@VBOX_PRODUCT@ Guest Additions"
+SUNW_PRODNAME="@VBOX_PRODUCT@ Guest Additions"
+ARCH="@UNAME_P@"
+VERSION="@VBOX_VERSION_STRING@,REV=r@VBOX_SVN_REV@.@VBOX_VERSION_REVSTAMP@"
+SUNW_PRODVERS="@VBOX_VERSION_STRING@"
+SUNW_PKGVERS=1.0
+CATEGORY="application"
+VENDOR="@VBOX_VENDOR@"
+EMAIL="info@virtualbox.org"
+HOTLINE="Please contact your local service provider"
+BASEDIR="/"
+CLASSES=none manifest
+DESC="@VBOX_PRODUCT@ Guest Additions for Solaris guests"
+
diff --git a/src/VBox/Additions/solaris/Installer/vboxguest.sh b/src/VBox/Additions/solaris/Installer/vboxguest.sh
new file mode 100755
index 00000000..abf04ec2
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/vboxguest.sh
@@ -0,0 +1,310 @@
+#!/bin/sh
+# $Id: vboxguest.sh $
+## @file
+# VirtualBox Guest Additions kernel module control script for Solaris.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+LC_ALL=C
+export LC_ALL
+
+LANG=C
+export LANG
+
+SILENTUNLOAD=""
+MODNAME="vboxguest"
+VFSMODNAME="vboxfs"
+VMSMODNAME="vboxms"
+MODDIR32="${PKG_INSTALL_ROOT}/usr/kernel/drv"
+MODDIR64="${PKG_INSTALL_ROOT}/usr/kernel/drv/amd64"
+VFSDIR32="${PKG_INSTALL_ROOT}/usr/kernel/fs"
+VFSDIR64="${PKG_INSTALL_ROOT}/usr/kernel/fs/amd64"
+
+abort()
+{
+ echo 1>&2 "## $1"
+ exit 1
+}
+
+info()
+{
+ echo 1>&2 "$1"
+}
+
+check_if_installed()
+{
+ cputype=`isainfo -k`
+ modulepath="$MODDIR32/$MODNAME"
+ if test "$cputype" = "amd64"; then
+ modulepath="$MODDIR64/$MODNAME"
+ fi
+ if test -f "$modulepath"; then
+ return 0
+ fi
+ abort "VirtualBox kernel module ($MODNAME) NOT installed."
+}
+
+module_loaded()
+{
+ if test -z "$1"; then
+ abort "missing argument to module_loaded()"
+ fi
+
+ if test "$REMOTE_INST" -eq 1; then
+ return 1
+ fi
+
+ modname=$1
+ # modinfo should now work properly since we prevent module autounloading.
+ loadentry=`/usr/sbin/modinfo | grep "$modname "`
+ if test -z "$loadentry"; then
+ return 1
+ fi
+ return 0
+}
+
+vboxguest_loaded()
+{
+ module_loaded $MODNAME
+ return $?
+}
+
+vboxfs_loaded()
+{
+ module_loaded $VFSMODNAME
+ return $?
+}
+
+vboxms_loaded()
+{
+ module_loaded $VMSMODNAME
+ return $?
+}
+
+check_root()
+{
+ # the reason we don't use "-u" is that some versions of id are old and do not
+ # support this option (eg. Solaris 10) and do not have a "--version" to check it either
+ # so go with the uglier but more generic approach
+ idbin=`which id`
+ isroot=`$idbin | grep "uid=0"`
+ if test -z "$isroot"; then
+ abort "This program must be run with administrator privileges. Aborting"
+ fi
+}
+
+start_module()
+{
+ if test "$REMOTE_INST" -eq 1; then
+ /usr/sbin/add_drv $BASEDIR_OPT -i'pci80ee,cafe' -m'* 0666 root sys' $MODNAME 2>/dev/null || \
+ abort "Failed to install VirtualBox guest kernel module into ${PKG_INSTALL_ROOT}."
+ info "VirtualBox guest kernel module installed."
+ return
+ fi
+
+ /usr/sbin/add_drv -i'pci80ee,cafe' -m'* 0666 root sys' $MODNAME
+ if test ! vboxguest_loaded; then
+ abort "Failed to load VirtualBox guest kernel module."
+ elif test -c "/devices/pci@0,0/pci80ee,cafe@4:$MODNAME"; then
+ info "VirtualBox guest kernel module loaded."
+ else
+ info "VirtualBox guest kernel module failed to attach."
+ fi
+}
+
+stop_module()
+{
+ if test "$REMOTE_INST" -eq 1; then
+ /usr/sbin/rem_drv $BASEDIR_OPT $MODNAME || abort "Failed to uninstall VirtualBox guest kernel module."
+ info "VirtualBox guest kernel module uninstalled."
+ return
+ fi
+
+ if vboxguest_loaded; then
+ /usr/sbin/rem_drv $MODNAME || abort "Failed to unload VirtualBox guest kernel module."
+ info "VirtualBox guest kernel module unloaded."
+ elif test -z "$SILENTUNLOAD"; then
+ info "VirtualBox guest kernel module not loaded."
+ fi
+}
+
+start_vboxfs()
+{
+ if test "$REMOTE_INST" -eq 1; then
+ return
+ fi
+
+ if vboxfs_loaded; then
+ info "VirtualBox FileSystem kernel module already loaded."
+ else
+ /usr/sbin/modload -p fs/$VFSMODNAME || abort "Failed to load VirtualBox FileSystem kernel module."
+ if test ! vboxfs_loaded; then
+ info "Failed to load VirtualBox FileSystem kernel module."
+ else
+ info "VirtualBox FileSystem kernel module loaded."
+ fi
+ fi
+}
+
+stop_vboxfs()
+{
+ if test "$REMOTE_INST" -eq 1; then
+ return
+ fi
+
+ if vboxfs_loaded; then
+ vboxfs_mod_id=`/usr/sbin/modinfo | grep $VFSMODNAME | cut -f 1 -d ' ' `
+ if test -n "$vboxfs_mod_id"; then
+ /usr/sbin/modunload -i $vboxfs_mod_id || abort "Failed to unload VirtualBox FileSystem module."
+ info "VirtualBox FileSystem kernel module unloaded."
+ fi
+ elif test -z "$SILENTUNLOAD"; then
+ info "VirtualBox FileSystem kernel module not loaded."
+ fi
+}
+
+start_vboxms()
+{
+ if test "$REMOTE_INST" -eq 1; then
+ /usr/sbin/add_drv $BASEDIR_OPT -m'* 0666 root sys' $VMSMODNAME 2>/dev/null ||
+ abort "Failed to install VirtualBox pointer integration module."
+ info "VirtualBox pointer integration module installed."
+ return
+ fi
+
+ /usr/sbin/add_drv -m'* 0666 root sys' $VMSMODNAME
+ if test ! vboxms_loaded; then
+ abort "Failed to load VirtualBox pointer integration module."
+ elif test -c "/devices/pseudo/$VMSMODNAME@0:$VMSMODNAME"; then
+ info "VirtualBox pointer integration module loaded."
+ else
+ info "VirtualBox pointer integration module failed to attach."
+ fi
+}
+
+stop_vboxms()
+{
+ if test "$REMOTE_INST" -eq 1; then
+ /usr/sbin/rem_drv $BASEDIR_OPT $VMSMODNAME || abort "Failed to uninstall VirtualBox pointer integration module."
+ info "VirtualBox pointer integration module uninstalled."
+ return
+ fi
+
+ if vboxms_loaded; then
+ /usr/sbin/rem_drv $VMSMODNAME || abort "Failed to unload VirtualBox pointer integration module."
+ info "VirtualBox pointer integration module unloaded."
+ elif test -z "$SILENTUNLOAD"; then
+ info "VirtualBox pointer integration module not loaded."
+ fi
+}
+
+status_module()
+{
+ if vboxguest_loaded; then
+ info "Running."
+ else
+ info "Stopped."
+ fi
+}
+
+stop_all()
+{
+ stop_vboxms
+ stop_vboxfs
+ stop_module
+ return 0
+}
+
+restart_all()
+{
+ stop_all
+ start_module
+ start_vboxfs
+ start_vboxms
+ return 0
+}
+
+# "Remote" installs ('pkgadd -R') can skip many of the steps below.
+REMOTE_INST=0
+BASEDIR_OPT=""
+if test "x${PKG_INSTALL_ROOT:-/}" != "x/"; then
+ BASEDIR_OPT="-b $PKG_INSTALL_ROOT"
+ REMOTE_INST=1
+fi
+export REMOTE_INST
+export BASEDIR_OPT
+
+check_root
+check_if_installed
+
+if test "$2" = "silentunload"; then
+ SILENTUNLOAD="$2"
+fi
+
+case "$1" in
+stopall)
+ stop_all
+ ;;
+restartall)
+ restart_all
+ ;;
+start)
+ start_module
+ start_vboxms
+ ;;
+stop)
+ stop_vboxms
+ stop_module
+ ;;
+status)
+ status_module
+ ;;
+vfsstart)
+ start_vboxfs
+ ;;
+vfsstop)
+ stop_vboxfs
+ ;;
+vmsstart)
+ start_vboxms
+ ;;
+vmsstop)
+ stop_vboxms
+ ;;
+*)
+ echo "Usage: $0 {start|stop|restart|status}"
+ exit 1
+esac
+
+exit 0
+
diff --git a/src/VBox/Additions/solaris/Installer/vboxguest.space b/src/VBox/Additions/solaris/Installer/vboxguest.space
new file mode 100644
index 00000000..993a6f6d
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/vboxguest.space
@@ -0,0 +1,5 @@
+# Space file for VirtualBox Guest Additions installer
+# We only create a few links and copy a few files, reserve some space
+# pathname blocks inodes
+/ 5000 8
+
diff --git a/src/VBox/Additions/solaris/Installer/vboxservice.xml b/src/VBox/Additions/solaris/Installer/vboxservice.xml
new file mode 100644
index 00000000..25ac7162
--- /dev/null
+++ b/src/VBox/Additions/solaris/Installer/vboxservice.xml
@@ -0,0 +1,88 @@
+<?xml version='1.0'?>
+<!--
+#
+# Solaris SMF service manifest for VBoxService (timesync).
+#
+-->
+<!--
+ Copyright (C) 2008-2023 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ The contents of this file may alternatively be used under the terms
+ of the Common Development and Distribution License Version 1.0
+ (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ in the VirtualBox distribution, in which case the provisions of the
+ CDDL are applicable instead of those of the GPL.
+
+ You may elect to license modified versions of this file under the
+ terms and conditions of either the GPL or the CDDL or both.
+
+ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+-->
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+
+<service_bundle type='manifest' name='SUNWvboxguest:vboxservice'>
+
+<service
+ name='application/virtualbox/vboxservice'
+ type='service'
+ version='1'>
+
+ <create_default_instance enabled='true' />
+
+ <single_instance/>
+
+ <!-- Wait for devices to be initialized as we depend on vboxguest (PCI) -->
+ <dependency
+ name='milestone'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/milestone/devices:default' />
+ </dependency>
+
+ <!-- Wait for local filesystems to be mounted (just to be safe, don't start too early) -->
+ <dependency
+ name='filesystem-local'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/filesystem/local:default' />
+ </dependency>
+
+ <exec_method
+ type='method'
+ name='start'
+ exec='/usr/bin/VBoxService'
+ timeout_seconds='30' />
+
+ <exec_method
+ type='method'
+ name='stop'
+ exec=':kill'
+ timeout_seconds='60' />
+
+ <template>
+ <common_name>
+ <loctext xml:lang='C'>VirtualBox Service.</loctext>
+ </common_name>
+ </template>
+</service>
+
+</service_bundle>
+
diff --git a/src/VBox/Additions/solaris/Makefile.kmk b/src/VBox/Additions/solaris/Makefile.kmk
new file mode 100644
index 00000000..6151ff6b
--- /dev/null
+++ b/src/VBox/Additions/solaris/Makefile.kmk
@@ -0,0 +1,413 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for the Solaris guest additions base directory.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#ifneq ($(KBUILD_HOST),solaris)
+#$(error "The Solaris guest additions installer can only be built on Solaris!")
+#endif
+
+ifeq ($(KBUILD_TARGET),solaris)
+ include $(PATH_SUB_CURRENT)/SharedFolders/Makefile.kmk
+ include $(PATH_SUB_CURRENT)/Mouse/Makefile.kmk
+ include $(PATH_SUB_CURRENT)/DRM/Makefile.kmk
+endif
+
+ifdef VBOX_WITH_COMBINED_SOLARIS_PACKAGE ## @todo remove this when tinderclient.pl is updated.
+ VBOX_WITH_COMBINED_GUEST_PACKAGE := 1
+endif
+
+PKGFILENAME := VBoxSolarisAdditions.pkg
+PKGINFO_ARCH = $(shell uname -p)
+PKGINFO_REVSTAMP = $(date %Y.%m.%d.%H.%M)
+VBOX_PATH_SOLARIS_ADDITION_INSTALLER := $(PATH_SUB_CURRENT)/Installer
+VBOX_PATH_X11_ADDITION_INSTALLER := $(PATH_ROOT)/src/VBox/Additions/x11/Installer
+
+SOLARIS_ADDDIR_NAME_64 := amd64
+SOLARIS_ADDDIR_NAME_32 := i386
+
+SOLARIS_ADDINST_OUT_DIR := $(PATH_TARGET)/AdditionsInstaller
+SOLARIS_VBOXADDINST_SUBDIR := /opt/VirtualBoxAdditions
+SOLARIS_VBOXADDINST_DIR := $(SOLARIS_ADDINST_OUT_DIR)$(SOLARIS_VBOXADDINST_SUBDIR)
+SOLARIS_VBOXADDINST_DIR_32 := $(SOLARIS_VBOXADDINST_DIR)/$(SOLARIS_ADDDIR_NAME_32)
+SOLARIS_VBOXADDINST_DIR_64 := $(SOLARIS_VBOXADDINST_DIR)/$(SOLARIS_ADDDIR_NAME_64)
+
+SOLARIS_ADD_OUT_BIN_64 := $(PATH_OUT_BASE)/solaris.amd64/$(KBUILD_TYPE)/bin/additions
+SOLARIS_ADD_OUT_BIN_32 := $(PATH_OUT_BASE)/solaris.x86/$(KBUILD_TYPE)/bin/additions
+SOLARIS_ADD_OUT_BIN_ISA := $(PATH_OUT_BASE)/solaris.$(KBUILD_TARGET_ARCH)/$(KBUILD_TYPE)/bin/additions
+
+SOLARIS_ADD_DRIVERINST_DIR := $(SOLARIS_ADDINST_OUT_DIR)/usr/kernel/drv
+SOLARIS_ADD_DRIVERINST_DIR_32 := $(SOLARIS_ADD_DRIVERINST_DIR)
+SOLARIS_ADD_DRIVERINST_DIR_64 := $(SOLARIS_ADD_DRIVERINST_DIR)/amd64
+
+SOLARIS_ADD_SYSLIBINST_DIR_32 := $(SOLARIS_ADDINST_OUT_DIR)/usr/lib
+SOLARIS_ADD_SYSLIBINST_DIR_64 := $(SOLARIS_ADDINST_OUT_DIR)/usr/lib/amd64
+
+SOLARIS_ADD_USRBIN_DIR := $(SOLARIS_ADDINST_OUT_DIR)/usr/bin
+SOLARIS_ADD_USRSBIN_DIR := $(SOLARIS_ADDINST_OUT_DIR)/usr/sbin
+SOLARIS_ADD_ETCFS_DIR := $(SOLARIS_ADDINST_OUT_DIR)/etc/fs/vboxfs
+SOLARIS_ADD_SERVICESINST_DIR := $(SOLARIS_ADDINST_OUT_DIR)/var/svc/manifest/application/virtualbox
+
+ifeq ($(KBUILD_TARGET_ARCH),x86)
+ SOLARIS_ADDDIR_NAME_ISA := $(SOLARIS_ADDDIR_NAME_32)
+ SOLARIS_VBOXADDINST_DIR_ISA := $(SOLARIS_VBOXADDINST_DIR_32)
+ SOLARIS_ADD_DRIVERINST_DIR_ISA := $(SOLARIS_ADD_DRIVERINST_DIR_32)
+ SOLARIS_ADD_SYSLIBINST_DIR_ISA := $(SOLARIS_ADD_SYSLIBINST_DIR_32)
+else
+ SOLARIS_ADDDIR_NAME_ISA := $(SOLARIS_ADDDIR_NAME_64)
+ SOLARIS_VBOXADDINST_DIR_ISA := $(SOLARIS_VBOXADDINST_DIR_64)
+ SOLARIS_ADD_DRIVERINST_DIR_ISA := $(SOLARIS_ADD_DRIVERINST_DIR_64)
+ SOLARIS_ADD_SYSLIBINST_DIR_ISA := $(SOLARIS_ADD_SYSLIBINST_DIR_64)
+endif
+
+ifeq ($(KBUILD_TYPE),debug)
+ BIN_COPY := $(CP) -f
+ BIN_COPY_SYMBOLS := $(CP) -f
+else
+ BIN_COPY := /usr/sfw/bin/gobjcopy -S -R .comment
+ BIN_COPY_SYMBOLS := /usr/sfw/bin/gobjcopy -g -R .comment
+ VBOX_COMPRESS := compress -f
+endif
+
+INSTALLS += solaris-addcommon solaris-addcommonbins
+PROGRAMS += VBoxAddISAExec
+PACKING += $(PATH_STAGE_BIN)/additions/$(PKGFILENAME)
+OTHER_CLEAN += $(PACKING) $(SOLARIS_ADDINST_OUT_DIR)/$(PKGFILENAME)
+
+#
+# VBoxAddISAExec
+#
+VBoxAddISAExec_TEMPLATE = VBoxGuestR3Exe
+VBoxAddISAExec_NAME = VBoxISAExec
+VBoxAddISAExec_INST = $(INST_ADDITIONS)
+VBoxAddISAExec_DEPS = $(VBOX_SVN_REV_KMK)
+VBoxAddISAExec_SOURCES = $(PATH_ROOT)/src/VBox/Installer/solaris/VBoxISAExec.c
+
+#
+# Install to $(PATH_STAGE_BIN)/additions/ files from various source paths (to pack them using rules)
+#
+solaris-addcommonbins_INST = bin/additions/
+solaris-addcommonbins_MODE = a+rx,u+w
+solaris-addcommonbins_SOURCES = \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxguest.sh \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/x11config.pl \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/x11config15sol.pl \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/x11restore.pl \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/98vboxadd-xclient=>1099.vboxclient \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/solaris_xorg.conf \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/solaris_xorg_modeless.conf \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vbox_vendor_select \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/VBox.sh
+
+solaris-addcommon_INST = bin/additions/
+solaris-addcommon_MODE = a+r,u+w
+solaris-addcommon_SOURCES = \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf=>vboxguest.conf \
+ $(PATH_ROOT)/src/VBox/Additions/solaris/Mouse/vboxms.conf=>vboxms.conf \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/vboxclient.desktop \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxservice.xml \
+ $(PATH_ROOT)/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml \
+ $(VBOX_BRAND_LICENSE_TXT)=>LICENSE
+
+#
+# Create full directory tree
+#
+BLDDIRS += \
+ $(SOLARIS_ADDINST_OUT_DIR) \
+ $(addprefix $(SOLARIS_ADDINST_OUT_DIR)/, \
+ opt \
+ opt/VirtualBoxAdditions \
+ opt/VirtualBoxAdditions/$(SOLARIS_ADDDIR_NAME_32) \
+ opt/VirtualBoxAdditions/$(SOLARIS_ADDDIR_NAME_64) \
+ usr \
+ usr/bin \
+ usr/sbin \
+ usr/lib \
+ usr/lib/amd64 \
+ usr/kernel \
+ usr/kernel/drv \
+ usr/kernel/drv/amd64 \
+ var \
+ var/svc \
+ var/svc/manifest \
+ var/svc/manifest/application \
+ var/svc/manifest/application/virtualbox \
+ )
+
+SOLARIS_ADD_STRIP_BINS = \
+ VBoxClient \
+ VBoxService \
+ VBoxControl \
+ vboxfsmount \
+ vboxfs \
+ vboxmslnk \
+ $(if ($VBOX_WITH_PAM),pam_vbox.so,) \
+ $(if $(VBOX_OSE),,vboxfs_s10) \
+ $(if $(VBOX_WITH_ADDITIONS_SHIPPING_AUDIO_TEST),VBoxAudioTest,)
+
+SOLARIS_ADD_DRIVERS = \
+ vboxguest \
+ vboxms
+
+SOLARIS_ADD_DRIVERS_CONF = \
+ vboxguest.conf \
+ vboxms.conf
+
+SOLARIS_ADD_XORG_DRIVERS = \
+ vboxvideo_drv_13.so \
+ vboxvideo_drv_14.so \
+ vboxvideo_drv_15.so \
+ vboxvideo_drv_16.so \
+ vboxvideo_drv_17.so \
+ vboxvideo_drv_18.so \
+ vboxvideo_drv_19.so \
+ vboxvideo_drv_110.so \
+ vboxvideo_drv_111.so \
+ vboxvideo_drv_112.so \
+ vboxvideo_drv_113.so \
+ vboxvideo_drv_114.so \
+ vboxvideo_drv_117.so \
+ vboxvideo_drv_118.so \
+ vboxvideo_drv_70.so \
+ vboxvideo_drv_71.so
+
+SOLARIS_ADD_COMMON_BINS = \
+ vboxguest.sh \
+ x11config15sol.pl \
+ x11restore.pl \
+ VBox.sh \
+ 1099.vboxclient \
+ vbox_vendor_select \
+ VBoxISAExec
+
+SOLARIS_ADD_USRBIN_LINKS = \
+ VBoxService \
+ VBoxClient \
+ VBoxControl
+
+SOLARIS_ADD_USRSBIN_LINKS = \
+ vboxmslnk
+
+SOLARIS_ADD_COMMON = \
+ vboxclient.desktop \
+ solaris_xorg.conf \
+ solaris_xorg_modeless.conf \
+ LICENSE
+
+SOLARIS_ADD_SERVICES = \
+ vboxservice.xml \
+ vboxmslnk.xml
+
+ifdef VBOX_COMPRESS
+ SOLARIS_ADD_COMPRESS_FILES = \
+ $(SOLARIS_ADD_XORG_DRIVERS) \
+ VBoxService \
+ VBoxClient \
+ VBoxControl
+endif
+
+ifdef VBOX_WITH_COMBINED_SOLARIS_GUEST_PACKAGE
+ SOLARIS_ARCH_ADD_DEPFILES = \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR_64)/,$(SOLARIS_ADD_STRIP_BINS)) \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR_64)/,$(SOLARIS_ADD_XORG_DRIVERS)) \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR_32)/,$(SOLARIS_ADD_STRIP_BINS)) \
+ $(addprefix $(SOLARIS_ADD_DRIVERINST_DIR_64)/,$(SOLARIS_ADD_DRIVERS)) \
+ $(addprefix $(SOLARIS_ADD_DRIVERINST_DIR_32)/,$(SOLARIS_ADD_DRIVERS)) \
+ $(addprefix $(SOLARIS_ADD_DRIVERINST_DIR)/,$(SOLARIS_ADD_DRIVERS_CONF)) \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR_32)/,$(SOLARIS_ADD_XORG_DRIVERS))
+else
+ SOLARIS_ARCH_ADD_DEPFILES = \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR_ISA)/,$(SOLARIS_ADD_STRIP_BINS)) \
+ $(addprefix $(SOLARIS_ADD_DRIVERINST_DIR_ISA)/,$(SOLARIS_ADD_DRIVERS)) \
+ $(addprefix $(SOLARIS_ADD_DRIVERINST_DIR)/,$(SOLARIS_ADD_DRIVERS_CONF)) \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR_ISA)/,$(SOLARIS_ADD_XORG_DRIVERS))
+endif
+
+SOLARIS_ARCH_ADD_DEPFILES += \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR)/,$(SOLARIS_ADD_COMMON_BINS)) \
+ $(addprefix $(SOLARIS_VBOXADDINST_DIR)/,$(SOLARIS_ADD_COMMON)) \
+ $(addprefix $(SOLARIS_ADD_SERVICESINST_DIR)/,$(SOLARIS_ADD_SERVICES)) \
+ $(addprefix $(SOLARIS_ADD_USRBIN_DIR)/,$(SOLARIS_ADD_USRBIN_LINKS)) \
+ $(addprefix $(SOLARIS_ADD_USRSBIN_DIR)/,$(SOLARIS_ADD_USRSBIN_LINKS))
+
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+#
+# -=-=-=-=-=-=-=- Additions package -=-=-=-=-=-=-=-
+#
+
+$(PATH_STAGE_BIN)/additions/$(PKGFILENAME): \
+ $(VBOX_VERSION_STAMP) \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/makepackage.sh \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/postinstall.sh \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/preremove.sh \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxguest.pkginfo \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxguest.depend \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxguest.sh \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxservice.xml \
+ $(PATH_ROOT)/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/VBox.sh \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/98vboxadd-xclient \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/x11config15sol.pl \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/x11restore.pl \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/solaris_xorg.conf \
+ $(VBOX_PATH_X11_ADDITION_INSTALLER)/solaris_xorg_modeless.conf \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vbox_vendor_select \
+ $(VBOX_BRAND_LICENSE_TXT) \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/VBoxGuest-solaris.conf \
+ $(PATH_SUB_CURRENT)/solaris/Makefile.kmk \
+ $(SOLARIS_ARCH_ADD_DEPFILES)
+ $(call MSG_L1,Installing guest additions)
+ $(QUIET)$(SED) \
+ -e "s+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g" \
+ -e "s+@VBOX_VENDOR@+$(VBOX_VENDOR)+g" \
+ -e "s+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g" \
+ -e "s+@VBOX_SVN_REV@+$(VBOX_SVN_REV)+g" \
+ -e "s+@VBOX_VERSION_REVSTAMP@+$(PKGINFO_REVSTAMP)+g" \
+ -e "s+@UNAME_P@+$(PKGINFO_ARCH)+g" \
+ --output $(SOLARIS_ADDINST_OUT_DIR)/vboxguest.pkginfo \
+ $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxguest.pkginfo
+ $(QUIET)$(INSTALL) -m 0755 $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/makepackage.sh $(SOLARIS_ADDINST_OUT_DIR)/makepackage.sh
+ $(QUIET)$(INSTALL) -m 0755 $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/postinstall.sh $(SOLARIS_ADDINST_OUT_DIR)/postinstall.sh
+ $(QUIET)$(INSTALL) -m 0755 $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/preremove.sh $(SOLARIS_ADDINST_OUT_DIR)/preremove.sh
+ $(QUIET)$(INSTALL) -m 0644 $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxguest.space $(SOLARIS_ADDINST_OUT_DIR)/vboxguest.space
+ $(QUIET)$(INSTALL) -m 0644 $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxguest.depend $(SOLARIS_ADDINST_OUT_DIR)/vboxguest.depend
+ # don't display the license on package install, since 4.0
+ #$(QUIET)$(INSTALL) -m 0644 $(VBOX_BRAND_LICENSE_TXT) $(SOLARIS_ADDINST_OUT_DIR)/vboxguest.copyright
+ $(call MSG_L1,Creating install package: $@)
+ $(QUIET)$(MKDIR) -p $(SOLARIS_ADD_ETCFS_DIR)
+ $(QUIET)$(MKDIR) -p $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)
+ $(QUIET)$(LN_SYMLINK) -f ../..$(SOLARIS_VBOXADDINST_SUBDIR)/1099.vboxclient $(SOLARIS_ADD_USRBIN_DIR)/VBoxClient-all
+ifdef VBOX_WITH_COMBINED_SOLARIS_GUEST_PACKAGE
+ ifdef VBOX_COMPRESS
+ $(QUIET)$(VBOX_COMPRESS) $(addprefix $(SOLARIS_VBOXADDINST_DIR_32)/,$(SOLARIS_ADD_COMPRESS_FILES))
+ $(QUIET)$(VBOX_COMPRESS) $(addprefix $(SOLARIS_VBOXADDINST_DIR_64)/,$(SOLARIS_ADD_COMPRESS_FILES))
+ endif
+ $(QUIET)$(LN_SYMLINK) -f ../../..$(SOLARIS_VBOXADDINST_SUBDIR)/$(SOLARIS_ADDDIR_NAME_32)/vboxfsmount $(SOLARIS_ADD_ETCFS_DIR)/mount
+else # !VBOX_WITH_COMBINED_SOLARIS_GUEST_PACKAGE
+ ifdef VBOX_COMPRESS
+ $(QUIET)$(VBOX_COMPRESS) $(addprefix $(SOLARIS_VBOXADDINST_DIR_ISA)/,$(SOLARIS_ADD_COMPRESS_FILES))
+ endif
+ $(LN_SYMLINK) -f ../../..$(SOLARIS_VBOXADDINST_SUBDIR)/$(SOLARIS_ADDDIR_NAME_ISA)/vboxfsmount $(SOLARIS_ADD_ETCFS_DIR)/mount
+endif
+ $(QUIET)$(INSTALL) -m 0644 $(VBOX_PATH_SOLARIS_ADDITION_INSTALLER)/vboxservice.xml $(SOLARIS_ADD_SERVICESINST_DIR)/vboxservice.xml
+ $(QUIET)$(INSTALL) -m 0644 $(PATH_ROOT)/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml $(SOLARIS_ADD_SERVICESINST_DIR)/vboxmslnk.xml
+ $(QUIET)$(SOLARIS_ADDINST_OUT_DIR)/makepackage.sh $(SOLARIS_ADDINST_OUT_DIR) $(PKGFILENAME) $(VBOX_SVN_REV) $(VBOX_SOL_PKG_DEV)
+ $(QUIET)$(INSTALL) -m 0644 $(SOLARIS_ADDINST_OUT_DIR)/$(PKGFILENAME) $(PATH_STAGE_BIN)/additions/$(PKGFILENAME)
+ $(QUIET)$(RM) -f $(SOLARIS_ADDINST_OUT_DIR)/$(PKGFILENAME)
+
+
+
+#
+# -=-=-=-=-=-=-=- Package rules -=-=-=-=-=-=-=-
+#
+
+#
+# 32-bit files
+#
+$(addprefix $(SOLARIS_VBOXADDINST_DIR_32)/,$(SOLARIS_ADD_STRIP_BINS)): \
+ $(SOLARIS_VBOXADDINST_DIR_32)/% : $(SOLARIS_ADD_OUT_BIN_32)/% | $$(dir $$@)
+ $(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+
+$(addprefix $(SOLARIS_ADD_DRIVERINST_DIR_32)/,$(SOLARIS_ADD_DRIVERS)): \
+ $(SOLARIS_ADD_DRIVERINST_DIR_32)/% : $(SOLARIS_ADD_OUT_BIN_32)/% | $$(dir $$@)
+ $(INSTALL) -m 0644 $< $@
+
+$(addprefix $(SOLARIS_VBOXADDINST_DIR_32)/,$(SOLARIS_ADD_XORG_DRIVERS)): \
+ $(SOLARIS_VBOXADDINST_DIR_32)/% : $(SOLARIS_ADD_OUT_BIN_32)/% | $$(dir $$@)
+ $(BIN_COPY) $< $@
+
+
+#
+# 64-bit files
+#
+$(addprefix $(SOLARIS_VBOXADDINST_DIR_64)/,$(SOLARIS_ADD_STRIP_BINS)): \
+ $(SOLARIS_VBOXADDINST_DIR_64)/% : $(SOLARIS_ADD_OUT_BIN_64)/% | $$(dir $$@)
+ $(INSTALL) -m 0755 $(if $(VBOX_DO_STRIP),-s,) $< $@
+
+$(addprefix $(SOLARIS_ADD_DRIVERINST_DIR_64)/,$(SOLARIS_ADD_DRIVERS)): \
+ $(SOLARIS_ADD_DRIVERINST_DIR_64)/% : $(SOLARIS_ADD_OUT_BIN_64)/% | $$(dir $$@)
+ $(INSTALL) -m 0644 $< $@
+
+$(addprefix $(SOLARIS_VBOXADDINST_DIR_64)/,$(SOLARIS_ADD_XORG_DRIVERS)): \
+ $(SOLARIS_VBOXADDINST_DIR_64)/% : $(SOLARIS_ADD_OUT_BIN_64)/% | $$(dir $$@)
+ $(BIN_COPY) $< $@
+
+
+#
+# Common files
+#
+$(addprefix $(SOLARIS_VBOXADDINST_DIR)/,$(SOLARIS_ADD_COMMON)): \
+ $(SOLARIS_VBOXADDINST_DIR)/% : $(SOLARIS_ADD_OUT_BIN_ISA)/% | $$(dir $$@)
+ $(INSTALL) -m 0644 $< $@
+
+#
+# Common binaries/shell scripts
+#
+$(addprefix $(SOLARIS_VBOXADDINST_DIR)/,$(SOLARIS_ADD_COMMON_BINS)): \
+ $(SOLARIS_VBOXADDINST_DIR)/% : $(SOLARIS_ADD_OUT_BIN_ISA)/% | $$(dir $$@)
+ $(INSTALL) -m 0755 $< $@
+
+#
+# Driver .conf files
+#
+$(addprefix $(SOLARIS_ADD_DRIVERINST_DIR)/,$(SOLARIS_ADD_DRIVERS_CONF)): \
+ $(SOLARIS_ADD_DRIVERINST_DIR)/% : $(SOLARIS_ADD_OUT_BIN_ISA)/% | $$(dir $$@)
+ $(INSTALL) -m 0644 $< $@
+
+#
+# SMF Service files
+#
+$(addprefix $(SOLARIS_ADD_SERVICESINST_DIR)/,$(SOLARIS_ADD_SERVICES)): \
+ $(SOLARIS_ADD_SERVICESINST_DIR)/% : $(SOLARIS_ADD_OUT_BIN_ISA)/% | $$(dir $$@)
+ $(INSTALL) -m 0644 $< $@
+
+#
+# Symlinks from /usr/bin/ to /opt/VirtualBoxAdditions
+#
+$(addprefix $(SOLARIS_ADD_USRBIN_DIR)/,$(SOLARIS_ADD_USRBIN_LINKS)): \
+ $(SOLARIS_ADD_USRBIN_DIR)/% : % | $$(dir $$@)
+ $(LN_SYMLINK) -f ../..$(SOLARIS_VBOXADDINST_SUBDIR)/$< $@
+
+#
+# Symlinks from /usr/sbin/ to /opt/VirtualBoxAdditions
+#
+$(addprefix $(SOLARIS_ADD_USRSBIN_DIR)/,$(SOLARIS_ADD_USRSBIN_LINKS)): \
+ $(SOLARIS_ADD_USRSBIN_DIR)/% : % | $$(dir $$@)
+ $(LN_SYMLINK) -f ../..$(SOLARIS_VBOXADDINST_SUBDIR)/$< $@
diff --git a/src/VBox/Additions/solaris/Mouse/Makefile.kmk b/src/VBox/Additions/solaris/Mouse/Makefile.kmk
new file mode 100644
index 00000000..b3fec956
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/Makefile.kmk
@@ -0,0 +1,81 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Solaris Mouse Integration kernel module.
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#ifneq ($(KBUILD_HOST),solaris)
+#$(error "The Solaris guest additions can only be built on Solaris!")
+#endif
+
+#
+# vboxms - The Mouse Integration Driver
+#
+SYSMODS.solaris += vboxms
+vboxms_TEMPLATE = VBoxGuestR0Drv
+vboxms_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV)
+vboxms_DEPS += $(VBOX_SVN_REV_KMK)
+vboxms_SOURCES = \
+ vboxms.c
+vboxms_LIBS = \
+ $(VBOX_LIB_VBGL_R0)
+ifeq ($(KBUILD_HOST),solaris)
+ vboxms_LDFLAGS += -N drv/vboxguest -N misc/ctf
+else
+ vboxms_SOURCES += deps.asm
+ vboxms_deps.asm_ASFLAGS = -f bin -g null
+endif
+
+
+PROGRAMS += vboxmslnk
+vboxmslnk_TEMPLATE = VBoxGuestR3Exe
+vboxmslnk_SOURCES = vboxmslnk.c
+
+
+if 0 # Broken - unresolved externals: vbglDriver*, RTR0AssertPanicSystem.
+ if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+ PROGRAMS += tstVBoxMouse-solaris
+ tstVBoxMouse-solaris_TEMPLATE = VBoxR3TstExe
+ tstVBoxMouse-solaris_SOURCES = \
+ vboxms.c \
+ testcase/tstVBoxMouse-solaris.c
+ tstVBoxMouse-solaris_DEFS = TESTCASE
+ tstVBoxMouse-solaris_LIBS = $(LIB_RUNTIME)
+ endif
+endif
+
+include $(KBUILD_PATH)/subfooter.kmk
+
diff --git a/src/VBox/Additions/solaris/Mouse/deps.asm b/src/VBox/Additions/solaris/Mouse/deps.asm
new file mode 100644
index 00000000..d01557ed
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/deps.asm
@@ -0,0 +1,49 @@
+; $Id: deps.asm $
+;; @file
+; Solaris kernel module dependency
+;
+
+;
+; Copyright (C) 2012-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/solaris/kmoddeps.mac"
+
+kmoddeps_header ; ELF header, section table and shared string table
+
+kmoddeps_dynstr_start ; ELF .dynstr section
+kmoddeps_dynstr_string str_misc_ctf, "misc/ctf"
+kmoddeps_dynstr_string str_drv_vboxguest, "drv/vboxguest"
+kmoddeps_dynstr_end
+
+kmoddeps_dynamic_start ; ELF .dynamic section
+kmoddeps_dynamic_needed str_misc_ctf
+kmoddeps_dynamic_needed str_drv_vboxguest
+kmoddeps_dynamic_end
diff --git a/src/VBox/Additions/solaris/Mouse/testcase/Makefile.kup b/src/VBox/Additions/solaris/Mouse/testcase/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/testcase/Makefile.kup
diff --git a/src/VBox/Additions/solaris/Mouse/testcase/solaris.h b/src/VBox/Additions/solaris/Mouse/testcase/solaris.h
new file mode 100644
index 00000000..2040bba6
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/testcase/solaris.h
@@ -0,0 +1,454 @@
+/* $Id: solaris.h $ */
+/** @file
+ * VBoxGuest - Guest Additions Driver for Solaris - testcase stubs.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_solaris_Mouse_testcase_solaris_h
+#define GA_INCLUDED_SRC_solaris_Mouse_testcase_solaris_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/string.h> /* RT_ZERO */
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/winsock2.h> /* struct timeval */
+#endif
+#include <errno.h>
+#include <time.h> /* struct timeval */
+
+/* Overrides */
+#define dev_t unsigned
+
+/* Constants */
+#define DDI_FAILURE (-1)
+#define DDI_SUCCESS (0)
+
+#define MODMAXNAMELEN 32
+#define MODMAXLINKINFOLEN 32
+#define MODMAXLINK 10
+
+#define MOD_NOAUTOUNLOAD 0x1
+
+#define M_DATA 0x00
+#define M_BREAK 0x08
+#define M_PASSFP 0x09
+#define M_EVENT 0x0a
+#define M_SIG 0x0b
+#define M_DELAY 0x0c
+#define M_CTL 0x0d
+#define M_IOCTL 0x0e
+#define M_SETOPTS 0x10
+#define M_RSE 0x11
+
+#define M_IOCACK 0x81
+#define M_IOCNAK 0x82
+#define M_PCPROTO 0x83
+#define M_PCSIG 0x84
+#define M_READ 0x85
+#define M_FLUSH 0x86
+#define M_STOP 0x87
+#define M_START 0x88
+#define M_HANGUP 0x89
+#define M_ERROR 0x8a
+#define M_COPYIN 0x8b
+#define M_COPYOUT 0x8c
+#define M_IOCDATA 0x8d
+#define M_PCRSE 0x8e
+#define M_STOPI 0x8f
+#define M_STARTI 0x90
+#define M_PCEVENT 0x91
+#define M_UNHANGUP 0x92
+#define M_CMD 0x93
+
+#define BPRI_LO 1
+#define BPRI_MED 2
+#define BPRI_HI 3
+
+#define FLUSHALL 1
+#define FLUSHDATA 0
+
+#define TRANSPARENT (unsigned int)(-1)
+
+#define FLUSHR 0x01
+#define FLUSHW 0x02
+
+#define MSIOC ('m'<<8)
+#define MSIOGETPARMS (MSIOC|1)
+#define MSIOSETPARMS (MSIOC|2)
+#define MSIOBUTTONS (MSIOC|3)
+#define MSIOSRESOLUTION (MSIOC|4)
+
+#define VUIOC ('v'<<8)
+#define VUIDSFORMAT (VUIOC|1)
+#define VUIDGFORMAT (VUIOC|2)
+#define VUID_NATIVE 0
+#define VUID_FIRM_EVENT 1
+
+#define VUIDSADDR (VUIOC|3)
+#define VUIDGADDR (VUIOC|4)
+
+#define VUID_WHEEL_MAX_COUNT 256
+#define VUIDGWHEELCOUNT (VUIOC|15)
+#define VUIDGWHEELINFO (VUIOC|16)
+#define VUIDGWHEELSTATE (VUIOC|17)
+#define VUIDSWHEELSTATE (VUIOC|18)
+
+#define DDI_DEVICE_ATTR_V0 0x0001
+#define DDI_DEVICE_ATTR_V1 0x0002
+
+#define DDI_NEVERSWAP_ACC 0x00
+#define DDI_STRUCTURE_LE_ACC 0x01
+#define DDI_STRUCTURE_BE_ACC 0x02
+
+#define DDI_STRICTORDER_ACC 0x00
+#define DDI_UNORDERED_OK_ACC 0x01
+#define DDI_MERGING_OK_ACC 0x02
+#define DDI_LOADCACHING_OK_ACC 0x03
+#define DDI_STORECACHING_OK_ACC 0x04
+
+/** @todo fix this */
+#define DDI_DEFAULT_ACC DDI_STRICTORDER_ACC
+
+#define DDI_INTR_CLAIMED 1
+#define DDI_INTR_UNCLAIMED 0
+
+#define DDI_INTR_TYPE_FIXED 0x1
+#define DDI_INTR_TYPE_MSI 0x2
+#define DDI_INTR_TYPE_MSIX 0x4
+
+#define LOC_FIRST_DELTA 32640
+#define LOC_X_DELTA 32640
+#define LOC_Y_DELTA 32641
+#define LOC_LAST_DELTA 32641
+#define LOC_FIRST_ABSOLUTE 32642
+#define LOC_X_ABSOLUTE 32642
+#define LOC_Y_ABSOLUTE 32643
+#define LOC_LAST_ABSOLUTE 32643
+
+#define FE_PAIR_NONE 0
+#define FE_PAIR_SET 1
+#define FE_PAIR_DELTA 2
+#define FE_PAIR_ABSOLUTE 3
+
+typedef struct __ldi_handle *ldi_handle_t;
+
+typedef enum
+{
+ DDI_INFO_DEVT2DEVINFO = 0,
+ DDI_INFO_DEVT2INSTANCE = 1
+} ddi_info_cmd_t;
+
+typedef enum
+{
+ DDI_ATTACH = 0,
+ DDI_RESUME = 1,
+ DDI_PM_RESUME = 2
+} ddi_attach_cmd_t;
+
+typedef enum
+{
+ DDI_DETACH = 0,
+ DDI_SUSPEND = 1,
+ DDI_PM_SUSPEND = 2,
+ DDI_HOTPLUG_DETACH = 3
+} ddi_detach_cmd_t;
+
+/* Simple types */
+
+typedef struct cred *cred_t;
+typedef struct dev_info *dev_info_t;
+typedef struct __ddi_acc_handle * ddi_acc_handle_t;
+typedef struct __ddi_intr_handle *ddi_intr_handle_t;
+typedef struct mutex *kmutex_t;
+typedef unsigned int uint_t;
+typedef unsigned short ushort_t;
+typedef unsigned char uchar_t;
+
+/* Structures */
+
+struct modspecific_info {
+ char msi_linkinfo[MODMAXLINKINFOLEN];
+ int msi_p0;
+};
+
+struct modinfo {
+ int mi_info;
+ int mi_state;
+ int mi_id;
+ int mi_nextid;
+ char *mi_base; /* Was caddr_t. */
+ size_t mi_size;
+ int mi_rev;
+ int mi_loadcnt;
+ char mi_name[MODMAXNAMELEN];
+ struct modspecific_info mi_msinfo[MODMAXLINK];
+};
+
+typedef struct queue
+{
+ struct qinit *q_qinfo;
+ struct msgb *q_first;
+ struct msgb *q_last;
+ struct queue *q_next;
+ void *q_ptr;
+ size_t q_count;
+ uint_t q_flag;
+ ssize_t q_minpsz;
+ ssize_t q_maxpsz;
+ size_t q_hiwat;
+ size_t q_lowat;
+} queue_t;
+
+typedef struct msgb
+{
+ struct msgb *b_next;
+ struct msgb *b_prev;
+ struct msgb *b_cont;
+ unsigned char *b_rptr;
+ unsigned char *b_wptr;
+ struct datab *b_datap;
+ unsigned char b_band;
+ unsigned short b_flag;
+} mblk_t;
+
+typedef struct datab
+{
+ unsigned char *db_base;
+ unsigned char *db_lim;
+ unsigned char db_ref;
+ unsigned char db_type;
+} dblk_t;
+
+struct iocblk
+{
+ int ioc_cmd;
+ cred_t *ioc_cr;
+ uint_t ioc_id;
+ uint_t ioc_flag;
+ size_t ioc_count;
+ int ioc_rval;
+ int ioc_error;
+#if defined(RT_ARCH_AMD64) /* Actually this should be LP64. */
+ int dummy; /* For simplicity, to ensure the structure size matches
+ struct copyreq. */
+#endif
+};
+
+struct copyreq
+{
+ int cq_cmd;
+ cred_t *cq_cr;
+ uint_t cq_id;
+ uint_t cq_flag;
+ mblk_t *cq_private;
+ char *cq_addr; /* Was caddr_t. */
+ size_t cq_size;
+};
+
+struct copyresp
+{
+ int cp_cmd;
+ cred_t *cp_cr;
+ uint_t cp_id;
+ uint_t cp_flag;
+ mblk_t *cp_private;
+ char *cp_rval; /* Was caddr_t. */
+};
+
+typedef struct modctl
+{
+ /* ... */
+ char mod_loadflags;
+ /* ... */
+} modctl_t;
+
+typedef struct {
+ int jitter_thresh;
+ int speed_law;
+ int speed_limit;
+} Ms_parms;
+
+typedef struct {
+ int height;
+ int width;
+} Ms_screen_resolution;
+
+typedef struct vuid_addr_probe {
+ short base;
+ union
+ {
+ short next;
+ short current;
+ } data;
+} Vuid_addr_probe;
+
+typedef struct ddi_device_acc_attr
+{
+ ushort_t devacc_attr_version;
+ uchar_t devacc_attr_endian_flags;
+ uchar_t devacc_attr_dataorder;
+ uchar_t devacc_attr_access;
+} ddi_device_acc_attr_t;
+
+typedef struct firm_event
+{
+ ushort_t id;
+ uchar_t pair_type;
+ uchar_t pair;
+ int value;
+ struct timeval time;
+} Firm_event;
+
+/* Prototypes */
+
+#define _init vboxguestSolarisInit
+extern int vboxguestSolarisInit(void);
+#define _fini vboxguestSolarisFini
+extern int vboxguestSolarisFini(void);
+#define _info vboxguestSolarisInfo
+extern int vboxguestSolarisInfo(struct modinfo *pModInfo);
+
+/* Simple API stubs */
+
+#define cmn_err(...) do {} while(0)
+#define mod_remove(...) 0
+#define mod_info(...) 0
+#define RTR0Init(...) VINF_SUCCESS
+#define RTR0Term(...) do {} while(0)
+#define RTR0AssertPanicSystem(...) do {} while(0)
+#define RTLogCreate(...) VINF_SUCCESS
+#define RTLogRelSetDefaultInstance(...) do {} while(0)
+#define RTLogDestroy(...) do {} while(0)
+#if 0
+#define VBoxGuestCreateKernelSession(...) VINF_SUCCESS
+#define VBoxGuestCreateUserSession(...) VINF_SUCCESS
+#define VBoxGuestCloseSession(...) do {} while(0)
+#define VBoxGuestInitDevExt(...) VINF_SUCCESS
+#define VBoxGuestDeleteDevExt(...) do {} while(0)
+#define VBoxGuestCommonIOCtl(...) VINF_SUCCESS
+#define VBoxGuestCommonISR(...) true
+#define VbglR0GRAlloc(...) VINF_SUCCESS
+#define VbglR0GRPerform(...) VINF_SUCCESS
+#define VbglR0GRFree(...) do {} while(0)
+#endif
+#define VbglR0InitClient(...) VINF_SUCCESS
+#define vbglDriverOpen(...) VINF_SUCCESS
+#define vbglDriverClose(...) do {} while(0)
+#define vbglDriverIOCtl(...) VINF_SUCCESS
+#define qprocson(...) do {} while(0)
+#define qprocsoff(...) do {} while(0)
+#define flushq(...) do {} while(0)
+#define putnext(...) do {} while(0)
+#define ddi_get_instance(...) 0
+#define pci_config_setup(...) DDI_SUCCESS
+#define pci_config_teardown(...) do {} while(0)
+#define ddi_regs_map_setup(...) DDI_SUCCESS
+#define ddi_regs_map_free(...) do {} while(0)
+#define ddi_dev_regsize(...) DDI_SUCCESS
+#define ddi_create_minor_node(...) DDI_SUCCESS
+#define ddi_remove_minor_node(...) do {} while(0)
+#define ddi_intr_get_supported_types(...) DDI_SUCCESS
+#define ddi_intr_get_nintrs(...) DDI_SUCCESS
+#define ddi_intr_get_navail(...) DDI_SUCCESS
+#define ddi_intr_alloc(...) DDI_SUCCESS
+#define ddi_intr_free(...) do {} while(0)
+#define ddi_intr_get_pri(...) DDI_SUCCESS
+#define ddi_intr_enable(...) DDI_SUCCESS
+#define ddi_intr_disable(...) DDI_SUCCESS
+#define ddi_intr_add_handler(...) DDI_SUCCESS
+#define ddi_intr_remove_handler(...) DDI_SUCCESS
+#define mutex_init(...) do {} while(0)
+#define mutex_destroy(...) do {} while(0)
+#define mutex_enter(...) do {} while(0)
+#define mutex_exit(...) do {} while(0)
+#define uniqtime32(...) do {} while(0)
+#define canput(...) true
+#define putbq(...) do {} while(0)
+
+/* Externally defined helpers. */
+
+/** Flags set in the struct mblk b_flag member for verification purposes.
+ * @{ */
+/** miocpullup was called for this message. */
+#define F_TEST_PULLUP 1
+/** @} */
+
+extern void miocack(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int rc);
+extern void miocnak(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int iErr);
+extern int miocpullup(mblk_t *pMBlk, size_t cbMsg);
+extern void mcopyin(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser);
+extern void mcopyout(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser,
+ mblk_t *pMBlkData);
+extern void qreply(queue_t *pQueue, mblk_t *pMBlk);
+extern mblk_t *allocb(size_t cb, uint_t cPrio);
+extern void freemsg(mblk_t *pMsg);
+
+/* API stubs with simple logic */
+
+static modctl_t s_ModCtl;
+static void **s_pvLinkage;
+
+static inline modctl_t *mod_getctl(void **linkage)
+{
+ s_pvLinkage = linkage;
+ return s_pvLinkage ? &s_ModCtl : NULL;
+}
+
+#define mod_install(linkage) (s_pvLinkage && ((linkage) == s_pvLinkage) ? 0 : EINVAL)
+#define QREADR 0x00000010
+#define OTHERQ(q) ((q)->q_flag & QREADR ? (q) + 1 : (q) - 1)
+#define WR(q) ((q)->q_flag & QREADR ? (q) + 1 : (q))
+#define RD(q) ((q)->q_flag & QREADR ? (q) : (q) - 1)
+
+
+/* Basic initialisation of a queue structure pair for testing. */
+static inline void doInitQueues(queue_t aQueues[2])
+{
+ aQueues[0].q_flag = QREADR;
+}
+
+static inline dev_t makedevice(unsigned cMajor, unsigned cMinor)
+{
+ return cMajor * 4096 + cMinor;
+}
+
+static inline unsigned getmajor(dev_t device)
+{
+ return device / 4096;
+}
+
+/* API stubs with controllable logic */
+
+#endif /* !GA_INCLUDED_SRC_solaris_Mouse_testcase_solaris_h */
diff --git a/src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c b/src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c
new file mode 100644
index 00000000..f95fbd99
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/testcase/tstVBoxMouse-solaris.c
@@ -0,0 +1,170 @@
+/** @file
+ * VirtualBox Guest Additions Driver for Solaris - Solaris helper functions.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+
+#include "solaris.h"
+#include <iprt/alloc.h>
+
+
+/*********************************************************************************************************************************
+* Helper functions *
+*********************************************************************************************************************************/
+
+void miocack(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int rc)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+
+ pMBlk->b_datap->db_type = M_IOCACK;
+ pIOCBlk->ioc_count = cbData;
+ pIOCBlk->ioc_rval = rc;
+ pIOCBlk->ioc_error = 0;
+ qreply(pWriteQueue, pMBlk);
+}
+
+void miocnak(queue_t *pWriteQueue, mblk_t *pMBlk, int cbData, int iErr)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+
+ pMBlk->b_datap->db_type = M_IOCNAK;
+ pIOCBlk->ioc_count = cbData;
+ pIOCBlk->ioc_error = iErr ? iErr : EINVAL;
+ pIOCBlk->ioc_rval = 0;
+ qreply(pWriteQueue, pMBlk);
+}
+
+/* This does not work like the real version, but does some sanity testing
+ * and sets a flag. */
+int miocpullup(mblk_t *pMBlk, size_t cbMsg)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+
+ if (pIOCBlk->ioc_count == TRANSPARENT)
+ return EINVAL;
+ if ( !pMBlk->b_cont
+ || pMBlk->b_cont->b_wptr < pMBlk->b_cont->b_rptr + cbMsg)
+ return EINVAL;
+ pMBlk->b_flag |= F_TEST_PULLUP;
+ return 0;
+}
+
+void mcopyin(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+ struct copyreq *pCopyReq = (struct copyreq *)pMBlk->b_rptr;
+
+ AssertReturnVoid( pvUser
+ || ( pMBlk->b_datap->db_type == M_IOCTL
+ && pIOCBlk->ioc_count == TRANSPARENT
+ && pMBlk->b_cont->b_rptr));
+ pMBlk->b_datap->db_type = M_COPYIN;
+ pMBlk->b_wptr = pMBlk->b_rptr + sizeof(*pCopyReq);
+ pCopyReq->cq_private = pvState;
+ pCopyReq->cq_size = cbData;
+ pCopyReq->cq_addr = pvUser ? pvUser : *(void **)pMBlk->b_cont->b_rptr;
+ if (pMBlk->b_cont)
+ {
+ freemsg(pMBlk->b_cont);
+ pMBlk->b_cont = NULL;
+ }
+}
+
+void mcopyout(mblk_t *pMBlk, void *pvState, size_t cbData, void *pvUser,
+ mblk_t *pMBlkData)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+ struct copyreq *pCopyReq = (struct copyreq *)pMBlk->b_rptr;
+
+ AssertReturnVoid( pvUser
+ || ( pMBlk->b_datap->db_type == M_IOCTL
+ && pIOCBlk->ioc_count == TRANSPARENT
+ && pMBlk->b_cont->b_rptr));
+ pMBlk->b_datap->db_type = M_COPYOUT;
+ pMBlk->b_wptr = pMBlk->b_rptr + sizeof(*pCopyReq);
+ pCopyReq->cq_private = pvState;
+ pCopyReq->cq_size = cbData;
+ pCopyReq->cq_addr = pvUser ? pvUser : *(void **)pMBlk->b_cont->b_rptr;
+ if (pMBlkData)
+ {
+ if (pMBlk->b_cont)
+ freemsg(pMBlk->b_cont);
+ pMBlk->b_cont = pMBlkData;
+ pMBlkData->b_wptr = pMBlkData->b_rptr + cbData;
+ }
+}
+
+/* This does not work like the real version but is easy to test the result of.
+ */
+void qreply(queue_t *pQueue, mblk_t *pMBlk)
+{
+ OTHERQ(pQueue)->q_first = pMBlk;
+}
+
+/** @todo reference counting */
+mblk_t *allocb(size_t cb, uint_t cPrio)
+{
+ unsigned char *pch = RTMemAllocZ(cb);
+ struct msgb *pMBlk = (struct msgb *)RTMemAllocZ(sizeof(struct msgb));
+ struct datab *pDBlk = (struct datab *)RTMemAllocZ(sizeof(struct datab));
+ if (!pch || !pMBlk || !pDBlk)
+ {
+ RTMemFree(pch);
+ RTMemFree(pMBlk);
+ RTMemFree(pDBlk);
+ return NULL;
+ }
+ NOREF(cPrio);
+ pMBlk->b_rptr = pch;
+ pMBlk->b_wptr = pMBlk->b_rptr + cb;
+ pMBlk->b_datap = pDBlk;
+ pDBlk->db_base = pMBlk->b_rptr;
+ pDBlk->db_lim = pMBlk->b_wptr;
+ pDBlk->db_type = M_DATA;
+ return pMBlk;
+}
+
+/** @todo reference counting */
+void freemsg(mblk_t *pMBlk)
+{
+ if (!pMBlk)
+ return;
+ RTMemFree(pMBlk->b_rptr);
+ RTMemFree(pMBlk->b_datap);
+ freemsg(pMBlk->b_cont);
+ RTMemFree(pMBlk);
+}
diff --git a/src/VBox/Additions/solaris/Mouse/vboxms.c b/src/VBox/Additions/solaris/Mouse/vboxms.c
new file mode 100644
index 00000000..173db83e
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/vboxms.c
@@ -0,0 +1,1450 @@
+/* $Id: vboxms.c $ */
+/** @file
+ * VirtualBox Guest Additions Mouse Driver for Solaris.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_MOUSE
+#include <VBox/VMMDev.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+
+#ifndef TESTCASE
+# include <sys/modctl.h>
+# include <sys/msio.h>
+# include <sys/stat.h>
+# include <sys/ddi.h>
+# include <sys/strsun.h>
+# include <sys/stropts.h>
+# include <sys/sunddi.h>
+# include <sys/vuid_event.h>
+# include <sys/vuid_wheel.h>
+#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
+#else /* TESTCASE */
+# undef IN_RING3
+# define IN_RING0
+#endif /* TESTCASE */
+
+#ifdef TESTCASE /* Include this last as we . */
+# include "testcase/solaris.h"
+# include <iprt/test.h>
+#endif /* TESTCASE */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** The module name. */
+#define DEVICE_NAME "vboxms"
+/** The module description as seen in 'modinfo'. */
+#define DEVICE_DESC "VBoxMouseIntegr"
+
+
+/*********************************************************************************************************************************
+* Internal functions used in global structures *
+*********************************************************************************************************************************/
+
+static int vbmsSolAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd);
+static int vbmsSolDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd);
+static int vbmsSolGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg,
+ void **ppvResult);
+static int vbmsSolOpen(queue_t *pReadQueue, dev_t *pDev, int fFlag,
+ int fMode, cred_t *pCred);
+static int vbmsSolClose(queue_t *pReadQueue, int fFlag, cred_t *pCred);
+static int vbmsSolWPut(queue_t *pWriteQueue, mblk_t *pMBlk);
+
+
+/*********************************************************************************************************************************
+* Driver global structures *
+*********************************************************************************************************************************/
+
+#ifndef TESTCASE /* I see no value in including these in the test. */
+
+/*
+ * mod_info: STREAMS module information.
+ */
+static struct module_info g_vbmsSolModInfo =
+{
+ 0, /* module id number */
+ "vboxms",
+ 0, /* minimum packet size */
+ INFPSZ, /* maximum packet size accepted */
+ 512, /* high water mark for data flow control */
+ 128 /* low water mark */
+};
+
+/*
+ * rinit: read queue structure for handling messages coming from below. In
+ * our case this means the host and the virtual hardware, so we do not need
+ * the put and service procedures.
+ */
+static struct qinit g_vbmsSolRInit =
+{
+ NULL, /* put */
+ NULL, /* service thread procedure */
+ vbmsSolOpen,
+ vbmsSolClose,
+ NULL, /* reserved */
+ &g_vbmsSolModInfo,
+ NULL /* module statistics structure */
+};
+
+/*
+ * winit: write queue structure for handling messages coming from above. Above
+ * means user space applications: either Guest Additions user space tools or
+ * applications reading pointer input. Messages from the last most likely pass
+ * through at least the "consms" console mouse streams module which multiplexes
+ * hardware pointer drivers to a single virtual pointer.
+ */
+static struct qinit g_vbmsSolWInit =
+{
+ vbmsSolWPut,
+ NULL, /* service thread procedure */
+ NULL, /* open */
+ NULL, /* close */
+ NULL, /* reserved */
+ &g_vbmsSolModInfo,
+ NULL /* module statistics structure */
+};
+
+/**
+ * streamtab: for drivers that support char/block entry points.
+ */
+static struct streamtab g_vbmsSolStreamTab =
+{
+ &g_vbmsSolRInit,
+ &g_vbmsSolWInit,
+ NULL, /* MUX rinit */
+ NULL /* MUX winit */
+};
+
+/**
+ * cb_ops: for drivers that support char/block entry points
+ */
+static struct cb_ops g_vbmsSolCbOps =
+{
+ nodev, /* open */
+ nodev, /* close */
+ nodev, /* b strategy */
+ nodev, /* b dump */
+ nodev, /* b print */
+ nodev, /* c read */
+ nodev, /* c write */
+ nodev, /* c ioctl */
+ nodev, /* c devmap */
+ nodev, /* c mmap */
+ nodev, /* c segmap */
+ nochpoll, /* c poll */
+ ddi_prop_op, /* property ops */
+ &g_vbmsSolStreamTab,
+ D_MP,
+ CB_REV /* revision */
+};
+
+/**
+ * dev_ops: for driver device operations
+ */
+static struct dev_ops g_vbmsSolDevOps =
+{
+ DEVO_REV, /* driver build revision */
+ 0, /* ref count */
+ vbmsSolGetInfo,
+ nulldev, /* identify */
+ nulldev, /* probe */
+ vbmsSolAttach,
+ vbmsSolDetach,
+ nodev, /* reset */
+ &g_vbmsSolCbOps,
+ NULL, /* bus operations */
+ nodev /* power */
+};
+
+/**
+ * modldrv: export driver specifics to the kernel
+ */
+static struct modldrv g_vbmsSolModule =
+{
+ &mod_driverops, /* extern from kernel */
+ DEVICE_DESC " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV),
+ &g_vbmsSolDevOps
+};
+
+/**
+ * modlinkage: export install/remove/info to the kernel.
+ */
+static struct modlinkage g_vbmsSolModLinkage =
+{
+ MODREV_1, /* loadable module system revision */
+ &g_vbmsSolModule,
+ NULL /* terminate array of linkage structures */
+};
+
+#else /* TESTCASE */
+static void *g_vbmsSolModLinkage;
+#endif /* TESTCASE */
+
+/**
+ * State info for each open file handle.
+ */
+typedef struct
+{
+ /** Device handle. */
+ dev_info_t *pDip;
+ /** Mutex protecting the guest library against multiple initialistation or
+ * uninitialisation. */
+ kmutex_t InitMtx;
+ /** Initialisation counter for the guest library. */
+ size_t cInits;
+ /** The STREAMS write queue which we need for sending messages up to
+ * user-space. */
+ queue_t *pWriteQueue;
+ /** Pre-allocated mouse status VMMDev request for use in the IRQ
+ * handler. */
+ VMMDevReqMouseStatus *pMouseStatusReq;
+ /* The current greatest horizontal pixel offset on the screen, used for
+ * absolute mouse position reporting.
+ */
+ int cMaxScreenX;
+ /* The current greatest vertical pixel offset on the screen, used for
+ * absolute mouse position reporting.
+ */
+ int cMaxScreenY;
+} VBMSSTATE, *PVBMSSTATE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Global driver state. Actually this could be allocated dynamically. */
+static VBMSSTATE g_OpenNodeState /* = { 0 } */;
+
+
+/*********************************************************************************************************************************
+* Kernel entry points *
+*********************************************************************************************************************************/
+
+/** Driver initialisation. */
+int _init(void)
+{
+ int rc;
+ LogRelFlow((DEVICE_NAME ": built on " __DATE__ " at " __TIME__ "\n"));
+ mutex_init(&g_OpenNodeState.InitMtx, NULL, MUTEX_DRIVER, NULL);
+ /*
+ * Prevent module autounloading.
+ */
+ modctl_t *pModCtl = mod_getctl(&g_vbmsSolModLinkage);
+ if (pModCtl)
+ pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD;
+ else
+ LogRel((DEVICE_NAME ": failed to disable autounloading!\n"));
+ rc = mod_install(&g_vbmsSolModLinkage);
+
+ LogRelFlow((DEVICE_NAME ": initialisation returning %d.\n", rc));
+ return rc;
+}
+
+
+#ifdef TESTCASE
+/** Simple test of the flow through _init. */
+static void test_init(RTTEST hTest)
+{
+ RTTestSub(hTest, "Testing _init");
+ RTTEST_CHECK(hTest, _init() == 0);
+}
+#endif
+
+
+/** Driver cleanup. */
+int _fini(void)
+{
+ int rc;
+
+ LogRelFlow((DEVICE_NAME ":_fini\n"));
+ rc = mod_remove(&g_vbmsSolModLinkage);
+ if (!rc)
+ mutex_destroy(&g_OpenNodeState.InitMtx);
+
+ return rc;
+}
+
+
+/** Driver identification. */
+int _info(struct modinfo *pModInfo)
+{
+ int rc;
+ LogRelFlow((DEVICE_NAME ":_info\n"));
+ rc = mod_info(&g_vbmsSolModLinkage, pModInfo);
+ LogRelFlow((DEVICE_NAME ":_info returning %d\n", rc));
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Initialisation entry points *
+*********************************************************************************************************************************/
+
+/**
+ * Attach entry point, to attach a device to the system or resume it.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Attach type (ddi_attach_cmd_t)
+ *
+ * @return corresponding solaris error code.
+ */
+int vbmsSolAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd)
+{
+ LogRelFlow((DEVICE_NAME "::Attach\n"));
+ switch (enmCmd)
+ {
+ case DDI_ATTACH:
+ {
+ int rc;
+ /* Only one instance supported. */
+ if (!ASMAtomicCmpXchgPtr(&g_OpenNodeState.pDip, pDip, NULL))
+ return DDI_FAILURE;
+ rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, 0 /* flags */);
+ if (rc == DDI_SUCCESS)
+ return DDI_SUCCESS;
+ ASMAtomicWritePtr(&g_OpenNodeState.pDip, NULL);
+ return DDI_FAILURE;
+ }
+
+ case DDI_RESUME:
+ {
+ /** @todo implement resume for guest driver. */
+ return DDI_SUCCESS;
+ }
+
+ default:
+ return DDI_FAILURE;
+ }
+}
+
+
+/**
+ * Detach entry point, to detach a device to the system or suspend it.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Attach type (ddi_attach_cmd_t)
+ *
+ * @return corresponding solaris error code.
+ */
+int vbmsSolDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd)
+{
+ LogRelFlow((DEVICE_NAME "::Detach\n"));
+ switch (enmCmd)
+ {
+ case DDI_DETACH:
+ {
+ ddi_remove_minor_node(pDip, NULL);
+ ASMAtomicWritePtr(&g_OpenNodeState.pDip, NULL);
+ return DDI_SUCCESS;
+ }
+
+ case DDI_SUSPEND:
+ {
+ /** @todo implement suspend for guest driver. */
+ return DDI_SUCCESS;
+ }
+
+ default:
+ return DDI_FAILURE;
+ }
+}
+
+
+/**
+ * Info entry point, called by solaris kernel for obtaining driver info.
+ *
+ * @param pDip The module structure instance (do not use).
+ * @param enmCmd Information request type.
+ * @param pvArg Type specific argument.
+ * @param ppvResult Where to store the requested info.
+ *
+ * @return corresponding solaris error code.
+ */
+int vbmsSolGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg,
+ void **ppvResult)
+{
+ LogRelFlow((DEVICE_NAME "::GetInfo\n"));
+
+ int rc = DDI_SUCCESS;
+ switch (enmCmd)
+ {
+ case DDI_INFO_DEVT2DEVINFO:
+ {
+ *ppvResult = (void *)g_OpenNodeState.pDip;
+ if (!*ppvResult)
+ rc = DDI_FAILURE;
+ break;
+ }
+
+ case DDI_INFO_DEVT2INSTANCE:
+ {
+ /* There can only be a single-instance of this driver and thus its instance number is 0. */
+ *ppvResult = (void *)0;
+ break;
+ }
+
+ default:
+ rc = DDI_FAILURE;
+ break;
+ }
+
+ NOREF(pvArg);
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Main code *
+*********************************************************************************************************************************/
+
+static void vbmsSolNotify(void *pvState);
+static void vbmsSolVUIDPutAbsEvent(PVBMSSTATE pState, ushort_t cEvent,
+ int cValue);
+
+/**
+ * Open callback for the read queue, which we use as a generic device open
+ * handler.
+ */
+int vbmsSolOpen(queue_t *pReadQueue, dev_t *pDev, int fFlag, int fMode,
+ cred_t *pCred)
+{
+ PVBMSSTATE pState = NULL;
+ int rc = VINF_SUCCESS;
+
+ NOREF(fFlag);
+ NOREF(pCred);
+ LogRelFlow((DEVICE_NAME "::Open, pWriteQueue=%p\n", WR(pReadQueue)));
+
+ /*
+ * Sanity check on the mode parameter - only open as a driver, not a
+ * module, and we do cloning ourselves.
+ */
+ if (fMode)
+ {
+ LogRel(("::Open: invalid attempt to clone device."));
+ return EINVAL;
+ }
+
+ pState = &g_OpenNodeState;
+ mutex_enter(&pState->InitMtx);
+ /*
+ * Check and remember our STREAM queue.
+ */
+ if ( pState->pWriteQueue
+ && (pState->pWriteQueue != WR(pReadQueue)))
+ {
+ mutex_exit(&pState->InitMtx);
+ LogRel((DEVICE_NAME "::Open: unexpectedly called with a different queue to previous calls. Exiting.\n"));
+ return EINVAL;
+ }
+ if (!pState->cInits)
+ {
+ /*
+ * Initialize IPRT R0 driver, which internally calls OS-specific r0
+ * init, and create a new session.
+ */
+ rc = VbglR0InitClient();
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0GRAlloc((VMMDevRequestHeader **)
+ &pState->pMouseStatusReq,
+ sizeof(*pState->pMouseStatusReq),
+ VMMDevReq_GetMouseStatus);
+ if (RT_FAILURE(rc))
+ VbglR0TerminateClient();
+ else
+ {
+ int rc2;
+ /* Initialise user data for the queues to our state and
+ * vice-versa. */
+ pState->pWriteQueue = WR(pReadQueue);
+ WR(pReadQueue)->q_ptr = (char *)pState;
+ pReadQueue->q_ptr = (char *)pState;
+ qprocson(pReadQueue);
+ /* Enable our IRQ handler. */
+ rc2 = VbglR0SetMouseNotifyCallback(vbmsSolNotify, (void *)pState);
+ if (RT_FAILURE(rc2))
+ /* Log the failure. I may well have not understood what
+ * is going on here, and the logging may help me. */
+ LogRelFlow(("Failed to install the event handler call-back, rc=%Rrc\n",
+ rc2));
+ }
+ }
+ }
+ if (RT_SUCCESS(rc))
+ ++pState->cInits;
+ mutex_exit(&pState->InitMtx);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("open time initialisation failed. rc=%d\n", rc));
+ ASMAtomicWriteNullPtr(&pState->pWriteQueue);
+ return EINVAL;
+ }
+ return 0;
+}
+
+
+/**
+ * Notification callback, called when the VBoxGuest mouse pointer is moved.
+ * We send a VUID event up to user space. We may send a miscalculated event
+ * if a resolution change is half-way through, but that is pretty much to be
+ * expected, so we won't worry about it.
+ */
+void vbmsSolNotify(void *pvState)
+{
+ PVBMSSTATE pState = (PVBMSSTATE)pvState;
+ int rc;
+
+ pState->pMouseStatusReq->mouseFeatures = 0;
+ pState->pMouseStatusReq->pointerXPos = 0;
+ pState->pMouseStatusReq->pointerYPos = 0;
+ rc = VbglR0GRPerform(&pState->pMouseStatusReq->header);
+ if (RT_SUCCESS(rc))
+ {
+ int cMaxScreenX = pState->cMaxScreenX;
+ int cMaxScreenY = pState->cMaxScreenY;
+ int x = pState->pMouseStatusReq->pointerXPos;
+ int y = pState->pMouseStatusReq->pointerYPos;
+
+ if (cMaxScreenX && cMaxScreenY)
+ {
+ vbmsSolVUIDPutAbsEvent(pState, LOC_X_ABSOLUTE,
+ x * cMaxScreenX / VMMDEV_MOUSE_RANGE_MAX);
+ vbmsSolVUIDPutAbsEvent(pState, LOC_Y_ABSOLUTE,
+ y * cMaxScreenY / VMMDEV_MOUSE_RANGE_MAX);
+ }
+ }
+}
+
+
+void vbmsSolVUIDPutAbsEvent(PVBMSSTATE pState, ushort_t cEvent,
+ int cValue)
+{
+ queue_t *pReadQueue = RD(pState->pWriteQueue);
+ mblk_t *pMBlk = allocb(sizeof(Firm_event), BPRI_HI);
+ Firm_event *pEvent;
+ AssertReturnVoid(cEvent == LOC_X_ABSOLUTE || cEvent == LOC_Y_ABSOLUTE);
+ if (!pMBlk)
+ return; /* If kernel memory is short a missed event is acceptable! */
+ pEvent = (Firm_event *)pMBlk->b_wptr;
+ pEvent->id = cEvent;
+ pEvent->pair_type = FE_PAIR_DELTA;
+ pEvent->pair = cEvent == LOC_X_ABSOLUTE ? LOC_X_DELTA : LOC_Y_DELTA;
+ pEvent->value = cValue;
+ uniqtime32(&pEvent->time);
+ pMBlk->b_wptr += sizeof(Firm_event);
+ /* Put the message on the queue immediately if it is not blocked. */
+ if (canput(pReadQueue->q_next))
+ putnext(pReadQueue, pMBlk);
+ else
+ putq(pReadQueue, pMBlk);
+}
+
+
+/**
+ * Close callback for the read queue, which we use as a generic device close
+ * handler.
+ */
+int vbmsSolClose(queue_t *pReadQueue, int fFlag, cred_t *pCred)
+{
+ PVBMSSTATE pState = (PVBMSSTATE)pReadQueue->q_ptr;
+
+ LogRelFlow((DEVICE_NAME "::Close, pWriteQueue=%p\n", WR(pReadQueue)));
+ NOREF(fFlag);
+ NOREF(pCred);
+
+ if (!pState)
+ {
+ Log((DEVICE_NAME "::Close: failed to get pState.\n"));
+ return EFAULT;
+ }
+
+ mutex_enter(&pState->InitMtx);
+ --pState->cInits;
+ if (!pState->cInits)
+ {
+ VbglR0SetMouseStatus(0);
+ /* Disable our IRQ handler. */
+ VbglR0SetMouseNotifyCallback(NULL, NULL);
+ qprocsoff(pReadQueue);
+
+ /*
+ * Close the session.
+ */
+ ASMAtomicWriteNullPtr(&pState->pWriteQueue);
+ pReadQueue->q_ptr = NULL;
+ VbglR0GRFree(&pState->pMouseStatusReq->header);
+ VbglR0TerminateClient();
+ }
+ mutex_exit(&pState->InitMtx);
+ return 0;
+}
+
+
+#ifdef TESTCASE
+/** Simple test of vbmsSolOpen and vbmsSolClose. */
+static void testOpenClose(RTTEST hTest)
+{
+ queue_t aQueues[2];
+ dev_t device = 0;
+ int rc;
+
+ RTTestSub(hTest, "Testing vbmsSolOpen and vbmsSolClose");
+ RT_ZERO(g_OpenNodeState);
+ RT_ZERO(aQueues);
+ doInitQueues(&aQueues[0]);
+ rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL);
+ RTTEST_CHECK(hTest, rc == 0);
+ RTTEST_CHECK(hTest, g_OpenNodeState.pWriteQueue == WR(&aQueues[0]));
+ vbmsSolClose(RD(&aQueues[0]), 0, NULL);
+}
+#endif
+
+
+/* Helper for vbmsSolWPut. */
+static int vbmsSolDispatchIOCtl(PVBMSSTATE pState, mblk_t *pMBlk);
+
+/**
+ * Handler for messages sent from above (user-space and upper modules) which
+ * land in our write queue.
+ */
+int vbmsSolWPut(queue_t *pWriteQueue, mblk_t *pMBlk)
+{
+ PVBMSSTATE pState = (PVBMSSTATE)pWriteQueue->q_ptr;
+ LogRelFlowFunc((DEVICE_NAME "::"));
+ switch (pMBlk->b_datap->db_type)
+ {
+ case M_FLUSH:
+ LogRelFlow(("M_FLUSH, FLUSHW=%RTbool, FLUSHR=%RTbool\n",
+ *pMBlk->b_rptr & FLUSHW, *pMBlk->b_rptr & FLUSHR));
+ /* Flush the write queue if so requested. */
+ if (*pMBlk->b_rptr & FLUSHW)
+ flushq(pWriteQueue, FLUSHDATA);
+
+ /* Flush the read queue if so requested. */
+ if (*pMBlk->b_rptr & FLUSHR)
+ flushq(RD(pWriteQueue), FLUSHDATA);
+
+ /* We have no one below us to pass the message on to. */
+ freemsg(pMBlk);
+ return 0;
+ /* M_IOCDATA is additional data attached to (at least) transparent
+ * IOCtls. We handle the two together here and separate them further
+ * down. */
+ case M_IOCTL:
+ case M_IOCDATA:
+ {
+ int err;
+
+ LogRelFlow(( pMBlk->b_datap->db_type == M_IOCTL
+ ? "M_IOCTL\n" : "M_IOCDATA\n"));
+ err = vbmsSolDispatchIOCtl(pState, pMBlk);
+ if (!err)
+ qreply(pWriteQueue, pMBlk);
+ else
+ miocnak(pWriteQueue, pMBlk, 0, err);
+ break;
+ }
+ default:
+ LogRelFlow(("Unknown command, not acknowledging.\n"));
+ }
+ return 0;
+}
+
+
+#ifdef TESTCASE
+/* Constants, definitions and test functions for testWPut. */
+static const int g_ccTestWPutFirmEvent = VUID_FIRM_EVENT;
+# define PVGFORMAT (&g_ccTestWPutFirmEvent)
+# define CBGFORMAT (sizeof(g_ccTestWPutFirmEvent))
+static const Ms_screen_resolution g_TestResolution = { 640, 480 };
+# define PMSIOSRES (&g_TestResolution)
+# define CBMSIOSRES (sizeof(g_TestResolution))
+
+static inline bool testSetResolution(RTTEST hTest, queue_t *pWriteQueue,
+ struct msgb *pMBlk)
+{
+ PVBMSSTATE pState = (PVBMSSTATE)pWriteQueue->q_ptr;
+ RTTEST_CHECK_MSG_RET(hTest, pState->cMaxScreenX
+ == g_TestResolution.width - 1,
+ (hTest, "pState->cMaxScreenX=%d\n",
+ pState->cMaxScreenX), false);
+ RTTEST_CHECK_MSG_RET(hTest, pState->cMaxScreenY
+ == g_TestResolution.height - 1,
+ (hTest, "pState->cMaxScreenY=%d\n",
+ pState->cMaxScreenY), false);
+ return true;
+}
+
+/** Data table for testWPut. */
+static struct
+{
+ int iIOCCmd;
+ size_t cbData;
+ const void *pvDataIn;
+ size_t cbDataIn;
+ const void *pvDataOut;
+ size_t cbDataOut;
+ int rcExp;
+ bool (*pfnExtra)(RTTEST hTest, queue_t *pWriteQueue, struct msgb *pMBlk);
+ bool fCanTransparent;
+} g_asTestWPut[] =
+{
+ /* iIOCCmd cbData pvDataIn cbDataIn
+ pvDataOut cbDataOut rcExp pfnExtra fCanTransparent */
+ { VUIDGFORMAT, sizeof(int), NULL, 0,
+ PVGFORMAT, CBGFORMAT, 0, NULL, true },
+ { VUIDGFORMAT, sizeof(int) - 1, NULL, 0,
+ NULL, 0, EINVAL, NULL, false },
+ { VUIDGFORMAT, sizeof(int) + 1, NULL, 0,
+ PVGFORMAT, CBGFORMAT, 0, NULL, true },
+ { VUIDSFORMAT, sizeof(int), PVGFORMAT, CBGFORMAT,
+ NULL, 0, 0, NULL, true },
+ { MSIOSRESOLUTION, CBMSIOSRES, PMSIOSRES, CBMSIOSRES,
+ NULL, 0, 0, testSetResolution, true },
+ { VUIDGWHEELINFO, 0, NULL, 0,
+ NULL, 0, EINVAL, NULL, true }
+};
+
+# undef PVGFORMAT
+# undef CBGFORMAT
+# undef PMSIOSRES
+# undef CBMSIOSRES
+
+/* Helpers for testWPut. */
+static void testWPutStreams(RTTEST hTest, unsigned i);
+static void testWPutTransparent(RTTEST hTest, unsigned i);
+static void testWPutIOCDataIn(RTTEST hTest, unsigned i);
+static void testWPutIOCDataOut(RTTEST hTest, unsigned i);
+
+/** Test WPut's handling of different IOCtls, which is bulk of the logic in
+ * this file. */
+static void testWPut(RTTEST hTest)
+{
+ unsigned i;
+
+ RTTestSub(hTest, "Testing vbmsWPut");
+ for (i = 0; i < RT_ELEMENTS(g_asTestWPut); ++i)
+ {
+ AssertReturnVoid(g_asTestWPut[i].cbDataIn <= g_asTestWPut[i].cbData);
+ AssertReturnVoid(g_asTestWPut[i].cbDataOut <= g_asTestWPut[i].cbData);
+ testWPutStreams(hTest, i);
+ if (g_asTestWPut[i].fCanTransparent)
+ testWPutTransparent(hTest, i);
+ if (g_asTestWPut[i].fCanTransparent && g_asTestWPut[i].cbDataIn)
+ testWPutIOCDataIn(hTest, i);
+ if (g_asTestWPut[i].fCanTransparent && g_asTestWPut[i].cbDataOut)
+ testWPutIOCDataOut(hTest, i);
+ }
+}
+
+
+#define MSG_DATA_SIZE 1024
+
+/** Simulate sending a streams IOCtl to WPut with the parameters from table
+ * line @a i. */
+void testWPutStreams(RTTEST hTest, unsigned i)
+{
+ queue_t aQueues[2];
+ dev_t device = 0;
+ struct msgb *pMBlk = allocb(sizeof(struct iocblk), BPRI_MED);
+ struct msgb *pMBlkCont = allocb(MSG_DATA_SIZE, BPRI_MED);
+ struct iocblk *pIOCBlk = pMBlk ? (struct iocblk *)pMBlk->b_rptr : NULL;
+ int rc, cFormat = 0;
+
+ AssertReturnVoid(pMBlk);
+ AssertReturnVoidStmt(pMBlkCont, freemsg(pMBlk));
+ RT_ZERO(aQueues);
+ doInitQueues(&aQueues[0]);
+ rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL);
+ RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc));
+ RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue
+ == WR(&aQueues[0]), (hTest, "i=%u\n", i));
+ pMBlk->b_datap->db_type = M_IOCTL;
+ pIOCBlk->ioc_cmd = g_asTestWPut[i].iIOCCmd;
+ pIOCBlk->ioc_count = g_asTestWPut[i].cbData;
+ AssertReturnVoid(g_asTestWPut[i].cbData <= MSG_DATA_SIZE);
+ memcpy(pMBlkCont->b_rptr, g_asTestWPut[i].pvDataIn,
+ g_asTestWPut[i].cbDataIn);
+ pMBlk->b_cont = pMBlkCont;
+ rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk);
+ RTTEST_CHECK_MSG(hTest, pIOCBlk->ioc_error == g_asTestWPut[i].rcExp,
+ (hTest, "i=%u, IOCBlk.ioc_error=%d\n", i,
+ pIOCBlk->ioc_error));
+ RTTEST_CHECK_MSG(hTest, pIOCBlk->ioc_count == g_asTestWPut[i].cbDataOut,
+ (hTest, "i=%u, ioc_count=%u\n", i, pIOCBlk->ioc_count));
+ RTTEST_CHECK_MSG(hTest, !memcmp(pMBlkCont->b_rptr,
+ g_asTestWPut[i].pvDataOut,
+ g_asTestWPut[i].cbDataOut),
+ (hTest, "i=%u\n", i));
+ /* Hack to ensure that miocpullup() gets called when needed. */
+ if (g_asTestWPut[i].cbData > 0)
+ RTTEST_CHECK_MSG(hTest, pMBlk->b_flag == 1, (hTest, "i=%u\n", i));
+ if (!g_asTestWPut[i].rcExp)
+ RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk,
+ (hTest, "i=%u\n", i));
+ if (g_asTestWPut[i].pfnExtra)
+ if (!g_asTestWPut[i].pfnExtra(hTest, WR(&aQueues[0]), pMBlk))
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Called from %s.\n",
+ __PRETTY_FUNCTION__);
+ vbmsSolClose(RD(&aQueues[1]), 0, NULL);
+ freemsg(pMBlk);
+}
+
+
+#define USER_ADDRESS 0xfeedbacc
+
+/** Simulate sending a transparent IOCtl to WPut with the parameters from table
+ * line @a i. */
+void testWPutTransparent(RTTEST hTest, unsigned i)
+{
+ queue_t aQueues[2];
+ dev_t device = 0;
+ struct msgb *pMBlk = allocb(sizeof(struct iocblk), BPRI_MED);
+ struct msgb *pMBlkCont = allocb(sizeof(void *), BPRI_MED);
+ struct iocblk *pIOCBlk = pMBlk ? (struct iocblk *)pMBlk->b_rptr : NULL;
+ struct copyreq *pCopyReq;
+ int rc, cFormat = 0;
+
+ /* if (g_asTestWPut[i].cbDataIn == 0 && g_asTestWPut[i].cbDataOut != 0)
+ return; */ /* This case will be handled once the current ones work. */
+ AssertReturnVoid(pMBlk);
+ AssertReturnVoidStmt(pMBlkCont, freemsg(pMBlk));
+ RT_ZERO(aQueues);
+ doInitQueues(&aQueues[0]);
+ rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL);
+ RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc));
+ RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue
+ == WR(&aQueues[0]), (hTest, "i=%u\n", i));
+ pMBlk->b_datap->db_type = M_IOCTL;
+ pIOCBlk->ioc_cmd = g_asTestWPut[i].iIOCCmd;
+ pIOCBlk->ioc_count = TRANSPARENT;
+ *(void **)pMBlkCont->b_rptr = (void *)USER_ADDRESS;
+ pMBlk->b_cont = pMBlkCont;
+ rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk);
+ pCopyReq = (struct copyreq *)pMBlk->b_rptr;
+ RTTEST_CHECK_MSG(hTest, ( ( g_asTestWPut[i].cbDataIn
+ && (pMBlk->b_datap->db_type == M_COPYIN))
+ || ( g_asTestWPut[i].cbDataOut
+ && (pMBlk->b_datap->db_type == M_COPYOUT))
+ || ( (g_asTestWPut[i].rcExp == 0)
+ && pMBlk->b_datap->db_type == M_IOCACK)
+ || (pMBlk->b_datap->db_type == M_IOCNAK)),
+ (hTest, "i=%u, db_type=%u\n", i,
+ (unsigned) pMBlk->b_datap->db_type));
+ /* Our TRANSPARENT IOCtls can only return non-zero if they have no payload.
+ * Others should either return zero or be non-TRANSPARENT only. */
+ if (pMBlk->b_datap->db_type == M_IOCNAK)
+ RTTEST_CHECK_MSG(hTest, pIOCBlk->ioc_error == g_asTestWPut[i].rcExp,
+ (hTest, "i=%u, IOCBlk.ioc_error=%d\n", i,
+ pIOCBlk->ioc_error));
+ if (g_asTestWPut[i].cbData)
+ {
+ RTTEST_CHECK_MSG(hTest, pCopyReq->cq_addr == (char *)USER_ADDRESS,
+ (hTest, "i=%u, cq_addr=%p\n", i, pCopyReq->cq_addr));
+ RTTEST_CHECK_MSG( hTest, pCopyReq->cq_size
+ == g_asTestWPut[i].cbDataIn
+ ? g_asTestWPut[i].cbDataIn
+ : g_asTestWPut[i].cbDataOut,
+ (hTest, "i=%u, cq_size=%llu\n", i,
+ (unsigned long long)pCopyReq->cq_size));
+ }
+ /* Implementation detail - check that the private pointer is correctly
+ * set to the user address *for two direction IOCtls* or NULL otherwise. */
+ if (g_asTestWPut[i].cbDataIn && g_asTestWPut[i].cbDataOut)
+ RTTEST_CHECK_MSG(hTest, pCopyReq->cq_private == (mblk_t *)USER_ADDRESS,
+ (hTest, "i=%u, cq_private=%p\n", i,
+ pCopyReq->cq_private));
+ else if ( (pMBlk->b_datap->db_type == M_COPYIN)
+ || (pMBlk->b_datap->db_type == M_COPYOUT))
+ RTTEST_CHECK_MSG(hTest, !pCopyReq->cq_private,
+ (hTest, "i=%u, cq_private=%p\n", i,
+ pCopyReq->cq_private));
+ if (!g_asTestWPut[i].rcExp)
+ RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk,
+ (hTest, "i=%u\n", i));
+ if (g_asTestWPut[i].pfnExtra && !g_asTestWPut[i].cbData)
+ if (!g_asTestWPut[i].pfnExtra(hTest, WR(&aQueues[0]), pMBlk))
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Called from %s.\n",
+ __PRETTY_FUNCTION__);
+ vbmsSolClose(RD(&aQueues[1]), 0, NULL);
+ freemsg(pMBlk);
+}
+
+
+/** Simulate sending follow-on IOCData messages to a transparent IOCtl to WPut
+ * with the parameters from table line @a i. */
+void testWPutIOCDataIn(RTTEST hTest, unsigned i)
+{
+ queue_t aQueues[2];
+ dev_t device = 0;
+ struct msgb *pMBlk = allocb(sizeof(struct copyresp), BPRI_MED);
+ struct msgb *pMBlkCont = allocb(MSG_DATA_SIZE, BPRI_MED);
+ struct copyresp *pCopyResp = pMBlk ? (struct copyresp *)pMBlk->b_rptr
+ : NULL;
+ void *pvData = pMBlkCont ? pMBlkCont->b_rptr : NULL;
+ struct copyreq *pCopyReq;
+ int rc, cFormat = 0;
+
+ AssertReturnVoid(pMBlk);
+ AssertReturnVoidStmt(pMBlkCont, freemsg(pMBlk));
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: i=%u\n", __PRETTY_FUNCTION__,
+ i);
+ AssertReturnVoidStmt(g_asTestWPut[i].cbDataIn, freemsg(pMBlk));
+ RT_ZERO(aQueues);
+ doInitQueues(&aQueues[0]);
+ rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL);
+ RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc));
+ RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue
+ == WR(&aQueues[0]), (hTest, "i=%u\n", i));
+ pMBlk->b_datap->db_type = M_IOCDATA;
+ pCopyResp->cp_cmd = g_asTestWPut[i].iIOCCmd;
+ if (g_asTestWPut[i].cbDataOut)
+ pCopyResp->cp_private = USER_ADDRESS;
+ AssertReturnVoid(g_asTestWPut[i].cbData <= MSG_DATA_SIZE);
+ memcpy(pMBlkCont->b_rptr, g_asTestWPut[i].pvDataIn, g_asTestWPut[i].cbDataIn);
+ pMBlk->b_cont = pMBlkCont;
+ rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk);
+ pCopyReq = (struct copyreq *)pMBlk->b_rptr;
+ RTTEST_CHECK_MSG(hTest, ( ( g_asTestWPut[i].cbDataOut
+ && (pMBlk->b_datap->db_type == M_COPYOUT))
+ || ( (g_asTestWPut[i].rcExp == 0)
+ && pMBlk->b_datap->db_type == M_IOCACK)
+ || (pMBlk->b_datap->db_type == M_IOCNAK)),
+ (hTest, "i=%u, db_type=%u\n", i,
+ (unsigned) pMBlk->b_datap->db_type));
+ if (g_asTestWPut[i].cbDataOut)
+ {
+ RTTEST_CHECK_MSG(hTest, pCopyReq->cq_addr == (char *)pvData,
+ (hTest, "i=%u, cq_addr=%p\n", i, pCopyReq->cq_addr));
+ RTTEST_CHECK_MSG(hTest, pCopyReq->cq_size == g_asTestWPut[i].cbData,
+ (hTest, "i=%u, cq_size=%llu\n", i,
+ (unsigned long long)pCopyReq->cq_size));
+ RTTEST_CHECK_MSG(hTest, !memcmp(pvData, g_asTestWPut[i].pvDataOut,
+ g_asTestWPut[i].cbDataOut),
+ (hTest, "i=%u\n", i));
+ }
+ RTTEST_CHECK_MSG(hTest, !pCopyReq->cq_private,
+ (hTest, "i=%u, cq_private=%p\n", i,
+ pCopyReq->cq_private));
+ if (!g_asTestWPut[i].rcExp)
+ RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk,
+ (hTest, "i=%u\n", i));
+ if (g_asTestWPut[i].pfnExtra && !g_asTestWPut[i].cbDataOut)
+ if (!g_asTestWPut[i].pfnExtra(hTest, WR(&aQueues[0]), pMBlk))
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Called from %s.\n",
+ __PRETTY_FUNCTION__);
+ vbmsSolClose(RD(&aQueues[1]), 0, NULL);
+ freemsg(pMBlk);
+}
+
+
+/** Simulate sending follow-on IOCData messages to a transparent IOCtl to WPut
+ * with the parameters from table line @a i. */
+void testWPutIOCDataOut(RTTEST hTest, unsigned i)
+{
+ queue_t aQueues[2];
+ dev_t device = 0;
+ struct msgb *pMBlk = allocb(sizeof(struct copyresp), BPRI_MED);
+ struct copyresp *pCopyResp = pMBlk ? (struct copyresp *)pMBlk->b_rptr
+ : NULL;
+ int rc, cFormat = 0;
+
+ AssertReturnVoid(pMBlk);
+ AssertReturnVoidStmt(g_asTestWPut[i].cbDataOut, freemsg(pMBlk));
+ RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: i=%u\n", __PRETTY_FUNCTION__,
+ i);
+ RT_ZERO(aQueues);
+ doInitQueues(&aQueues[0]);
+ rc = vbmsSolOpen(RD(&aQueues[0]), &device, 0, 0, NULL);
+ RTTEST_CHECK_MSG(hTest, rc == 0, (hTest, "i=%u, rc=%d\n", i, rc));
+ RTTEST_CHECK_MSG(hTest, g_OpenNodeState.pWriteQueue
+ == WR(&aQueues[0]), (hTest, "i=%u\n", i));
+ pMBlk->b_datap->db_type = M_IOCDATA;
+ pCopyResp->cp_cmd = g_asTestWPut[i].iIOCCmd;
+ rc = vbmsSolWPut(WR(&aQueues[0]), pMBlk);
+ RTTEST_CHECK_MSG(hTest, pMBlk->b_datap->db_type == M_IOCACK,
+ (hTest, "i=%u, db_type=%u\n", i,
+ (unsigned) pMBlk->b_datap->db_type));
+ if (!g_asTestWPut[i].rcExp)
+ RTTEST_CHECK_MSG(hTest, RD(&aQueues[0])->q_first == pMBlk,
+ (hTest, "i=%u\n", i));
+ vbmsSolClose(RD(&aQueues[1]), 0, NULL);
+ freemsg(pMBlk);
+}
+#endif
+
+
+/** Data transfer direction of an IOCtl. This is used for describing
+ * transparent IOCtls, and @a UNSPECIFIED is not a valid value for them. */
+enum IOCTLDIRECTION
+{
+ /** This IOCtl transfers no data. */
+ NONE,
+ /** This IOCtl only transfers data from user to kernel. */
+ IN,
+ /** This IOCtl only transfers data from kernel to user. */
+ OUT,
+ /** This IOCtl transfers data from user to kernel and back. */
+ BOTH,
+ /** We aren't saying anything about how the IOCtl transfers data. */
+ UNSPECIFIED
+};
+
+/**
+ * IOCtl handler function.
+ * @returns 0 on success, error code on failure.
+ * @param iCmd The IOCtl command number.
+ * @param pvData Buffer for the user data.
+ * @param cbBuffer Size of the buffer in @a pvData or zero.
+ * @param pcbData Where to set the size of the data returned. Required for
+ * handlers which return data.
+ * @param prc Where to store the return code. Default is zero. Only
+ * used for IOCtls without data for convenience of
+ * implemention.
+ */
+typedef int FNVBMSSOLIOCTL(PVBMSSTATE pState, int iCmd, void *pvData,
+ size_t cbBuffer, size_t *pcbData, int *prc);
+typedef FNVBMSSOLIOCTL *PFNVBMSSOLIOCTL;
+
+/* Helpers for vbmsSolDispatchIOCtl. */
+static int vbmsSolHandleIOCtl(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler,
+ int iCmd, size_t cbCmd,
+ enum IOCTLDIRECTION enmDirection);
+static int vbmsSolVUIDIOCtl(PVBMSSTATE pState, int iCmd, void *pvData,
+ size_t cbBuffer, size_t *pcbData, int *prc);
+
+/** Table of supported VUID IOCtls. */
+struct
+{
+ /** The IOCtl number. */
+ int iCmd;
+ /** The size of the buffer which needs to be copied between user and kernel
+ * space, or zero if unknown (must be known for tranparent IOCtls). */
+ size_t cbBuffer;
+ /** The direction the buffer data needs to be copied. This must be
+ * specified for transparent IOCtls. */
+ enum IOCTLDIRECTION enmDirection;
+} g_aVUIDIOCtlDescriptions[] =
+{
+ { VUIDGFORMAT, sizeof(int), OUT },
+ { VUIDSFORMAT, sizeof(int), IN },
+ { VUIDGADDR, 0, UNSPECIFIED },
+ { VUIDGADDR, 0, UNSPECIFIED },
+ { MSIOGETPARMS, sizeof(Ms_parms), OUT },
+ { MSIOSETPARMS, sizeof(Ms_parms), IN },
+ { MSIOSRESOLUTION, sizeof(Ms_screen_resolution), IN },
+ { MSIOBUTTONS, sizeof(int), OUT },
+ { VUIDGWHEELCOUNT, sizeof(int), OUT },
+ { VUIDGWHEELINFO, 0, UNSPECIFIED },
+ { VUIDGWHEELSTATE, 0, UNSPECIFIED },
+ { VUIDSWHEELSTATE, 0, UNSPECIFIED }
+};
+
+/**
+ * Handle a STREAMS IOCtl message for our driver on the write stream. This
+ * function takes care of the IOCtl logic only and does not call qreply() or
+ * miocnak() at all - the caller must call these on success or failure
+ * respectively.
+ * @returns 0 on success or the IOCtl error code on failure.
+ * @param pState pointer to the state structure.
+ * @param pMBlk pointer to the STREAMS message block structure.
+ */
+static int vbmsSolDispatchIOCtl(PVBMSSTATE pState, mblk_t *pMBlk)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+ int iCmd = pIOCBlk->ioc_cmd, iCmdType = iCmd & (0xff << 8);
+ size_t cbBuffer;
+ enum IOCTLDIRECTION enmDirection;
+
+ LogRelFlowFunc((DEVICE_NAME "::pIOCBlk=%p, iCmdType=%c, iCmd=0x%x\n",
+ pIOCBlk, (char) (iCmdType >> 8), (unsigned)iCmd));
+ switch (iCmdType)
+ {
+ case MSIOC:
+ case VUIOC:
+ {
+ unsigned i;
+
+ for (i = 0; i < RT_ELEMENTS(g_aVUIDIOCtlDescriptions); ++i)
+ if (g_aVUIDIOCtlDescriptions[i].iCmd == iCmd)
+ {
+ cbBuffer = g_aVUIDIOCtlDescriptions[i].cbBuffer;
+ enmDirection = g_aVUIDIOCtlDescriptions[i].enmDirection;
+ return vbmsSolHandleIOCtl(pState, pMBlk,
+ vbmsSolVUIDIOCtl, iCmd,
+ cbBuffer, enmDirection);
+ }
+ return EINVAL;
+ }
+ default:
+ return ENOTTY;
+ }
+}
+
+
+/* Helpers for vbmsSolHandleIOCtl. */
+static int vbmsSolHandleIOCtlData(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler, int iCmd,
+ size_t cbCmd,
+ enum IOCTLDIRECTION enmDirection);
+
+static int vbmsSolHandleTransparentIOCtl(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler,
+ int iCmd, size_t cbCmd,
+ enum IOCTLDIRECTION enmDirection);
+
+static int vbmsSolHandleIStrIOCtl(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler, int iCmd);
+
+static void vbmsSolAcknowledgeIOCtl(mblk_t *pMBlk, int cbData, int rc)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+
+ pMBlk->b_datap->db_type = M_IOCACK;
+ pIOCBlk->ioc_count = cbData;
+ pIOCBlk->ioc_rval = rc;
+ pIOCBlk->ioc_error = 0;
+}
+
+/**
+ * Generic code for handling STREAMS-specific IOCtl logic and boilerplate. It
+ * calls the IOCtl handler passed to it without the handler having to be aware
+ * of STREAMS structures, or whether this is a transparent (traditional) or an
+ * I_STR (using a STREAMS structure to describe the data) IOCtl. With the
+ * caveat that we only support transparent IOCtls which pass all data in a
+ * single buffer of a fixed size (I_STR IOCtls are restricted to a single
+ * buffer anyway, but the caller can choose the buffer size).
+ * @returns 0 on success or the IOCtl error code on failure.
+ * @param pState pointer to the state structure.
+ * @param pMBlk pointer to the STREAMS message block structure.
+ * @param pfnHandler pointer to the right IOCtl handler function for this
+ * IOCtl number.
+ * @param iCmd IOCtl command number.
+ * @param cbCmd size of the user space buffer for this IOCtl number,
+ * used for processing transparent IOCtls. Pass zero
+ * for IOCtls with no maximum buffer size (which will
+ * not be able to be handled as transparent) or with
+ * no argument.
+ * @param enmDirection data transfer direction of the IOCtl.
+ */
+static int vbmsSolHandleIOCtl(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler, int iCmd,
+ size_t cbCmd, enum IOCTLDIRECTION enmDirection)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+
+ LogFlowFunc(("iCmd=0x%x, cbBuffer=%d, enmDirection=%d\n",
+ (unsigned)iCmd, (int)cbCmd, (int)enmDirection));
+ if (pMBlk->b_datap->db_type == M_IOCDATA)
+ return vbmsSolHandleIOCtlData(pState, pMBlk, pfnHandler, iCmd,
+ cbCmd, enmDirection);
+ else if ( pMBlk->b_datap->db_type == M_IOCTL
+ && pIOCBlk->ioc_count == TRANSPARENT)
+ return vbmsSolHandleTransparentIOCtl(pState, pMBlk, pfnHandler,
+ iCmd, cbCmd, enmDirection);
+ else if (pMBlk->b_datap->db_type == M_IOCTL)
+ return vbmsSolHandleIStrIOCtl(pState, pMBlk, pfnHandler, iCmd);
+ return EINVAL;
+}
+
+
+/**
+ * Helper for vbmsSolHandleIOCtl. This rather complicated-looking
+ * code is basically the standard boilerplate for handling any streams IOCtl
+ * additional data, which we currently only use for transparent IOCtls.
+ * @copydoc vbmsSolHandleIOCtl
+ */
+static int vbmsSolHandleIOCtlData(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler, int iCmd,
+ size_t cbCmd,
+ enum IOCTLDIRECTION enmDirection)
+{
+ struct copyresp *pCopyResp = (struct copyresp *)pMBlk->b_rptr;
+
+ LogFlowFunc(("iCmd=0x%x, cbBuffer=%d, enmDirection=%d, cp_rval=%d, cp_private=%p\n",
+ (unsigned)iCmd, (int)cbCmd, (int)enmDirection,
+ (int)(uintptr_t)pCopyResp->cp_rval,
+ (void *)pCopyResp->cp_private));
+ if (pCopyResp->cp_rval) /* cp_rval is a pointer used as a boolean. */
+ return EAGAIN;
+ if ((pCopyResp->cp_private && enmDirection == BOTH) || enmDirection == IN)
+ {
+ size_t cbData = 0;
+ void *pvData = NULL;
+ int err;
+
+ if (!pMBlk->b_cont)
+ return EINVAL;
+ pvData = pMBlk->b_cont->b_rptr;
+ err = pfnHandler(pState, iCmd, pvData, cbCmd, &cbData, NULL);
+ if (!err && enmDirection == BOTH)
+ mcopyout(pMBlk, NULL, cbData, pCopyResp->cp_private, NULL);
+ else if (!err && enmDirection == IN)
+ vbmsSolAcknowledgeIOCtl(pMBlk, 0, 0);
+ if ((err || enmDirection == IN) && pCopyResp->cp_private)
+ freemsg(pCopyResp->cp_private);
+ return err;
+ }
+ else
+ {
+ if (pCopyResp->cp_private)
+ freemsg(pCopyResp->cp_private);
+ AssertReturn(enmDirection == OUT || enmDirection == BOTH, EINVAL);
+ vbmsSolAcknowledgeIOCtl(pMBlk, 0, 0);
+ return 0;
+ }
+}
+
+/**
+ * Helper for vbmsSolHandleIOCtl. This rather complicated-looking
+ * code is basically the standard boilerplate for handling transparent IOCtls,
+ * that is, IOCtls which are not re-packed inside STREAMS IOCtls.
+ * @copydoc vbmsSolHandleIOCtl
+ */
+int vbmsSolHandleTransparentIOCtl(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler, int iCmd,
+ size_t cbCmd,
+ enum IOCTLDIRECTION enmDirection)
+{
+ int err = 0, rc = 0;
+ size_t cbData = 0;
+
+ LogFlowFunc(("iCmd=0x%x, cbBuffer=%d, enmDirection=%d\n",
+ (unsigned)iCmd, (int)cbCmd, (int)enmDirection));
+ if ( (enmDirection != NONE && !pMBlk->b_cont)
+ || enmDirection == UNSPECIFIED)
+ return EINVAL;
+ if (enmDirection == IN || enmDirection == BOTH)
+ {
+ void *pUserAddr = NULL;
+ /* We only need state data if there is something to copy back. */
+ if (enmDirection == BOTH)
+ pUserAddr = *(void **)pMBlk->b_cont->b_rptr;
+ mcopyin(pMBlk, pUserAddr /* state data */, cbCmd, NULL);
+ }
+ else if (enmDirection == OUT)
+ {
+ mblk_t *pMBlkOut = allocb(cbCmd, BPRI_MED);
+ void *pvData;
+
+ if (!pMBlkOut)
+ return EAGAIN;
+ pvData = pMBlkOut->b_rptr;
+ err = pfnHandler(pState, iCmd, pvData, cbCmd, &cbData, NULL);
+ if (!err)
+ mcopyout(pMBlk, NULL, cbData, NULL, pMBlkOut);
+ else
+ freemsg(pMBlkOut);
+ }
+ else
+ {
+ AssertReturn(enmDirection == NONE, EINVAL);
+ err = pfnHandler(pState, iCmd, NULL, 0, NULL, &rc);
+ if (!err)
+ vbmsSolAcknowledgeIOCtl(pMBlk, 0, rc);
+ }
+ return err;
+}
+
+/**
+ * Helper for vbmsSolHandleIOCtl. This rather complicated-looking
+ * code is basically the standard boilerplate for handling any streams IOCtl.
+ * @copydoc vbmsSolHandleIOCtl
+ */
+static int vbmsSolHandleIStrIOCtl(PVBMSSTATE pState, mblk_t *pMBlk,
+ PFNVBMSSOLIOCTL pfnHandler, int iCmd)
+{
+ struct iocblk *pIOCBlk = (struct iocblk *)pMBlk->b_rptr;
+ uint_t cbBuffer = pIOCBlk->ioc_count;
+ void *pvData = NULL;
+ int err, rc = 0;
+ size_t cbData = 0;
+
+ LogFlowFunc(("iCmd=0x%x, cbBuffer=%u, b_cont=%p\n",
+ (unsigned)iCmd, cbBuffer, (void *)pMBlk->b_cont));
+ if (cbBuffer && !pMBlk->b_cont)
+ return EINVAL;
+ /* Repack the whole buffer into a single message block if needed. */
+ if (cbBuffer)
+ {
+ err = miocpullup(pMBlk, cbBuffer);
+ if (err)
+ return err;
+ pvData = pMBlk->b_cont->b_rptr;
+ }
+ else if (pMBlk->b_cont) /* consms forgets to set ioc_count. */
+ {
+ pvData = pMBlk->b_cont->b_rptr;
+ cbBuffer = pMBlk->b_cont->b_datap->db_lim
+ - pMBlk->b_cont->b_datap->db_base;
+ }
+ err = pfnHandler(pState, iCmd, pvData, cbBuffer, &cbData, &rc);
+ if (!err)
+ {
+ LogRelFlowFunc(("pMBlk=%p, pMBlk->b_datap=%p, pMBlk->b_rptr=%p\n",
+ pMBlk, pMBlk->b_datap, pMBlk->b_rptr));
+ vbmsSolAcknowledgeIOCtl(pMBlk, cbData, rc);
+ }
+ return err;
+}
+
+
+/**
+ * Handle a VUID input device IOCtl.
+ * @copydoc FNVBMSSOLIOCTL
+ */
+static int vbmsSolVUIDIOCtl(PVBMSSTATE pState, int iCmd, void *pvData,
+ size_t cbBuffer, size_t *pcbData, int *prc)
+{
+ LogRelFlowFunc((DEVICE_NAME "::pvData=%p " /* no '\n' */, pvData));
+ switch (iCmd)
+ {
+ case VUIDGFORMAT:
+ {
+ LogRelFlowFunc(("VUIDGFORMAT\n"));
+ if (cbBuffer < sizeof(int))
+ return EINVAL;
+ *(int *)pvData = VUID_FIRM_EVENT;
+ *pcbData = sizeof(int);
+ return 0;
+ }
+ case VUIDSFORMAT:
+ LogRelFlowFunc(("VUIDSFORMAT\n"));
+ /* We define our native format to be VUID_FIRM_EVENT, so there
+ * is nothing more to do and we exit here on success or on
+ * failure. */
+ return 0;
+ case VUIDGADDR:
+ case VUIDSADDR:
+ LogRelFlowFunc(("VUIDGADDR/VUIDSADDR\n"));
+ return ENOTTY;
+ case MSIOGETPARMS:
+ {
+ Ms_parms parms = { 0 };
+
+ LogRelFlowFunc(("MSIOGETPARMS\n"));
+ if (cbBuffer < sizeof(Ms_parms))
+ return EINVAL;
+ *(Ms_parms *)pvData = parms;
+ *pcbData = sizeof(Ms_parms);
+ return 0;
+ }
+ case MSIOSETPARMS:
+ LogRelFlowFunc(("MSIOSETPARMS\n"));
+ return 0;
+ case MSIOSRESOLUTION:
+ {
+ Ms_screen_resolution *pResolution = (Ms_screen_resolution *)pvData;
+ int rc;
+
+ LogRelFlowFunc(("MSIOSRESOLUTION, cbBuffer=%d, sizeof(Ms_screen_resolution)=%d\n",
+ (int) cbBuffer,
+ (int) sizeof(Ms_screen_resolution)));
+ if (cbBuffer < sizeof(Ms_screen_resolution))
+ return EINVAL;
+ LogRelFlowFunc(("%dx%d\n", pResolution->width,
+ pResolution->height));
+ pState->cMaxScreenX = pResolution->width - 1;
+ pState->cMaxScreenY = pResolution->height - 1;
+ /* Note: we don't disable this again until session close. */
+ rc = VbglR0SetMouseStatus( VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
+ | VMMDEV_MOUSE_NEW_PROTOCOL);
+ if (RT_SUCCESS(rc))
+ return 0;
+ pState->cMaxScreenX = 0;
+ pState->cMaxScreenY = 0;
+ return ENODEV;
+ }
+ case MSIOBUTTONS:
+ {
+ LogRelFlowFunc(("MSIOBUTTONS\n"));
+ if (cbBuffer < sizeof(int))
+ return EINVAL;
+ *(int *)pvData = 0;
+ *pcbData = sizeof(int);
+ return 0;
+ }
+ case VUIDGWHEELCOUNT:
+ {
+ LogRelFlowFunc(("VUIDGWHEELCOUNT\n"));
+ if (cbBuffer < sizeof(int))
+ return EINVAL;
+ *(int *)pvData = 0;
+ *pcbData = sizeof(int);
+ return 0;
+ }
+ case VUIDGWHEELINFO:
+ case VUIDGWHEELSTATE:
+ case VUIDSWHEELSTATE:
+ LogRelFlowFunc(("VUIDGWHEELINFO/VUIDGWHEELSTATE/VUIDSWHEELSTATE\n"));
+ return EINVAL;
+ default:
+ LogRelFlowFunc(("Invalid IOCtl command %x\n", iCmd));
+ return EINVAL;
+ }
+}
+
+
+#ifdef TESTCASE
+int main(void)
+{
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstVBoxGuest-solaris", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+ test_init(hTest);
+ testOpenClose(hTest);
+ testWPut(hTest);
+
+ /*
+ * Summary.
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+#endif
+
diff --git a/src/VBox/Additions/solaris/Mouse/vboxms.conf b/src/VBox/Additions/solaris/Mouse/vboxms.conf
new file mode 100644
index 00000000..eb180e18
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/vboxms.conf
@@ -0,0 +1,42 @@
+# $Id: vboxms.conf $
+## @file
+# OpenSolaris Guest Mouse Driver Configuration
+#
+
+#
+# Copyright (C) 2012-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# This needs to go into /usr/kernel/drv,
+# while the 64-bit driver object goes into the amd64
+# subdirectory (32-bit drivers goes into the same
+# directory).
+#
+name="vboxms" parent="pseudo" instance=0;
diff --git a/src/VBox/Additions/solaris/Mouse/vboxmslnk.c b/src/VBox/Additions/solaris/Mouse/vboxmslnk.c
new file mode 100644
index 00000000..12c5ad34
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/vboxmslnk.c
@@ -0,0 +1,217 @@
+/* $Id: vboxmslnk.c $ */
+/** @file
+ * VirtualBox Guest Additions Mouse Driver for Solaris: user space loader tool.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <VBox/version.h>
+#include <iprt/buildconfig.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <getopt.h>
+
+#define VBOXMSLNK_MUXID_FILE "/system/volatile/vboxmslnk.muxid"
+
+static const char *g_pszProgName;
+
+
+static void vboxmslnk_fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void) vfprintf(stderr, fmt, ap);
+ if (fmt[strlen(fmt) - 1] != '\n')
+ (void) fprintf(stderr, " The error reported was: %s\n", strerror(errno));
+ va_end(ap);
+
+ exit(EXIT_FAILURE);
+}
+
+static void vboxmslnk_start(bool fNoLogo)
+{
+ /* Open our pointer integration driver (vboxms). */
+ int hVBoxMS = open("/dev/vboxms", O_RDWR);
+ if (hVBoxMS < 0)
+ vboxmslnk_fatal("Failed to open /dev/vboxms - please make sure that the node exists and that\n"
+ "you have permission to open it.");
+
+ /* Open the Solaris virtual mouse driver (consms). */
+ int hConsMS = open("/dev/mouse", O_RDWR);
+ if (hConsMS < 0)
+ vboxmslnk_fatal("Failed to open /dev/mouse - please make sure that the node exists and that\n"
+ "you have permission to open it.");
+
+ /* Link vboxms to consms from below. What this means is that vboxms is
+ * added to the list of input sources multiplexed by consms, and vboxms
+ * will receive any control messages (such as information about guest
+ * resolution changes) sent to consms. The link can only be broken
+ * explicitly using the connection ID returned from the IOCtl. */
+ int idConnection = ioctl(hConsMS, I_PLINK, hVBoxMS);
+ if (idConnection < 0)
+ vboxmslnk_fatal("Failed to add /dev/vboxms (the pointer integration driver) to /dev/mouse\n"
+ "(the Solaris virtual master mouse).");
+
+ (void) close(hVBoxMS);
+ (void) close(hConsMS);
+
+ if (!fNoLogo)
+ (void) printf("Successfully enabled pointer integration. Connection ID number to the\n"
+ "Solaris virtual master mouse is:\n");
+ (void) printf("%d\n", idConnection);
+
+ /* Save the connection ID (aka mux ID) so it can be retrieved later. */
+ FILE *fp = fopen(VBOXMSLNK_MUXID_FILE, "w");
+ if (fp == NULL)
+ vboxmslnk_fatal("Failed to open %s for writing the connection ID.", VBOXMSLNK_MUXID_FILE);
+ int rc = fprintf(fp, "%d\n", idConnection);
+ if (rc <= 0)
+ vboxmslnk_fatal("Failed to write the connection ID to %s.", VBOXMSLNK_MUXID_FILE);
+ (void) fclose(fp);
+}
+
+static void vboxmslnk_stop()
+{
+ /* Open the Solaris virtual mouse driver (consms). */
+ int hConsMS = open("/dev/mouse", O_RDWR);
+ if (hConsMS < 0)
+ vboxmslnk_fatal("Failed to open /dev/mouse - please make sure that the node exists and that\n"
+ "you have permission to open it.");
+
+ /* Open the vboxmslnk.muxid file and retrieve the saved mux ID. */
+ FILE *fp = fopen(VBOXMSLNK_MUXID_FILE, "r");
+ if (fp == NULL)
+ vboxmslnk_fatal("Failed to open %s for reading the connection ID.", VBOXMSLNK_MUXID_FILE);
+ int idConnection;
+ int rc = fscanf(fp, "%d\n", &idConnection);
+ if (rc <= 0)
+ vboxmslnk_fatal("Failed to read the connection ID from %s.", VBOXMSLNK_MUXID_FILE);
+ (void) fclose(fp);
+ (void) unlink(VBOXMSLNK_MUXID_FILE);
+
+ /* Unlink vboxms from consms so that vboxms is able to be unloaded. */
+ rc = ioctl(hConsMS, I_PUNLINK, idConnection);
+ if (rc < 0)
+ vboxmslnk_fatal("Failed to disconnect /dev/vboxms (the pointer integration driver) from\n"
+ "/dev/mouse (the Solaris virtual master mouse).");
+ (void) close(hConsMS);
+}
+
+static void vboxmslnk_usage()
+{
+ (void) printf("Usage:\n"
+ " %s [--nologo] <--start | --stop>\n"
+ " %s [-V|--version]\n\n"
+ " -V|--version print the tool version.\n"
+ " --nologo do not display the logo text and only output the connection\n"
+ " ID number needed to disable pointer integration\n"
+ " again.\n"
+ " --start Connect the VirtualBox pointer integration kernel module\n"
+ " to the Solaris mouse driver kernel module.\n"
+ " --stop Disconnect the VirtualBox pointer integration kernel module\n"
+ " from the Solaris mouse driver kernel module.\n"
+ " -h|--help display this help text.\n",
+ g_pszProgName, g_pszProgName);
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ bool fShowVersion = false, fNoLogo = false, fStart = false, fStop = false;
+ int c;
+
+ g_pszProgName = basename(argv[0]);
+
+ static const struct option vboxmslnk_lopts[] = {
+ {"version", no_argument, 0, 'V' },
+ {"nologo", no_argument, 0, 'n' },
+ {"start", no_argument, 0, 's' },
+ {"stop", no_argument, 0, 't' },
+ {"help", no_argument, 0, 'h' },
+ { 0, 0, 0, 0}
+ };
+
+ while ((c = getopt_long(argc, argv, "Vh", vboxmslnk_lopts, NULL)) != -1)
+ {
+ switch (c)
+ {
+ case 'V':
+ fShowVersion = true;
+ break;
+ case 'n':
+ fNoLogo = true;
+ break;
+ case 's':
+ fStart = true;
+ break;
+ case 't':
+ fStop = true;
+ break;
+ case 'h':
+ default:
+ vboxmslnk_usage();
+ }
+ }
+
+ if ( (!fStart && !fStop && !fShowVersion)
+ || (fStart && fStop)
+ || (fShowVersion && (fNoLogo || fStart || fStop)))
+ vboxmslnk_usage();
+
+ if (fShowVersion)
+ {
+ (void) printf("%sr%u\n", VBOX_VERSION_STRING, RTBldCfgRevision());
+ exit(EXIT_SUCCESS);
+ }
+
+ if (!fNoLogo)
+ (void) printf(VBOX_PRODUCT
+ " Guest Additions utility for enabling Solaris pointer\nintegration Version "
+ VBOX_VERSION_STRING "\n"
+ "Copyright (C) " VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+
+ if (fStart)
+ vboxmslnk_start(fNoLogo);
+
+ if (fStop)
+ vboxmslnk_stop();
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml b/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml
new file mode 100644
index 00000000..59662494
--- /dev/null
+++ b/src/VBox/Additions/solaris/Mouse/vboxmslnk.xml
@@ -0,0 +1,92 @@
+<?xml version='1.0'?>
+<!--
+#
+# Solaris SMF service manifest for VBoxService (timesync).
+#
+-->
+<!--
+ Copyright (C) 2012-2023 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ The contents of this file may alternatively be used under the terms
+ of the Common Development and Distribution License Version 1.0
+ (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ in the VirtualBox distribution, in which case the provisions of the
+ CDDL are applicable instead of those of the GPL.
+
+ You may elect to license modified versions of this file under the
+ terms and conditions of either the GPL or the CDDL or both.
+
+ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+-->
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+
+<service_bundle type='manifest' name='SUNWvboxguest:vboxmslnk'>
+
+<service
+ name='application/virtualbox/vboxmslnk'
+ type='service'
+ version='1'>
+
+ <create_default_instance enabled='true' />
+
+ <single_instance/>
+
+ <!-- Wait for devices to be initialized as we depend on vboxms (pseudo) -->
+ <dependency
+ name='milestone'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/milestone/devices:default' />
+ </dependency>
+
+ <!-- Wait for local filesystems to be mounted (just to be safe, don't start too early) -->
+ <dependency
+ name='filesystem-local'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/filesystem/local:default' />
+ </dependency>
+
+ <exec_method
+ type='method'
+ name='start'
+ exec='/usr/sbin/vboxmslnk --start'
+ timeout_seconds='30' />
+
+ <exec_method
+ type='method'
+ name='stop'
+ exec='/usr/sbin/vboxmslnk --stop'
+ timeout_seconds='60' />
+
+ <property_group name='startd' type='framework'>
+ <propval name='duration' type='astring' value='transient' />
+ </property_group>
+
+ <template>
+ <common_name>
+ <loctext xml:lang='C'>VirtualBox Mouse module link.</loctext>
+ </common_name>
+ </template>
+</service>
+
+</service_bundle>
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk b/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk
new file mode 100644
index 00000000..9568ad75
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/Makefile.kmk
@@ -0,0 +1,113 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Solaris Shared folder kernel module.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#ifneq ($(KBUILD_HOST),solaris)
+#$(error "The Solaris guest additions can only be built on Solaris!")
+#endif
+
+#
+# vboxfs - The Shared Folder Driver
+#
+SYSMODS.solaris += vboxfs
+vboxfs_TEMPLATE = VBoxGuestR0Drv
+vboxfs_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV)
+vboxfs_DEPS += $(VBOX_SVN_REV_KMK)
+vboxfs_INCS := \
+ .
+vboxfs_SOURCES = \
+ vboxfs_vfs.c \
+ vboxfs_vnode.c \
+ vboxfs_prov.c
+vboxfs_LIBS = \
+ $(VBOX_LIB_VBGL_R0)
+ifeq ($(KBUILD_HOST),solaris)
+ vboxfs_LDFLAGS.solaris += -N drv/vboxguest -N misc/ctf
+else
+ vboxfs_SOURCES += deps.asm
+ vboxfs_deps.asm_ASFLAGS = -f bin -g null
+endif
+if ($(VBOX_SOLARIS_11_UPDATE_VERSION) > 1 \
+ || ($(VBOX_SOLARIS_11_UPDATE_VERSION) == 1 && $(VBOX_SOLARIS_11_BUILD_VERSION) >= 10))
+ vboxfs_DEFS += VBOX_VFS_EXTENDED_POLICY
+endif
+
+
+ifndef VBOX_OSE
+ #
+ # vboxfs_s10 - The Shared Folder Driver for Solaris 10
+ #
+ SYSMODS.solaris += vboxfs_s10
+ vboxfs_s10_TEMPLATE = VBoxGuestR0Drv
+ vboxfs_s10_DEFS = VBOX_WITH_HGCM VBOX_VFS_SOLARIS_10U6 VBOX_SVN_REV=$(VBOX_SVN_REV)
+ vboxfs_s10_DEPS += $(VBOX_SVN_REV_KMK)
+ vboxfs_s10_INCS := solaris10/
+ vboxfs_s10_SOURCES = \
+ vboxfs_vfs.c \
+ vboxfs_vnode.c \
+ vboxfs_prov.c
+ vboxfs_s10_LIBS = \
+ $(VBOX_LIB_VBGL_R0) \
+ $(VBOX_LIB_IPRT_GUEST_R0)
+ ifeq ($(KBUILD_HOST),solaris)
+ vboxfs_s10_LDFLAGS += -N drv/vboxguest -N misc/ctf
+ else
+ vboxfs_s10_SOURCES += deps.asm
+ vboxfs_s10_deps.asm_ASFLAGS = -f bin -g null
+ endif
+endif # VBOX_OSE
+
+
+#
+# mount - Userland mount wrapper for vboxfs
+#
+PROGRAMS += vboxfsmount
+vboxfsmount_TEMPLATE = VBoxGuestR3Exe
+vboxfsmount_SOURCES = vboxfs_mount.c
+
+
+#
+# Load script.
+#
+INSTALLS += vboxfsload
+vboxfsload_TEMPLATE = VBoxGuestR0Drv
+vboxfsload_EXEC_SOURCES = loadfs.sh
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/deps.asm b/src/VBox/Additions/solaris/SharedFolders/deps.asm
new file mode 100644
index 00000000..d01557ed
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/deps.asm
@@ -0,0 +1,49 @@
+; $Id: deps.asm $
+;; @file
+; Solaris kernel module dependency
+;
+
+;
+; Copyright (C) 2012-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program is distributed in the hope that it will be useful, but
+; WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/solaris/kmoddeps.mac"
+
+kmoddeps_header ; ELF header, section table and shared string table
+
+kmoddeps_dynstr_start ; ELF .dynstr section
+kmoddeps_dynstr_string str_misc_ctf, "misc/ctf"
+kmoddeps_dynstr_string str_drv_vboxguest, "drv/vboxguest"
+kmoddeps_dynstr_end
+
+kmoddeps_dynamic_start ; ELF .dynamic section
+kmoddeps_dynamic_needed str_misc_ctf
+kmoddeps_dynamic_needed str_drv_vboxguest
+kmoddeps_dynamic_end
diff --git a/src/VBox/Additions/solaris/SharedFolders/loadfs.sh b/src/VBox/Additions/solaris/SharedFolders/loadfs.sh
new file mode 100755
index 00000000..7a5c1006
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/loadfs.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+# $Id: loadfs.sh $
+## @file
+# For GA development.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+DRVNAME="vboxfs"
+MOUNTHLP="vboxfsmount"
+
+DRVFILE=`dirname "$0"`
+DRVFILE=`cd "$DRVFILE" && pwd`
+MOUNTHLPFILE="$DRVFILE/$MOUNTHLP"
+DRVFILE="$DRVFILE/$DRVNAME"
+if [ ! -f "$DRVFILE" ]; then
+ echo "loadfs.sh: Cannot find $DRVFILE or it's not a file..."
+ exit 1;
+fi
+if [ ! -f "$MOUNTHLPFILE" ]; then
+ echo "load.sh: Cannot find $MOUNTHLPFILE or it's not a file..."
+ exit 1;
+fi
+
+SUDO=sudo
+#set -x
+
+# Unload the driver if loaded.
+for drv in $DRVNAME;
+do
+ LOADED=`modinfo | grep -w "$drv"`
+ if test -n "$LOADED"; then
+ MODID=`echo "$LOADED" | cut -d ' ' -f 1`
+ $SUDO modunload -i $MODID;
+ LOADED=`modinfo | grep -w "$drv"`;
+ if test -n "$LOADED"; then
+ echo "load.sh: failed to unload $drv";
+ dmesg | tail
+ exit 1;
+ fi
+ fi
+done
+
+#
+# Remove old stuff.
+#
+MY_RC=1
+set -e
+$SUDO rm -f \
+ "/usr/kernel/fs/${DRVNAME}" \
+ "/usr/kernel/fs/amd64/${DRVNAME}" \
+ "/etc/fs/vboxfs/mount"
+sync
+set +e
+
+#
+# Install the mount program.
+#
+if [ ! -d /etc/fs/vboxfs ]; then
+ $SUDO mkdir -p /etc/fs/vboxfs
+fi
+$SUDO ln -sf "$MOUNTHLPFILE" /etc/fs/vboxfs/mount
+
+#
+# Load the module. We can load it without copying it to /usr/kernel/fs/.
+#
+if $SUDO modload "$DRVFILE"; then
+ sync
+ MY_RC=0
+else
+ dmesg | tail
+ echo "load.sh: add_drv failed."
+fi
+
+exit $MY_RC;
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs.h
new file mode 100644
index 00000000..db72f7a7
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs.h
@@ -0,0 +1,106 @@
+/* $Id: vboxfs.h $ */
+/** @file
+ * VirtualBox File System Driver for Solaris Guests, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_h
+#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX_HOST_NAME 256
+#define MAX_NLS_NAME 32
+/** Default stat cache ttl (in ms) */
+#define DEF_STAT_TTL_MS 200
+
+/** The module name. */
+#define DEVICE_NAME "vboxfs"
+
+#ifdef _KERNEL
+
+#include <VBox/VBoxGuestLibSharedFolders.h>
+#include <sys/vfs.h>
+
+/** VNode for VBoxVFS */
+typedef struct vboxvfs_vnode
+{
+ vnode_t *pVNode;
+ vattr_t Attr;
+ SHFLSTRING *pPath;
+ kmutex_t MtxContents;
+} vboxvfs_vnode_t;
+
+
+/** Per-file system mount instance data. */
+typedef struct vboxvfs_globinfo
+{
+ VBGLSFMAP Map;
+ int Ttl;
+ int Uid;
+ int Gid;
+ vfs_t *pVFS;
+ vboxvfs_vnode_t *pVNodeRoot;
+ kmutex_t MtxFS;
+} vboxvfs_globinfo_t;
+
+extern struct vnodeops *g_pVBoxVFS_vnodeops;
+extern const fs_operation_def_t g_VBoxVFS_vnodeops_template[];
+extern VBGLSFCLIENT g_VBoxVFSClient;
+
+/** Helper functions */
+extern int vboxvfs_Stat(const char *pszCaller, vboxvfs_globinfo_t *pVBoxVFSGlobalInfo, SHFLSTRING *pPath,
+ PSHFLFSOBJINFO pResult, boolean_t fAllowFailure);
+extern void vboxvfs_InitVNode(vboxvfs_globinfo_t *pVBoxVFSGlobalInfo, vboxvfs_vnode_t *pVBoxVNode,
+ PSHFLFSOBJINFO pFSInfo);
+
+
+/** Helper macros */
+#define VFS_TO_VBOXVFS(vfs) ((vboxvfs_globinfo_t *)((vfs)->vfs_data))
+#define VBOXVFS_TO_VFS(vboxvfs) ((vboxvfs)->pVFS)
+#define VN_TO_VBOXVN(vnode) ((vboxvfs_vnode_t *)((vnode)->v_data))
+#define VBOXVN_TO_VN(vboxvnode) ((vboxvnode)->pVNode)
+
+#endif /* _KERNEL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_h */
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c
new file mode 100644
index 00000000..08505afb
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_mount.c
@@ -0,0 +1,179 @@
+/* $Id: vboxfs_mount.c $ */
+/** @file
+ * VirtualBox File System Mount Helper, Solaris host.
+ * Userspace mount wrapper that parses mount (or user-specified) options
+ * and passes it to mount(2) syscall
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sys/vfs.h>
+#include <sys/mount.h>
+
+#include "vboxfs.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static char g_achOptBuf[MAX_MNTOPT_STR] = { '\0', };
+static const int g_RetErr = 33;
+static const int g_RetMagic = 2;
+static const int g_RetOK = 0;
+
+static void Usage(char *pszName)
+{
+ fprintf(stderr, "Usage: %s [OPTIONS] NAME MOUNTPOINT\n"
+ "Mount the VirtualBox shared folder NAME from the host system to MOUNTPOINT.\n"
+ "\n"
+ " -w mount the shared folder writable (the default)\n"
+ " -r mount the shared folder read-only\n"
+ " -o OPTION[,OPTION...] use the mount options specified\n"
+ "\n", pszName);
+ fprintf(stderr, "Available mount options are:\n"
+ "\n"
+ " rw mount writable (the default)\n"
+ " ro mount read only\n"
+ " uid=UID set the default file owner user id to UID\n"
+ " gid=GID set the default file owner group id to GID\n");
+ fprintf(stderr,
+ " dmode=MODE override the mode for all directories (octal) to MODE\n"
+ " fmode=MODE override the mode for all regular files (octal) to MODE\n"
+ " umask=UMASK set the umask (bitmask of permissions not present) in (octal) UMASK\n"
+ " dmask=UMASK set the umask applied to directories only in (octal) UMASK\n"
+ " fmask=UMASK set the umask applied to regular files only in (octal) UMASK\n"
+ " stat_ttl=TTL set the \"time to live\" (in ms) for the stat caches (default %d)\n", DEF_STAT_TTL_MS);
+ fprintf(stderr,
+ " fsync honor fsync calls instead of ignoring them\n"
+ " ttl=TTL set the \"time to live\" to TID for the dentry\n"
+ " iocharset CHARSET use the character set CHARSET for i/o operations (default utf8)\n"
+ " convertcp CHARSET convert the shared folder name from the character set CHARSET to utf8\n\n"
+ "Less common used options:\n"
+ " noexec,exec,nodev,dev,nosuid,suid\n");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ char *pszName = NULL;
+ char *pszSpecial = NULL;
+ char *pszMount = NULL;
+ char achType[MAXFIDSZ];
+ int c = '?';
+ int rc = -1;
+ int parseError = 0;
+ int mntFlags = 0;
+ int quietFlag = 0;
+
+ pszName = strrchr(argv[0], '/');
+ pszName = pszName ? pszName + 1 : argv[0];
+ snprintf(achType, sizeof(achType), "%s_%s", DEVICE_NAME, pszName);
+
+ while ((c = getopt(argc, argv, "o:rmoQ")) != EOF)
+ {
+ switch (c)
+ {
+ case '?':
+ {
+ parseError = 1;
+ break;
+ }
+
+ case 'q':
+ {
+ quietFlag = 1;
+ break;
+ }
+
+ case 'r':
+ {
+ mntFlags |= MS_RDONLY;
+ break;
+ }
+
+ case 'O':
+ {
+ mntFlags |= MS_OVERLAY;
+ break;
+ }
+
+ case 'm':
+ {
+ mntFlags |= MS_NOMNTTAB;
+ break;
+ }
+
+ case 'o':
+ {
+ if (strlcpy(g_achOptBuf, optarg, sizeof(g_achOptBuf)) >= sizeof(g_achOptBuf))
+ {
+ fprintf(stderr, "%s: invalid argument: %s\n", pszName, optarg);
+ return g_RetMagic;
+ }
+ break;
+ }
+
+ default:
+ {
+ Usage(pszName);
+ break;
+ }
+ }
+ }
+
+ if ( argc - optind != 2
+ || parseError)
+ {
+ Usage(pszName);
+ }
+
+ pszSpecial = argv[argc - 2];
+ pszMount = argv[argc - 1];
+
+ rc = mount(pszSpecial, pszMount, mntFlags | MS_OPTIONSTR, DEVICE_NAME, NULL, 0, g_achOptBuf, sizeof(g_achOptBuf));
+ if (rc)
+ {
+ fprintf(stderr, "mount:");
+ perror(pszSpecial);
+ return g_RetErr;
+ }
+
+ return g_RetOK;
+}
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c
new file mode 100644
index 00000000..94251d63
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.c
@@ -0,0 +1,1070 @@
+/* $Id: vboxfs_prov.c $ */
+/** @file
+ * VirtualBox File System for Solaris Guests, provider implementation.
+ * Portions contributed by: Ronald.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/*
+ * Provider interfaces for shared folder file system.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mntent.h>
+#include <sys/param.h>
+#include <sys/modctl.h>
+#include <sys/mount.h>
+#include <sys/policy.h>
+#include <sys/atomic.h>
+#include <sys/sysmacros.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/dirent.h>
+#include <sys/file.h>
+#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
+
+#include "vboxfs_prov.h"
+#include <iprt/err.h>
+
+#define SFPROV_VERSION 1
+
+static VBGLSFCLIENT vbox_client;
+
+static int sfprov_vbox2errno(int rc)
+{
+ if (rc == VERR_ACCESS_DENIED)
+ return (EACCES);
+ if (rc == VERR_INVALID_NAME)
+ return (ENOENT);
+ return (RTErrConvertToErrno(rc));
+}
+
+/*
+ * utility to create strings
+ */
+static SHFLSTRING *
+sfprov_string(char *path, int *sz)
+{
+ SHFLSTRING *str;
+ int len = strlen(path);
+
+ *sz = len + 1 + sizeof (*str) - sizeof (str->String);
+ str = kmem_zalloc(*sz, KM_SLEEP);
+ str->u16Size = len + 1;
+ str->u16Length = len;
+ strcpy(str->String.utf8, path);
+ return (str);
+}
+
+sfp_connection_t *
+sfprov_connect(int version)
+{
+ /*
+ * only one version for now, so must match
+ */
+ int rc = -1;
+ if (version != SFPROV_VERSION)
+ {
+ cmn_err(CE_WARN, "sfprov_connect: wrong version. version=%d expected=%d\n", version, SFPROV_VERSION);
+ return NULL;
+ }
+ rc = VbglR0SfInit();
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0SfConnect(&vbox_client);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR0SfSetUtf8(&vbox_client);
+ if (RT_SUCCESS(rc))
+ {
+ return ((sfp_connection_t *)&vbox_client);
+ }
+ else
+ cmn_err(CE_WARN, "sfprov_connect: VbglR0SfSetUtf8() failed\n");
+
+ VbglR0SfDisconnect(&vbox_client);
+ }
+ else
+ cmn_err(CE_WARN, "sfprov_connect: VbglR0SfConnect() failed rc=%d\n", rc);
+ VbglR0SfTerm();
+ }
+ else
+ cmn_err(CE_WARN, "sfprov_connect: VbglR0SfInit() failed rc=%d\n", rc);
+ return (NULL);
+}
+
+void
+sfprov_disconnect(sfp_connection_t *conn)
+{
+ if (conn != (sfp_connection_t *)&vbox_client)
+ cmn_err(CE_WARN, "sfprov_disconnect: bad argument\n");
+ VbglR0SfDisconnect(&vbox_client);
+ VbglR0SfTerm();
+}
+
+
+int
+sfprov_mount(sfp_connection_t *conn, char *path, sfp_mount_t **mnt)
+{
+ sfp_mount_t *m;
+ SHFLSTRING *str;
+ int size;
+ int rc;
+
+ m = kmem_zalloc(sizeof (*m), KM_SLEEP);
+ str = sfprov_string(path, &size);
+ rc = VbglR0SfMapFolder(&vbox_client, str, &m->map);
+ if (RT_FAILURE(rc)) {
+ cmn_err(CE_WARN, "sfprov_mount: VbglR0SfMapFolder() failed. path=%s rc=%d\n", path, rc);
+ kmem_free(m, sizeof (*m));
+ *mnt = NULL;
+ rc = EINVAL;
+ } else {
+ *mnt = m;
+ rc = 0;
+ }
+ kmem_free(str, size);
+ return (rc);
+}
+
+int
+sfprov_unmount(sfp_mount_t *mnt)
+{
+ int rc;
+
+ rc = VbglR0SfUnmapFolder(&vbox_client, &mnt->map);
+ if (RT_FAILURE(rc)) {
+ cmn_err(CE_WARN, "sfprov_mount: VbglR0SfUnmapFolder() failed rc=%d\n", rc);
+ rc = EINVAL;
+ } else {
+ rc = 0;
+ }
+ kmem_free(mnt, sizeof (*mnt));
+ return (rc);
+}
+
+/*
+ * query information about a mounted file system
+ */
+int
+sfprov_get_fsinfo(sfp_mount_t *mnt, sffs_fsinfo_t *fsinfo)
+{
+ int rc;
+ SHFLVOLINFO info;
+ uint32_t bytes = sizeof(SHFLVOLINFO);
+
+ rc = VbglR0SfFsInfo(&vbox_client, &mnt->map, 0, SHFL_INFO_GET | SHFL_INFO_VOLUME,
+ &bytes, (SHFLDIRINFO *)&info);
+ if (RT_FAILURE(rc))
+ return (EINVAL);
+
+ fsinfo->blksize = info.ulBytesPerAllocationUnit;
+ fsinfo->blksused = (info.ullTotalAllocationBytes - info.ullAvailableAllocationBytes) / info.ulBytesPerAllocationUnit;
+ fsinfo->blksavail = info.ullAvailableAllocationBytes / info.ulBytesPerAllocationUnit;
+ fsinfo->maxnamesize = info.fsProperties.cbMaxComponent;
+ fsinfo->readonly = info.fsProperties.fReadOnly;
+ return (0);
+}
+
+/*
+ * file/directory information conversions.
+ */
+static void
+sfprov_fmode_from_mode(RTFMODE *fMode, mode_t mode)
+{
+ RTFMODE m = 0;
+
+#define mode_set(r) ((mode) & (S_##r)) ? RTFS_UNIX_##r : 0
+ m = mode_set (ISUID);
+ m |= mode_set (ISGID);
+ m |= (mode & S_ISVTX) ? RTFS_UNIX_ISTXT : 0;
+
+ m |= mode_set (IRUSR);
+ m |= mode_set (IWUSR);
+ m |= mode_set (IXUSR);
+
+ m |= mode_set (IRGRP);
+ m |= mode_set (IWGRP);
+ m |= mode_set (IXGRP);
+
+ m |= mode_set (IROTH);
+ m |= mode_set (IWOTH);
+ m |= mode_set (IXOTH);
+#undef mode_set
+
+ if (S_ISDIR(mode))
+ m |= RTFS_TYPE_DIRECTORY;
+ else if (S_ISREG(mode))
+ m |= RTFS_TYPE_FILE;
+ else if (S_ISFIFO(mode))
+ m |= RTFS_TYPE_FIFO;
+ else if (S_ISCHR(mode))
+ m |= RTFS_TYPE_DEV_CHAR;
+ else if (S_ISBLK(mode))
+ m |= RTFS_TYPE_DEV_BLOCK;
+ else if (S_ISLNK(mode))
+ m |= RTFS_TYPE_SYMLINK;
+ else if (S_ISSOCK(mode))
+ m |= RTFS_TYPE_SOCKET;
+ else
+ m |= RTFS_TYPE_FILE;
+
+ *fMode = m;
+}
+
+static void
+sfprov_mode_from_fmode(sfp_mount_t *mnt, mode_t *mode, RTFMODE fMode)
+{
+ mode_t m = 0;
+
+#define mode_set_from_rt(r) ((fMode) & (RTFS_UNIX_##r)) ? (S_##r) : 0;
+ m = mode_set_from_rt(ISUID);
+ m |= mode_set_from_rt(ISGID);
+ m |= (fMode & RTFS_UNIX_ISTXT) ? S_ISVTX : 0;
+
+ m |= mode_set_from_rt(IRUSR);
+ m |= mode_set_from_rt(IWUSR);
+ m |= mode_set_from_rt(IXUSR);
+
+ m |= mode_set_from_rt(IRGRP);
+ m |= mode_set_from_rt(IWGRP);
+ m |= mode_set_from_rt(IXGRP);
+
+ m |= mode_set_from_rt(IROTH);
+ m |= mode_set_from_rt(IWOTH);
+ m |= mode_set_from_rt(IXOTH);
+#undef mode_set_from_rt
+
+ if (RTFS_IS_DIRECTORY(fMode))
+ {
+ m = mnt->sf_dmode != ~0U ? (mnt->sf_dmode & PERMMASK) : m;
+ m &= ~mnt->sf_dmask;
+ m |= S_IFDIR;
+ }
+ else
+ {
+ m = mnt->sf_fmode != ~0U ? (mnt->sf_fmode & PERMMASK) : m;
+ m &= ~mnt->sf_fmask;
+
+ if (RTFS_IS_FILE(fMode))
+ m |= S_IFREG;
+ else if (RTFS_IS_SYMLINK(fMode))
+ m |= S_IFLNK;
+ else if (RTFS_IS_FIFO(fMode))
+ m |= S_IFIFO;
+ else if (RTFS_IS_DEV_CHAR(fMode))
+ m |= S_IFCHR;
+ else if (RTFS_IS_DEV_BLOCK(fMode))
+ m |= S_IFBLK;
+ else if (RTFS_IS_SOCKET(fMode))
+ m |= S_IFSOCK;
+ }
+
+ *mode = m;
+}
+
+static void
+sfprov_ftime_from_timespec(timestruc_t *time, RTTIMESPEC *ts)
+{
+ uint64_t nanosec = RTTimeSpecGetNano(ts);
+ time->tv_sec = nanosec / UINT64_C(1000000000);
+ time->tv_nsec = nanosec % UINT64_C(1000000000);
+}
+
+static void
+sfprov_stat_from_info(sfp_mount_t *mnt, sffs_stat_t *stat, SHFLFSOBJINFO *info)
+{
+ sfprov_mode_from_fmode(mnt, &stat->sf_mode, info->Attr.fMode);
+ stat->sf_size = info->cbObject;
+ stat->sf_alloc = info->cbAllocated;
+ sfprov_ftime_from_timespec(&stat->sf_atime, &info->AccessTime);
+ sfprov_ftime_from_timespec(&stat->sf_mtime, &info->ModificationTime);
+ sfprov_ftime_from_timespec(&stat->sf_ctime, &info->ChangeTime);
+}
+
+/*
+ * File operations: open/close/read/write/etc.
+ *
+ * open/create can return any relevant errno, however ENOENT
+ * generally means that the host file didn't exist.
+ */
+struct sfp_file {
+ SHFLHANDLE handle;
+ VBGLSFMAP map; /**< need this again for the close operation */
+};
+
+int
+sfprov_create(
+ sfp_mount_t *mnt,
+ char *path,
+ mode_t mode,
+ sfp_file_t **fp,
+ sffs_stat_t *stat)
+{
+
+ int rc;
+ SHFLCREATEPARMS parms;
+ SHFLSTRING *str;
+ int size;
+ sfp_file_t *newfp;
+
+ str = sfprov_string(path, &size);
+ parms.Handle = SHFL_HANDLE_NIL;
+ parms.Info.cbObject = 0;
+ sfprov_fmode_from_mode(&parms.Info.Attr.fMode, mode);
+ parms.CreateFlags = SHFL_CF_ACT_CREATE_IF_NEW |
+ SHFL_CF_ACT_REPLACE_IF_EXISTS | SHFL_CF_ACCESS_READWRITE;
+ rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms);
+ kmem_free(str, size);
+
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_ACCESS_DENIED && rc != VERR_WRITE_PROTECT)
+ cmn_err(CE_WARN, "sfprov_create: VbglR0SfCreate failed! path=%s rc=%d\n", path, rc);
+ return (sfprov_vbox2errno(rc));
+ }
+ if (parms.Handle == SHFL_HANDLE_NIL) {
+ if (parms.Result == SHFL_FILE_EXISTS)
+ return (EEXIST);
+ return (ENOENT);
+ }
+ newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP);
+ newfp->handle = parms.Handle;
+ newfp->map = mnt->map;
+ *fp = newfp;
+ sfprov_stat_from_info(mnt, stat, &parms.Info);
+ return (0);
+}
+
+int
+sfprov_diropen(sfp_mount_t *mnt, char *path, sfp_file_t **fp)
+{
+ int rc;
+ SHFLCREATEPARMS parms;
+ SHFLSTRING *str;
+ int size;
+ sfp_file_t *newfp;
+
+ bzero(&parms, sizeof(parms));
+ str = sfprov_string(path, &size);
+ parms.Handle = SHFL_HANDLE_NIL;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = SHFL_CF_DIRECTORY
+ | SHFL_CF_ACCESS_READ
+ | SHFL_CF_ACT_OPEN_IF_EXISTS
+ | SHFL_CF_ACT_FAIL_IF_NEW;
+
+ /*
+ * Open the host directory.
+ */
+ rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms);
+
+ /*
+ * Our VBoxFS interface here isn't very clear regarding failure and informational status.
+ * Check the file-handle as well as the return code to make sure the operation succeeded.
+ */
+ if (RT_FAILURE(rc)) {
+ kmem_free(str, size);
+ return (sfprov_vbox2errno(rc));
+ }
+
+ if (parms.Handle == SHFL_HANDLE_NIL) {
+ kmem_free(str, size);
+ return (ENOENT);
+ }
+
+ newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP);
+ newfp->handle = parms.Handle;
+ newfp->map = mnt->map;
+ *fp = newfp;
+ return (0);
+}
+
+int
+sfprov_open(sfp_mount_t *mnt, char *path, sfp_file_t **fp, int flag)
+{
+ int rc;
+ SHFLCREATEPARMS parms;
+ SHFLSTRING *str;
+ int size;
+ sfp_file_t *newfp;
+
+ bzero(&parms, sizeof(parms));
+ str = sfprov_string(path, &size);
+ parms.Handle = SHFL_HANDLE_NIL;
+ parms.Info.cbObject = 0;
+
+ /*
+ * Translate file modes.
+ */
+ if (flag & FCREAT) {
+ parms.CreateFlags |= SHFL_CF_ACT_CREATE_IF_NEW;
+ if (!(flag & FTRUNC))
+ parms.CreateFlags |= SHFL_CF_ACT_OPEN_IF_EXISTS;
+ }
+ else
+ parms.CreateFlags |= SHFL_CF_ACT_FAIL_IF_NEW;
+
+ if (flag & FTRUNC)
+ parms.CreateFlags |= SHFL_CF_ACT_OVERWRITE_IF_EXISTS | SHFL_CF_ACCESS_WRITE;
+ if (flag & FWRITE)
+ parms.CreateFlags |= SHFL_CF_ACCESS_WRITE;
+ if (flag & FREAD)
+ parms.CreateFlags |= SHFL_CF_ACCESS_READ;
+ if (flag & FAPPEND)
+ parms.CreateFlags |= SHFL_CF_ACCESS_APPEND;
+
+ /*
+ * Open/create the host file.
+ */
+ rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms);
+
+ /*
+ * Our VBoxFS interface here isn't very clear regarding failure and informational status.
+ * Check the file-handle as well as the return code to make sure the operation succeeded.
+ */
+ if (RT_FAILURE(rc)) {
+ kmem_free(str, size);
+ return (sfprov_vbox2errno(rc));
+ }
+
+ if (parms.Handle == SHFL_HANDLE_NIL) {
+ kmem_free(str, size);
+ return (ENOENT);
+ }
+
+ newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP);
+ newfp->handle = parms.Handle;
+ newfp->map = mnt->map;
+ *fp = newfp;
+ return (0);
+}
+
+int
+sfprov_close(sfp_file_t *fp)
+{
+ int rc;
+
+ rc = VbglR0SfClose(&vbox_client, &fp->map, fp->handle);
+ kmem_free(fp, sizeof(sfp_file_t));
+ return (0);
+}
+
+int
+sfprov_read(sfp_file_t *fp, char *buffer, uint64_t offset, uint32_t *numbytes)
+{
+ int rc;
+
+ rc = VbglR0SfRead(&vbox_client, &fp->map, fp->handle, offset,
+ numbytes, (uint8_t *)buffer, 0 /*fLocked*/);
+ if (RT_FAILURE(rc))
+ return (EINVAL);
+ return (0);
+}
+
+int
+sfprov_write(sfp_file_t *fp, char *buffer, uint64_t offset, uint32_t *numbytes)
+{
+ int rc;
+
+ rc = VbglR0SfWrite(&vbox_client, &fp->map, fp->handle, offset,
+ numbytes, (uint8_t *)buffer, 0 /*fLocked*/);
+ if (RT_FAILURE(rc))
+ return (EINVAL);
+ return (0);
+}
+
+int
+sfprov_fsync(sfp_file_t *fp)
+{
+ int rc;
+
+ rc = VbglR0SfFlush(&vbox_client, &fp->map, fp->handle);
+ if (RT_FAILURE(rc))
+ return (EIO);
+ return (0);
+}
+
+
+static int
+sfprov_getinfo(sfp_mount_t *mnt, char *path, PSHFLFSOBJINFO info)
+{
+ int rc;
+ SHFLCREATEPARMS parms;
+ SHFLSTRING *str;
+ int size;
+
+ str = sfprov_string(path, &size);
+ parms.Handle = 0;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = SHFL_CF_LOOKUP | SHFL_CF_ACT_FAIL_IF_NEW;
+ rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms);
+ kmem_free(str, size);
+
+ if (RT_FAILURE(rc))
+ return (EINVAL);
+ if (parms.Result != SHFL_FILE_EXISTS)
+ return (ENOENT);
+ *info = parms.Info;
+ return (0);
+}
+
+/*
+ * get information about a file (or directory)
+ */
+int
+sfprov_get_mode(sfp_mount_t *mnt, char *path, mode_t *mode)
+{
+ int rc;
+ SHFLFSOBJINFO info;
+
+ rc = sfprov_getinfo(mnt, path, &info);
+ if (rc)
+ return (rc);
+ sfprov_mode_from_fmode(mnt, mode, info.Attr.fMode);
+ return (0);
+}
+
+int
+sfprov_get_size(sfp_mount_t *mnt, char *path, uint64_t *size)
+{
+ int rc;
+ SHFLFSOBJINFO info;
+
+ rc = sfprov_getinfo(mnt, path, &info);
+ if (rc)
+ return (rc);
+ *size = info.cbObject;
+ return (0);
+}
+
+
+int
+sfprov_get_atime(sfp_mount_t *mnt, char *path, timestruc_t *time)
+{
+ int rc;
+ SHFLFSOBJINFO info;
+
+ rc = sfprov_getinfo(mnt, path, &info);
+ if (rc)
+ return (rc);
+ sfprov_ftime_from_timespec(time, &info.AccessTime);
+ return (0);
+}
+
+int
+sfprov_get_mtime(sfp_mount_t *mnt, char *path, timestruc_t *time)
+{
+ int rc;
+ SHFLFSOBJINFO info;
+
+ rc = sfprov_getinfo(mnt, path, &info);
+ if (rc)
+ return (rc);
+ sfprov_ftime_from_timespec(time, &info.ModificationTime);
+ return (0);
+}
+
+int
+sfprov_get_ctime(sfp_mount_t *mnt, char *path, timestruc_t *time)
+{
+ int rc;
+ SHFLFSOBJINFO info;
+
+ rc = sfprov_getinfo(mnt, path, &info);
+ if (rc)
+ return (rc);
+ sfprov_ftime_from_timespec(time, &info.ChangeTime);
+ return (0);
+}
+
+int
+sfprov_get_attr(sfp_mount_t *mnt, char *path, sffs_stat_t *attr)
+{
+ int rc;
+ SHFLFSOBJINFO info;
+
+ rc = sfprov_getinfo(mnt, path, &info);
+ if (rc)
+ return (rc);
+ sfprov_stat_from_info(mnt, attr, &info);
+ return (0);
+}
+
+static void
+sfprov_timespec_from_ftime(RTTIMESPEC *ts, timestruc_t time)
+{
+ uint64_t nanosec = UINT64_C(1000000000) * time.tv_sec + time.tv_nsec;
+ RTTimeSpecSetNano(ts, nanosec);
+}
+
+int
+sfprov_set_attr(
+ sfp_mount_t *mnt,
+ char *path,
+ uint_t mask,
+ mode_t mode,
+ timestruc_t atime,
+ timestruc_t mtime,
+ timestruc_t ctime)
+{
+ int rc, err;
+ SHFLCREATEPARMS parms;
+ SHFLSTRING *str;
+ SHFLFSOBJINFO info;
+ uint32_t bytes;
+ int str_size;
+
+ str = sfprov_string(path, &str_size);
+ parms.Handle = 0;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = SHFL_CF_ACT_OPEN_IF_EXISTS
+ | SHFL_CF_ACT_FAIL_IF_NEW
+ | SHFL_CF_ACCESS_ATTR_WRITE;
+
+ rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms);
+
+ if (RT_FAILURE(rc)) {
+ cmn_err(CE_WARN, "sfprov_set_attr: VbglR0SfCreate(%s) failed rc=%d\n",
+ path, rc);
+ err = EINVAL;
+ goto fail2;
+ }
+ if (parms.Result != SHFL_FILE_EXISTS) {
+ err = ENOENT;
+ goto fail1;
+ }
+
+ RT_ZERO(info);
+ if (mask & AT_MODE)
+ sfprov_fmode_from_mode(&info.Attr.fMode, mode);
+ if (mask & AT_ATIME)
+ sfprov_timespec_from_ftime(&info.AccessTime, atime);
+ if (mask & AT_MTIME)
+ sfprov_timespec_from_ftime(&info.ModificationTime, mtime);
+ if (mask & AT_CTIME)
+ sfprov_timespec_from_ftime(&info.ChangeTime, ctime);
+
+ bytes = sizeof(info);
+ rc = VbglR0SfFsInfo(&vbox_client, &mnt->map, parms.Handle, SHFL_INFO_SET | SHFL_INFO_FILE,
+ &bytes, (SHFLDIRINFO *)&info);
+ if (RT_FAILURE(rc)) {
+ if (rc != VERR_ACCESS_DENIED && rc != VERR_WRITE_PROTECT)
+ {
+ cmn_err(CE_WARN, "sfprov_set_attr: VbglR0SfFsInfo(%s, FILE) failed rc=%d\n",
+ path, rc);
+ }
+ err = sfprov_vbox2errno(rc);
+ goto fail1;
+ }
+
+ err = 0;
+
+fail1:
+ rc = VbglR0SfClose(&vbox_client, &mnt->map, parms.Handle);
+ if (RT_FAILURE(rc)) {
+ cmn_err(CE_WARN, "sfprov_set_attr: VbglR0SfClose(%s) failed rc=%d\n",
+ path, rc);
+ }
+fail2:
+ kmem_free(str, str_size);
+ return err;
+}
+
+int
+sfprov_set_size(sfp_mount_t *mnt, char *path, uint64_t size)
+{
+ int rc, err;
+ SHFLCREATEPARMS parms;
+ SHFLSTRING *str;
+ SHFLFSOBJINFO info;
+ uint32_t bytes;
+ int str_size;
+
+ str = sfprov_string(path, &str_size);
+ parms.Handle = 0;
+ parms.Info.cbObject = 0;
+ parms.CreateFlags = SHFL_CF_ACT_OPEN_IF_EXISTS
+ | SHFL_CF_ACT_FAIL_IF_NEW
+ | SHFL_CF_ACCESS_WRITE;
+
+ rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms);
+
+ if (RT_FAILURE(rc)) {
+ cmn_err(CE_WARN, "sfprov_set_size: VbglR0SfCreate(%s) failed rc=%d\n",
+ path, rc);
+ err = EINVAL;
+ goto fail2;
+ }
+ if (parms.Result != SHFL_FILE_EXISTS) {
+ err = ENOENT;
+ goto fail1;
+ }
+
+ RT_ZERO(info);
+ info.cbObject = size;
+ bytes = sizeof(info);
+ rc = VbglR0SfFsInfo(&vbox_client, &mnt->map, parms.Handle, SHFL_INFO_SET | SHFL_INFO_SIZE,
+ &bytes, (SHFLDIRINFO *)&info);
+ if (RT_FAILURE(rc)) {
+ cmn_err(CE_WARN, "sfprov_set_size: VbglR0SfFsInfo(%s, SIZE) failed rc=%d\n",
+ path, rc);
+ err = sfprov_vbox2errno(rc);
+ goto fail1;
+ }
+
+ err = 0;
+
+fail1:
+ rc = VbglR0SfClose(&vbox_client, &mnt->map, parms.Handle);
+ if (RT_FAILURE(rc)) {
+ cmn_err(CE_WARN, "sfprov_set_size: VbglR0SfClose(%s) failed rc=%d\n",
+ path, rc);
+ }
+fail2:
+ kmem_free(str, str_size);
+ return err;
+}
+
+/*
+ * Directory operations
+ */
+int
+sfprov_mkdir(
+ sfp_mount_t *mnt,
+ char *path,
+ mode_t mode,
+ sfp_file_t **fp,
+ sffs_stat_t *stat)
+{
+ int rc;
+ SHFLCREATEPARMS parms;
+ SHFLSTRING *str;
+ int size;
+ sfp_file_t *newfp;
+
+ str = sfprov_string(path, &size);
+ parms.Handle = SHFL_HANDLE_NIL;
+ parms.Info.cbObject = 0;
+ sfprov_fmode_from_mode(&parms.Info.Attr.fMode, mode);
+ parms.CreateFlags = SHFL_CF_DIRECTORY | SHFL_CF_ACT_CREATE_IF_NEW |
+ SHFL_CF_ACT_FAIL_IF_EXISTS | SHFL_CF_ACCESS_READ;
+ rc = VbglR0SfCreate(&vbox_client, &mnt->map, str, &parms);
+ kmem_free(str, size);
+
+ if (RT_FAILURE(rc))
+ return (sfprov_vbox2errno(rc));
+ if (parms.Handle == SHFL_HANDLE_NIL) {
+ if (parms.Result == SHFL_FILE_EXISTS)
+ return (EEXIST);
+ return (ENOENT);
+ }
+ newfp = kmem_alloc(sizeof(sfp_file_t), KM_SLEEP);
+ newfp->handle = parms.Handle;
+ newfp->map = mnt->map;
+ *fp = newfp;
+ sfprov_stat_from_info(mnt, stat, &parms.Info);
+ return (0);
+}
+
+int
+sfprov_set_show_symlinks(void)
+{
+ int rc;
+
+ rc = VbglR0SfSetSymlinks(&vbox_client);
+ if (RT_FAILURE(rc))
+ return (sfprov_vbox2errno(rc));
+
+ return (0);
+}
+
+int
+sfprov_remove(sfp_mount_t *mnt, char *path, uint_t is_link)
+{
+ int rc;
+ SHFLSTRING *str;
+ int size;
+
+ str = sfprov_string(path, &size);
+ rc = VbglR0SfRemove(&vbox_client, &mnt->map, str,
+ SHFL_REMOVE_FILE | (is_link ? SHFL_REMOVE_SYMLINK : 0));
+ kmem_free(str, size);
+ if (RT_FAILURE(rc))
+ return (sfprov_vbox2errno(rc));
+ return (0);
+}
+
+int
+sfprov_readlink(
+ sfp_mount_t *mnt,
+ char *path,
+ char *target,
+ size_t tgt_size)
+{
+ int rc;
+ SHFLSTRING *str;
+ int size;
+
+ str = sfprov_string(path, &size);
+
+ rc = VbglR0SfReadLink(&vbox_client, &mnt->map, str, (uint32_t) tgt_size,
+ target);
+ if (RT_FAILURE(rc))
+ rc = sfprov_vbox2errno(rc);
+
+ kmem_free(str, size);
+ return (rc);
+}
+
+int
+sfprov_symlink(
+ sfp_mount_t *mnt,
+ char *linkname,
+ char *target,
+ sffs_stat_t *stat)
+{
+ int rc;
+ SHFLSTRING *lnk, *tgt;
+ int lnk_size, tgt_size;
+ SHFLFSOBJINFO info;
+
+ lnk = sfprov_string(linkname, &lnk_size);
+ tgt = sfprov_string(target, &tgt_size);
+
+ rc = VbglR0SfSymlink(&vbox_client, &mnt->map, lnk, tgt, &info);
+ if (RT_FAILURE(rc)) {
+ rc = sfprov_vbox2errno(rc);
+ goto done;
+ }
+
+ if (stat != NULL)
+ sfprov_stat_from_info(mnt, stat, &info);
+
+done:
+ kmem_free(lnk, lnk_size);
+ kmem_free(tgt, tgt_size);
+
+ return (rc);
+}
+
+int
+sfprov_rmdir(sfp_mount_t *mnt, char *path)
+{
+ int rc;
+ SHFLSTRING *str;
+ int size;
+
+ str = sfprov_string(path, &size);
+ rc = VbglR0SfRemove(&vbox_client, &mnt->map, str, SHFL_REMOVE_DIR);
+ kmem_free(str, size);
+ if (RT_FAILURE(rc))
+ return (sfprov_vbox2errno(rc));
+ return (0);
+}
+
+int
+sfprov_rename(sfp_mount_t *mnt, char *from, char *to, uint_t is_dir)
+{
+ int rc;
+ SHFLSTRING *old, *new;
+ int old_size, new_size;
+
+ old = sfprov_string(from, &old_size);
+ new = sfprov_string(to, &new_size);
+ rc = VbglR0SfRename(&vbox_client, &mnt->map, old, new,
+ (is_dir ? SHFL_RENAME_DIR : SHFL_RENAME_FILE) | SHFL_RENAME_REPLACE_IF_EXISTS);
+ kmem_free(old, old_size);
+ kmem_free(new, new_size);
+ if (RT_FAILURE(rc))
+ return (sfprov_vbox2errno(rc));
+ return (0);
+}
+
+
+/*
+ * Read all filenames in a directory.
+ *
+ * - success - all entries read and returned
+ * - ENOENT - Couldn't open the directory for reading
+ * - EINVAL - Internal error of some kind
+ *
+ * On successful return, *dirents points to a list of sffs_dirents_t;
+ * for each dirent, all fields except the d_ino will be set appropriately.
+ * The caller is responsible for freeing the dirents buffer.
+ */
+int
+sfprov_readdir(
+ sfp_mount_t *mnt,
+ char *path,
+ sffs_dirents_t **dirents,
+ int flag)
+{
+ int error;
+ char *cp;
+ int len;
+ SHFLSTRING *mask_str = NULL; /* must be path with "/ *" appended */
+ int mask_size;
+ sfp_file_t *fp;
+ uint32_t infobuff_alloc = 16384;
+ SHFLDIRINFO *infobuff = NULL, *info;
+ uint32_t numbytes;
+ uint32_t nents;
+ uint32_t size;
+ off_t offset;
+ sffs_dirents_t *cur_buf;
+ struct sffs_dirent *dirent;
+ unsigned short reclen;
+ unsigned short entlen;
+
+ *dirents = NULL;
+
+ error = sfprov_diropen(mnt, path, &fp);
+ if (error != 0)
+ return (ENOENT);
+
+ /*
+ * Allocate the first dirents buffers.
+ */
+ *dirents = kmem_alloc(SFFS_DIRENTS_SIZE, KM_SLEEP);
+ if (*dirents == NULL) {
+ error = (ENOSPC);
+ goto done;
+ }
+ cur_buf = *dirents;
+ cur_buf->sf_next = NULL;
+ cur_buf->sf_len = 0;
+
+ /*
+ * Create mask that VBox expects. This needs to be the directory path,
+ * plus a "*" wildcard to get all files.
+ */
+ len = strlen(path) + 3;
+ cp = kmem_alloc(len, KM_SLEEP);
+ if (cp == NULL) {
+ error = (ENOSPC);
+ goto done;
+ }
+ strcpy(cp, path);
+ strcat(cp, "/*");
+ mask_str = sfprov_string(cp, &mask_size);
+ kmem_free(cp, len);
+
+ /*
+ * Now loop using VbglR0SfDirInfo
+ */
+ infobuff = kmem_alloc(infobuff_alloc, KM_SLEEP);
+ if (infobuff == NULL) {
+ error = (ENOSPC);
+ goto done;
+ }
+
+ offset = 0;
+ for (;;) {
+ numbytes = infobuff_alloc;
+ error = VbglR0SfDirInfo(&vbox_client, &fp->map, fp->handle,
+ mask_str, 0, 0, &numbytes, infobuff, &nents);
+ switch (error) {
+
+ case VINF_SUCCESS:
+ /* fallthrough */
+ case VERR_NO_MORE_FILES:
+ break;
+
+ case VERR_NO_TRANSLATION:
+ /* XXX ??? */
+ break;
+
+ default:
+ error = sfprov_vbox2errno(error);
+ goto done;
+ }
+
+ /*
+ * Create the dirent_t's and save the stats for each name
+ */
+ for (info = infobuff; (char *) info < (char *) infobuff + numbytes; nents--) {
+ /* expand buffers if we need more space */
+ reclen = DIRENT64_RECLEN(strlen(info->name.String.utf8));
+ entlen = sizeof(sffs_stat_t) + reclen;
+ if (SFFS_DIRENTS_OFF + cur_buf->sf_len + entlen > SFFS_DIRENTS_SIZE) {
+ cur_buf->sf_next = kmem_alloc(SFFS_DIRENTS_SIZE, KM_SLEEP);
+ if (cur_buf->sf_next == NULL) {
+ error = ENOSPC;
+ goto done;
+ }
+ cur_buf = cur_buf->sf_next;
+ cur_buf->sf_next = NULL;
+ cur_buf->sf_len = 0;
+ }
+
+ /* create the dirent with the name, offset, and len */
+ dirent = (struct sffs_dirent *)
+ (((char *) &cur_buf->sf_entries[0]) + cur_buf->sf_len);
+ strncpy(&dirent->sf_entry.d_name[0], info->name.String.utf8, DIRENT64_NAMELEN(reclen));
+ dirent->sf_entry.d_reclen = reclen;
+ offset += entlen;
+ dirent->sf_entry.d_off = offset;
+
+ /* save the stats */
+ sfprov_stat_from_info(mnt, &dirent->sf_stat, &info->Info);
+
+ /* next info */
+ cur_buf->sf_len += entlen;
+ size = offsetof (SHFLDIRINFO, name.String) + info->name.u16Size;
+ info = (SHFLDIRINFO *) ((uintptr_t) info + size);
+ }
+ ASSERT(nents == 0);
+ ASSERT((char *) info == (char *) infobuff + numbytes);
+
+ if (error == VERR_NO_MORE_FILES)
+ break;
+ }
+ error = 0;
+
+done:
+ if (error != 0) {
+ while (*dirents) {
+ cur_buf = (*dirents)->sf_next;
+ kmem_free(*dirents, SFFS_DIRENTS_SIZE);
+ *dirents = cur_buf;
+ }
+ }
+ if (infobuff != NULL)
+ kmem_free(infobuff, infobuff_alloc);
+ if (mask_str != NULL)
+ kmem_free(mask_str, mask_size);
+ sfprov_close(fp);
+ return (error);
+}
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h
new file mode 100644
index 00000000..b7bada48
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_prov.h
@@ -0,0 +1,200 @@
+/* $Id: vboxfs_prov.h $ */
+/** @file
+ * VirtualBox File System for Solaris Guests, provider header.
+ * Portions contributed by: Ronald.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_prov_h
+#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_prov_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/VBoxGuestLibSharedFolders.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*
+ * These are the provider interfaces used by sffs to access the underlying
+ * shared file system.
+ */
+#define SFPROV_VERSION 1
+
+/*
+ * Initialization and termination.
+ * sfprov_connect() is called once before any other interfaces and returns
+ * a handle used in further calls. The argument should be SFPROV_VERSION
+ * from above. On failure it returns a NULL pointer.
+ *
+ * sfprov_disconnect() must only be called after all sf file systems have been
+ * unmounted.
+ */
+typedef struct sfp_connection sfp_connection_t;
+
+extern sfp_connection_t *sfprov_connect(int);
+extern void sfprov_disconnect(sfp_connection_t *);
+
+
+/*
+ * Mount / Unmount a shared folder.
+ *
+ * sfprov_mount() takes as input the connection pointer and the name of
+ * the shared folder. On success, it returns zero and supplies an
+ * sfp_mount_t handle. On failure it returns any relevant errno value.
+ *
+ * sfprov_unmount() unmounts the mounted file system. It returns 0 on
+ * success and any relevant errno on failure.
+ *
+ * spf_mount_t is the representation of an active mount point.
+ */
+typedef struct spf_mount_t {
+ VBGLSFMAP map; /**< guest<->host mapping */
+ uid_t sf_uid; /**< owner of the mount point */
+ gid_t sf_gid; /**< group of the mount point */
+ mode_t sf_dmode; /**< mode of all directories if != ~0U */
+ mode_t sf_fmode; /**< mode of all files if != ~0U */
+ mode_t sf_dmask; /**< mask of all directories */
+ mode_t sf_fmask; /**< mask of all files */
+} sfp_mount_t;
+
+extern int sfprov_mount(sfp_connection_t *, char *, sfp_mount_t **);
+extern int sfprov_unmount(sfp_mount_t *);
+
+/*
+ * query information about a mounted file system
+ */
+typedef struct sffs_fsinfo {
+ uint64_t blksize;
+ uint64_t blksused;
+ uint64_t blksavail;
+ uint32_t maxnamesize;
+ uint32_t readonly;
+} sffs_fsinfo_t;
+
+extern int sfprov_get_fsinfo(sfp_mount_t *, sffs_fsinfo_t *);
+
+/*
+ * File operations: open/close/read/write/etc.
+ *
+ * open/create can return any relevant errno, however ENOENT
+ * generally means that the host file didn't exist.
+ */
+typedef struct sffs_stat {
+ mode_t sf_mode;
+ off_t sf_size;
+ off_t sf_alloc;
+ timestruc_t sf_atime;
+ timestruc_t sf_mtime;
+ timestruc_t sf_ctime;
+} sffs_stat_t;
+
+typedef struct sfp_file sfp_file_t;
+
+extern int sfprov_create(sfp_mount_t *, char *path, mode_t mode,
+ sfp_file_t **fp, sffs_stat_t *stat);
+extern int sfprov_diropen(sfp_mount_t *mnt, char *path, sfp_file_t **fp);
+extern int sfprov_open(sfp_mount_t *, char *path, sfp_file_t **fp, int flag);
+extern int sfprov_close(sfp_file_t *fp);
+extern int sfprov_read(sfp_file_t *, char * buffer, uint64_t offset,
+ uint32_t *numbytes);
+extern int sfprov_write(sfp_file_t *, char * buffer, uint64_t offset,
+ uint32_t *numbytes);
+extern int sfprov_fsync(sfp_file_t *fp);
+
+
+/*
+ * get/set information about a file (or directory) using pathname
+ */
+extern int sfprov_get_mode(sfp_mount_t *, char *, mode_t *);
+extern int sfprov_get_size(sfp_mount_t *, char *, uint64_t *);
+extern int sfprov_get_atime(sfp_mount_t *, char *, timestruc_t *);
+extern int sfprov_get_mtime(sfp_mount_t *, char *, timestruc_t *);
+extern int sfprov_get_ctime(sfp_mount_t *, char *, timestruc_t *);
+extern int sfprov_get_attr(sfp_mount_t *, char *, sffs_stat_t *);
+extern int sfprov_set_attr(sfp_mount_t *, char *, uint_t, mode_t,
+ timestruc_t, timestruc_t, timestruc_t);
+extern int sfprov_set_size(sfp_mount_t *, char *, uint64_t);
+
+
+/*
+ * File/Directory operations
+ */
+extern int sfprov_remove(sfp_mount_t *, char *path, uint_t is_link);
+extern int sfprov_mkdir(sfp_mount_t *, char *path, mode_t mode,
+ sfp_file_t **fp, sffs_stat_t *stat);
+extern int sfprov_rmdir(sfp_mount_t *, char *path);
+extern int sfprov_rename(sfp_mount_t *, char *from, char *to, uint_t is_dir);
+
+
+/*
+ * Symbolic link operations
+ */
+extern int sfprov_set_show_symlinks(void);
+extern int sfprov_readlink(sfp_mount_t *, char *path, char *target,
+ size_t tgt_size);
+extern int sfprov_symlink(sfp_mount_t *, char *linkname, char *target,
+ sffs_stat_t *stat);
+
+
+/*
+ * Read directory entries.
+ */
+/*
+ * a singly linked list of buffers, each containing an array of stat's+dirent's.
+ * sf_len is length of the sf_entries array, in bytes.
+ */
+typedef struct sffs_dirents {
+ struct sffs_dirents *sf_next;
+ len_t sf_len;
+ struct sffs_dirent {
+ sffs_stat_t sf_stat;
+ dirent64_t sf_entry; /* this is variable length */
+ } sf_entries[1];
+} sffs_dirents_t;
+
+#define SFFS_DIRENTS_SIZE 8192
+#define SFFS_DIRENTS_OFF (offsetof(sffs_dirents_t, sf_entries[0]))
+
+extern int sfprov_readdir(sfp_mount_t *mnt, char *path,
+ sffs_dirents_t **dirents, int flag);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_prov_h */
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c
new file mode 100644
index 00000000..2f2d9d17
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.c
@@ -0,0 +1,641 @@
+/* $Id: vboxfs_vfs.c $ */
+/** @file
+ * VirtualBox File System for Solaris Guests, VFS implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <VBox/log.h>
+#include <VBox/version.h>
+#include <iprt/dbg.h>
+
+#include <sys/types.h>
+#include <sys/mntent.h>
+#include <sys/param.h>
+#include <sys/modctl.h>
+#include <sys/mount.h>
+#include <sys/policy.h>
+#include <sys/atomic.h>
+#include <sys/sysmacros.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/vfs.h>
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+# include <sys/vfs_opreg.h>
+#endif
+#include <sys/pathname.h>
+#include <sys/cmn_err.h>
+#include <sys/vmsystm.h>
+#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
+
+#include "vboxfs_prov.h"
+#include "vboxfs_vnode.h"
+#include "vboxfs_vfs.h"
+#include "vboxfs.h"
+
+
+#define VBOXSOLQUOTE2(x) #x
+#define VBOXSOLQUOTE(x) VBOXSOLQUOTE2(x)
+/** The module name. */
+#define DEVICE_NAME "vboxfs"
+/** The module description as seen in 'modinfo'. */
+#define DEVICE_DESC "VirtualBox ShrdFS"
+
+
+/*
+ * Shared Folders filesystem implementation of the Solaris VFS interfaces.
+ * Much of this is cookie cutter code for Solaris filesystem implementation.
+ */
+
+/* forward declarations */
+static int sffs_init(int fstype, char *name);
+static int sffs_mount(vfs_t *, vnode_t *, struct mounta *, cred_t *);
+static int sffs_unmount(vfs_t *vfsp, int flag, cred_t *cr);
+static int sffs_root(vfs_t *vfsp, vnode_t **vpp);
+static int sffs_statvfs(vfs_t *vfsp, statvfs64_t *sbp);
+
+static mntopt_t sffs_options[] = {
+ /* Option Cancels Opt Arg Flags Data */
+ {"uid", NULL, NULL, MO_HASVALUE, NULL},
+ {"gid", NULL, NULL, MO_HASVALUE, NULL},
+ {"dmode", NULL, NULL, MO_HASVALUE, NULL},
+ {"fmode", NULL, NULL, MO_HASVALUE, NULL},
+ {"dmask", NULL, NULL, MO_HASVALUE, NULL},
+ {"fmask", NULL, NULL, MO_HASVALUE, NULL},
+ {"stat_ttl", NULL, NULL, MO_HASVALUE, NULL},
+ {"fsync", NULL, NULL, 0, NULL},
+ {"tag", NULL, NULL, MO_HASVALUE, NULL}
+};
+
+static mntopts_t sffs_options_table = {
+ sizeof (sffs_options) / sizeof (mntopt_t),
+ sffs_options
+};
+
+static vfsdef_t sffs_vfsdef = {
+ VFSDEF_VERSION,
+ DEVICE_NAME,
+ sffs_init,
+ VSW_HASPROTO,
+ &sffs_options_table
+};
+
+static int sffs_fstype;
+static int sffs_major; /* major number for device */
+
+kmutex_t sffs_minor_lock;
+int sffs_minor; /* minor number for device */
+
+/** Whether to use the old-style map_addr()/choose_addr() routines. */
+bool g_fVBoxVFS_SolOldAddrMap;
+/** The map_addr()/choose_addr() hooks callout table structure. */
+VBoxVFS_SolAddrMap g_VBoxVFS_SolAddrMap;
+
+/*
+ * Module linkage information
+ */
+static struct modlfs modlfs = {
+ &mod_fsops,
+ DEVICE_DESC " " VBOX_VERSION_STRING "r" VBOXSOLQUOTE(VBOX_SVN_REV),
+ &sffs_vfsdef
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, &modlfs, NULL
+};
+
+static sfp_connection_t *sfprov = NULL;
+
+int
+_init()
+{
+ RTDBGKRNLINFO hKrnlDbgInfo;
+ int rc = RTR0DbgKrnlInfoOpen(&hKrnlDbgInfo, 0 /* fFlags */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTR0DbgKrnlInfoQuerySymbol(hKrnlDbgInfo, NULL /* pszModule */, "plat_map_align_amount", NULL /* ppvSymbol */);
+ if (RT_SUCCESS(rc))
+ {
+#if defined(VBOX_VFS_SOLARIS_10U6)
+ g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr = (void *)map_addr;
+#else
+ g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr = (void *)choose_addr;
+#endif
+ }
+ else
+ {
+ g_fVBoxVFS_SolOldAddrMap = true;
+#if defined(VBOX_VFS_SOLARIS_10U6)
+ g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr_old = (void *)map_addr;
+#else
+ g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr_old = (void *)choose_addr;
+#endif
+ }
+
+ RTR0DbgKrnlInfoRelease(hKrnlDbgInfo);
+ }
+ else
+ {
+ cmn_err(CE_NOTE, "RTR0DbgKrnlInfoOpen failed. rc=%d\n", rc);
+ return rc;
+ }
+
+ return (mod_install(&modlinkage));
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+
+int
+_fini()
+{
+ int error;
+
+ error = mod_remove(&modlinkage);
+ if (error)
+ return (error);
+
+ /*
+ * Tear down the operations vectors
+ */
+ sffs_vnode_fini();
+ (void) vfs_freevfsops_by_type(sffs_fstype);
+
+ /*
+ * close connection to the provider
+ */
+ sfprov_disconnect(sfprov);
+ return (0);
+}
+
+
+static int
+sffs_init(int fstype, char *name)
+{
+#if defined(VBOX_VFS_SOLARIS_10U6)
+ static const fs_operation_def_t sffs_vfsops_template[] = {
+ VFSNAME_MOUNT, sffs_mount,
+ VFSNAME_UNMOUNT, sffs_unmount,
+ VFSNAME_ROOT, sffs_root,
+ VFSNAME_STATVFS, sffs_statvfs,
+ NULL, NULL
+ };
+#else
+ static const fs_operation_def_t sffs_vfsops_template[] = {
+ VFSNAME_MOUNT, { .vfs_mount = sffs_mount },
+ VFSNAME_UNMOUNT, { .vfs_unmount = sffs_unmount },
+ VFSNAME_ROOT, { .vfs_root = sffs_root },
+ VFSNAME_STATVFS, { .vfs_statvfs = sffs_statvfs },
+ NULL, NULL
+ };
+#endif
+ int error;
+
+ ASSERT(fstype != 0);
+ sffs_fstype = fstype;
+ LogFlowFunc(("sffs_init() name=%s\n", name));
+
+ /*
+ * This may seem a silly way to do things for now. But the code
+ * is structured to easily allow it to be used on other hypervisors
+ * which would have a different implementation of the provider.
+ * Hopefully that'll never happen. :)
+ */
+ sfprov = sfprov_connect(SFPROV_VERSION);
+ if (sfprov == NULL) {
+ cmn_err(CE_WARN, "sffs_init: couldn't init sffs provider");
+ return (ENODEV);
+ }
+
+ error = sfprov_set_show_symlinks();
+ if (error != 0) {
+ cmn_err(CE_WARN, "sffs_init: host unable to show symlinks, "
+ "rc=%d\n", error);
+ }
+
+ error = vfs_setfsops(fstype, sffs_vfsops_template, NULL);
+ if (error != 0) {
+ cmn_err(CE_WARN, "sffs_init: bad vfs ops template");
+ return (error);
+ }
+
+ error = sffs_vnode_init();
+ if (error != 0) {
+ (void) vfs_freevfsops_by_type(fstype);
+ cmn_err(CE_WARN, "sffs_init: bad vnode ops template");
+ return (error);
+ }
+
+ if ((sffs_major = getudev()) == (major_t)-1) {
+ cmn_err(CE_WARN, "sffs_init: Can't get unique device number.");
+ sffs_major = 0;
+ }
+ mutex_init(&sffs_minor_lock, NULL, MUTEX_DEFAULT, NULL);
+ return (0);
+}
+
+/*
+ * wrapper for pn_get
+ */
+static int
+sf_pn_get(char *rawpath, struct mounta *uap, char **outpath)
+{
+ pathname_t path;
+ int error;
+
+ error = pn_get(rawpath, (uap->flags & MS_SYSSPACE) ? UIO_SYSSPACE :
+ UIO_USERSPACE, &path);
+ if (error) {
+ LogFlowFunc(("pn_get(%s) failed\n", rawpath));
+ return (error);
+ }
+ *outpath = kmem_alloc(path.pn_pathlen + 1, KM_SLEEP);
+ strcpy(*outpath, path.pn_path);
+ pn_free(&path);
+ return (0);
+}
+
+#ifdef DEBUG_ramshankar
+static void
+sffs_print(sffs_data_t *sffs)
+{
+ cmn_err(CE_NOTE, "sffs_data_t at 0x%p\n", sffs);
+ cmn_err(CE_NOTE, " vfs_t *sf_vfsp = 0x%p\n", sffs->sf_vfsp);
+ cmn_err(CE_NOTE, " vnode_t *sf_rootnode = 0x%p\n", sffs->sf_rootnode);
+ cmn_err(CE_NOTE, " uid_t sf_uid = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_uid);
+ cmn_err(CE_NOTE, " gid_t sf_gid = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_gid);
+ cmn_err(CE_NOTE, " mode_t sf_dmode = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_dmode);
+ cmn_err(CE_NOTE, " mode_t sf_fmode = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_fmode);
+ cmn_err(CE_NOTE, " mode_t sf_dmask = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_dmask);
+ cmn_err(CE_NOTE, " mode_t sf_fmask = 0x%lu\n", (ulong_t)sffs->sf_handle->sf_fmask);
+ cmn_err(CE_NOTE, " char *sf_share_name = %s\n", sffs->sf_share_name);
+ cmn_err(CE_NOTE, " char *sf_mntpath = %s\n", sffs->sf_mntpath);
+ cmn_err(CE_NOTE, " sfp_mount_t *sf_handle = 0x%p\n", sffs->sf_handle);
+}
+#endif
+
+static int
+sffs_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr)
+{
+ sffs_data_t *sffs;
+ char *mount_point = NULL;
+ char *share_name = NULL;
+ int error;
+ dev_t dev;
+ uid_t uid = 0;
+ gid_t gid = 0;
+ mode_t dmode = ~0U;
+ mode_t fmode = ~0U;
+ mode_t dmask = 0;
+ mode_t fmask = 0;
+ int stat_ttl = DEF_STAT_TTL_MS;
+ int fsync = 0;
+ char *optval;
+ long val;
+ char *path;
+ sfp_mount_t *handle;
+ sfnode_t *sfnode;
+
+ /*
+ * check we have permission to do the mount
+ */
+ LogFlowFunc(("sffs_mount() started\n"));
+ error = secpolicy_fs_mount(cr, mvp, vfsp);
+ if (error != 0)
+ return (error);
+
+ /*
+ * Mount point must be a directory
+ */
+ if (mvp->v_type != VDIR)
+ return (ENOTDIR);
+
+ /*
+ * no support for remount (what is it?)
+ */
+ if (uap->flags & MS_REMOUNT)
+ return (ENOTSUP);
+
+ /*
+ * Ensure that nothing else is actively in/under the mount point
+ */
+ mutex_enter(&mvp->v_lock);
+ if ((uap->flags & MS_OVERLAY) == 0 &&
+ (mvp->v_count != 1 || (mvp->v_flag & VROOT))) {
+ mutex_exit(&mvp->v_lock);
+ return (EBUSY);
+ }
+ mutex_exit(&mvp->v_lock);
+
+ /*
+ * check for read only has to be done early
+ */
+ if (uap->flags & MS_RDONLY) {
+ vfsp->vfs_flag |= VFS_RDONLY;
+ vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0);
+ }
+
+ /*
+ * UID to use for all files
+ */
+ if (vfs_optionisset(vfsp, "uid", &optval) &&
+ ddi_strtol(optval, NULL, 10, &val) == 0 &&
+ (uid_t)val == val)
+ uid = val;
+
+ /*
+ * GID to use for all files
+ */
+ if (vfs_optionisset(vfsp, "gid", &optval) &&
+ ddi_strtol(optval, NULL, 10, &val) == 0 &&
+ (gid_t)val == val)
+ gid = val;
+
+ /*
+ * dmode to use for all directories
+ */
+ if (vfs_optionisset(vfsp, "dmode", &optval) &&
+ ddi_strtol(optval, NULL, 8, &val) == 0 &&
+ (mode_t)val == val)
+ dmode = val;
+
+ /*
+ * fmode to use for all files
+ */
+ if (vfs_optionisset(vfsp, "fmode", &optval) &&
+ ddi_strtol(optval, NULL, 8, &val) == 0 &&
+ (mode_t)val == val)
+ fmode = val;
+
+ /*
+ * dmask to use for all directories
+ */
+ if (vfs_optionisset(vfsp, "dmask", &optval) &&
+ ddi_strtol(optval, NULL, 8, &val) == 0 &&
+ (mode_t)val == val)
+ dmask = val;
+
+ /*
+ * fmask to use for all files
+ */
+ if (vfs_optionisset(vfsp, "fmask", &optval) &&
+ ddi_strtol(optval, NULL, 8, &val) == 0 &&
+ (mode_t)val == val)
+ fmask = val;
+
+ /*
+ * umask to use for all directories & files
+ */
+ if (vfs_optionisset(vfsp, "umask", &optval) &&
+ ddi_strtol(optval, NULL, 8, &val) == 0 &&
+ (mode_t)val == val)
+ dmask = fmask = val;
+
+ /*
+ * ttl to use for stat caches
+ */
+ if (vfs_optionisset(vfsp, "stat_ttl", &optval) &&
+ ddi_strtol(optval, NULL, 10, &val) == 0 &&
+ (int)val == val)
+ {
+ stat_ttl = val;
+ }
+ else
+ vfs_setmntopt(vfsp, "stat_ttl", VBOXSOLQUOTE(DEF_STAT_TTL_MS), 0);
+
+ /*
+ * whether to honor fsync
+ */
+ if (vfs_optionisset(vfsp, "fsync", &optval))
+ fsync = 1;
+
+ /*
+ * Any unknown options are an error
+ */
+ if ((uap->flags & MS_DATA) && uap->datalen > 0) {
+ cmn_err(CE_WARN, "sffs: unknown mount options specified");
+ return (EINVAL);
+ }
+
+ /*
+ * get the mount point pathname
+ */
+ error = sf_pn_get(uap->dir, uap, &mount_point);
+ if (error)
+ return (error);
+
+ /*
+ * find what we are mounting
+ */
+ error = sf_pn_get(uap->spec, uap, &share_name);
+ if (error) {
+ kmem_free(mount_point, strlen(mount_point) + 1);
+ return (error);
+ }
+
+ /*
+ * Invoke Hypervisor mount interface before proceeding
+ */
+ error = sfprov_mount(sfprov, share_name, &handle);
+ if (error) {
+ kmem_free(share_name, strlen(share_name) + 1);
+ kmem_free(mount_point, strlen(mount_point) + 1);
+ return (error);
+ }
+
+ /*
+ * find an available minor device number for this mount
+ */
+ mutex_enter(&sffs_minor_lock);
+ do {
+ sffs_minor = (sffs_minor + 1) & L_MAXMIN32;
+ dev = makedevice(sffs_major, sffs_minor);
+ } while (vfs_devismounted(dev));
+ mutex_exit(&sffs_minor_lock);
+
+ /*
+ * allocate and fill in the sffs structure
+ */
+ sffs = kmem_alloc(sizeof (*sffs), KM_SLEEP);
+ sffs->sf_vfsp = vfsp;
+ sffs->sf_handle = handle;
+ sffs->sf_handle->sf_uid = uid;
+ sffs->sf_handle->sf_gid = gid;
+ sffs->sf_handle->sf_dmode = dmode;
+ sffs->sf_handle->sf_fmode = fmode;
+ sffs->sf_handle->sf_dmask = dmask;
+ sffs->sf_handle->sf_fmask = fmask;
+ sffs->sf_stat_ttl = stat_ttl;
+ sffs->sf_fsync = fsync;
+ sffs->sf_share_name = share_name;
+ sffs->sf_mntpath = mount_point;
+ sffs->sf_ino = 3; /* root mount point is always '3' */
+
+ /*
+ * fill in the vfs structure
+ */
+ vfsp->vfs_data = (caddr_t)sffs;
+ vfsp->vfs_fstype = sffs_fstype;
+ vfsp->vfs_dev = dev;
+ vfsp->vfs_bsize = PAGESIZE; /* HERE JOE ??? */
+ vfsp->vfs_flag |= VFS_NOTRUNC; /* HERE JOE ???? */
+ vfs_make_fsid(&vfsp->vfs_fsid, dev, sffs_fstype);
+
+ /*
+ * create the root vnode.
+ * XXX JOE What should the path be here? is "/" really right?
+ * other options?
+ */
+ path = kmem_alloc(2, KM_SLEEP);
+ strcpy(path, ".");
+ mutex_enter(&sffs_lock);
+ sfnode = sfnode_make(sffs, path, VDIR, NULL, NULL, NULL, 0);
+ sffs->sf_rootnode = sfnode_get_vnode(sfnode);
+ sffs->sf_rootnode->v_flag |= VROOT;
+ sffs->sf_rootnode->v_vfsp = vfsp;
+ mutex_exit(&sffs_lock);
+
+ LogFlowFunc(("sffs_mount() success sffs=0x%p\n", sffs));
+#ifdef DEBUG_ramshankar
+ sffs_print(sffs);
+#endif
+ return (error);
+}
+
+static int
+sffs_unmount(vfs_t *vfsp, int flag, cred_t *cr)
+{
+ sffs_data_t *sffs = (sffs_data_t *)vfsp->vfs_data;
+ int error;
+
+ /*
+ * generic security check
+ */
+ LogFlowFunc(("sffs_unmount() of sffs=0x%p\n", sffs));
+ if ((error = secpolicy_fs_unmount(cr, vfsp)) != 0)
+ return (error);
+
+ /*
+ * forced unmount is not supported by this file system
+ * and thus, ENOTSUP, is being returned.
+ */
+ if (flag & MS_FORCE) {
+ LogFlowFunc(("sffs_unmount(MS_FORCE) returns ENOSUP\n"));
+ return (ENOTSUP);
+ }
+
+ /*
+ * Mark the file system unmounted.
+ */
+ vfsp->vfs_flag |= VFS_UNMOUNTED;
+
+ /*
+ * Make sure nothing is still in use.
+ */
+ if (sffs_purge(sffs) != 0) {
+ vfsp->vfs_flag &= ~VFS_UNMOUNTED;
+ LogFlowFunc(("sffs_unmount() returns EBUSY\n"));
+ return (EBUSY);
+ }
+
+ /*
+ * Invoke Hypervisor unmount interface before proceeding
+ */
+ error = sfprov_unmount(sffs->sf_handle);
+ if (error != 0) {
+ /* TBD anything here? */
+ }
+
+ kmem_free(sffs->sf_share_name, strlen(sffs->sf_share_name) + 1);
+ kmem_free(sffs->sf_mntpath, strlen(sffs->sf_mntpath) + 1);
+ kmem_free(sffs, sizeof(*sffs));
+ LogFlowFunc(("sffs_unmount() done\n"));
+ return (0);
+}
+
+/*
+ * return the vnode for the root of the mounted file system
+ */
+static int
+sffs_root(vfs_t *vfsp, vnode_t **vpp)
+{
+ sffs_data_t *sffs = (sffs_data_t *)vfsp->vfs_data;
+ vnode_t *vp = sffs->sf_rootnode;
+
+ VN_HOLD(vp);
+ *vpp = vp;
+ return (0);
+}
+
+/*
+ * get some stats.. fake up the rest
+ */
+static int
+sffs_statvfs(vfs_t *vfsp, statvfs64_t *sbp)
+{
+ sffs_data_t *sffs = (sffs_data_t *)vfsp->vfs_data;
+ sffs_fsinfo_t fsinfo;
+ dev32_t d32;
+ int error;
+
+ bzero(sbp, sizeof(*sbp));
+ error = sfprov_get_fsinfo(sffs->sf_handle, &fsinfo);
+ if (error != 0)
+ return (error);
+
+ sbp->f_bsize = fsinfo.blksize;
+ sbp->f_frsize = fsinfo.blksize;
+
+ sbp->f_bfree = fsinfo.blksavail;
+ sbp->f_bavail = fsinfo.blksavail;
+ sbp->f_files = fsinfo.blksavail / 4; /* some kind of reasonable value */
+ sbp->f_ffree = fsinfo.blksavail / 4;
+ sbp->f_favail = fsinfo.blksavail / 4;
+
+ sbp->f_blocks = fsinfo.blksused + sbp->f_bavail;
+
+ (void) cmpldev(&d32, vfsp->vfs_dev);
+ sbp->f_fsid = d32;
+ strcpy(&sbp->f_basetype[0], "sffs");
+ sbp->f_flag |= ST_NOSUID;
+
+ if (fsinfo.readonly)
+ sbp->f_flag |= ST_RDONLY;
+
+ sbp->f_namemax = fsinfo.maxnamesize;
+ return (0);
+}
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h
new file mode 100644
index 00000000..6338d95d
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vfs.h
@@ -0,0 +1,90 @@
+/* $Id: vboxfs_vfs.h $ */
+/** @file
+ * VirtualBox File System for Solaris Guests, VFS header.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vfs_h
+#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vfs_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Shared Folders filesystem per-mount data structure.
+ */
+typedef struct sffs_data {
+ vfs_t *sf_vfsp; /* filesystem's vfs struct */
+ vnode_t *sf_rootnode; /* of vnode of the root directory */
+ int sf_stat_ttl; /* ttl for stat caches (in ms) */
+ int sf_fsync; /* whether to honor fsync or not */
+ char *sf_share_name;
+ char *sf_mntpath; /* name of mount point */
+ sfp_mount_t *sf_handle;
+ uint64_t sf_ino; /* per FS ino generator */
+} sffs_data_t;
+
+/*
+ * Workaround for older Solaris versions which called map_addr()/choose_addr()/
+ * map_addr_proc() with an 'alignment' argument that was removed in Solaris
+ * 11.4.
+ */
+typedef struct VBoxVFS_SolAddrMap
+{
+ union
+ {
+ void *(*pfnSol_map_addr) (caddr_t *, size_t, offset_t, uint_t);
+ void *(*pfnSol_map_addr_old) (caddr_t *, size_t, offset_t, int, uint_t);
+ } MapAddr;
+
+ union
+ {
+ int (*pfnSol_choose_addr) (struct as *, caddr_t *, size_t, offset_t, uint_t);
+ int (*pfnSol_choose_addr_old) (struct as *, caddr_t *, size_t, offset_t, int, uint_t);
+ } ChooseAddr;
+} VBoxVFS_SolAddrMap;
+typedef VBoxVFS_SolAddrMap *pVBoxVFS_SolAddrMap;
+
+extern bool g_fVBoxVFS_SolOldAddrMap;
+extern VBoxVFS_SolAddrMap g_VBoxVFS_SolAddrMap;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vfs_h */
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c
new file mode 100644
index 00000000..e639468c
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.c
@@ -0,0 +1,2500 @@
+/* $Id: vboxfs_vnode.c $ */
+/** @file
+ * VirtualBox File System for Solaris Guests, vnode implementation.
+ * Portions contributed by: Ronald.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/*
+ * Shared Folder File System is used from Solaris when run as a guest operating
+ * system on VirtualBox, though is meant to be usable with any hypervisor that
+ * can provide similar functionality. The sffs code handles all the Solaris
+ * specific semantics and relies on a provider module to actually access
+ * directories, files, etc. The provider interfaces are described in
+ * "vboxfs_prov.h" and the module implementing them is shipped as part of the
+ * VirtualBox Guest Additions for Solaris.
+ *
+ * The shared folder file system is similar to a networked file system,
+ * but with some caveats. The sffs code caches minimal information and proxies
+ * out to the provider whenever possible. Here are some things that are
+ * handled in this code and not by the proxy:
+ *
+ * - a way to open ".." from any already open directory
+ * - st_ino numbers
+ * - detecting directory changes that happened on the host.
+ *
+ * The implementation builds a cache of information for every file/directory
+ * ever accessed in all mounted sffs filesystems using sf_node structures.
+ *
+ * This information for both open or closed files can become invalid if
+ * asynchronous changes are made on the host. Solaris should not panic() in
+ * this event, but some file system operations may return unexpected errors.
+ * Information for such directories or files while they have active vnodes
+ * is removed from the regular cache and stored in a "stale" bucket until
+ * the vnode becomes completely inactive.
+ *
+ * We suppport only read-only mmap (VBOXVFS_WITH_MMAP) i.e. MAP_SHARED,
+ * MAP_PRIVATE in PROT_READ, this data caching would not be coherent with
+ * normal simultaneous read()/write() operations, nor will it be coherent
+ * with data access on the host. Writable mmap(MAP_SHARED) access is not
+ * implemented, as guaranteeing any kind of coherency with concurrent
+ * activity on the host would be near impossible with the existing
+ * interfaces.
+ *
+ * A note about locking. sffs is not a high performance file system.
+ * No fine grained locking is done. The one sffs_lock protects just about
+ * everything.
+ */
+
+#include <VBox/log.h>
+#include <iprt/asm.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mntent.h>
+#include <sys/param.h>
+#include <sys/modctl.h>
+#include <sys/mount.h>
+#include <sys/policy.h>
+#include <sys/atomic.h>
+#include <sys/sysmacros.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/vfs.h>
+#include <sys/vmsystm.h>
+#include <vm/seg_kpm.h>
+#include <vm/pvn.h>
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+# include <sys/vfs_opreg.h>
+#endif
+#include <sys/pathname.h>
+#include <sys/dirent.h>
+#include <sys/fs_subr.h>
+#include <sys/time.h>
+#include <sys/cmn_err.h>
+#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */
+
+#include "vboxfs_prov.h"
+#include "vboxfs_vnode.h"
+#include "vboxfs_vfs.h"
+
+/*
+ * Solaris 11u1b10 Extended Policy putback CR 7121445 removes secpolicy_vnode_access from sys/policy.h
+ */
+#ifdef VBOX_VFS_EXTENDED_POLICY
+int secpolicy_vnode_access(const cred_t *, vnode_t *, uid_t, mode_t);
+#endif
+
+#define VBOXVFS_WITH_MMAP
+
+static struct vnodeops *sffs_ops = NULL;
+
+kmutex_t sffs_lock;
+static avl_tree_t sfnodes;
+static avl_tree_t stale_sfnodes;
+
+/*
+ * For now we'll use an I/O buffer that doesn't page fault for VirtualBox
+ * to transfer data into.
+ */
+char *sffs_buffer;
+
+/*
+ * sfnode_compare() is needed for AVL tree functionality.
+ * The nodes are sorted by mounted filesystem, then path. If the
+ * nodes are stale, the node pointer itself is used to force uniqueness.
+ */
+static int
+sfnode_compare(const void *a, const void *b)
+{
+ sfnode_t *x = (sfnode_t *)a;
+ sfnode_t *y = (sfnode_t *)b;
+ int diff;
+
+ if (x->sf_is_stale) {
+ ASSERT(y->sf_is_stale);
+ diff = strcmp(x->sf_path, y->sf_path);
+ if (diff == 0)
+ diff = (uintptr_t)y - (uintptr_t)x;
+ } else {
+ ASSERT(!y->sf_is_stale);
+ diff = (uintptr_t)y->sf_sffs - (uintptr_t)x->sf_sffs;
+ if (diff == 0)
+ diff = strcmp(x->sf_path, y->sf_path);
+ }
+ if (diff < 0)
+ return (-1);
+ if (diff > 0)
+ return (1);
+ return (0);
+}
+
+/*
+ * Construct a new pathname given an sfnode plus an optional tail component.
+ * This handles ".." and "."
+ */
+static char *
+sfnode_construct_path(sfnode_t *node, char *tail)
+{
+ char *p;
+
+ if (strcmp(tail, ".") == 0 || strcmp(tail, "..") == 0)
+ panic("construct path for %s", tail);
+ p = kmem_alloc(strlen(node->sf_path) + 1 + strlen(tail) + 1, KM_SLEEP);
+ strcpy(p, node->sf_path);
+ strcat(p, "/");
+ strcat(p, tail);
+ return (p);
+}
+
+/*
+ * Clears the (cached) directory listing for the node.
+ */
+static void
+sfnode_clear_dir_list(sfnode_t *node)
+{
+ ASSERT(MUTEX_HELD(&sffs_lock));
+
+ while (node->sf_dir_list != NULL) {
+ sffs_dirents_t *next = node->sf_dir_list->sf_next;
+ kmem_free(node->sf_dir_list, SFFS_DIRENTS_SIZE);
+ node->sf_dir_list = next;
+ }
+}
+
+/*
+ * Open the provider file associated with a vnode. Holding the file open is
+ * the only way we have of trying to have a vnode continue to refer to the
+ * same host file in the host in light of the possibility of host side renames.
+ */
+static void
+sfnode_open(sfnode_t *node, int flag)
+{
+ int error;
+ sfp_file_t *fp;
+
+ if (node->sf_file != NULL)
+ return;
+ error = sfprov_open(node->sf_sffs->sf_handle, node->sf_path, &fp, flag);
+ if (error == 0)
+ {
+ node->sf_file = fp;
+ node->sf_flag = flag;
+ }
+ else
+ node->sf_flag = ~0;
+}
+
+/*
+ * get a new vnode reference for an sfnode
+ */
+vnode_t *
+sfnode_get_vnode(sfnode_t *node)
+{
+ vnode_t *vp;
+
+ if (node->sf_vnode != NULL) {
+ VN_HOLD(node->sf_vnode);
+ } else {
+ vp = vn_alloc(KM_SLEEP);
+ LogFlowFunc((" %s gets vnode 0x%p\n", node->sf_path, vp));
+ vp->v_type = node->sf_type;
+ vp->v_vfsp = node->sf_sffs->sf_vfsp;
+ vn_setops(vp, sffs_ops);
+ vp->v_flag = VNOSWAP;
+#ifndef VBOXVFS_WITH_MMAP
+ vp->v_flag |= VNOMAP;
+#endif
+ vn_exists(vp);
+ vp->v_data = node;
+ node->sf_vnode = vp;
+ }
+ return (node->sf_vnode);
+}
+
+/*
+ * Allocate and initialize a new sfnode and assign it a vnode
+ */
+sfnode_t *
+sfnode_make(
+ sffs_data_t *sffs,
+ char *path,
+ vtype_t type,
+ sfp_file_t *fp,
+ sfnode_t *parent, /* can be NULL for root */
+ sffs_stat_t *stat,
+ uint64_t stat_time)
+{
+ sfnode_t *node;
+ avl_index_t where;
+
+ ASSERT(MUTEX_HELD(&sffs_lock));
+ ASSERT(path != NULL);
+
+ /*
+ * build the sfnode
+ */
+ LogFlowFunc(("sffs_make(%s)\n", path));
+ node = kmem_alloc(sizeof (*node), KM_SLEEP);
+ node->sf_sffs = sffs;
+ VFS_HOLD(node->sf_sffs->sf_vfsp);
+ node->sf_path = path;
+ node->sf_ino = sffs->sf_ino++;
+ node->sf_type = type;
+ node->sf_is_stale = 0; /* never stale at creation */
+ node->sf_file = fp;
+ node->sf_flag = ~0;
+ node->sf_vnode = NULL; /* do this before any sfnode_get_vnode() */
+ node->sf_children = 0;
+ node->sf_parent = parent;
+ if (parent)
+ ++parent->sf_children;
+ node->sf_dir_list = NULL;
+ if (stat != NULL) {
+ node->sf_stat = *stat;
+ node->sf_stat_time = stat_time;
+ } else {
+ node->sf_stat_time = 0;
+ }
+
+ /*
+ * add the new node to our cache
+ */
+ if (avl_find(&sfnodes, node, &where) != NULL)
+ panic("sffs_create_sfnode(%s): duplicate sfnode_t", path);
+ avl_insert(&sfnodes, node, where);
+ return (node);
+}
+
+/*
+ * destroy an sfnode
+ */
+static void
+sfnode_destroy(sfnode_t *node)
+{
+ avl_index_t where;
+ avl_tree_t *tree;
+ sfnode_t *parent;
+top:
+ parent = node->sf_parent;
+ ASSERT(MUTEX_HELD(&sffs_lock));
+ ASSERT(node->sf_path != NULL);
+ LogFlowFunc(("sffs_destroy(%s)%s\n", node->sf_path, node->sf_is_stale ? " stale": ""));
+ if (node->sf_children != 0)
+ panic("sfnode_destroy(%s) has %d children", node->sf_path, node->sf_children);
+ if (node->sf_vnode != NULL)
+ panic("sfnode_destroy(%s) has active vnode", node->sf_path);
+
+ if (node->sf_is_stale)
+ tree = &stale_sfnodes;
+ else
+ tree = &sfnodes;
+ if (avl_find(tree, node, &where) == NULL)
+ panic("sfnode_destroy(%s) not found", node->sf_path);
+ avl_remove(tree, node);
+
+ VFS_RELE(node->sf_sffs->sf_vfsp);
+ sfnode_clear_dir_list(node);
+ kmem_free(node->sf_path, strlen(node->sf_path) + 1);
+ kmem_free(node, sizeof (*node));
+ if (parent != NULL) {
+ sfnode_clear_dir_list(parent);
+ if (parent->sf_children == 0)
+ panic("sfnode_destroy parent (%s) has no child", parent->sf_path);
+ --parent->sf_children;
+ if (parent->sf_children == 0 &&
+ parent->sf_is_stale &&
+ parent->sf_vnode == NULL) {
+ node = parent;
+ goto top;
+ }
+ }
+}
+
+/*
+ * Some sort of host operation on an sfnode has failed or it has been
+ * deleted. Mark this node and any children as stale, deleting knowledge
+ * about any which do not have active vnodes or children
+ * This also handle deleting an inactive node that was already stale.
+ */
+static void
+sfnode_make_stale(sfnode_t *node)
+{
+ sfnode_t *n;
+ int len;
+ ASSERT(MUTEX_HELD(&sffs_lock));
+ avl_index_t where;
+
+ /*
+ * First deal with any children of a directory node.
+ * If a directory becomes stale, anything below it becomes stale too.
+ */
+ if (!node->sf_is_stale && node->sf_type == VDIR) {
+ len = strlen(node->sf_path);
+
+ n = node;
+ while ((n = AVL_NEXT(&sfnodes, node)) != NULL) {
+ ASSERT(!n->sf_is_stale);
+
+ /*
+ * quit when no longer seeing children of node
+ */
+ if (n->sf_sffs != node->sf_sffs ||
+ strncmp(node->sf_path, n->sf_path, len) != 0 ||
+ n->sf_path[len] != '/')
+ break;
+
+ /*
+ * Either mark the child as stale or destroy it
+ */
+ if (n->sf_vnode == NULL && n->sf_children == 0) {
+ sfnode_destroy(n);
+ } else {
+ LogFlowFunc(("sffs_make_stale(%s) sub\n", n->sf_path));
+ sfnode_clear_dir_list(n);
+ if (avl_find(&sfnodes, n, &where) == NULL)
+ panic("sfnode_make_stale(%s)"
+ " not in sfnodes", n->sf_path);
+ avl_remove(&sfnodes, n);
+ n->sf_is_stale = 1;
+ if (avl_find(&stale_sfnodes, n, &where) != NULL)
+ panic("sffs_make_stale(%s) duplicates",
+ n->sf_path);
+ avl_insert(&stale_sfnodes, n, where);
+ }
+ }
+ }
+
+ /*
+ * Now deal with the given node.
+ */
+ if (node->sf_vnode == NULL && node->sf_children == 0) {
+ sfnode_destroy(node);
+ } else if (!node->sf_is_stale) {
+ LogFlowFunc(("sffs_make_stale(%s)\n", node->sf_path));
+ sfnode_clear_dir_list(node);
+ if (node->sf_parent)
+ sfnode_clear_dir_list(node->sf_parent);
+ if (avl_find(&sfnodes, node, &where) == NULL)
+ panic("sfnode_make_stale(%s) not in sfnodes",
+ node->sf_path);
+ avl_remove(&sfnodes, node);
+ node->sf_is_stale = 1;
+ if (avl_find(&stale_sfnodes, node, &where) != NULL)
+ panic("sffs_make_stale(%s) duplicates", node->sf_path);
+ avl_insert(&stale_sfnodes, node, where);
+ }
+}
+
+static uint64_t
+sfnode_cur_time_usec(void)
+{
+ clock_t now = drv_hztousec(ddi_get_lbolt());
+ return now;
+}
+
+static int
+sfnode_stat_cached(sfnode_t *node)
+{
+ return (sfnode_cur_time_usec() - node->sf_stat_time) <
+ node->sf_sffs->sf_stat_ttl * 1000L;
+}
+
+static void
+sfnode_invalidate_stat_cache(sfnode_t *node)
+{
+ node->sf_stat_time = 0;
+}
+
+static int
+sfnode_update_stat_cache(sfnode_t *node)
+{
+ int error;
+
+ error = sfprov_get_attr(node->sf_sffs->sf_handle, node->sf_path,
+ &node->sf_stat);
+ if (error == ENOENT)
+ sfnode_make_stale(node);
+ if (error == 0)
+ node->sf_stat_time = sfnode_cur_time_usec();
+
+ return (error);
+}
+
+/*
+ * Rename a file or a directory
+ */
+static void
+sfnode_rename(sfnode_t *node, sfnode_t *newparent, char *path)
+{
+ sfnode_t *n;
+ sfnode_t template;
+ avl_index_t where;
+ int len = strlen(path);
+ int old_len;
+ char *new_path;
+ char *tail;
+ ASSERT(MUTEX_HELD(&sffs_lock));
+
+ ASSERT(!node->sf_is_stale);
+
+ /*
+ * Have to remove anything existing that had the new name.
+ */
+ template.sf_sffs = node->sf_sffs;
+ template.sf_path = path;
+ template.sf_is_stale = 0;
+ n = avl_find(&sfnodes, &template, &where);
+ if (n != NULL)
+ sfnode_make_stale(n);
+
+ /*
+ * Do the renaming, deal with any children of this node first.
+ */
+ if (node->sf_type == VDIR) {
+ old_len = strlen(node->sf_path);
+ while ((n = AVL_NEXT(&sfnodes, node)) != NULL) {
+
+ /*
+ * quit when no longer seeing children of node
+ */
+ if (n->sf_sffs != node->sf_sffs ||
+ strncmp(node->sf_path, n->sf_path, old_len) != 0 ||
+ n->sf_path[old_len] != '/')
+ break;
+
+ /*
+ * Rename the child:
+ * - build the new path name
+ * - unlink the AVL node
+ * - assign the new name
+ * - re-insert the AVL name
+ */
+ ASSERT(strlen(n->sf_path) > old_len);
+ tail = n->sf_path + old_len; /* includes initial "/" */
+ new_path = kmem_alloc(len + strlen(tail) + 1,
+ KM_SLEEP);
+ strcpy(new_path, path);
+ strcat(new_path, tail);
+ if (avl_find(&sfnodes, n, &where) == NULL)
+ panic("sfnode_rename(%s) not in sfnodes",
+ n->sf_path);
+ avl_remove(&sfnodes, n);
+ LogFlowFunc(("sfnode_rname(%s to %s) sub\n", n->sf_path, new_path));
+ kmem_free(n->sf_path, strlen(n->sf_path) + 1);
+ n->sf_path = new_path;
+ if (avl_find(&sfnodes, n, &where) != NULL)
+ panic("sfnode_rename(%s) duplicates",
+ n->sf_path);
+ avl_insert(&sfnodes, n, where);
+ }
+ }
+
+ /*
+ * Deal with the given node.
+ */
+ if (avl_find(&sfnodes, node, &where) == NULL)
+ panic("sfnode_rename(%s) not in sfnodes", node->sf_path);
+ avl_remove(&sfnodes, node);
+ LogFlowFunc(("sfnode_rname(%s to %s)\n", node->sf_path, path));
+ kmem_free(node->sf_path, strlen(node->sf_path) + 1);
+ node->sf_path = path;
+ if (avl_find(&sfnodes, node, &where) != NULL)
+ panic("sfnode_rename(%s) duplicates", node->sf_path);
+ avl_insert(&sfnodes, node, where);
+
+ /*
+ * change the parent
+ */
+ if (node->sf_parent == NULL)
+ panic("sfnode_rename(%s) no parent", node->sf_path);
+ if (node->sf_parent->sf_children == 0)
+ panic("sfnode_rename(%s) parent has no child", node->sf_path);
+ sfnode_clear_dir_list(node->sf_parent);
+ sfnode_clear_dir_list(newparent);
+ --node->sf_parent->sf_children;
+ node->sf_parent = newparent;
+ ++newparent->sf_children;
+}
+
+/*
+ * Look for a cached node, if not found either handle ".." or try looking
+ * via the provider. Create an entry in sfnodes if found but not cached yet.
+ * If the create flag is set, a file or directory is created. If the file
+ * already existed, an error is returned.
+ * Nodes returned from this routine always have a vnode with its ref count
+ * bumped by 1.
+ */
+static sfnode_t *
+sfnode_lookup(
+ sfnode_t *dir,
+ char *name,
+ vtype_t create,
+ mode_t c_mode,
+ sffs_stat_t *stat,
+ uint64_t stat_time,
+ int *err)
+{
+ avl_index_t where;
+ sfnode_t template;
+ sfnode_t *node;
+ int error = 0;
+ int type;
+ char *fullpath;
+ sfp_file_t *fp;
+ sffs_stat_t tmp_stat;
+
+ ASSERT(MUTEX_HELD(&sffs_lock));
+
+ if (err)
+ *err = error;
+
+ /*
+ * handle referencing myself
+ */
+ if (strcmp(name, "") == 0 || strcmp(name, ".") == 0)
+ return (dir);
+
+ /*
+ * deal with parent
+ */
+ if (strcmp(name, "..") == 0)
+ return (dir->sf_parent);
+
+ /*
+ * Look for an existing node.
+ */
+ fullpath = sfnode_construct_path(dir, name);
+ template.sf_sffs = dir->sf_sffs;
+ template.sf_path = fullpath;
+ template.sf_is_stale = 0;
+ node = avl_find(&sfnodes, &template, &where);
+ if (node != NULL) {
+ kmem_free(fullpath, strlen(fullpath) + 1);
+ if (create != VNON)
+ return (NULL);
+ return (node);
+ }
+
+ /*
+ * No entry for this path currently.
+ * Check if the file exists with the provider and get the type from
+ * there.
+ */
+ if (create == VREG) {
+ type = VREG;
+ stat = &tmp_stat;
+ error = sfprov_create(dir->sf_sffs->sf_handle, fullpath, c_mode,
+ &fp, stat);
+ stat_time = sfnode_cur_time_usec();
+ } else if (create == VDIR) {
+ type = VDIR;
+ stat = &tmp_stat;
+ error = sfprov_mkdir(dir->sf_sffs->sf_handle, fullpath, c_mode,
+ &fp, stat);
+ stat_time = sfnode_cur_time_usec();
+ } else {
+ mode_t m;
+ fp = NULL;
+ type = VNON;
+ if (stat == NULL) {
+ stat = &tmp_stat;
+ error = sfprov_get_attr(dir->sf_sffs->sf_handle,
+ fullpath, stat);
+ stat_time = sfnode_cur_time_usec();
+ } else {
+ error = 0;
+ }
+ m = stat->sf_mode;
+ if (error != 0)
+ error = ENOENT;
+ else if (S_ISDIR(m))
+ type = VDIR;
+ else if (S_ISREG(m))
+ type = VREG;
+ else if (S_ISLNK(m))
+ type = VLNK;
+ }
+
+ if (err)
+ *err = error;
+
+ /*
+ * If no errors, make a new node and return it.
+ */
+ if (error) {
+ kmem_free(fullpath, strlen(fullpath) + 1);
+ return (NULL);
+ }
+ node = sfnode_make(dir->sf_sffs, fullpath, type, fp, dir, stat,
+ stat_time);
+ return (node);
+}
+
+
+/*
+ * uid and gid in sffs determine owner and group for all files.
+ */
+static int
+sfnode_access(sfnode_t *node, mode_t mode, cred_t *cr)
+{
+ sffs_data_t *sffs = node->sf_sffs;
+ mode_t m;
+ int shift = 0;
+ int error;
+ vnode_t *vp;
+
+ ASSERT(MUTEX_HELD(&sffs_lock));
+
+ /*
+ * get the mode from the cache or provider
+ */
+ if (sfnode_stat_cached(node))
+ error = 0;
+ else
+ error = sfnode_update_stat_cache(node);
+ m = (error == 0) ? (node->sf_stat.sf_mode & MODEMASK) : 0;
+
+ /*
+ * mask off the permissions based on uid/gid
+ */
+ if (crgetuid(cr) != sffs->sf_handle->sf_uid) {
+ shift += 3;
+ if (groupmember(sffs->sf_handle->sf_gid, cr) == 0)
+ shift += 3;
+ }
+ mode &= ~(m << shift);
+
+ if (mode == 0) {
+ error = 0;
+ } else {
+ /** @todo r=ramshankar: This can probably be optimized by holding static vnode
+ * templates for dir/file, as it only checks the type rather than
+ * fetching/allocating the real vnode. */
+ vp = sfnode_get_vnode(node);
+ error = secpolicy_vnode_access(cr, vp, sffs->sf_handle->sf_uid, mode);
+ VN_RELE(vp);
+ }
+ return (error);
+}
+
+
+/*
+ *
+ * Everything below this point are the vnode operations used by Solaris VFS
+ */
+static int
+sffs_readdir(
+ vnode_t *vp,
+ uio_t *uiop,
+ cred_t *cred,
+ int *eofp,
+ caller_context_t *ct,
+ int flag)
+{
+ sfnode_t *dir = VN2SFN(vp);
+ sfnode_t *node;
+ struct sffs_dirent *dirent = NULL;
+ sffs_dirents_t *cur_buf;
+ offset_t offset = 0;
+ offset_t orig_off = uiop->uio_loffset;
+ int dummy_eof;
+ int error = 0;
+
+ if (uiop->uio_iovcnt != 1)
+ return (EINVAL);
+
+ if (vp->v_type != VDIR)
+ return (ENOTDIR);
+
+ if (eofp == NULL)
+ eofp = &dummy_eof;
+ *eofp = 0;
+
+ if (uiop->uio_loffset >= MAXOFFSET_T) {
+ *eofp = 1;
+ return (0);
+ }
+
+ /*
+ * Get the directory entry names from the host. This gets all
+ * entries. These are stored in a linked list of sffs_dirents_t
+ * buffers, each of which contains a list of dirent64_t's.
+ */
+ mutex_enter(&sffs_lock);
+
+ if (dir->sf_dir_list == NULL) {
+ error = sfprov_readdir(dir->sf_sffs->sf_handle, dir->sf_path,
+ &dir->sf_dir_list, flag);
+ if (error != 0)
+ goto done;
+ }
+
+ /*
+ * Validate and skip to the desired offset.
+ */
+ cur_buf = dir->sf_dir_list;
+ offset = 0;
+
+ while (cur_buf != NULL &&
+ offset + cur_buf->sf_len <= uiop->uio_loffset) {
+ offset += cur_buf->sf_len;
+ cur_buf = cur_buf->sf_next;
+ }
+
+ if (cur_buf == NULL && offset != uiop->uio_loffset) {
+ error = EINVAL;
+ goto done;
+ }
+ if (cur_buf != NULL && offset != uiop->uio_loffset) {
+ offset_t off = offset;
+ int step;
+ dirent = &cur_buf->sf_entries[0];
+
+ while (off < uiop->uio_loffset) {
+ if (dirent->sf_entry.d_off == uiop->uio_loffset)
+ break;
+ step = sizeof(sffs_stat_t) + dirent->sf_entry.d_reclen;
+ dirent = (struct sffs_dirent *) (((char *) dirent) + step);
+ off += step;
+ }
+
+ if (off >= uiop->uio_loffset) {
+ error = EINVAL;
+ goto done;
+ }
+ }
+
+ offset = uiop->uio_loffset - offset;
+
+ /*
+ * Lookup each of the names, so that we have ino's, and copy to
+ * result buffer.
+ */
+ while (cur_buf != NULL) {
+ if (offset >= cur_buf->sf_len) {
+ cur_buf = cur_buf->sf_next;
+ offset = 0;
+ continue;
+ }
+
+ dirent = (struct sffs_dirent *)
+ (((char *) &cur_buf->sf_entries[0]) + offset);
+ if (dirent->sf_entry.d_reclen > uiop->uio_resid)
+ break;
+
+ if (strcmp(dirent->sf_entry.d_name, ".") == 0) {
+ node = dir;
+ } else if (strcmp(dirent->sf_entry.d_name, "..") == 0) {
+ node = dir->sf_parent;
+ if (node == NULL)
+ node = dir;
+ } else {
+ node = sfnode_lookup(dir, dirent->sf_entry.d_name, VNON,
+ 0, &dirent->sf_stat, sfnode_cur_time_usec(), NULL);
+ if (node == NULL)
+ panic("sffs_readdir() lookup failed");
+ }
+ dirent->sf_entry.d_ino = node->sf_ino;
+
+ error = uiomove(&dirent->sf_entry, dirent->sf_entry.d_reclen, UIO_READ, uiop);
+ if (error != 0)
+ break;
+
+ uiop->uio_loffset= dirent->sf_entry.d_off;
+ offset += sizeof(sffs_stat_t) + dirent->sf_entry.d_reclen;
+ }
+ if (error == 0 && cur_buf == NULL)
+ *eofp = 1;
+done:
+ mutex_exit(&sffs_lock);
+ if (error != 0)
+ uiop->uio_loffset = orig_off;
+ return (error);
+}
+
+
+#if defined(VBOX_VFS_SOLARIS_10U6)
+/*
+ * HERE JOE.. this may need more logic, need to look at other file systems
+ */
+static int
+sffs_pathconf(
+ vnode_t *vp,
+ int cmd,
+ ulong_t *valp,
+ cred_t *cr)
+{
+ return (fs_pathconf(vp, cmd, valp, cr));
+}
+#else
+/*
+ * HERE JOE.. this may need more logic, need to look at other file systems
+ */
+static int
+sffs_pathconf(
+ vnode_t *vp,
+ int cmd,
+ ulong_t *valp,
+ cred_t *cr,
+ caller_context_t *ct)
+{
+ return (fs_pathconf(vp, cmd, valp, cr, ct));
+}
+#endif
+
+static int
+sffs_getattr(
+ vnode_t *vp,
+ vattr_t *vap,
+ int flags,
+ cred_t *cred,
+ caller_context_t *ct)
+{
+ sfnode_t *node = VN2SFN(vp);
+ sffs_data_t *sffs = node->sf_sffs;
+ mode_t mode;
+ int error = 0;
+
+ mutex_enter(&sffs_lock);
+ vap->va_type = vp->v_type;
+ vap->va_uid = sffs->sf_handle->sf_uid;
+ vap->va_gid = sffs->sf_handle->sf_gid;
+ vap->va_fsid = sffs->sf_vfsp->vfs_dev;
+ vap->va_nodeid = node->sf_ino;
+ vap->va_nlink = 1;
+ vap->va_rdev = sffs->sf_vfsp->vfs_dev;
+ vap->va_seq = 0;
+
+ if (!sfnode_stat_cached(node)) {
+ error = sfnode_update_stat_cache(node);
+ if (error != 0)
+ goto done;
+ }
+
+ vap->va_atime = node->sf_stat.sf_atime;
+ vap->va_mtime = node->sf_stat.sf_mtime;
+ vap->va_ctime = node->sf_stat.sf_ctime;
+
+ mode = node->sf_stat.sf_mode;
+ vap->va_mode = mode & MODEMASK;
+
+ vap->va_size = node->sf_stat.sf_size;
+ vap->va_blksize = 512;
+ vap->va_nblocks = (node->sf_stat.sf_alloc + 511) / 512;
+
+done:
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+static int
+sffs_setattr(
+ vnode_t *vp,
+ vattr_t *vap,
+ int flags,
+ cred_t *cred,
+ caller_context_t *ct)
+{
+ sfnode_t *node = VN2SFN(vp);
+ int error;
+ mode_t mode;
+
+ mode = vap->va_mode;
+ if (vp->v_type == VREG)
+ mode |= S_IFREG;
+ else if (vp->v_type == VDIR)
+ mode |= S_IFDIR;
+ else if (vp->v_type == VBLK)
+ mode |= S_IFBLK;
+ else if (vp->v_type == VCHR)
+ mode |= S_IFCHR;
+ else if (vp->v_type == VLNK)
+ mode |= S_IFLNK;
+ else if (vp->v_type == VFIFO)
+ mode |= S_IFIFO;
+ else if (vp->v_type == VSOCK)
+ mode |= S_IFSOCK;
+
+ mutex_enter(&sffs_lock);
+
+ sfnode_invalidate_stat_cache(node);
+ error = sfprov_set_attr(node->sf_sffs->sf_handle, node->sf_path,
+ vap->va_mask, mode, vap->va_atime, vap->va_mtime, vap->va_ctime);
+ if (error == ENOENT)
+ sfnode_make_stale(node);
+
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+static int
+sffs_space(
+ vnode_t *vp,
+ int cmd,
+ struct flock64 *bfp,
+ int flags,
+ offset_t off,
+ cred_t *cred,
+ caller_context_t *ct)
+{
+ sfnode_t *node = VN2SFN(vp);
+ int error;
+
+ /* we only support changing the length of the file */
+ if (bfp->l_whence != SEEK_SET || bfp->l_len != 0)
+ return ENOSYS;
+
+ mutex_enter(&sffs_lock);
+
+ sfnode_invalidate_stat_cache(node);
+
+ error = sfprov_set_size(node->sf_sffs->sf_handle, node->sf_path,
+ bfp->l_start);
+ if (error == ENOENT)
+ sfnode_make_stale(node);
+
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+/*ARGSUSED*/
+static int
+sffs_read(
+ vnode_t *vp,
+ struct uio *uio,
+ int ioflag,
+ cred_t *cred,
+ caller_context_t *ct)
+{
+ sfnode_t *node = VN2SFN(vp);
+ int error = 0;
+ uint32_t bytes;
+ uint32_t done;
+ ulong_t offset;
+ ssize_t total;
+
+ if (vp->v_type == VDIR)
+ return (EISDIR);
+ if (vp->v_type != VREG)
+ return (EINVAL);
+ if (uio->uio_loffset >= MAXOFFSET_T)
+ return (0);
+ if (uio->uio_loffset < 0)
+ return (EINVAL);
+ total = uio->uio_resid;
+ if (total == 0)
+ return (0);
+
+ mutex_enter(&sffs_lock);
+ if (node->sf_file == NULL) {
+ ASSERT(node->sf_flag != ~0);
+ sfnode_open(node, node->sf_flag);
+ if (node->sf_file == NULL)
+ return (EBADF);
+ }
+
+ do {
+ offset = uio->uio_offset;
+ done = bytes = MIN(PAGESIZE, uio->uio_resid);
+ error = sfprov_read(node->sf_file, sffs_buffer, offset, &done);
+ if (error == 0 && done > 0)
+ error = uiomove(sffs_buffer, done, UIO_READ, uio);
+ } while (error == 0 && uio->uio_resid > 0 && done > 0);
+
+ mutex_exit(&sffs_lock);
+
+ /*
+ * a partial read is never an error
+ */
+ if (total != uio->uio_resid)
+ error = 0;
+ return (error);
+}
+
+/*ARGSUSED*/
+static int
+sffs_write(
+ vnode_t *vp,
+ struct uio *uiop,
+ int ioflag,
+ cred_t *cred,
+ caller_context_t *ct)
+{
+ sfnode_t *node = VN2SFN(vp);
+ int error = 0;
+ uint32_t bytes;
+ uint32_t done;
+ ulong_t offset;
+ ssize_t total;
+ rlim64_t limit = uiop->uio_llimit;
+
+ if (vp->v_type == VDIR)
+ return (EISDIR);
+ if (vp->v_type != VREG)
+ return (EINVAL);
+
+ /*
+ * We have to hold this lock for a long time to keep
+ * multiple FAPPEND writes from intermixing
+ */
+ mutex_enter(&sffs_lock);
+ if (node->sf_file == NULL) {
+ ASSERT(node->sf_flag != ~0);
+ sfnode_open(node, node->sf_flag);
+ if (node->sf_file == NULL)
+ return (EBADF);
+ }
+
+ sfnode_invalidate_stat_cache(node);
+
+ if (ioflag & FAPPEND) {
+ uint64_t endoffile;
+
+ error = sfprov_get_size(node->sf_sffs->sf_handle,
+ node->sf_path, &endoffile);
+ if (error == ENOENT)
+ sfnode_make_stale(node);
+ if (error != 0) {
+ mutex_exit(&sffs_lock);
+ return (error);
+ }
+ uiop->uio_loffset = endoffile;
+ }
+
+ if (vp->v_type != VREG || uiop->uio_loffset < 0) {
+ mutex_exit(&sffs_lock);
+ return (EINVAL);
+ }
+ if (limit == RLIM64_INFINITY || limit > MAXOFFSET_T)
+ limit = MAXOFFSET_T;
+
+ if (uiop->uio_loffset >= limit) {
+ mutex_exit(&sffs_lock);
+ return (EFBIG);
+ }
+
+ if (uiop->uio_loffset >= MAXOFFSET_T) {
+ mutex_exit(&sffs_lock);
+ return (EFBIG);
+ }
+
+ total = uiop->uio_resid;
+ if (total == 0) {
+ mutex_exit(&sffs_lock);
+ return (0);
+ }
+
+ do {
+ offset = uiop->uio_offset;
+ bytes = MIN(PAGESIZE, uiop->uio_resid);
+ if (offset + bytes >= limit) {
+ if (offset >= limit) {
+ error = EFBIG;
+ break;
+ }
+ bytes = limit - offset;
+ }
+ error = uiomove(sffs_buffer, bytes, UIO_WRITE, uiop);
+ if (error != 0)
+ break;
+ done = bytes;
+ if (error == 0)
+ error = sfprov_write(node->sf_file, sffs_buffer,
+ offset, &done);
+ total -= done;
+ if (done != bytes) {
+ uiop->uio_resid += bytes - done;
+ break;
+ }
+ } while (error == 0 && uiop->uio_resid > 0 && done > 0);
+
+ mutex_exit(&sffs_lock);
+
+ /*
+ * A short write is never really an error.
+ */
+ if (total != uiop->uio_resid)
+ error = 0;
+ return (error);
+}
+
+/*ARGSUSED*/
+static int
+sffs_access(vnode_t *vp, int mode, int flags, cred_t *cr, caller_context_t *ct)
+{
+ sfnode_t *node = VN2SFN(vp);
+ int error;
+
+ mutex_enter(&sffs_lock);
+ error = sfnode_access(node, mode, cr);
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+/*
+ * Lookup an entry in a directory and create a new vnode if found.
+ */
+/* ARGSUSED3 */
+static int
+sffs_lookup(
+ vnode_t *dvp, /* the directory vnode */
+ char *name, /* the name of the file or directory */
+ vnode_t **vpp, /* the vnode we found or NULL */
+ struct pathname *pnp,
+ int flags,
+ vnode_t *rdir,
+ cred_t *cred,
+ caller_context_t *ct,
+ int *direntflags,
+ struct pathname *realpnp)
+{
+ int error;
+ sfnode_t *node;
+
+ /*
+ * dvp must be a directory
+ */
+ if (dvp->v_type != VDIR)
+ return (ENOTDIR);
+
+ /*
+ * An empty component name or just "." means the directory itself.
+ * Don't do any further lookup or checking.
+ */
+ if (strcmp(name, "") == 0 || strcmp(name, ".") == 0) {
+ VN_HOLD(dvp);
+ *vpp = dvp;
+ return (0);
+ }
+
+ /*
+ * Check permission to look at this directory. We always allow "..".
+ */
+ mutex_enter(&sffs_lock);
+ if (strcmp(name, "..") != 0) {
+ error = sfnode_access(VN2SFN(dvp), VEXEC, cred);
+ if (error) {
+ mutex_exit(&sffs_lock);
+ return (error);
+ }
+ }
+
+ /*
+ * Lookup the node.
+ */
+ node = sfnode_lookup(VN2SFN(dvp), name, VNON, 0, NULL, 0, NULL);
+ if (node != NULL)
+ *vpp = sfnode_get_vnode(node);
+ mutex_exit(&sffs_lock);
+ return ((node == NULL) ? ENOENT : 0);
+}
+
+/*ARGSUSED*/
+static int
+sffs_create(
+ vnode_t *dvp,
+ char *name,
+ struct vattr *vap,
+ vcexcl_t exclusive,
+ int mode,
+ vnode_t **vpp,
+ cred_t *cr,
+ int flag,
+ caller_context_t *ct,
+ vsecattr_t *vsecp)
+{
+ vnode_t *vp;
+ sfnode_t *node;
+ int error;
+
+ ASSERT(name != NULL);
+
+ /*
+ * this is used for regular files, not mkdir
+ */
+ if (vap->va_type == VDIR)
+ return (EISDIR);
+ if (vap->va_type != VREG)
+ return (EINVAL);
+
+ /*
+ * is this a pre-existing file?
+ */
+ error = sffs_lookup(dvp, name, &vp,
+ NULL, 0, NULL, cr, ct, NULL, NULL);
+ if (error == ENOENT)
+ vp = NULL;
+ else if (error != 0)
+ return (error);
+
+ /*
+ * Operation on a pre-existing file.
+ */
+ if (vp != NULL) {
+ if (exclusive == EXCL) {
+ VN_RELE(vp);
+ return (EEXIST);
+ }
+ if (vp->v_type == VDIR && (mode & VWRITE) == VWRITE) {
+ VN_RELE(vp);
+ return (EISDIR);
+ }
+
+ mutex_enter(&sffs_lock);
+ node = VN2SFN(vp);
+ error = sfnode_access(node, mode, cr);
+ if (error != 0) {
+ mutex_exit(&sffs_lock);
+ VN_RELE(vp);
+ return (error);
+ }
+
+ sfnode_invalidate_stat_cache(VN2SFN(dvp));
+
+ /*
+ * handle truncating an existing file
+ */
+ if (vp->v_type == VREG && (vap->va_mask & AT_SIZE) &&
+ vap->va_size == 0) {
+ sfnode_open(node, flag | FTRUNC);
+ if (node->sf_path == NULL) {
+ mutex_exit(&sffs_lock);
+ VN_RELE(vp);
+ return (ENOENT);
+ }
+ }
+ mutex_exit(&sffs_lock);
+ *vpp = vp;
+ return (0);
+ }
+
+ /*
+ * Create a new node. First check for a race creating it.
+ */
+ mutex_enter(&sffs_lock);
+ node = sfnode_lookup(VN2SFN(dvp), name, VNON, 0, NULL, 0, NULL);
+ if (node != NULL) {
+ mutex_exit(&sffs_lock);
+ return (EEXIST);
+ }
+
+ /*
+ * Doesn't exist yet and we have the lock, so create it.
+ */
+ sfnode_invalidate_stat_cache(VN2SFN(dvp));
+ int lookuperr;
+ node = sfnode_lookup(VN2SFN(dvp), name, VREG,
+ (vap->va_mask & AT_MODE) ? vap->va_mode : 0, NULL, 0, &lookuperr);
+
+ if (node && node->sf_parent)
+ sfnode_clear_dir_list(node->sf_parent);
+
+ mutex_exit(&sffs_lock);
+ if (node == NULL)
+ return (lookuperr);
+ *vpp = sfnode_get_vnode(node);
+ return (0);
+}
+
+/*ARGSUSED*/
+static int
+sffs_mkdir(
+ vnode_t *dvp,
+ char *nm,
+ vattr_t *va,
+ vnode_t **vpp,
+ cred_t *cred,
+ caller_context_t *ct,
+ int flags,
+ vsecattr_t *vsecp)
+{
+ sfnode_t *node;
+ vnode_t *vp;
+ int error;
+
+ /*
+ * These should never happen
+ */
+ ASSERT(nm != NULL);
+ ASSERT(strcmp(nm, "") != 0);
+ ASSERT(strcmp(nm, ".") != 0);
+ ASSERT(strcmp(nm, "..") != 0);
+
+ /*
+ * Do an unlocked look up first
+ */
+ error = sffs_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL, NULL);
+ if (error == 0) {
+ VN_RELE(vp);
+ return (EEXIST);
+ }
+ if (error != ENOENT)
+ return (error);
+
+ /*
+ * Must be able to write in current directory
+ */
+ mutex_enter(&sffs_lock);
+ error = sfnode_access(VN2SFN(dvp), VWRITE, cred);
+ if (error) {
+ mutex_exit(&sffs_lock);
+ return (error);
+ }
+
+ sfnode_invalidate_stat_cache(VN2SFN(dvp));
+ int lookuperr = EACCES;
+ node = sfnode_lookup(VN2SFN(dvp), nm, VDIR,
+ (va->va_mode & AT_MODE) ? va->va_mode : 0, NULL, 0, &lookuperr);
+
+ if (node && node->sf_parent)
+ sfnode_clear_dir_list(node->sf_parent);
+
+ mutex_exit(&sffs_lock);
+ if (node == NULL)
+ return (lookuperr);
+ *vpp = sfnode_get_vnode(node);
+ return (0);
+}
+
+/*ARGSUSED*/
+static int
+sffs_rmdir(
+ struct vnode *dvp,
+ char *nm,
+ vnode_t *cdir,
+ cred_t *cred,
+ caller_context_t *ct,
+ int flags)
+{
+ sfnode_t *node;
+ vnode_t *vp;
+ int error;
+
+ /*
+ * Return error when removing . and ..
+ */
+ if (strcmp(nm, ".") == 0 || strcmp(nm, "") == 0)
+ return (EINVAL);
+ if (strcmp(nm, "..") == 0)
+ return (EEXIST);
+
+ error = sffs_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL, NULL);
+ if (error)
+ return (error);
+ if (vp->v_type != VDIR) {
+ VN_RELE(vp);
+ return (ENOTDIR);
+ }
+
+#ifdef VBOXVFS_WITH_MMAP
+ if (vn_vfswlock(vp)) {
+ VN_RELE(vp);
+ return (EBUSY);
+ }
+#endif
+
+ if (vn_mountedvfs(vp)) {
+ VN_RELE(vp);
+ return (EBUSY);
+ }
+
+ node = VN2SFN(vp);
+
+ mutex_enter(&sffs_lock);
+ error = sfnode_access(VN2SFN(dvp), VEXEC | VWRITE, cred);
+ if (error)
+ goto done;
+
+ /*
+ * If anything else is using this vnode, then fail the remove.
+ * Why? Windows hosts can't remove something that is open,
+ * so we have to sfprov_close() it first.
+ * There is no errno for this - since it's not a problem on UNIX,
+ * but EINVAL is the closest.
+ */
+ if (node->sf_file != NULL) {
+ if (vp->v_count > 1) {
+ error = EINVAL;
+ goto done;
+ }
+ (void)sfprov_close(node->sf_file);
+ node->sf_file = NULL;
+ }
+
+ /*
+ * Remove the directory on the host and mark the node as stale.
+ */
+ sfnode_invalidate_stat_cache(VN2SFN(dvp));
+ error = sfprov_rmdir(node->sf_sffs->sf_handle, node->sf_path);
+ if (error == ENOENT || error == 0)
+ sfnode_make_stale(node);
+
+ if (node->sf_parent)
+ sfnode_clear_dir_list(node->sf_parent);
+done:
+ mutex_exit(&sffs_lock);
+#ifdef VBOXVFS_WITH_MMAP
+ vn_vfsunlock(vp);
+#endif
+ VN_RELE(vp);
+ return (error);
+}
+
+
+#ifdef VBOXVFS_WITH_MMAP
+static caddr_t
+sffs_page_map(
+ page_t *ppage,
+ enum seg_rw segaccess)
+{
+ /* Use seg_kpm driver if possible (64-bit) */
+ if (kpm_enable)
+ return (hat_kpm_mapin(ppage, NULL));
+ ASSERT(segaccess == S_READ || segaccess == S_WRITE);
+ return (ppmapin(ppage, PROT_READ | ((segaccess == S_WRITE) ? PROT_WRITE : 0), (caddr_t)-1));
+}
+
+
+static void
+sffs_page_unmap(
+ page_t *ppage,
+ caddr_t addr)
+{
+ if (kpm_enable)
+ hat_kpm_mapout(ppage, NULL, addr);
+ else
+ ppmapout(addr);
+}
+
+
+/*
+ * Called when there's no page in the cache. This will create new page(s) and read
+ * the file data into it.
+ */
+static int
+sffs_readpages(
+ vnode_t *dvp,
+ offset_t off,
+ page_t *pagelist[],
+ size_t pagelistsize,
+ struct seg *segp,
+ caddr_t addr,
+ enum seg_rw segaccess)
+{
+ ASSERT(MUTEX_HELD(&sffs_lock));
+
+ int error = 0;
+ u_offset_t io_off, total;
+ size_t io_len;
+ page_t *ppages;
+ page_t *pcur;
+
+ sfnode_t *node = VN2SFN(dvp);
+ ASSERT(node);
+ ASSERT(node->sf_file);
+
+ if (pagelistsize == PAGESIZE)
+ {
+ io_off = off;
+ io_len = PAGESIZE;
+ ppages = page_create_va(dvp, io_off, io_len, PG_WAIT | PG_EXCL, segp, addr);
+ }
+ else
+ ppages = pvn_read_kluster(dvp, off, segp, addr, &io_off, &io_len, off, pagelistsize, 0);
+
+ /* If page already exists return success */
+ if (!ppages)
+ {
+ *pagelist = NULL;
+ return (0);
+ }
+
+ /*
+ * Map & read page-by-page.
+ */
+ total = io_off + io_len;
+ pcur = ppages;
+ while (io_off < total)
+ {
+ ASSERT3U(io_off, ==, pcur->p_offset);
+
+ caddr_t virtaddr = sffs_page_map(pcur, segaccess);
+ uint32_t bytes = PAGESIZE;
+ error = sfprov_read(node->sf_file, virtaddr, io_off, &bytes);
+ /*
+ * If we reuse pages without zero'ing them, one process can mmap() and read-past the length
+ * to read previously mmap'd contents (from possibly other processes).
+ */
+ if (error == 0 && bytes < PAGESIZE)
+ memset(virtaddr + bytes, 0, PAGESIZE - bytes);
+ sffs_page_unmap(pcur, virtaddr);
+ if (error != 0)
+ {
+ cmn_err(CE_WARN, "sffs_readpages: sfprov_read() failed. error=%d bytes=%u\n", error, bytes);
+ /* Get rid of all kluster pages read & bail. */
+ pvn_read_done(ppages, B_ERROR);
+ return (error);
+ }
+ pcur = pcur->p_next;
+ io_off += PAGESIZE;
+ }
+
+ /*
+ * Fill in the pagelist from kluster at the requested offset.
+ */
+ pvn_plist_init(ppages, pagelist, pagelistsize, off, io_len, segaccess);
+ ASSERT(pagelist == NULL || (*pagelist)->p_offset == off);
+ return (0);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_getpage(
+ vnode_t *dvp,
+ offset_t off,
+ size_t len,
+ uint_t *protp,
+ page_t *pagelist[],
+ size_t pagelistsize,
+ struct seg *segp,
+ caddr_t addr,
+ enum seg_rw segaccess,
+ cred_t *credp
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+ , caller_context_t *ct
+#endif
+ )
+{
+ int error = 0;
+ int is_recursive = 0;
+ page_t **pageliststart = pagelist;
+ sfnode_t *node = VN2SFN(dvp);
+ ASSERT(node);
+ ASSERT(node->sf_file);
+
+ if (segaccess == S_WRITE)
+ return (ENOSYS); /* Will this ever happen? */
+
+ /* Don't bother about faultahead for now. */
+ if (pagelist == NULL)
+ return (0);
+
+ if (len > pagelistsize)
+ len = pagelistsize;
+ else
+ len = P2ROUNDUP(len, PAGESIZE);
+ ASSERT(pagelistsize >= len);
+
+ if (protp)
+ *protp = PROT_ALL;
+
+ /*
+ * The buffer passed to sffs_write may be mmap'd so we may get a
+ * pagefault there, in which case we'll end up here with this thread
+ * already owning the mutex. Mutexes aren't recursive.
+ */
+ if (mutex_owner(&sffs_lock) == curthread)
+ is_recursive = 1;
+ else
+ mutex_enter(&sffs_lock);
+
+ /* Don't map pages past end of the file. */
+ if (off + len > node->sf_stat.sf_size + PAGEOFFSET)
+ {
+ if (!is_recursive)
+ mutex_exit(&sffs_lock);
+ return (EFAULT);
+ }
+
+ while (len > 0)
+ {
+ /*
+ * Look for pages in the requested offset range, or create them if we can't find any.
+ */
+ if ((*pagelist = page_lookup(dvp, off, SE_SHARED)) != NULL)
+ *(pagelist + 1) = NULL;
+ else if ((error = sffs_readpages(dvp, off, pagelist, pagelistsize, segp, addr, segaccess)) != 0)
+ {
+ while (pagelist > pageliststart)
+ page_unlock(*--pagelist);
+
+ *pagelist = NULL;
+ if (!is_recursive)
+ mutex_exit(&sffs_lock);
+ return (error);
+ }
+
+ while (*pagelist)
+ {
+ ASSERT3U((*pagelist)->p_offset, ==, off);
+ off += PAGESIZE;
+ addr += PAGESIZE;
+ if (len > 0)
+ {
+ ASSERT3U(len, >=, PAGESIZE);
+ len -= PAGESIZE;
+ }
+
+ ASSERT3U(pagelistsize, >=, PAGESIZE);
+ pagelistsize -= PAGESIZE;
+ pagelist++;
+ }
+ }
+
+ /*
+ * Fill the page list array with any pages left in the cache.
+ */
+ while ( pagelistsize > 0
+ && (*pagelist++ = page_lookup_nowait(dvp, off, SE_SHARED)))
+ {
+ off += PAGESIZE;
+ pagelistsize -= PAGESIZE;
+ }
+
+ *pagelist = NULL;
+ if (!is_recursive)
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_putpage(
+ vnode_t *dvp,
+ offset_t off,
+ size_t len,
+ int flags,
+ cred_t *credp
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+ , caller_context_t *ct
+#endif
+ )
+{
+ /*
+ * We don't support PROT_WRITE mmaps.
+ */
+ return (ENOSYS);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_discardpage(
+ vnode_t *dvp,
+ page_t *ppage,
+ u_offset_t *poff,
+ size_t *plen,
+ int flags,
+ cred_t *pcred)
+{
+ /*
+ * This would not get invoked i.e. via pvn_vplist_dirty() since we don't support
+ * PROT_WRITE mmaps and therefore will not have dirty pages.
+ */
+ pvn_write_done(ppage, B_INVAL | B_ERROR | B_FORCE);
+ return (0);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_map(
+ vnode_t *dvp,
+ offset_t off,
+ struct as *asp,
+ caddr_t *addrp,
+ size_t len,
+ uchar_t prot,
+ uchar_t maxprot,
+ uint_t flags,
+ cred_t *credp
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+ , caller_context_t *ct
+#endif
+ )
+{
+ /*
+ * Invocation: mmap()->smmap_common()->VOP_MAP()->sffs_map(). Once the
+ * segment driver creates the new segment via segvn_create(), it'll
+ * invoke down the line VOP_ADDMAP()->sffs_addmap()
+ */
+ int error = 0;
+ sfnode_t *node = VN2SFN(dvp);
+ ASSERT(node);
+ if ((flags & MAP_SHARED) && (prot & PROT_WRITE))
+ return (ENOTSUP);
+
+ if (off < 0 || len > MAXOFFSET_T - off)
+ return (ENXIO);
+
+ if (dvp->v_type != VREG)
+ return (ENODEV);
+
+ if (dvp->v_flag & VNOMAP)
+ return (ENOSYS);
+
+ if (vn_has_mandatory_locks(dvp, node->sf_stat.sf_mode))
+ return (EAGAIN);
+
+ mutex_enter(&sffs_lock);
+ as_rangelock(asp);
+
+#if defined(VBOX_VFS_SOLARIS_10U6)
+ if ((flags & MAP_FIXED) == 0)
+ {
+ if (g_fVBoxVFS_SolOldAddrMap)
+ g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr_old(addrp, len, off, 1, flags);
+ else
+ g_VBoxVFS_SolAddrMap.MapAddr.pfnSol_map_addr(addrp, len, off, flags);
+ if (*addrp == NULL)
+ error = ENOMEM;
+ }
+ else
+ as_unmap(asp, *addrp, len); /* User specified address, remove any previous mappings */
+#else
+ if (g_fVBoxVFS_SolOldAddrMap)
+ error = g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr_old(asp, addrp, len, off, 1, flags);
+ else
+ error = g_VBoxVFS_SolAddrMap.ChooseAddr.pfnSol_choose_addr(asp, addrp, len, off, flags);
+#endif
+
+ if (error)
+ {
+ as_rangeunlock(asp);
+ mutex_exit(&sffs_lock);
+ return (error);
+ }
+
+ segvn_crargs_t vnodeargs;
+ memset(&vnodeargs, 0, sizeof(vnodeargs));
+ vnodeargs.vp = dvp;
+ vnodeargs.cred = credp;
+ vnodeargs.offset = off;
+ vnodeargs.type = flags & MAP_TYPE;
+ vnodeargs.prot = prot;
+ vnodeargs.maxprot = maxprot;
+ vnodeargs.flags = flags & ~MAP_TYPE;
+ vnodeargs.amp = NULL; /* anon. mapping */
+ vnodeargs.szc = 0; /* preferred page size code */
+ vnodeargs.lgrp_mem_policy_flags = 0;
+
+ error = as_map(asp, *addrp, len, segvn_create, &vnodeargs);
+
+ as_rangeunlock(asp);
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_addmap(
+ vnode_t *dvp,
+ offset_t off,
+ struct as *asp,
+ caddr_t addr,
+ size_t len,
+ uchar_t prot,
+ uchar_t maxprot,
+ uint_t flags,
+ cred_t *credp
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+ , caller_context_t *ct
+#endif
+ )
+{
+ if (dvp->v_flag & VNOMAP)
+ return (ENOSYS);
+ return (0);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_delmap(
+ vnode_t *dvp,
+ offset_t off,
+ struct as *asp,
+ caddr_t addr,
+ size_t len,
+ uint_t prot,
+ uint_t maxprot,
+ uint_t flags,
+ cred_t *credp
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+ , caller_context_t *ct
+#endif
+ )
+{
+ if (dvp->v_flag & VNOMAP)
+ return (ENOSYS);
+
+ return (0);
+}
+#endif /* VBOXVFS_WITH_MMAP */
+
+
+/*ARGSUSED*/
+static int
+sffs_readlink(
+ vnode_t *vp,
+ uio_t *uiop,
+ cred_t *cred
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+ ,
+ caller_context_t *ct
+#endif
+ )
+{
+ sfnode_t *node;
+ int error = 0;
+ char *target = NULL;
+
+ if (uiop->uio_iovcnt != 1)
+ return (EINVAL);
+
+ if (vp->v_type != VLNK)
+ return (EINVAL);
+
+ mutex_enter(&sffs_lock);
+ node = VN2SFN(vp);
+
+ target = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+
+ error = sfprov_readlink(node->sf_sffs->sf_handle, node->sf_path, target,
+ MAXPATHLEN);
+ if (error)
+ goto done;
+
+ error = uiomove(target, strlen(target), UIO_READ, uiop);
+
+done:
+ mutex_exit(&sffs_lock);
+ if (target)
+ kmem_free(target, MAXPATHLEN);
+ return (error);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_symlink(
+ vnode_t *dvp,
+ char *linkname,
+ vattr_t *vap,
+ char *target,
+ cred_t *cred
+#if !defined(VBOX_VFS_SOLARIS_10U6)
+ ,
+ caller_context_t *ct,
+ int flags
+#endif
+ )
+{
+ sfnode_t *dir;
+ sfnode_t *node;
+ sffs_stat_t stat;
+ int error = 0;
+ char *fullpath;
+
+ /*
+ * These should never happen
+ */
+ ASSERT(linkname != NULL);
+ ASSERT(strcmp(linkname, "") != 0);
+ ASSERT(strcmp(linkname, ".") != 0);
+ ASSERT(strcmp(linkname, "..") != 0);
+
+ /*
+ * Basic checks.
+ */
+ if (vap->va_type != VLNK)
+ return (EINVAL);
+
+ mutex_enter(&sffs_lock);
+
+ if (sfnode_lookup(VN2SFN(dvp), linkname, VNON, 0, NULL, 0, NULL) !=
+ NULL) {
+ error = EEXIST;
+ goto done;
+ }
+
+ dir = VN2SFN(dvp);
+ error = sfnode_access(dir, VWRITE, cred);
+ if (error)
+ goto done;
+
+ /*
+ * Create symlink. Note that we ignore vap->va_mode because generally
+ * we can't change the attributes of the symlink itself.
+ */
+ fullpath = sfnode_construct_path(dir, linkname);
+ error = sfprov_symlink(dir->sf_sffs->sf_handle, fullpath, target,
+ &stat);
+ kmem_free(fullpath, strlen(fullpath) + 1);
+ if (error)
+ goto done;
+
+ node = sfnode_lookup(dir, linkname, VLNK, 0, &stat,
+ sfnode_cur_time_usec(), NULL);
+
+ sfnode_invalidate_stat_cache(dir);
+ sfnode_clear_dir_list(dir);
+
+done:
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_remove(
+ vnode_t *dvp,
+ char *name,
+ cred_t *cred,
+ caller_context_t *ct,
+ int flags)
+{
+ vnode_t *vp;
+ sfnode_t *node;
+ int error;
+
+ /*
+ * These should never happen
+ */
+ ASSERT(name != NULL);
+ ASSERT(strcmp(name, "..") != 0);
+
+ error = sffs_lookup(dvp, name, &vp,
+ NULL, 0, NULL, cred, ct, NULL, NULL);
+ if (error)
+ return (error);
+ node = VN2SFN(vp);
+
+ mutex_enter(&sffs_lock);
+ error = sfnode_access(VN2SFN(dvp), VEXEC | VWRITE, cred);
+ if (error)
+ goto done;
+
+ /*
+ * If anything else is using this vnode, then fail the remove.
+ * Why? Windows hosts can't sfprov_remove() a file that is open,
+ * so we have to sfprov_close() it first.
+ * There is no errno for this - since it's not a problem on UNIX,
+ * but ETXTBSY is the closest.
+ */
+ if (node->sf_file != NULL) {
+ if (vp->v_count > 1) {
+ error = ETXTBSY;
+ goto done;
+ }
+ (void)sfprov_close(node->sf_file);
+ node->sf_file = NULL;
+ }
+
+ /*
+ * Remove the file on the host and mark the node as stale.
+ */
+ sfnode_invalidate_stat_cache(VN2SFN(dvp));
+
+ error = sfprov_remove(node->sf_sffs->sf_handle, node->sf_path,
+ node->sf_type == VLNK);
+ if (error == ENOENT || error == 0)
+ sfnode_make_stale(node);
+
+ if (node->sf_parent)
+ sfnode_clear_dir_list(node->sf_parent);
+done:
+ mutex_exit(&sffs_lock);
+ VN_RELE(vp);
+ return (error);
+}
+
+/*ARGSUSED*/
+static int
+sffs_rename(
+ vnode_t *old_dir,
+ char *old_nm,
+ vnode_t *new_dir,
+ char *new_nm,
+ cred_t *cred,
+ caller_context_t *ct,
+ int flags)
+{
+ char *newpath;
+ int error;
+ sfnode_t *node;
+
+ if (strcmp(new_nm, "") == 0 ||
+ strcmp(new_nm, ".") == 0 ||
+ strcmp(new_nm, "..") == 0 ||
+ strcmp(old_nm, "") == 0 ||
+ strcmp(old_nm, ".") == 0 ||
+ strcmp(old_nm, "..") == 0)
+ return (EINVAL);
+
+ /*
+ * make sure we have permission to do the rename
+ */
+ mutex_enter(&sffs_lock);
+ error = sfnode_access(VN2SFN(old_dir), VEXEC | VWRITE, cred);
+ if (error == 0 && new_dir != old_dir)
+ error = sfnode_access(VN2SFN(new_dir), VEXEC | VWRITE, cred);
+ if (error)
+ goto done;
+
+ node = sfnode_lookup(VN2SFN(old_dir), old_nm, VNON, 0, NULL, 0, NULL);
+ if (node == NULL) {
+ error = ENOENT;
+ goto done;
+ }
+
+ /*
+ * Rename the file on the host and in our caches.
+ */
+ sfnode_invalidate_stat_cache(node);
+ sfnode_invalidate_stat_cache(VN2SFN(old_dir));
+ sfnode_invalidate_stat_cache(VN2SFN(new_dir));
+
+ newpath = sfnode_construct_path(VN2SFN(new_dir), new_nm);
+ error = sfprov_rename(node->sf_sffs->sf_handle, node->sf_path, newpath,
+ node->sf_type == VDIR);
+ if (error == 0)
+ sfnode_rename(node, VN2SFN(new_dir), newpath);
+ else {
+ kmem_free(newpath, strlen(newpath) + 1);
+ if (error == ENOENT)
+ sfnode_make_stale(node);
+ }
+done:
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+
+/*ARGSUSED*/
+static int
+sffs_fsync(vnode_t *vp, int flag, cred_t *cr, caller_context_t *ct)
+{
+ sfnode_t *node;
+ int error;
+
+ /*
+ * Ask the host to sync any data it may have cached for open files.
+ */
+ mutex_enter(&sffs_lock);
+ node = VN2SFN(vp);
+ if (node->sf_file == NULL)
+ error = EBADF;
+ else if (node->sf_sffs->sf_fsync)
+ error = sfprov_fsync(node->sf_file);
+ else
+ error = 0;
+ mutex_exit(&sffs_lock);
+ return (error);
+}
+
+/*
+ * This may be the last reference, possibly time to close the file and
+ * destroy the vnode. If the sfnode is stale, we'll destroy that too.
+ */
+/*ARGSUSED*/
+static void
+#if defined(VBOX_VFS_SOLARIS_10U6)
+sffs_inactive(vnode_t *vp, cred_t *cr)
+#else
+sffs_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct)
+#endif
+{
+ sfnode_t *node;
+
+ /*
+ * nothing to do if this isn't the last use
+ */
+ mutex_enter(&sffs_lock);
+ node = VN2SFN(vp);
+ mutex_enter(&vp->v_lock);
+ if (vp->v_count > 1) {
+ --vp->v_count;
+ mutex_exit(&vp->v_lock);
+ mutex_exit(&sffs_lock);
+ return;
+ }
+
+ if (vn_has_cached_data(vp)) {
+#ifdef VBOXVFS_WITH_MMAP
+ /* We're fine with releasing the vnode lock here as we should be covered by the sffs_lock */
+ mutex_exit(&vp->v_lock);
+ /* We won't have any dirty pages, this will just invalidate (destroy) the pages and move it to the cachelist. */
+ pvn_vplist_dirty(vp, 0 /* offset */, sffs_discardpage, B_INVAL, cr);
+ mutex_enter(&vp->v_lock);
+#else
+ panic("sffs_inactive() found cached data");
+#endif
+ }
+
+ /*
+ * destroy the vnode
+ */
+ node->sf_vnode = NULL;
+ mutex_exit(&vp->v_lock);
+ vn_invalid(vp);
+ vn_free(vp);
+ LogFlowFunc((" %s vnode cleared\n", node->sf_path));
+
+ /*
+ * Close the sf_file for the node.
+ */
+ if (node->sf_file != NULL) {
+ (void)sfprov_close(node->sf_file);
+ node->sf_file = NULL;
+ }
+
+ /*
+ * Free the directory entries for the node. This should normally
+ * have been taken care of in sffs_close(), but better safe than
+ * sorry.
+ */
+ sfnode_clear_dir_list(node);
+
+ /*
+ * If the node is stale, we can also destroy it.
+ */
+ if (node->sf_is_stale && node->sf_children == 0)
+ sfnode_destroy(node);
+
+ mutex_exit(&sffs_lock);
+ return;
+}
+
+/*
+ * All the work for this is really done in sffs_lookup().
+ */
+/*ARGSUSED*/
+static int
+sffs_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *ct)
+{
+ sfnode_t *node;
+ int error = 0;
+
+ mutex_enter(&sffs_lock);
+
+ node = VN2SFN(*vpp);
+ sfnode_open(node, flag);
+ if (node->sf_file == NULL)
+ error = EINVAL;
+ mutex_exit(&sffs_lock);
+
+ return (error);
+}
+
+/*
+ * All the work for this is really done in inactive.
+ */
+/*ARGSUSED*/
+static int
+sffs_close(
+ vnode_t *vp,
+ int flag,
+ int count,
+ offset_t offset,
+ cred_t *cr,
+ caller_context_t *ct)
+{
+ sfnode_t *node;
+
+ mutex_enter(&sffs_lock);
+ node = VN2SFN(vp);
+
+ /*
+ * Free the directory entries for the node. We do this on this call
+ * here because the directory node may not become inactive for a long
+ * time after the readdir is over. Case in point, if somebody cd's into
+ * the directory then it won't become inactive until they cd away again.
+ * In such a case we would end up with the directory listing not getting
+ * updated (i.e. the result of 'ls' always being the same) until they
+ * change the working directory.
+ */
+ sfnode_clear_dir_list(node);
+
+ sfnode_invalidate_stat_cache(node);
+
+ if (node->sf_file != NULL && vp->v_count <= 1)
+ {
+ (void)sfprov_close(node->sf_file);
+ node->sf_file = NULL;
+ }
+
+ mutex_exit(&sffs_lock);
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+sffs_seek(vnode_t *v, offset_t o, offset_t *no, caller_context_t *ct)
+{
+ if (*no < 0 || *no > MAXOFFSET_T)
+ return (EINVAL);
+
+ if (v->v_type == VDIR)
+ {
+ sffs_dirents_t *cur_buf = VN2SFN(v)->sf_dir_list;
+ off_t offset = 0;
+
+ if (cur_buf == NULL)
+ return (0);
+
+ while (cur_buf != NULL) {
+ if (*no >= offset && *no <= offset + cur_buf->sf_len)
+ return (0);
+ offset += cur_buf->sf_len;
+ cur_buf = cur_buf->sf_next;
+ }
+ return (EINVAL);
+ }
+ return (0);
+}
+
+
+
+/*
+ * By returning an error for this, we prevent anything in sffs from
+ * being re-exported by NFS
+ */
+/* ARGSUSED */
+static int
+sffs_fid(vnode_t *vp, fid_t *fidp, caller_context_t *ct)
+{
+ return (ENOTSUP);
+}
+
+/*
+ * vnode operations for regular files
+ */
+const fs_operation_def_t sffs_ops_template[] = {
+#if defined(VBOX_VFS_SOLARIS_10U6)
+ VOPNAME_ACCESS, sffs_access,
+ VOPNAME_CLOSE, sffs_close,
+ VOPNAME_CREATE, sffs_create,
+ VOPNAME_FID, sffs_fid,
+ VOPNAME_FSYNC, sffs_fsync,
+ VOPNAME_GETATTR, sffs_getattr,
+ VOPNAME_INACTIVE, sffs_inactive,
+ VOPNAME_LOOKUP, sffs_lookup,
+ VOPNAME_MKDIR, sffs_mkdir,
+ VOPNAME_OPEN, sffs_open,
+ VOPNAME_PATHCONF, sffs_pathconf,
+ VOPNAME_READ, sffs_read,
+ VOPNAME_READDIR, sffs_readdir,
+ VOPNAME_READLINK, sffs_readlink,
+ VOPNAME_REMOVE, sffs_remove,
+ VOPNAME_RENAME, sffs_rename,
+ VOPNAME_RMDIR, sffs_rmdir,
+ VOPNAME_SEEK, sffs_seek,
+ VOPNAME_SETATTR, sffs_setattr,
+ VOPNAME_SPACE, sffs_space,
+ VOPNAME_SYMLINK, sffs_symlink,
+ VOPNAME_WRITE, sffs_write,
+
+# ifdef VBOXVFS_WITH_MMAP
+ VOPNAME_MAP, sffs_map,
+ VOPNAME_ADDMAP, sffs_addmap,
+ VOPNAME_DELMAP, sffs_delmap,
+ VOPNAME_GETPAGE, sffs_getpage,
+ VOPNAME_PUTPAGE, sffs_putpage,
+# endif
+
+ NULL, NULL
+#else
+ VOPNAME_ACCESS, { .vop_access = sffs_access },
+ VOPNAME_CLOSE, { .vop_close = sffs_close },
+ VOPNAME_CREATE, { .vop_create = sffs_create },
+ VOPNAME_FID, { .vop_fid = sffs_fid },
+ VOPNAME_FSYNC, { .vop_fsync = sffs_fsync },
+ VOPNAME_GETATTR, { .vop_getattr = sffs_getattr },
+ VOPNAME_INACTIVE, { .vop_inactive = sffs_inactive },
+ VOPNAME_LOOKUP, { .vop_lookup = sffs_lookup },
+ VOPNAME_MKDIR, { .vop_mkdir = sffs_mkdir },
+ VOPNAME_OPEN, { .vop_open = sffs_open },
+ VOPNAME_PATHCONF, { .vop_pathconf = sffs_pathconf },
+ VOPNAME_READ, { .vop_read = sffs_read },
+ VOPNAME_READDIR, { .vop_readdir = sffs_readdir },
+ VOPNAME_READLINK, { .vop_readlink = sffs_readlink },
+ VOPNAME_REMOVE, { .vop_remove = sffs_remove },
+ VOPNAME_RENAME, { .vop_rename = sffs_rename },
+ VOPNAME_RMDIR, { .vop_rmdir = sffs_rmdir },
+ VOPNAME_SEEK, { .vop_seek = sffs_seek },
+ VOPNAME_SETATTR, { .vop_setattr = sffs_setattr },
+ VOPNAME_SPACE, { .vop_space = sffs_space },
+ VOPNAME_SYMLINK, { .vop_symlink = sffs_symlink },
+ VOPNAME_WRITE, { .vop_write = sffs_write },
+
+# ifdef VBOXVFS_WITH_MMAP
+ VOPNAME_MAP, { .vop_map = sffs_map },
+ VOPNAME_ADDMAP, { .vop_addmap = sffs_addmap },
+ VOPNAME_DELMAP, { .vop_delmap = sffs_delmap },
+ VOPNAME_GETPAGE, { .vop_getpage = sffs_getpage },
+ VOPNAME_PUTPAGE, { .vop_putpage = sffs_putpage },
+# endif
+
+ NULL, NULL
+#endif
+};
+
+/*
+ * Also, init and fini functions...
+ */
+int
+sffs_vnode_init(void)
+{
+ int err;
+
+ err = vn_make_ops("sffs", sffs_ops_template, &sffs_ops);
+ if (err)
+ return (err);
+
+ avl_create(&sfnodes, sfnode_compare, sizeof (sfnode_t),
+ offsetof(sfnode_t, sf_linkage));
+ avl_create(&stale_sfnodes, sfnode_compare, sizeof (sfnode_t),
+ offsetof(sfnode_t, sf_linkage));
+
+ sffs_buffer = kmem_alloc(PAGESIZE, KM_SLEEP);
+
+ return (0);
+}
+
+void
+sffs_vnode_fini(void)
+{
+ if (sffs_ops)
+ vn_freevnodeops(sffs_ops);
+ ASSERT(avl_first(&sfnodes) == NULL);
+ avl_destroy(&sfnodes);
+ if (sffs_buffer != NULL) {
+ kmem_free(sffs_buffer, PAGESIZE);
+ sffs_buffer = NULL;
+ }
+}
+
+/*
+ * Utility at unmount to get all nodes in that mounted filesystem removed.
+ */
+int
+sffs_purge(struct sffs_data *sffs)
+{
+ sfnode_t *node;
+ sfnode_t *prev;
+
+ /*
+ * Check that no vnodes are active.
+ */
+ if (sffs->sf_rootnode->v_count > 1)
+ return (-1);
+ for (node = avl_first(&sfnodes); node;
+ node = AVL_NEXT(&sfnodes, node)) {
+ if (node->sf_sffs == sffs && node->sf_vnode &&
+ node->sf_vnode != sffs->sf_rootnode)
+ return (-1);
+ }
+ for (node = avl_first(&stale_sfnodes); node;
+ node = AVL_NEXT(&stale_sfnodes, node)) {
+ if (node->sf_sffs == sffs && node->sf_vnode &&
+ node->sf_vnode != sffs->sf_rootnode)
+ return (-1);
+ }
+
+ /*
+ * All clear to destroy all node information. Since there are no
+ * vnodes, the make stale will cause deletion.
+ */
+ VN_RELE(sffs->sf_rootnode);
+ mutex_enter(&sffs_lock);
+ for (prev = NULL;;) {
+ if (prev == NULL)
+ node = avl_first(&sfnodes);
+ else
+ node = AVL_NEXT(&sfnodes, prev);
+
+ if (node == NULL)
+ break;
+
+ if (node->sf_sffs == sffs) {
+ if (node->sf_vnode != NULL)
+ panic("vboxfs: purge hit active vnode");
+ sfnode_make_stale(node);
+ } else {
+ prev = node;
+ }
+ }
+ mutex_exit(&sffs_lock);
+ return (0);
+}
+
+#if 0
+/* Debug helper functions */
+static void
+sfnode_print(sfnode_t *node)
+{
+ Log(("0x%p", node));
+ Log((" type=%s (%d)",
+ node->sf_type == VDIR ? "VDIR" :
+ node->sf_type == VNON ? "VNON" :
+ node->sf_type == VLNK ? "VLNK" :
+ node->sf_type == VREG ? "VREG" : "other", node->sf_type));
+ Log((" ino=%d", (uint_t)node->sf_ino));
+ Log((" path=%s", node->sf_path));
+ Log((" parent=0x%p", node->sf_parent));
+ if (node->sf_children)
+ Log((" children=%d", node->sf_children));
+ if (node->sf_vnode)
+ Log((" vnode=0x%p", node->sf_vnode));
+ Log(("%s\n", node->sf_is_stale ? " STALE" : ""));
+}
+
+static void
+sfnode_list(void)
+{
+ sfnode_t *n;
+ for (n = avl_first(&sfnodes); n != NULL; n = AVL_NEXT(&sfnodes, n))
+ sfnode_print(n);
+ for (n = avl_first(&stale_sfnodes); n != NULL;
+ n = AVL_NEXT(&stale_sfnodes, n))
+ sfnode_print(n);
+}
+#endif
+
diff --git a/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h
new file mode 100644
index 00000000..04e06225
--- /dev/null
+++ b/src/VBox/Additions/solaris/SharedFolders/vboxfs_vnode.h
@@ -0,0 +1,99 @@
+/* $Id: vboxfs_vnode.h $ */
+/** @file
+ * VirtualBox File System for Solaris Guests, VNode header.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vnode_h
+#define GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vnode_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <sys/t_lock.h>
+#include <sys/avl.h>
+#include <vm/seg.h>
+#include <vm/seg_vn.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * sfnode is the file system dependent vnode data for vboxsf.
+ * sfnode's also track all files ever accessed, both open and closed.
+ * It duplicates some of the information in vnode, since it holds
+ * information for files that may have been completely closed.
+ *
+ * The sfnode_t's are stored in an AVL tree sorted by:
+ * sf_sffs, sf_path
+ */
+typedef struct sfnode {
+ avl_node_t sf_linkage; /* AVL tree linkage */
+ struct sffs_data *sf_sffs; /* containing mounted file system */
+ char *sf_path; /* full pathname to file or dir */
+ uint64_t sf_ino; /* assigned unique ID number */
+ vnode_t *sf_vnode; /* vnode if active */
+ sfp_file_t *sf_file; /* non NULL if open */
+ int sf_flag; /* last opened file-mode. */
+ struct sfnode *sf_parent; /* parent sfnode of this one */
+ uint16_t sf_children; /* number of children sfnodes */
+ uint8_t sf_type; /* VDIR or VREG */
+ uint8_t sf_is_stale; /* this is stale and should be purged */
+ sffs_stat_t sf_stat; /* cached file attrs for this node */
+ uint64_t sf_stat_time; /* last-modified time of sf_stat */
+ sffs_dirents_t *sf_dir_list; /* list of entries for this directory */
+} sfnode_t;
+
+#define VN2SFN(vp) ((sfnode_t *)(vp)->v_data)
+
+#ifdef _KERNEL
+extern int sffs_vnode_init(void);
+extern void sffs_vnode_fini(void);
+extern sfnode_t *sfnode_make(struct sffs_data *, char *, vtype_t, sfp_file_t *,
+ sfnode_t *parent, sffs_stat_t *, uint64_t stat_time);
+extern vnode_t *sfnode_get_vnode(sfnode_t *);
+
+/*
+ * Purge all cached information about a shared file system at unmount
+ */
+extern int sffs_purge(struct sffs_data *);
+
+extern kmutex_t sffs_lock;
+#endif /* _KERNEL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !GA_INCLUDED_SRC_solaris_SharedFolders_vboxfs_vnode_h */
diff --git a/src/VBox/Additions/solaris/Virtio/Makefile.kmk b/src/VBox/Additions/solaris/Virtio/Makefile.kmk
new file mode 100644
index 00000000..cd56717e
--- /dev/null
+++ b/src/VBox/Additions/solaris/Virtio/Makefile.kmk
@@ -0,0 +1,68 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for Solaris Virtio drivers.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#ifneq ($(KBUILD_HOST),solaris)
+#$(error "The Solaris guest additions can only be built on Solaris!")
+#endif
+
+#
+# virtionet ("virtnet" on Solaris) - The Virtio Network Drivers
+#
+SYSMODS.solaris += virtionet
+virtionet_NAME.solaris = virtnet
+virtionet_TEMPLATE = VBoxGuestR0Drv
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ virtionet_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV) VBOX_VERSION_STRING="$(VBOX_VERSION_STRING)"
+else
+ virtionet_DEFS = VBOX_WITH_HGCM VBOX_SVN_REV=$(VBOX_SVN_REV) VBOX_VERSION_STRING=\"$(VBOX_VERSION_STRING)\"
+endif
+virtionet_DEPS = $(VBOX_SVN_REV_KMK)
+virtionet_INCS := .
+virtionet_SOURCES = \
+ Virtio-solaris.c \
+ VirtioPci-solaris.c \
+ VirtioRing-solaris.c \
+ VirtioNet-solaris.c
+# virtionet should resolve all IPRT bits from vboxguest.
+#virtionet_LIBS = \
+# $(VBOX_LIB_IPRT_GUEST_R0)
+virtionet_LDFLAGS += -N drv/vboxguest -N misc/mac
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/solaris/Virtio/Virtio-solaris.c b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.c
new file mode 100644
index 00000000..7406f68d
--- /dev/null
+++ b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.c
@@ -0,0 +1,224 @@
+/* $Id: Virtio-solaris.c $ */
+/** @file
+ * VirtualBox Guest Additions - Virtio Driver for Solaris.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "Virtio-solaris.h"
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <VBox/log.h>
+
+
+/**
+ * Virtio Attach routine that should be called from all Virtio drivers' attach
+ * routines.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Operation type (attach/resume).
+ * @param pDeviceOps Pointer to device ops structure.
+ * @param pHyperOps Pointer to hypervisor ops structure.
+ *
+ * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
+ */
+int VirtioAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd, PVIRTIODEVICEOPS pDeviceOps, PVIRTIOHYPEROPS pHyperOps)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioAttach: pDip=%p enmCmd=%d pDeviceOps=%p pHyperOps=%p\n", pDip, enmCmd, pDeviceOps, pHyperOps));
+
+ AssertReturn(pDip, DDI_EINVAL);
+ AssertReturn(pDeviceOps, DDI_EINVAL);
+ AssertReturn(pHyperOps, DDI_EINVAL);
+
+ if (enmCmd != DDI_ATTACH)
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioAttach: Invalid enmCmd=%#x expected DDI_ATTACH\n", enmCmd));
+ return DDI_FAILURE;
+ }
+
+ int rc = DDI_FAILURE;
+ PVIRTIODEVICE pDevice = RTMemAllocZ(sizeof(VIRTIODEVICE));
+ if (RT_LIKELY(pDevice))
+ {
+ pDevice->pDip = pDip;
+ pDevice->pDeviceOps = pDeviceOps;
+ pDevice->pHyperOps = pHyperOps;
+
+ pDevice->pvDevice = pDevice->pDeviceOps->pfnAlloc(pDevice);
+ if (RT_LIKELY(pDevice->pvDevice))
+ {
+ pDevice->pvHyper = pDevice->pHyperOps->pfnAlloc(pDevice);
+ if (RT_LIKELY(pDevice->pvHyper))
+ {
+ /*
+ * Attach hypervisor interface and obtain features supported by host.
+ */
+ rc = pDevice->pHyperOps->pfnAttach(pDevice);
+ if (rc == DDI_SUCCESS)
+ {
+ pDevice->fHostFeatures = pDevice->pHyperOps->pfnGetFeatures(pDevice);
+ LogFlow((VIRTIOLOGNAME ":VirtioAttach: Host features=%#x\n", pDevice->fHostFeatures));
+
+ /*
+ * Attach the device type interface.
+ */
+ rc = pDevice->pDeviceOps->pfnAttach(pDevice);
+ if (rc == DDI_SUCCESS)
+ {
+ ddi_set_driver_private(pDip, pDevice);
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioAttach: DeviceOps pfnAttach failed. rc=%d\n", rc));
+
+ pDevice->pHyperOps->pfnDetach(pDevice);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioAttach: HyperOps pfnAttach failed. rc=%d\n", rc));
+
+ pDevice->pHyperOps->pfnFree(pDevice);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioAttach: HyperOps->pfnAlloc failed!\n"));
+
+ pDevice->pDeviceOps->pfnFree(pDevice);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioAttach: DeviceOps->pfnAlloc failed!\n"));
+
+ RTMemFree(pDevice);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioAttach: failed to alloc %u bytes for device structure.\n", sizeof(VIRTIODEVICE)));
+
+ return DDI_FAILURE;
+}
+
+
+/**
+ * Virtio Detach routine that should be called from all Virtio drivers' detach
+ * routines.
+ *
+ * @param pDip The module structure instance.
+ * @param enmCmd Operation type (detach/suspend).
+ *
+ * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
+ */
+int VirtioDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioDetach pDip=%p enmCmd=%d\n", pDip, enmCmd));
+
+ PVIRTIODEVICE pDevice = ddi_get_driver_private(pDip);
+ if (RT_UNLIKELY(!pDevice))
+ return DDI_FAILURE;
+
+ if (enmCmd != DDI_DETACH)
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioDetach: Invalid enmCmd=%#x expected DDI_DETACH.\n", enmCmd));
+ return DDI_FAILURE;
+ }
+
+ int rc = pDevice->pDeviceOps->pfnDetach(pDevice);
+ if (rc == DDI_SUCCESS)
+ {
+ pDevice->pHyperOps->pfnDetach(pDevice);
+ pDevice->pDeviceOps->pfnFree(pDevice);
+ pDevice->pvDevice = NULL;
+ pDevice->pHyperOps->pfnFree(pDevice);
+ pDevice->pvHyper = NULL;
+
+ ddi_set_driver_private(pDevice->pDip, NULL);
+ RTMemFree(pDevice);
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioDetach: DeviceOps pfnDetach failed. rc=%d\n", rc));
+
+ return DDI_FAILURE;
+}
+
+
+/**
+ * Allocates a Virtio Queue object and assigns it an index.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param Index Queue index.
+ *
+ * @return A pointer to a Virtio Queue instance.
+ */
+PVIRTIOQUEUE VirtioGetQueue(PVIRTIODEVICE pDevice, uint16_t Index)
+{
+ PVIRTIOQUEUE pQueue = RTMemAllocZ(sizeof(VIRTIOQUEUE));
+ if (RT_UNLIKELY(!pQueue))
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioGetQueue: failed to alloc memory for %u bytes.\n", sizeof(VIRTIOQUEUE)));
+ return NULL;
+ }
+
+ pQueue->QueueIndex = Index;
+ pQueue->pvData = pDevice->pHyperOps->pfnGetQueue(pDevice, pQueue);
+ if (RT_UNLIKELY(!pQueue->pvData))
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioGetQueue: HyperOps GetQueue failed!\n"));
+ RTMemFree(pQueue);
+ return NULL;
+ }
+
+ AssertReturn(pQueue->pQueue, NULL);
+ AssertReturn(pQueue->Ring.cDesc > 0, NULL);
+
+ /** @todo enable interrupt. */
+
+ return pQueue;
+}
+
+
+/**
+ * Puts a queue and destroys the instance.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param pQueue Pointer to the Virtio queue.
+ */
+void VirtioPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue)
+{
+ AssertReturnVoid(pDevice);
+ AssertReturnVoid(pQueue);
+
+ pDevice->pHyperOps->pfnPutQueue(pDevice, pQueue);
+ RTMemFree(pQueue);
+}
+
diff --git a/src/VBox/Additions/solaris/Virtio/Virtio-solaris.h b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.h
new file mode 100644
index 00000000..9badca92
--- /dev/null
+++ b/src/VBox/Additions/solaris/Virtio/Virtio-solaris.h
@@ -0,0 +1,205 @@
+/* $Id: Virtio-solaris.h $ */
+/** @file
+ * VirtualBox Guest Additions: Virtio Driver for Solaris, header.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_solaris_Virtio_Virtio_solaris_h
+#define GA_INCLUDED_SRC_solaris_Virtio_Virtio_solaris_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <sys/sunddi.h>
+
+/** Release log descriptive prefix. */
+#define VIRTIOLOGNAME "Virtio"
+/** Buffer continues via the Next field */
+#define VIRTIO_FLAGS_RING_DESC_NEXT RT_BIT(0)
+/** Buffer is write-only, else is read-only. */
+#define VIRTIO_FLAGS_RING_DESC_WRITE RT_BIT(1)
+/** Indirect buffer (buffer contains list of buffer descriptors) */
+#define VIRTIO_FLAGS_RING_DESC_INDIRECT RT_BIT(2)
+
+/* Values from our Virtio.h */
+#define VIRTIO_PCI_STATUS_ACK 0x01
+#define VIRTIO_PCI_STATUS_DRV 0x02
+#define VIRTIO_PCI_STATUS_DRV_OK 0x04
+#define VIRTIO_PCI_STATUS_FAILED 0x80
+
+/**
+ * The ring descriptor table refers to the buffers the guest is using for the
+ * device.
+ */
+struct VirtioRingDesc
+{
+ uint64_t AddrBuf; /* Physical address of buffer. */
+ uint32_t cbBuf; /* Length of the buffer in bytes. */
+ uint16_t fFlags; /* Flags of the next buffer. */
+ uint16_t Next; /* Index of the next buffer. */
+};
+typedef struct VirtioRingDesc VIRTIORINGDESC;
+typedef VIRTIORINGDESC *PVIRTIORINGDESC;
+
+/**
+ * The available ring refers to what descriptors are being offered to the
+ * device.
+ */
+struct VirtioRingAvail
+{
+ uint16_t fFlags; /* Interrupt supression flag. */
+ uint16_t Index; /* Index of available ring. */
+ uint16_t aRings[1]; /* Array of indices into descriptor table. */
+};
+typedef struct VirtioRingAvail VIRTIORINGAVAIL;
+typedef VIRTIORINGAVAIL *PVIRTIORINGAVAIL;
+
+/**
+ * The used ring refers to the buffers the device is done using them. The
+ * element is a pair-descriptor refers to the buffer once the device is done
+ * with the buffer.
+ */
+struct VirtioRingUsedElem
+{
+ uint32_t Index; /* Index of start of used descriptor chain. */
+ uint32_t cbElem; /* Number of bytes written into the buffer. */
+};
+typedef struct VirtioRingUsedElem VIRTIORINGUSEDELEM;
+typedef VIRTIORINGUSEDELEM *PVIRTIORINGUSEDELEM;
+
+/**
+ * The Virtio Ring which contains the descriptors.
+ */
+struct VirtioRing
+{
+ uint_t cDesc; /* Number of descriptors. */
+ PVIRTIORINGDESC pRingDesc; /* Pointer to ring descriptor. */
+ PVIRTIORINGAVAIL pRingAvail; /* Pointer to available ring. */
+ PVIRTIORINGUSEDELEM pRingUsedElem; /* Pointer to used ring element. */
+};
+typedef struct VirtioRing VIRTIORING;
+typedef VIRTIORING *PVIRTIORING;
+
+struct VirtioDevice;
+struct VirtioQueue;
+
+typedef void *(*PFNVIRTIOALLOC)(struct VirtioDevice *pDevice);
+typedef void (*PFNVIRTIOFREE)(struct VirtioDevice *pDevice);
+typedef int (*PFNVIRTIOATTACH)(struct VirtioDevice *pDevice);
+typedef int (*PFNVIRTIODETACH)(struct VirtioDevice *pDevice);
+typedef uint32_t (*PFNVIRTIOGETFEATURES)(struct VirtioDevice *pDevice);
+typedef void (*PFNVIRTIOSETFEATURES)(struct VirtioDevice *pDevice, uint32_t fFeatures);
+typedef void (*PFNVIRTIOGET)(struct VirtioDevice *pDevice, off_t off, void *pv, size_t cb);
+typedef void (*PFNVIRTIOSET)(struct VirtioDevice *pDevice, off_t off, void *pv, size_t cb);
+typedef void *(*PFNVIRTIOGETQUEUE)(struct VirtioDevice *pDevice, struct VirtioQueue *pQueue);
+typedef void (*PFNVIRTIOPUTQUEUE)(struct VirtioDevice *pDevice, struct VirtioQueue *pQueue);
+typedef int (*PFNVIRTIONOTIFYQUEUE)(struct VirtioDevice *pDevice, struct VirtioQueue *pQueue);
+typedef void (*PFNVIRTIOSETSTATUS)(struct VirtioDevice *pDevice, uint8_t Status);
+
+/**
+ * Virtio device operations.
+ */
+struct VirtioDeviceOps
+{
+ PFNVIRTIOALLOC pfnAlloc; /* Device alloc. */
+ PFNVIRTIOFREE pfnFree; /* Device free. */
+ PFNVIRTIOATTACH pfnAttach; /* Device attach. */
+ PFNVIRTIODETACH pfnDetach; /* Device detach. */
+};
+typedef struct VirtioDeviceOps VIRTIODEVICEOPS;
+typedef VIRTIODEVICEOPS *PVIRTIODEVICEOPS;
+
+/**
+ * Hypervisor access operations.
+ */
+struct VirtioHyperOps
+{
+ PFNVIRTIOALLOC pfnAlloc; /* Hypervisor alloc. */
+ PFNVIRTIOFREE pfnFree; /* Hypervisor free */
+ PFNVIRTIOATTACH pfnAttach; /* Hypervisor attach. */
+ PFNVIRTIODETACH pfnDetach; /* Hypervisor detach. */
+ PFNVIRTIOGETFEATURES pfnGetFeatures; /* Hypervisor get features. */
+ PFNVIRTIOSETFEATURES pfnSetFeatures; /* Hypervisor set features. */
+ PFNVIRTIONOTIFYQUEUE pfnNotifyQueue; /* Hypervisor notify queue. */
+ PFNVIRTIOGET pfnGet; /* Hypervisor get. */
+ PFNVIRTIOSET pfnSet; /* Hypervisor set. */
+ PFNVIRTIOGETQUEUE pfnGetQueue; /* Hypervisor get queue. */
+ PFNVIRTIOPUTQUEUE pfnPutQueue; /* Hypervisor put queue. */
+ PFNVIRTIOSETSTATUS pfnSetStatus; /* Hypervisor set status. */
+};
+typedef struct VirtioHyperOps VIRTIOHYPEROPS;
+typedef VIRTIOHYPEROPS *PVIRTIOHYPEROPS;
+
+/**
+ * Virtio Queue into which buffers are posted.
+ */
+struct VirtioQueue
+{
+ VIRTIORING Ring; /* Ring buffer of this queue. */
+ uint16_t cBufs; /* Number of pushed, unnotified buffers. */
+ uint16_t FreeHeadIndex; /* Index of head of free list. */
+ uint16_t QueueIndex; /* Index of this queue. */
+ caddr_t pQueue; /* Allocated DMA region for queue. */
+ void *pvData; /* Queue private data. */
+};
+typedef struct VirtioQueue VIRTIOQUEUE;
+typedef VIRTIOQUEUE *PVIRTIOQUEUE;
+
+/**
+ * Virtio device descriptor, common to all Virtio devices.
+ */
+struct VirtioDevice
+{
+ dev_info_t *pDip; /* OS device info. */
+ PVIRTIODEVICEOPS pDeviceOps; /* Device hooks. */
+ void *pvDevice; /* Device opaque data. */
+ PVIRTIOHYPEROPS pHyperOps; /* Hypervisor hooks. */
+ void *pvHyper; /* Hypervisor opaque data. */
+ uint32_t fHostFeatures; /* Features provided by the host. */
+};
+typedef struct VirtioDevice VIRTIODEVICE;
+typedef VIRTIODEVICE *PVIRTIODEVICE;
+
+
+int VirtioAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd, PVIRTIODEVICEOPS pDeviceOps, PVIRTIOHYPEROPS pHyperOps);
+int VirtioDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd);
+
+PVIRTIOQUEUE VirtioGetQueue(PVIRTIODEVICE pDevice, uint16_t Index);
+void VirtioPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue);
+
+void VirtioRingInit(PVIRTIOQUEUE pQueue, uint_t cDescs, caddr_t virtBuf, ulong_t Align);
+int VirtioRingPush(PVIRTIOQUEUE pQueue, paddr_t physBuf, uint32_t cbBuf, uint16_t fFlags);
+size_t VirtioRingSize(uint64_t cElements, ulong_t Align);
+
+#endif /* !GA_INCLUDED_SRC_solaris_Virtio_Virtio_solaris_h */
+
diff --git a/src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c b/src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c
new file mode 100644
index 00000000..dee46ad7
--- /dev/null
+++ b/src/VBox/Additions/solaris/Virtio/VirtioNet-solaris.c
@@ -0,0 +1,852 @@
+/* $Id: VirtioNet-solaris.c $ */
+/** @file
+ * VirtualBox Guest Additions - Virtio Network Driver for Solaris.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "Virtio-solaris.h"
+#include "VirtioPci-solaris.h"
+
+#include <sys/conf.h>
+#include <sys/sunddi.h>
+#include <sys/mac_provider.h>
+#include <sys/strsun.h>
+#include <sys/cmn_err.h>
+
+#include <iprt/assert.h>
+#include <iprt/initterm.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define DEVICE_NAME "virtnet"
+/** The module descriptions as seen in 'modinfo'. */
+#define DEVICE_DESC_DRV "VirtualBox VirtioNet"
+
+/** Copied from "mac_ether.h" - why the heck is this not public?? All Solaris
+ * mac clients use it... */
+#define MAC_PLUGIN_IDENT_ETHER "mac_ether"
+
+/* Copied from our Virtio Device emulation. */
+#define VIRTIO_NET_CSUM 0x00000001 /* Host handles pkts w/ partial csum */
+#define VIRTIO_NET_GUEST_CSUM 0x00000002 /* Guest handles pkts w/ partial csum */
+#define VIRTIO_NET_MAC 0x00000020 /* Host has given MAC address. */
+#define VIRTIO_NET_GSO 0x00000040 /* Host handles pkts w/ any GSO type */
+#define VIRTIO_NET_GUEST_TSO4 0x00000080 /* Guest can handle TSOv4 in. */
+#define VIRTIO_NET_GUEST_TSO6 0x00000100 /* Guest can handle TSOv6 in. */
+#define VIRTIO_NET_GUEST_ECN 0x00000200 /* Guest can handle TSO[6] w/ ECN in. */
+#define VIRTIO_NET_GUEST_UFO 0x00000400 /* Guest can handle UFO in. */
+#define VIRTIO_NET_HOST_TSO4 0x00000800 /* Host can handle TSOv4 in. */
+#define VIRTIO_NET_HOST_TSO6 0x00001000 /* Host can handle TSOv6 in. */
+#define VIRTIO_NET_HOST_ECN 0x00002000 /* Host can handle TSO[6] w/ ECN in. */
+#define VIRTIO_NET_HOST_UFO 0x00004000 /* Host can handle UFO in. */
+#define VIRTIO_NET_MRG_RXBUF 0x00008000 /* Host can merge receive buffers. */
+#define VIRTIO_NET_STATUS 0x00010000 /* virtio_net_config.status available */
+#define VIRTIO_NET_CTRL_VQ 0x00020000 /* Control channel available */
+#define VIRTIO_NET_CTRL_RX 0x00040000 /* Control channel RX mode support */
+#define VIRTIO_NET_CTRL_VLAN 0x00080000 /* Control channel VLAN filtering */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void *VirtioNetDevAlloc(PVIRTIODEVICE pDevice);
+static void VirtioNetDevFree(PVIRTIODEVICE pDevice);
+static int VirtioNetDevAttach(PVIRTIODEVICE pDevice);
+static int VirtioNetDevDetach(PVIRTIODEVICE pDevice);
+
+static int VirtioNetAttach(dev_info_t *pDip, ddi_attach_cmd_t Cmd);
+static int VirtioNetDetach(dev_info_t *pDip, ddi_detach_cmd_t Cmd);
+
+static int VirtioNetStat(void *pvArg, uint_t cmdStat, uint64_t *pu64Val);
+static int VirtioNetStart(void *pvArg);
+static void VirtioNetStop(void *pvArg);
+static int VirtioNetSetPromisc(void *pvArg, boolean_t fPromiscOn);
+static int VirtioNetSetMulticast(void *pvArg, boolean_t fAdd, const uint8_t *pbMac);
+static int VirtioNetSetUnicast(void *pvArg, const uint8_t *pbMac);
+static boolean_t VirtioNetGetCapab(void *pvArg, mac_capab_t Capab, void *pvCapabData);
+static mblk_t *VirtioNetXmit(void *pvArg, mblk_t *pMsg);
+static uint_t VirtioNetISR(caddr_t addrArg);
+
+static int VirtioNetAttachQueues(PVIRTIODEVICE pDevice);
+static void VirtioNetDetachQueues(PVIRTIODEVICE pDevice);
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Device operations for Virtio Net.
+ */
+VIRTIODEVICEOPS g_VirtioDeviceOpsNet =
+{
+ VirtioNetDevAlloc,
+ VirtioNetDevFree,
+ VirtioNetDevAttach,
+ VirtioNetDevDetach
+};
+
+/**
+ * virtio_net_t: Private data per Virtio Device.
+ */
+typedef struct virtio_net_t
+{
+ mac_handle_t hMac; /* Handle to the MAC layer. */
+ RTMAC MacAddr; /* MAC address. */
+ PVIRTIOQUEUE pRxQueue; /* Receive Queue. */
+ PVIRTIOQUEUE pTxQueue; /* Xmit Queue. */
+ PVIRTIOQUEUE pCtrlQueue; /* Control Queue. */
+ kmem_cache_t *pTxCache; /* TX buffer cache. */
+} virtio_net_t;
+
+/**
+ * virtio_txbuf_t: Virtio Net TX buffer.
+ */
+typedef struct virtio_net_txbuf_t
+{
+ ddi_dma_handle_t hDMA; /* DMA TX handle. */
+} virtio_net_txbuf_t;
+
+/*
+ * virtio_net_header_t: Virtio Net TX/RX buffer header.
+ */
+typedef struct virtio_net_header_t
+{
+ uint8_t u8Flags; /* Flags. */
+ uint8_t u8GSOType; /* GSO type. */
+ uint16_t u16HdrLen; /* Length of this header. */
+ uint16_t u16GSOSize; /* GSO length if applicable. */
+ uint16_t u16CSumStart; /* Checksum start.*/
+ uint16_t u16CSumOffset; /* Checksum offset.*/
+} virtio_net_header_t;
+
+/**
+ * MAC layer hooks for VirtioNet.
+ */
+static mac_callbacks_t g_VirtioNetCallbacks =
+{
+ MC_GETCAPAB, /* Mask of available extra hooks. */
+ VirtioNetStat,
+ VirtioNetStart,
+ VirtioNetStop,
+ VirtioNetSetPromisc,
+ VirtioNetSetMulticast,
+ VirtioNetSetUnicast,
+ VirtioNetXmit,
+ NULL, /* Reserved. */
+ NULL, /* IOCtl. */
+ VirtioNetGetCapab,
+};
+
+/**
+ * DMA transfer attributes for Xmit/Recv. buffers.
+ */
+static ddi_dma_attr_t g_VirtioNetBufDmaAttr =
+{
+ DMA_ATTR_V0, /* Version. */
+ 0, /* Lowest usable address. */
+ 0xffffffffffffffffULL, /* Highest usable address. */
+ 0x7fffffff, /* Maximum DMAable byte count. */
+ MMU_PAGESIZE, /* Alignment in bytes. */
+ 0x7ff, /* Bitmap of burst sizes */
+ 1, /* Minimum transfer. */
+ 0xffffffffU, /* Maximum transfer. */
+ 0xffffffffffffffffULL, /* Maximum segment length. */
+ 1, /* Maximum number of segments. */
+ 1, /* Granularity. */
+ 0 /* Flags (reserved). */
+};
+
+/**
+ * cb_ops: driver char/block entry points
+ */
+static struct cb_ops g_VirtioNetCbOps =
+{
+ nulldev, /* cb open */
+ nulldev, /* cb close */
+ nodev, /* b strategy */
+ nodev, /* b dump */
+ nodev, /* b print */
+ nodev, /* cb read */
+ nodev, /* cb write */
+ nodev, /* cb ioctl */
+ nodev, /* c devmap */
+ nodev, /* c mmap */
+ nodev, /* c segmap */
+ nochpoll, /* c poll */
+ ddi_prop_op, /* property ops */
+ NULL, /* streamtab */
+ D_MP, /* compat. flag */
+ CB_REV /* revision */
+};
+
+/**
+ * dev_ops: driver entry/exit and other ops.
+ */
+static struct dev_ops g_VirtioNetDevOps =
+{
+ DEVO_REV, /* driver build revision */
+ 0, /* ref count */
+ NULL, /* get info */
+ nulldev, /* identify */
+ nulldev, /* probe */
+ VirtioNetAttach,
+ VirtioNetDetach,
+ nodev, /* reset */
+ &g_VirtioNetCbOps,
+ (struct bus_ops *)0,
+ nodev /* power */
+};
+
+/**
+ * modldrv: export driver specifics to kernel
+ */
+static struct modldrv g_VirtioNetDriver =
+{
+ &mod_driverops, /* extern from kernel */
+ DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV),
+ &g_VirtioNetDevOps
+};
+
+/**
+ * modlinkage: export install/remove/info to the kernel
+ */
+static struct modlinkage g_VirtioNetModLinkage =
+{
+ MODREV_1, /* loadable module system revision */
+ {
+ &g_VirtioNetDriver, /* driver framework */
+ NULL /* terminate array of linkage structures */
+ }
+};
+
+
+/**
+ * Kernel entry points
+ */
+int _init(void)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":_init\n"));
+
+ /*
+ * Prevent module autounloading.
+ */
+ modctl_t *pModCtl = mod_getctl(&g_VirtioNetModLinkage);
+ if (pModCtl)
+ pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD;
+ else
+ LogRel((VIRTIOLOGNAME ":failed to disable autounloading!\n"));
+
+ /*
+ * Initialize IPRT.
+ */
+ int rc = RTR0Init(0);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize Solaris specific globals here.
+ */
+ mac_init_ops(&g_VirtioNetDevOps, DEVICE_NAME);
+ rc = mod_install(&g_VirtioNetModLinkage);
+ if (!rc)
+ return rc;
+
+ LogRel((VIRTIOLOGNAME ":mod_install failed. rc=%d\n", rc));
+ mac_fini_ops(&g_VirtioNetDevOps);
+ RTR0Term();
+ return rc;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":failed to initialize IPRT (rc=%d)\n", rc));
+
+ return RTErrConvertToErrno(rc);
+}
+
+
+int _fini(void)
+{
+ int rc;
+ LogFlowFunc((VIRTIOLOGNAME ":_fini\n"));
+
+ rc = mod_remove(&g_VirtioNetModLinkage);
+ if (!rc)
+ {
+ mac_fini_ops(&g_VirtioNetDevOps);
+ RTR0Term();
+ }
+ return rc;
+}
+
+
+int _info(struct modinfo *pModInfo)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":_info\n"));
+
+ int rc = mod_info(&g_VirtioNetModLinkage, pModInfo);
+
+ LogFlow((VIRTIOLOGNAME ":_info returns %d\n", rc));
+ return rc;
+}
+
+
+/**
+ * Attach entry point, to attach a device to the system or resume it.
+ *
+ * @param pDip The module structure instance.
+ * @param Cmd Operation type (attach/resume).
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetAttach(dev_info_t *pDip, ddi_attach_cmd_t Cmd)
+{
+ return VirtioAttach(pDip, Cmd, &g_VirtioDeviceOpsNet, &g_VirtioHyperOpsPci);
+}
+
+
+/**
+ * Detach entry point, to detach a device to the system or suspend it.
+ *
+ * @param pDip The module structure instance.
+ * @param Cmd Operation type (detach/suspend).
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetDetach(dev_info_t *pDip, ddi_detach_cmd_t Cmd)
+{
+ return VirtioDetach(pDip, Cmd);
+}
+
+
+/**
+ * Virtio Net TX buffer constructor for kmem_cache_create().
+ *
+ * @param pvBuf Pointer to the allocated buffer.
+ * @param pvArg Opaque private data.
+ * @param fFlags Propagated KM flag values.
+ *
+ * @return 0 on success, or -1 on failure.
+ */
+static int VirtioNetTxBufCreate(void *pvBuf, void *pvArg, int fFlags)
+{
+ virtio_net_txbuf_t *pTxBuf = pvBuf;
+ PVIRTIODEVICE pDevice = pvArg;
+
+ /** @todo ncookies handles? */
+ int rc = ddi_dma_alloc_handle(pDevice->pDip, &g_VirtioNetBufDmaAttr,
+ fFlags & KM_NOSLEEP ? DDI_DMA_DONTWAIT : DDI_DMA_SLEEP,
+ 0 /* Arg */, &pTxBuf->hDMA);
+ if (rc == DDI_SUCCESS)
+ return 0;
+ return -1;
+}
+
+
+/**
+ * Virtio Net TX buffer destructor for kmem_cache_create().
+ *
+ * @param pvBuf Pointer to the allocated buffer.
+ * @param pvArg
+ */
+static void VirtioNetTxBufDestroy(void *pvBuf, void *pvArg)
+{
+ NOREF(pvArg);
+ virtio_net_txbuf_t *pTxBuf = pvBuf;
+
+ ddi_dma_free_handle(&pTxBuf->hDMA);
+}
+
+
+/**
+ * Virtio Net private data allocation routine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ *
+ * @return Allocated private data that must only be freed by calling
+ * VirtioNetDevFree().
+ */
+static void *VirtioNetDevAlloc(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevAlloc pDevice=%p\n", pDevice));
+
+ AssertReturn(pDevice, NULL);
+ virtio_net_t *pNet = RTMemAllocZ(sizeof(virtio_net_t));
+ if (RT_LIKELY(pNet))
+ {
+ /*
+ * Create a kernel memory cache for frequently allocated/deallocated
+ * buffers.
+ */
+ char szCachename[KSTAT_STRLEN];
+ RTStrPrintf(szCachename, sizeof(szCachename), "VirtioNet_Cache_%d", ddi_get_instance(pDevice->pDip));
+ pNet->pTxCache = kmem_cache_create(szCachename, /* Cache name */
+ sizeof(virtio_net_txbuf_t), /* Size of buffers in cache */
+ 0, /* Align */
+ VirtioNetTxBufCreate, /* Buffer constructor */
+ VirtioNetTxBufDestroy, /* Buffer destructor */
+ NULL, /* pfnReclaim */
+ pDevice, /* Private data */
+ NULL, /* "vmp", MBZ (man page) */
+ 0 /* "cflags", MBZ (man page) */
+ );
+ if (RT_LIKELY(pNet->pTxCache))
+ return pNet;
+ else
+ LogRel((VIRTIOLOGNAME ":kmem_cache_create failed.\n"));
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":failed to alloc %u bytes for Net instance.\n", sizeof(virtio_net_t)));
+
+ return NULL;
+}
+
+
+/**
+ * Virtio Net private data free routine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ */
+static void VirtioNetDevFree(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevFree pDevice=%p\n", pDevice));
+ AssertReturnVoid(pDevice);
+
+ virtio_net_t *pNet = pDevice->pvDevice;
+ kmem_cache_destroy(pNet->pTxCache);
+ RTMemFree(pNet);
+ pDevice->pvDevice = NULL;
+}
+
+
+/**
+ * Virtio Net device attach rountine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetDevAttach(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevAttach pDevice=%p\n", pDevice));
+
+ virtio_net_t *pNet = pDevice->pvDevice;
+ mac_register_t *pMacRegHandle = mac_alloc(MAC_VERSION);
+ if (pMacRegHandle)
+ {
+ pMacRegHandle->m_driver = pDevice;
+ pMacRegHandle->m_dip = pDevice->pDip;
+ pMacRegHandle->m_callbacks = &g_VirtioNetCallbacks;
+ pMacRegHandle->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
+ pMacRegHandle->m_min_sdu = 0;
+ pMacRegHandle->m_max_sdu = 1500; /** @todo verify */
+ /** @todo should we set the margin size? */
+ pMacRegHandle->m_src_addr = pNet->MacAddr.au8;
+
+ /*
+ * Get MAC from the host or generate a random MAC address.
+ */
+ if (pDevice->fHostFeatures & VIRTIO_NET_MAC)
+ {
+ pDevice->pHyperOps->pfnGet(pDevice, 0 /* offset */, &pNet->MacAddr.au8, sizeof(pNet->MacAddr));
+ LogFlow((VIRTIOLOGNAME ":VirtioNetDevAttach: Obtained MAC address from host: %.6Rhxs\n", pNet->MacAddr.au8));
+ }
+ else
+ {
+ pNet->MacAddr.au8[0] = 0x08;
+ pNet->MacAddr.au8[1] = 0x00;
+ pNet->MacAddr.au8[2] = 0x27;
+ RTRandBytes(&pNet->MacAddr.au8[3], 3);
+ LogFlow((VIRTIOLOGNAME ":VirtioNetDevAttach: Generated MAC address %.6Rhxs\n", pNet->MacAddr.au8));
+ }
+
+ int rc = VirtioNetAttachQueues(pDevice);
+ if (rc == DDI_SUCCESS)
+ {
+ rc = mac_register(pMacRegHandle, &pNet->hMac);
+ if (rc == 0)
+ {
+ mac_link_update(pNet->hMac, LINK_STATE_DOWN);
+ mac_free(pMacRegHandle);
+ LogFlow((VIRTIOLOGNAME ":VirtioNetDevAttach: successfully registered mac.\n"));
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioNetDevAttach: mac_register failed. rc=%d\n", rc));
+
+ VirtioNetDetachQueues(pDevice);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioNetDevAttach: VirtioNetAttachQueues failed. rc=%d\n", rc));
+
+ mac_free(pMacRegHandle);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioNetDevAttach: mac_alloc failed. Invalid version!?!\n"));
+
+ return DDI_FAILURE;
+}
+
+
+/**
+ * Virtio Net device detach routine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetDevDetach(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDevDetach pDevice=%p\n", pDevice));
+ virtio_net_t *pNet = pDevice->pvDevice;
+
+ int rc = mac_unregister(pNet->hMac);
+ if (rc == 0)
+ {
+ VirtioNetDetachQueues(pDevice);
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioNetDevDetach: mac_unregister failed. rc=%d\n", rc));
+
+ return DDI_FAILURE;
+}
+
+
+/**
+ * Attach the Virtio Net TX, RX and control queues.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetAttachQueues(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioNetAttachQueues pDevice=%p\n", pDevice));
+
+ virtio_net_t *pNet = pDevice->pvDevice;
+
+ pNet->pRxQueue = VirtioGetQueue(pDevice, 0 /* index */ );
+ if (pNet->pRxQueue)
+ {
+ pNet->pTxQueue = VirtioGetQueue(pDevice, 1 /* index */);
+ if (pNet->pTxQueue)
+ {
+ if (pDevice->fHostFeatures & VIRTIO_NET_CTRL_VQ)
+ {
+ pNet->pCtrlQueue = VirtioGetQueue(pDevice, 2 /* index */);
+ if (pNet->pCtrlQueue)
+ {
+ LogFlow((VIRTIOLOGNAME ":VirtioNetAttachQueues successfully obtained 3 queues.\n"));
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioNetAttachQueues: failed to get Control queue.\n"));
+ }
+ else
+ {
+ LogFlow((VIRTIOLOGNAME ":VirtioNetAttachQueues successfully obtained 2 queues.\n"));
+ return DDI_SUCCESS;
+ }
+
+ VirtioPutQueue(pDevice, pNet->pTxQueue);
+ pNet->pTxQueue = NULL;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioNetAttachQueues: failed to get TX queue.\n"));
+
+ VirtioPutQueue(pDevice, pNet->pRxQueue);
+ pNet->pRxQueue = NULL;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioNetAttachQueues: failed to get RX queue.\n"));
+
+ return DDI_FAILURE;
+}
+
+
+/**
+ * Detach the Virtio Net TX, RX and control queues.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ */
+static void VirtioNetDetachQueues(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioNetDetachQueues pDevice=%p\n", pDevice));
+ virtio_net_t *pNet = pDevice->pvDevice;
+
+ if ( pDevice->fHostFeatures & VIRTIO_NET_CTRL_VQ
+ && pNet->pCtrlQueue)
+ VirtioPutQueue(pDevice, pNet->pCtrlQueue);
+
+ if (pNet->pTxCache)
+ VirtioPutQueue(pDevice, pNet->pTxQueue);
+
+ if (pNet->pRxQueue)
+ VirtioPutQueue(pDevice, pNet->pRxQueue);
+}
+
+
+
+/* -=-=-=-=- Virtio Net MAC layer callbacks -=-=-=-=- */
+
+/**
+ * Virtio Net statistics.
+ *
+ * @param pvArg Pointer to private data.
+ * @param cmdStat Which statistics to provide.
+ * @param pu64Val Where to write statistics data.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetStat(void *pvArg, uint_t cmdStat, uint64_t *pu64Val)
+{
+ NOREF(pvArg);
+ NOREF(cmdStat);
+ NOREF(pu64Val);
+ return ENOTSUP;
+}
+
+
+/**
+ * Virtio Net Start.
+ *
+ * @param pvArg Pointer to private data.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetStart(void *pvArg)
+{
+ PVIRTIODEVICE pDevice = pvArg;
+ virtio_net_t *pNet = pDevice->pvDevice;
+ mac_link_update(pNet->hMac, LINK_STATE_UP);
+
+ pDevice->pHyperOps->pfnSetStatus(pDevice, VIRTIO_PCI_STATUS_DRV_OK);
+ return 0;
+}
+
+
+/**
+ * Virtio Net Stop.
+ *
+ * @param pvArg Pointer to private data.
+ */
+static void VirtioNetStop(void *pvArg)
+{
+ PVIRTIODEVICE pDevice = pvArg;
+ virtio_net_t *pNet = pDevice->pvDevice;
+ mac_link_update(pNet->hMac, LINK_STATE_DOWN);
+
+ /*
+ * I don't think we should set status here as the host checks the status on every Xmit. This means pending Xmits
+ * would also be dropped.
+ * @todo: Not sure what's the best way to signal connect/disconnect of the link to the host. Figure it out.
+ */
+}
+
+
+/**
+ * Virtio Net toggle Promiscuous mode.
+ *
+ * @param pvArg Pointer to private data.
+ * @param fPromiscOn Promiscuous On/Off.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetSetPromisc(void *pvArg, boolean_t fPromiscOn)
+{
+ NOREF(pvArg);
+ NOREF(fPromiscOn);
+ return 0;
+}
+
+
+/**
+ * Virtio Net set/add multicast address.
+ *
+ * @param pvArg Pointer to private data.
+ * @param fAdd Whether to add multicast address or not.
+ * @param pbMac Pointer to the multicast MAC address to set/add.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetSetMulticast(void *pvArg, boolean_t fAdd, const uint8_t *pbMac)
+{
+ NOREF(pvArg);
+ NOREF(fAdd);
+ NOREF(pbMac);
+ return 1;
+}
+
+
+/**
+ * Virtio Net set unicast address.
+ *
+ * @param pvArg Pointer to private data.
+ * @param pbMac Pointer to the unicast MAC address to set.
+ *
+ * @return corresponding solaris error code.
+ */
+static int VirtioNetSetUnicast(void *pvArg, const uint8_t *pbMac)
+{
+ NOREF(pvArg);
+ NOREF(pbMac);
+ return ENOTSUP;
+}
+
+
+/**
+ * Virtio Net get capabilities hook.
+ *
+ * @param pvArg Pointer to private data.
+ * @param Capab MAC layer capabilities.
+ * @param pvCapabData Pointer to store capability info.
+ *
+ * @return B_TRUE upon success, otherwise B_FALSE.
+ */
+static boolean_t VirtioNetGetCapab(void *pvArg, mac_capab_t Capab, void *pvCapabData)
+{
+ NOREF(pvArg);
+ NOREF(Capab);
+ NOREF(pvCapabData);
+ return B_FALSE;
+}
+
+
+/**
+ * Virtio Net Xmit hook.
+ *
+ * @param pvArg Pointer to private data.
+ * @param pMsg Pointer to the message.
+ *
+ * @return Pointer to message not Xmited.
+ */
+static mblk_t *VirtioNetXmit(void *pvArg, mblk_t *pMsg)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioNetXmit pMsg=%p\n", pMsg));
+ cmn_err(CE_NOTE, "Xmit pMsg=%p\n", pMsg);
+
+ PVIRTIODEVICE pDevice = pvArg;
+ virtio_net_t *pNet = pDevice->pvDevice;
+ bool fNotify = false;
+
+ while (pMsg)
+ {
+ mblk_t *pNextMsg = pMsg->b_next;
+
+#if 0
+ mblk_t *pHdr = allocb(sizeof(virtio_net_header_t), BPRI_HI);
+ if (RT_UNLIKELY(!pHdr))
+ break;
+
+ virtio_net_header_t *pNetHdr = pHdr->b_rptr;
+ memset(pNetHdr, 0, sizeof(virtio_net_header_t));
+ pNetHdr->u8Flags = VIRTIO_NET_GUEST_CSUM;
+ pNetHdr->u16HdrLen = sizeof(virtio_net_header_t);
+
+ pHdr->b_wptr += sizeof(virtio_net_header_t);
+ pHdr->b_cont = pMsg;
+#endif
+
+ virtio_net_txbuf_t *pTxBuf = kmem_cache_alloc(pNet->pTxCache, KM_SLEEP);
+ if (!pTxBuf)
+ break;
+
+ ddi_dma_cookie_t DmaCookie;
+ uint_t cCookies;
+ int rc = ddi_dma_addr_bind_handle(pTxBuf->hDMA, NULL /* addrspace */, (char *)pMsg->b_rptr, MBLKL(pMsg),
+ DDI_DMA_WRITE | DDI_DMA_STREAMING, DDI_DMA_SLEEP, 0 /* addr */,
+ &DmaCookie, &cCookies);
+ cmn_err(CE_NOTE, "VirtioNetXmit: MBLKL pMsg=%u\n", MBLKL(pMsg));
+ if (rc != DDI_DMA_MAPPED)
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioNetXmit failed to map address to DMA handle. rc=%d\n", rc));
+ kmem_cache_free(pNet->pTxCache, pTxBuf);
+ break;
+ }
+
+ /** @todo get 'cCookies' slots from the ring. */
+
+ for (uint_t i = 0; i < cCookies; i++)
+ {
+ uint16_t fFlags = 0;
+ if (i < cCookies - 1)
+ fFlags |= VIRTIO_FLAGS_RING_DESC_NEXT;
+
+ rc = VirtioRingPush(pNet->pTxQueue, DmaCookie.dmac_laddress, DmaCookie.dmac_size, fFlags);
+ if (RT_FAILURE(rc))
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioNetXmit failed. rc=%Rrc\n", rc));
+ break;
+ }
+
+ ddi_dma_nextcookie(pTxBuf->hDMA, &DmaCookie);
+ }
+
+ pMsg = pNextMsg;
+ fNotify = true;
+ if (RT_FAILURE(rc))
+ {
+ ddi_dma_unbind_handle(pTxBuf->hDMA);
+ break;
+ }
+ }
+
+ if (fNotify)
+ pDevice->pHyperOps->pfnNotifyQueue(pDevice, pNet->pTxQueue);
+
+ return pMsg;
+}
+
+
+/**
+ * Interrupt Service Routine for Virtio Net.
+ *
+ * @param Arg Private data (unused, will be NULL).
+ * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't.
+ */
+static uint_t VirtioNetISR(caddr_t Arg)
+{
+ cmn_err(CE_NOTE, "VirtioNetISR Arg=%p\n", Arg);
+ NOREF(Arg);
+ return DDI_INTR_UNCLAIMED;
+}
+
diff --git a/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c
new file mode 100644
index 00000000..36f69e1b
--- /dev/null
+++ b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c
@@ -0,0 +1,665 @@
+/* $Id: VirtioPci-solaris.c $ */
+/** @file
+ * VirtualBox Guest Additions - Virtio Driver for Solaris, PCI Hypervisor Interface.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "VirtioPci-solaris.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <VBox/log.h>
+
+#include <sys/pci.h>
+#include <sys/param.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * Pci Register offsets.
+ */
+#define VIRTIO_PCI_HOST_FEATURES 0x00
+#define VIRTIO_PCI_GUEST_FEATURES 0x04
+#define VIRTIO_PCI_QUEUE_PFN 0x08
+#define VIRTIO_PCI_QUEUE_NUM 0x0C
+#define VIRTIO_PCI_QUEUE_SEL 0x0E
+#define VIRTIO_PCI_QUEUE_NOTIFY 0x10
+#define VIRTIO_PCI_STATUS 0x12
+#define VIRTIO_PCI_ISR 0x13
+#define VIRTIO_PCI_CONFIG 0x14
+
+#define VIRTIO_PCI_RING_ALIGN PAGE_SIZE
+#define VIRTIO_PCI_QUEUE_ADDR_SHIFT PAGE_SHIFT
+
+/**
+ * virtio_pci_t: Private data per device instance.
+ */
+typedef struct virtio_pci_t
+{
+ ddi_acc_handle_t hIO; /* IO handle */
+ caddr_t addrIOBase; /* IO base address */
+} virtio_pci_t;
+
+/**
+ * virtio_pci_queue_t: Private data per queue instance.
+ */
+typedef struct virtio_pci_queue_t
+{
+ ddi_dma_handle_t hDMA; /* DMA handle. */
+ ddi_acc_handle_t hIO; /* IO handle. */
+ size_t cbBuf; /* Physical address of buffer. */
+ paddr_t physBuf; /* Size of buffer. */
+ pfn_t pageBuf; /* Page frame number of buffer. */
+} virtio_pci_queue_t;
+
+static ddi_device_acc_attr_t g_VirtioPciAccAttrRegs =
+{
+ DDI_DEVICE_ATTR_V0, /* Version */
+ DDI_STRUCTURE_LE_ACC, /* Structural data access in little endian. */
+ DDI_STRICTORDER_ACC, /* Strict ordering. */
+ DDI_DEFAULT_ACC /* Default access, possible panic on errors*/
+};
+
+static ddi_device_acc_attr_t g_VirtioPciAccAttrRing =
+{
+ DDI_DEVICE_ATTR_V0, /* Version. */
+ DDI_NEVERSWAP_ACC, /* Data access with no byte swapping*/
+ DDI_STRICTORDER_ACC, /* Strict ordering. */
+ DDI_DEFAULT_ACC /* Default access, possible panic on errors*/
+};
+
+static ddi_dma_attr_t g_VirtioPciDmaAttrRing =
+{
+ DMA_ATTR_V0, /* Version. */
+ 0, /* Lowest usable address. */
+ 0xffffffffffffffffULL, /* Highest usable address. */
+ 0x7fffffff, /* Maximum DMAable byte count. */
+ VIRTIO_PCI_RING_ALIGN, /* Alignment in bytes. */
+ 0x7ff, /* Bitmap of burst sizes */
+ 1, /* Minimum transfer. */
+ 0xffffffffU, /* Maximum transfer. */
+ 0xffffffffffffffffULL, /* Maximum segment length. */
+ 1, /* Maximum number of segments. */
+ 1, /* Granularity. */
+ 0 /* Flags (reserved). */
+};
+
+/** Pointer to the interrupt handle vector */
+static ddi_intr_handle_t *g_pIntr;
+/** Number of actually allocated interrupt handles */
+static size_t g_cIntrAllocated;
+/** The IRQ Mutex */
+static kmutex_t g_IrqMtx;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void *VirtioPciAlloc(PVIRTIODEVICE pDevice);
+static void VirtioPciFree(PVIRTIODEVICE pDevice);
+static int VirtioPciAttach(PVIRTIODEVICE pDevice);
+static int VirtioPciDetach(PVIRTIODEVICE pDevice);
+static uint32_t VirtioPciGetFeatures(PVIRTIODEVICE pDevice);
+static void VirtioPciSetFeatures(PVIRTIODEVICE pDevice, uint32_t fFeatures);
+static int VirtioPciNotifyQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue);
+static void VirtioPciGet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb);
+static void VirtioPciSet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb);
+static void *VirtioPciGetQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue);
+static void VirtioPciPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue);
+static void VirtioPciSetStatus(PVIRTIODEVICE pDevice, uint8_t Status);
+
+static uint_t VirtioPciISR(caddr_t Arg);
+static int VirtioPciSetupIRQ(dev_info_t *pDip);
+static void VirtioPciRemoveIRQ(dev_info_t *pDip);
+
+/**
+ * Hypervisor operations for Virtio Pci.
+ */
+VIRTIOHYPEROPS g_VirtioHyperOpsPci =
+{
+ VirtioPciAlloc,
+ VirtioPciFree,
+ VirtioPciAttach,
+ VirtioPciDetach,
+ VirtioPciGetFeatures,
+ VirtioPciSetFeatures,
+ VirtioPciNotifyQueue,
+ VirtioPciGet,
+ VirtioPciSet,
+ VirtioPciGetQueue,
+ VirtioPciPutQueue,
+ VirtioPciSetStatus
+};
+
+
+/**
+ * Virtio Pci private data allocation routine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @return Allocated private data structure which must only be freed by calling
+ * VirtioPciFree().
+ */
+static void *VirtioPciAlloc(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciAlloc pDevice=%p\n", pDevice));
+ virtio_pci_t *pPciData = RTMemAllocZ(sizeof(virtio_pci_t));
+ return pPciData;
+}
+
+
+/**
+ * Virtio Pci private data deallocation routine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ */
+static void VirtioPciFree(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciFree pDevice=%p\n", pDevice));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ if (pPciData)
+ {
+ RTMemFree(pDevice->pvHyper);
+ pDevice->pvHyper = NULL;
+ }
+}
+
+
+/**
+ * Virtio Pci attach routine, called from driver attach.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ *
+ * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
+ */
+static int VirtioPciAttach(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciAttach pDevice=%p\n", pDevice));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ AssertReturn(pPciData, DDI_FAILURE);
+
+ int rc = ddi_regs_map_setup(pDevice->pDip,
+ 1, /* reg. num */
+ &pPciData->addrIOBase,
+ 0, /* offset */
+ 0, /* length */
+ &g_VirtioPciAccAttrRegs,
+ &pPciData->hIO);
+ if (rc == DDI_SUCCESS)
+ {
+ /*
+ * Reset the device.
+ */
+ VirtioPciSetStatus(pDevice, 0);
+
+ /*
+ * Add interrupt handler.
+ */
+ VirtioPciSetupIRQ(pDevice->pDip);
+
+ LogFlow((VIRTIOLOGNAME ":VirtioPciAttach: successfully mapped registers.\n"));
+ return DDI_SUCCESS;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciAttach: ddi_regs_map_setup failed. rc=%d\n", rc));
+ return DDI_FAILURE;
+}
+
+
+/**
+ * Virtio Pci detach routine, called from driver detach.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ *
+ * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
+ */
+static int VirtioPciDetach(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciDetach pDevice=%p\n", pDevice));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ AssertReturn(pPciData, DDI_FAILURE);
+
+ VirtioPciRemoveIRQ(pDevice->pDip);
+ ddi_regs_map_free(&pPciData->hIO);
+ return DDI_SUCCESS;
+}
+
+
+/**
+ * Get host supported features.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ *
+ * @return Mask of host features.
+ */
+static uint32_t VirtioPciGetFeatures(PVIRTIODEVICE pDevice)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGetFeatures pDevice=%p\n", pDevice));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ AssertReturn(pPciData, 0);
+
+ return ddi_get32(pPciData->hIO, (uint32_t *)(pPciData->addrIOBase + VIRTIO_PCI_HOST_FEATURES));
+}
+
+
+/**
+ * Set guest supported features.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param u32Features Mask of guest supported features.
+ */
+static void VirtioPciSetFeatures(PVIRTIODEVICE pDevice, uint32_t u32Features)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciSetFeatures pDevice=%p\n", pDevice));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ AssertReturnVoid(pPciData);
+
+ ddi_put32(pPciData->hIO, (uint32_t *)(pPciData->addrIOBase + VIRTIO_PCI_GUEST_FEATURES), u32Features);
+}
+
+
+/**
+ * Update the queue, notify the host.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param pQueue Pointer to the Queue that is doing the notification.
+ *
+ * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
+ */
+static int VirtioPciNotifyQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciNotifyQueue pDevice=%p pQueue=%p\n", pDevice, pQueue));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ AssertReturn(pPciData, DDI_FAILURE);
+
+ pQueue->Ring.pRingAvail->Index += pQueue->cBufs;
+ pQueue->cBufs = 0;
+
+ ASMCompilerBarrier();
+
+ ddi_put16(pPciData->hIO, (uint16_t *)(pPciData->addrIOBase + VIRTIO_PCI_QUEUE_NOTIFY), pQueue->QueueIndex);
+ cmn_err(CE_NOTE, "VirtioPciNotifyQueue\n");
+}
+
+
+
+/**
+ * Virtio Pci set (write) routine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param off Offset into the PCI config space.
+ * @param pv Pointer to the buffer to write from.
+ * @param cb Size of the buffer in bytes.
+ */
+static void VirtioPciSet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciSet pDevice=%p\n", pDevice));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ AssertReturnVoid(pPciData);
+
+ uint8_t *pb = pv;
+ for (size_t i = 0; i < cb; i++, pb++)
+ ddi_put8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_CONFIG + off + i), *pb);
+}
+
+
+/**
+ * Virtio Pci get (read) routine.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param off Offset into the PCI config space.
+ * @param pv Where to store the read data.
+ * @param cb Size of the buffer in bytes.
+ */
+static void VirtioPciGet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGet pDevice=%p off=%u pv=%p cb=%u\n", pDevice, off, pv, cb));
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ AssertReturnVoid(pPciData);
+
+ uint8_t *pb = pv;
+ for (size_t i = 0; i < cb; i++, pb++)
+ *pb = ddi_get8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_CONFIG + off + i));
+}
+
+
+/**
+ * Virtio Pci put queue routine. Places the queue and frees associated queue.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param pQueue Pointer to the queue.
+ */
+static void VirtioPciPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciPutQueue pDevice=%p pQueue=%p\n", pDevice, pQueue));
+ AssertReturnVoid(pDevice);
+ AssertReturnVoid(pQueue);
+
+ virtio_pci_t *pPci = pDevice->pvHyper;
+ AssertReturnVoid(pPci);
+ virtio_pci_queue_t *pPciQueue = pQueue->pvData;
+ if (RT_UNLIKELY(!pPciQueue))
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioPciPutQueue missing Pci queue.\n"));
+ return;
+ }
+
+ ddi_put16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_SEL), pQueue->QueueIndex);
+ ddi_put32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN), 0);
+
+ ddi_dma_unbind_handle(pPciQueue->hDMA);
+ ddi_dma_mem_free(&pPciQueue->hIO);
+ ddi_dma_free_handle(&pPciQueue->hDMA);
+ RTMemFree(pPciQueue);
+}
+
+
+/**
+ * Virtio Pci get queue routine. Allocates a PCI queue and DMA resources.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param pQueue Where to store the queue.
+ *
+ * @return An allocated Virtio Pci queue, or NULL in case of errors.
+ */
+static void *VirtioPciGetQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue)
+{
+ LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGetQueue pDevice=%p pQueue=%p\n", pDevice, pQueue));
+ AssertReturn(pDevice, NULL);
+
+ virtio_pci_t *pPci = pDevice->pvHyper;
+ AssertReturn(pPci, NULL);
+
+ /*
+ * Select a Queue.
+ */
+ ddi_put16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_SEL), pQueue->QueueIndex);
+
+ /*
+ * Get the currently selected Queue's size.
+ */
+ pQueue->Ring.cDesc = ddi_get16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_NUM));
+ if (RT_UNLIKELY(!pQueue->Ring.cDesc))
+ {
+ LogRel((VIRTIOLOGNAME ": VirtioPciGetQueue: Queue[%d] has no descriptors.\n", pQueue->QueueIndex));
+ return NULL;
+ }
+
+ /*
+ * Check if it's already active.
+ */
+ uint32_t QueuePFN = ddi_get32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN));
+ if (QueuePFN != 0)
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: Queue[%d] is already used.\n", pQueue->QueueIndex));
+ return NULL;
+ }
+
+ LogFlow(("Queue[%d] has %d slots.\n", pQueue->QueueIndex, pQueue->Ring.cDesc));
+
+ /*
+ * Allocate and initialize Pci queue data.
+ */
+ virtio_pci_queue_t *pPciQueue = RTMemAllocZ(sizeof(virtio_pci_queue_t));
+ if (pPciQueue)
+ {
+ /*
+ * Setup DMA.
+ */
+ size_t cbQueue = VirtioRingSize(pQueue->Ring.cDesc, VIRTIO_PCI_RING_ALIGN);
+ int rc = ddi_dma_alloc_handle(pDevice->pDip, &g_VirtioPciDmaAttrRing, DDI_DMA_SLEEP, 0 /* addr */, &pPciQueue->hDMA);
+ if (rc == DDI_SUCCESS)
+ {
+ rc = ddi_dma_mem_alloc(pPciQueue->hDMA, cbQueue, &g_VirtioPciAccAttrRing, DDI_DMA_CONSISTENT,
+ DDI_DMA_SLEEP, 0 /* addr */, &pQueue->pQueue, &pPciQueue->cbBuf,
+ &pPciQueue->hIO);
+ if (rc == DDI_SUCCESS)
+ {
+ AssertRelease(pPciQueue->cbBuf >= cbQueue);
+ ddi_dma_cookie_t DmaCookie;
+ uint_t cCookies;
+ rc = ddi_dma_addr_bind_handle(pPciQueue->hDMA, NULL /* addrspace */, pQueue->pQueue, pPciQueue->cbBuf,
+ DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP,
+ 0 /* addr */, &DmaCookie, &cCookies);
+ if (rc == DDI_SUCCESS)
+ {
+ pPciQueue->physBuf = DmaCookie.dmac_laddress;
+ pPciQueue->pageBuf = pPciQueue->physBuf >> VIRTIO_PCI_QUEUE_ADDR_SHIFT;
+
+ LogFlow((VIRTIOLOGNAME ":VirtioPciGetQueue: Queue[%d]%p physBuf=%x pfn of Buf %#x\n", pQueue->QueueIndex,
+ pQueue->pQueue, pPciQueue->physBuf, pPciQueue->pageBuf));
+ cmn_err(CE_NOTE, ":VirtioPciGetQueue: Queue[%d]%p physBuf=%x pfn of Buf %x\n", pQueue->QueueIndex,
+ pQueue->pQueue, pPciQueue->physBuf, pPciQueue->pageBuf);
+
+ /*
+ * Activate the queue and initialize a ring for the queue.
+ */
+ memset(pQueue->pQueue, 0, pPciQueue->cbBuf);
+ ddi_put32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN), pPciQueue->pageBuf);
+ VirtioRingInit(pQueue, pQueue->Ring.cDesc, pQueue->pQueue, VIRTIO_PCI_RING_ALIGN);
+ return pPciQueue;
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_addr_bind_handle failed. rc=%d\n", rc));
+
+ ddi_dma_mem_free(&pPciQueue->hIO);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_mem_alloc failed for %u bytes rc=%d\n", cbQueue, rc));
+
+ ddi_dma_free_handle(&pPciQueue->hDMA);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_alloc_handle failed. rc=%d\n", rc));
+
+ RTMemFree(pPciQueue);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: failed to alloc %u bytes for Pci Queue data.\n", sizeof(virtio_pci_queue_t)));
+
+ return NULL;
+}
+
+
+/**
+ * Set the Virtio PCI status bit.
+ *
+ * @param pDevice Pointer to the Virtio device instance.
+ * @param Status The status to set.
+ */
+static void VirtioPciSetStatus(PVIRTIODEVICE pDevice, uint8_t Status)
+{
+ virtio_pci_t *pPciData = pDevice->pvHyper;
+ ddi_put8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_STATUS), Status);
+}
+
+
+/**
+ * Sets up IRQ for Virtio PCI.
+ *
+ * @param pDip Pointer to the device info structure.
+ *
+ * @return Solaris error code.
+ */
+static int VirtioPciSetupIRQ(dev_info_t *pDip)
+{
+ LogFlow((VIRTIOLOGNAME ":VirtioPciSetupIRQ: pDip=%p\n", pDip));
+ cmn_err(CE_NOTE, "VirtioPciSetupIRQ\n");
+
+ int IntrType = 0;
+ int rc = ddi_intr_get_supported_types(pDip, &IntrType);
+ if (rc == DDI_SUCCESS)
+ {
+ /* We won't need to bother about MSIs. */
+ if (IntrType & DDI_INTR_TYPE_FIXED)
+ {
+ int IntrCount = 0;
+ rc = ddi_intr_get_nintrs(pDip, IntrType, &IntrCount);
+ if ( rc == DDI_SUCCESS
+ && IntrCount > 0)
+ {
+ int IntrAvail = 0;
+ rc = ddi_intr_get_navail(pDip, IntrType, &IntrAvail);
+ if ( rc == DDI_SUCCESS
+ && IntrAvail > 0)
+ {
+ /* Allocated kernel memory for the interrupt handles. The allocation size is stored internally. */
+ g_pIntr = RTMemAllocZ(IntrCount * sizeof(ddi_intr_handle_t));
+ if (g_pIntr)
+ {
+ int IntrAllocated;
+ rc = ddi_intr_alloc(pDip, g_pIntr, IntrType, 0, IntrCount, &IntrAllocated, DDI_INTR_ALLOC_NORMAL);
+ if ( rc == DDI_SUCCESS
+ && IntrAllocated > 0)
+ {
+ g_cIntrAllocated = IntrAllocated;
+ uint_t uIntrPriority;
+ rc = ddi_intr_get_pri(g_pIntr[0], &uIntrPriority);
+ if (rc == DDI_SUCCESS)
+ {
+ /* Initialize the mutex. */
+ mutex_init(&g_IrqMtx, NULL, MUTEX_DRIVER, DDI_INTR_PRI(uIntrPriority));
+
+ /* Assign interrupt handler functions and enable interrupts. */
+ for (int i = 0; i < IntrAllocated; i++)
+ {
+ rc = ddi_intr_add_handler(g_pIntr[i], (ddi_intr_handler_t *)VirtioPciISR,
+ NULL /* No Private Data */, NULL);
+ if (rc == DDI_SUCCESS)
+ rc = ddi_intr_enable(g_pIntr[i]);
+ if (rc != DDI_SUCCESS)
+ {
+ /* Changing local IntrAllocated to hold so-far allocated handles for freeing. */
+ IntrAllocated = i;
+ break;
+ }
+ }
+ if (rc == DDI_SUCCESS)
+ {
+ cmn_err(CE_NOTE, "VirtioPciSetupIRQ success\n");
+ return rc;
+ }
+
+ /* Remove any assigned handlers */
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ failed to assign IRQs allocated=%d\n", IntrAllocated));
+ for (int x = 0; x < IntrAllocated; x++)
+ ddi_intr_remove_handler(g_pIntr[x]);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ failed to get priority of interrupt. rc=%d\n", rc));
+
+ /* Remove allocated IRQs, too bad we can free only one handle at a time. */
+ for (int k = 0; k < g_cIntrAllocated; k++)
+ ddi_intr_free(g_pIntr[k]);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to allocated IRQs. count=%d\n", IntrCount));
+ RTMemFree(g_pIntr);
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to allocated IRQs. count=%d\n", IntrCount));
+ }
+ else
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get or insufficient available IRQs. rc=%d IntrAvail=%d\n",
+ rc, IntrAvail));
+ }
+ }
+ else
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get or insufficient number of IRQs. rc=%d IntrCount=%d\n", rc,
+ IntrCount));
+ }
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: invalid irq type. IntrType=%#x\n", IntrType));
+ }
+ else
+ LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get supported interrupt types\n"));
+ return rc;
+}
+
+
+/**
+ * Removes IRQ for Virtio PCI device.
+ *
+ * @param pDip Pointer to the device info structure.
+ */
+static void VirtioPciRemoveIRQ(dev_info_t *pDip)
+{
+ LogFlow((VIRTIOLOGNAME ":VirtioPciRemoveIRQ pDip=%p:\n", pDip));
+
+ for (int i = 0; i < g_cIntrAllocated; i++)
+ {
+ int rc = ddi_intr_disable(g_pIntr[i]);
+ if (rc == DDI_SUCCESS)
+ {
+ rc = ddi_intr_remove_handler(g_pIntr[i]);
+ if (rc == DDI_SUCCESS)
+ ddi_intr_free(g_pIntr[i]);
+ }
+ }
+ RTMemFree(g_pIntr);
+ mutex_destroy(&g_IrqMtx);
+}
+
+
+/**
+ * Interrupt Service Routine for Virtio PCI device.
+ *
+ * @param Arg Private data (unused, will be NULL).
+ * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't.
+ */
+static uint_t VirtioPciISR(caddr_t Arg)
+{
+ LogFlow((VIRTIOLOGNAME ":VBoxGuestSolarisISR\n"));
+ cmn_err(CE_NOTE, "VBoxGuestSolarisISRd Arg=%p\n", Arg);
+
+ mutex_enter(&g_IrqMtx);
+ bool fOurIRQ = false;
+ /*
+ * Call the DeviceOps ISR routine somehow which should notify all Virtio queues
+ * on the interrupt.
+ */
+ mutex_exit(&g_IrqMtx);
+
+ return fOurIRQ ? DDI_INTR_CLAIMED : DDI_INTR_UNCLAIMED;
+}
+
diff --git a/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h
new file mode 100644
index 00000000..5d0aaf0c
--- /dev/null
+++ b/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.h
@@ -0,0 +1,48 @@
+/* $Id: VirtioPci-solaris.h $ */
+/** @file
+ * VirtualBox Guest Additions: Virtio Driver for Solaris, PCI Hypervisor Interface.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef GA_INCLUDED_SRC_solaris_Virtio_VirtioPci_solaris_h
+#define GA_INCLUDED_SRC_solaris_Virtio_VirtioPci_solaris_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "Virtio-solaris.h"
+
+extern VIRTIOHYPEROPS g_VirtioHyperOpsPci;
+
+#endif /* !GA_INCLUDED_SRC_solaris_Virtio_VirtioPci_solaris_h */
+
diff --git a/src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c b/src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c
new file mode 100644
index 00000000..004075c1
--- /dev/null
+++ b/src/VBox/Additions/solaris/Virtio/VirtioRing-solaris.c
@@ -0,0 +1,148 @@
+/* $Id: VirtioRing-solaris.c $ */
+/** @file
+ * VirtualBox Guest Additions: Virtio Driver for Solaris, Ring implementation.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "Virtio-solaris.h"
+
+#include <iprt/asm.h>
+#include <iprt/cdefs.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+
+#include <sys/cmn_err.h>
+
+/**
+ * Returns the size of the ring in bytes given the number of elements and
+ * alignment requirements.
+ *
+ * @param cElements Number of elements.
+ * @param Align Alignment (must be a power of two).
+ *
+ * @return Size of the Virtio ring.
+ */
+size_t VirtioRingSize(uint64_t cElements, ulong_t Align)
+{
+ size_t cb = 0;
+ cb = cElements * sizeof(VIRTIORINGDESC); /* Ring descriptors. */
+ cb += 2 * sizeof(uint16_t); /* Available flags and index. */
+ cb += cElements * sizeof(uint16_t); /* Available descriptors. */
+
+ size_t cbAlign = RT_ALIGN_Z(cb, Align);
+ cbAlign += 2 * sizeof(uint16_t); /* Used flags and index. */
+ cbAlign += cElements * sizeof(VIRTIORINGUSEDELEM); /* Used descriptors. */
+
+ return cbAlign;
+}
+
+
+/**
+ * Initializes a ring of a queue. This associates the DMA virtual address
+ * with the Ring structure's "pRingDesc".
+ *
+ * @param pQueue Pointer to the Virtio Queue.
+ * @param cDescs Number of descriptors.
+ * @param virtBuf Buffer associated with the ring.
+ * @param Align Alignment (must be power of two).
+ */
+void VirtioRingInit(PVIRTIOQUEUE pQueue, uint_t cDescs, caddr_t virtBuf, ulong_t Align)
+{
+ PVIRTIORING pRing = &pQueue->Ring;
+ pRing->cDesc = cDescs;
+ pRing->pRingDesc = (void *)virtBuf;
+ pRing->pRingAvail = (PVIRTIORINGAVAIL)(virtBuf + (cDescs * sizeof(pRing->pRingDesc[0])));
+ pRing->pRingUsedElem = RT_ALIGN_PT(pRing->pRingAvail + RT_UOFFSETOF_DYN(VIRTIORINGAVAIL, aRings[pQueue->Ring.cDesc]), Align,
+ PVIRTIORINGUSEDELEM);
+
+ for (uint_t i = 0; i < pRing->cDesc - 1; i++)
+ pRing->pRingDesc[i].Next = i + 1;
+
+ pQueue->FreeHeadIndex = 0;
+
+ cmn_err(CE_NOTE, "cDesc=%u pRingDesc=%p pRingAvail=%p\n", pRing->cDesc, pRing->pRingDesc, pRing->pRingAvail);
+}
+
+
+/**
+ * Push a buffer into the ring.
+ *
+ * @param pQueue Pointer to the Virtio queue.
+ * @param physBuf Physical address of the buffer.
+ * @param cbBuf Size of the buffer in bytes.
+ * @param fFlags Buffer flags, see VIRTIO_FLAGS_RING_DESC_*.
+ *
+ * @return IPRT error code.
+ */
+int VirtioRingPush(PVIRTIOQUEUE pQueue, paddr_t physBuf, uint32_t cbBuf, uint16_t fFlags)
+{
+ /*
+ * Claim a slot, fill the buffer and move head pointer.
+ */
+ uint_t FreeIndex = pQueue->FreeHeadIndex;
+ PVIRTIORING pRing = &pQueue->Ring;
+
+ if (FreeIndex >= pRing->cDesc - 1)
+ {
+ LogRel((VIRTIOLOGNAME ":VirtioRingPush: failed. No free descriptors. cDesc=%u\n", pRing->cDesc));
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ PVIRTIORINGDESC pRingDesc = &pRing->pRingDesc[FreeIndex];
+
+ AssertCompile(sizeof(physBuf) == sizeof(pRingDesc->AddrBuf));
+
+ pQueue->cBufs++;
+ uint_t AvailIndex = (pRing->pRingAvail->Index + pQueue->cBufs) % pQueue->Ring.cDesc;
+ pRing->pRingAvail->aRings[AvailIndex - 1] = FreeIndex;
+
+ pRingDesc->AddrBuf = physBuf;
+ pRingDesc->cbBuf = cbBuf;
+ pRingDesc->fFlags = fFlags;
+
+ pQueue->FreeHeadIndex = pRingDesc->Next;
+
+ ASMCompilerBarrier();
+
+ cmn_err(CE_NOTE, "VirtioRingPush: cbBuf=%u FreeIndex=%u AvailIndex=%u cDesc=%u Queue->cBufs=%u\n",
+ cbBuf, FreeIndex, AvailIndex, pQueue->Ring.cDesc,
+ pQueue->cBufs);
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Additions/x11/.scm-settings b/src/VBox/Additions/x11/.scm-settings
new file mode 100644
index 00000000..be559aa2
--- /dev/null
+++ b/src/VBox/Additions/x11/.scm-settings
@@ -0,0 +1,48 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the x11 guest addition components.
+#
+
+#
+# Copyright (C) 2010-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+--filter-out-files /undefined_*
+
+# x11include
+--filter-out-files /x11include/*README
+/x11include/*: --external-copyright --no-convert-tabs --strip-no-trailing-lines --no-strip-trailing-blanks --no-fix-flower-box-markers --no-convert-eol --no-force-final-eol --dont-set-svn-eol --dont-set-svn-keywords --no-fix-header-guards --skip-unicode-checks
+/x11include/*h.in: --treat-as .h
+
+# vboxvideo
+--filter-out-files /vboxvideo/README.testing
+/vboxvideo/*.c|/vboxvideo/*.h: --license-mit
+/vboxvideo/vboxvideo*.c|/vboxvideo/vboxvideo*.h: --license-based-on-mit --no-convert-tabs --dont-set-svn-keywords
+/vboxvideo/edid.c: --license-based-on-mit
+/vboxvideo/setmode.c: --license-based-on-mit
+/VBoxClient/display-svga-xf86cvt.cpp: --license-based-on-mit
+
+# Installer
+/Installer/98vboxadd-xclient: --treat-as .sh
+--filter-out-files /Installer/vboxvideo.ids
+--filter-out-files /Installer/linux_xorg_suse11.conf
+--filter-out-files /Installer/solaris_xorg.conf
+--filter-out-files /Installer/solaris_xorg_modeless.conf
diff --git a/src/VBox/Additions/x11/Installer/98vboxadd-xclient b/src/VBox/Additions/x11/Installer/98vboxadd-xclient
new file mode 100755
index 00000000..718a43bd
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/98vboxadd-xclient
@@ -0,0 +1,48 @@
+#!/bin/sh
+## @file
+# Start the Guest Additions X11 Client
+#
+
+#
+# Copyright (C) 2007-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Sanity check: if non-writeable PID-files are present in the user home
+# directory VBoxClient will fail to start.
+for i in $HOME/.vboxclient-*.pid; do
+ test -w $i || rm -f $i
+done
+
+if ! test -c /dev/vboxguest 2>/dev/null; then
+ # Do not start if the kernel module is not present.
+ # Execute notify-send in the back-ground to avoid racing with sddm,
+ # as notify-send may wait for sddm to start while it waits for us to exit.
+ notify-send "VBoxClient: the VirtualBox kernel service is not running. Exiting." &
+elif test -z "${SSH_CONNECTION}"; then
+ # This script can also be triggered by a connection over SSH, which is not
+ # what we had in mind, so we do not start VBoxClient in that case. We do
+ # not use "exit" here as this script is "source"d, not executed.
+ /usr/bin/VBoxClient --clipboard
+ /usr/bin/VBoxClient --checkhostversion
+ /usr/bin/VBoxClient --seamless
+ /usr/bin/VBoxClient --draganddrop
+ /usr/bin/VBoxClient --vmsvga-session # In case VMSVGA emulation is enabled
+fi
diff --git a/src/VBox/Additions/x11/Installer/linux_xorg_suse11.conf b/src/VBox/Additions/x11/Installer/linux_xorg_suse11.conf
new file mode 100644
index 00000000..c55ea88e
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/linux_xorg_suse11.conf
@@ -0,0 +1,130 @@
+# Default X11 configuration file for SUSE and openSUSE 11 guests in VirtualBox.
+# Based on:
+# SaX generated X11 config file
+# Version: 8.1
+#
+
+Section "Files"
+ FontPath "/usr/share/fonts/misc:unscaled"
+ FontPath "/usr/share/fonts/local"
+ FontPath "/usr/share/fonts/75dpi:unscaled"
+ FontPath "/usr/share/fonts/100dpi:unscaled"
+ FontPath "/usr/share/fonts/Type1"
+ FontPath "/usr/share/fonts/URW"
+ FontPath "/usr/share/fonts/Speedo"
+ FontPath "/usr/share/fonts/PEX"
+ FontPath "/usr/share/fonts/cyrillic"
+ FontPath "/usr/share/fonts/latin2/misc:unscaled"
+ FontPath "/usr/share/fonts/latin2/75dpi:unscaled"
+ FontPath "/usr/share/fonts/latin2/100dpi:unscaled"
+ FontPath "/usr/share/fonts/latin2/Type1"
+ FontPath "/usr/share/fonts/latin7/75dpi:unscaled"
+ FontPath "/usr/share/fonts/baekmuk:unscaled"
+ FontPath "/usr/share/fonts/japanese:unscaled"
+ FontPath "/usr/share/fonts/kwintv"
+ FontPath "/usr/share/fonts/truetype"
+ FontPath "/usr/share/fonts/uni:unscaled"
+ FontPath "/usr/share/fonts/CID"
+ FontPath "/usr/share/fonts/ucs/misc:unscaled"
+ FontPath "/usr/share/fonts/ucs/75dpi:unscaled"
+ FontPath "/usr/share/fonts/ucs/100dpi:unscaled"
+ FontPath "/usr/share/fonts/hellas/misc:unscaled"
+ FontPath "/usr/share/fonts/hellas/75dpi:unscaled"
+ FontPath "/usr/share/fonts/hellas/100dpi:unscaled"
+ FontPath "/usr/share/fonts/hellas/Type1"
+ FontPath "/usr/share/fonts/misc/sgi:unscaled"
+ FontPath "/usr/share/fonts/xtest"
+ FontPath "/opt/kde3/share/fonts"
+ InputDevices "/dev/gpmdata"
+ InputDevices "/dev/input/mice"
+EndSection
+
+Section "ServerFlags"
+ Option "AllowMouseOpenFail" "on"
+ Option "ZapWarning" "on"
+EndSection
+
+Section "Module"
+ Load "dri"
+ Load "dbe"
+ Load "freetype"
+ Load "extmod"
+ Load "glx"
+EndSection
+
+Section "InputDevice"
+ Driver "kbd"
+ Identifier "Keyboard[0]"
+ Option "Protocol" "Standard"
+ Option "XkbLayout" "us"
+ Option "XkbModel" "microsoftpro"
+ Option "XkbRules" "xfree86"
+EndSection
+
+
+Section "InputDevice"
+ Driver "mouse"
+ Identifier "Mouse[1]"
+ Option "Buttons" "9"
+ Option "Device" "/dev/input/mice"
+ Option "Name" "VirtualBox Mouse Buttons"
+ Option "Protocol" "explorerps/2"
+ Option "Vendor" "Oracle Corporation"
+ Option "ZAxisMapping" "4 5"
+EndSection
+
+
+Section "InputDevice"
+ Driver "vboxmouse"
+ Identifier "Mouse[2]"
+ Option "Device" "/dev/vboxguest"
+ Option "Name" "VirtualBox Mouse"
+ Option "Vendor" "Oracle Corporation"
+EndSection
+
+
+Section "Monitor"
+ Identifier "Monitor[0]"
+ ModelName "VirtualBox Virtual Output"
+ VendorName "Oracle Corporation"
+EndSection
+
+
+Section "Screen"
+ SubSection "Display"
+ Depth 24
+ EndSubSection
+ Device "Device[0]"
+ Identifier "Screen[0]"
+ Monitor "Monitor[0]"
+EndSection
+
+
+Section "Device"
+ BoardName "VirtualBox Graphics"
+ Driver "vboxvideo"
+ Identifier "Device[0]"
+ VendorName "Oracle Corporation"
+EndSection
+
+
+
+Section "ServerLayout"
+ Identifier "Layout[all]"
+ InputDevice "Keyboard[0]" "CoreKeyboard"
+ InputDevice "Mouse[1]" "CorePointer"
+ InputDevice "Mouse[2]" "SendCoreEvents"
+ Option "Clone" "off"
+ Option "Xinerama" "off"
+ Screen "Screen[0]"
+EndSection
+
+
+Section "DRI"
+ Group "video"
+ Mode 0660
+EndSection
+
+Section "Extensions"
+EndSection
+
diff --git a/src/VBox/Additions/x11/Installer/solaris_xorg.conf b/src/VBox/Additions/x11/Installer/solaris_xorg.conf
new file mode 100644
index 00000000..dd5d780e
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/solaris_xorg.conf
@@ -0,0 +1,113 @@
+# Default xorg.conf for Solaris guests.
+#
+# This file was created by VirtualBox Additions installer as it
+# was unable to find any existing configuration file for X.
+
+Section "ServerLayout"
+ Identifier "X.org Configured"
+ Screen 0 "Screen0" 0 0
+ InputDevice "Mouse0" "CorePointer"
+ InputDevice "Keyboard0" "CoreKeyboard"
+EndSection
+
+Section "Files"
+ RgbPath "/usr/X11/lib/X11/rgb"
+ FontPath "/usr/X11/lib/X11/fonts/misc/:unscaled"
+ FontPath "/usr/X11/lib/X11/fonts/100dpi/:unscaled"
+ FontPath "/usr/X11/lib/X11/fonts/75dpi/:unscaled"
+ FontPath "/usr/X11/lib/X11/fonts/misc/"
+ FontPath "/usr/X11/lib/X11/fonts/Type1/"
+ FontPath "/usr/X11/lib/X11/fonts/100dpi/"
+ FontPath "/usr/X11/lib/X11/fonts/75dpi/"
+ FontPath "/usr/X11/lib/X11/fonts/TrueType/"
+ FontPath "/usr/X11/lib/X11/fonts/Type1/sun/"
+ FontPath "/usr/X11/lib/X11/fonts/F3bitmaps/"
+EndSection
+
+Section "Module"
+ Load "IA"
+ Load "dbe"
+ Load "extmod"
+ Load "record"
+ Load "xtrap"
+ Load "Glcore"
+ Load "glx"
+ Load "dri"
+ Load "xtsol"
+ Load "type1"
+ Load "freetype"
+EndSection
+
+Section "InputDevice"
+ Identifier "Keyboard0"
+ Driver "kbd"
+EndSection
+
+Section "InputDevice"
+ Identifier "Mouse0"
+ Driver "mouse"
+ Option "CorePointer"
+ Option "Device" "/dev/mouse"
+ Option "Protocol" "auto"
+ Option "ZAxisMapping" "4 5"
+EndSection
+
+Section "Device"
+ Identifier "Generic Video Card"
+ Driver "vesa"
+ BusID "0:2:0"
+EndSection
+
+Section "Monitor"
+ Identifier "Monitor0"
+ VendorName "Monitor Vendor"
+ ModelName "Monitor Model"
+ HorizSync 30.0 - 110.0
+ VertRefresh 50.0 - 150.0
+EndSection
+
+Section "Device"
+ Identifier "Card0"
+ Driver "vesa"
+ VendorName "Unknown Vendor"
+ BoardName "Unknown Board"
+ BusID "PCI:0:2:0"
+EndSection
+
+Section "Screen"
+ Identifier "Screen0"
+ Device "Card0"
+ Monitor "Monitor0"
+ DefaultDepth 24
+ SubSection "Display"
+ ViewPort 0 0
+ Depth 1
+ Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00"
+ EndSubSection
+ SubSection "Display"
+ ViewPort 0 0
+ Depth 4
+ Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00"
+ EndSubSection
+ SubSection "Display"
+ ViewPort 0 0
+ Depth 8
+ Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00"
+ EndSubSection
+ SubSection "Display"
+ ViewPort 0 0
+ Depth 15
+ Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00"
+ EndSubSection
+ SubSection "Display"
+ ViewPort 0 0
+ Depth 16
+ Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00"
+ EndSubSection
+ SubSection "Display"
+ ViewPort 0 0
+ Depth 24
+ Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00"
+ EndSubSection
+EndSection
+
diff --git a/src/VBox/Additions/x11/Installer/solaris_xorg_modeless.conf b/src/VBox/Additions/x11/Installer/solaris_xorg_modeless.conf
new file mode 100644
index 00000000..0468ba24
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/solaris_xorg_modeless.conf
@@ -0,0 +1,81 @@
+# Default xorg.conf for Solaris guests.
+#
+# This file was created by VirtualBox Additions installer as it
+# was unable to find any existing configuration file for X.
+
+Section "ServerLayout"
+ Identifier "X.org Configured"
+ Screen 0 "Screen0" 0 0
+ InputDevice "Mouse0" "CorePointer"
+ InputDevice "Keyboard0" "CoreKeyboard"
+EndSection
+
+Section "Files"
+ FontPath "/usr/X11/lib/X11/fonts/misc/:unscaled"
+ FontPath "/usr/X11/lib/X11/fonts/100dpi/:unscaled"
+ FontPath "/usr/X11/lib/X11/fonts/75dpi/:unscaled"
+ FontPath "/usr/X11/lib/X11/fonts/misc/"
+ FontPath "/usr/X11/lib/X11/fonts/Type1/"
+ FontPath "/usr/X11/lib/X11/fonts/100dpi/"
+ FontPath "/usr/X11/lib/X11/fonts/75dpi/"
+ FontPath "/usr/X11/lib/X11/fonts/TrueType/"
+ FontPath "/usr/X11/lib/X11/fonts/Type1/sun/"
+ FontPath "/usr/X11/lib/X11/fonts/F3bitmaps/"
+EndSection
+
+Section "Module"
+ Load "IA"
+ Load "dbe"
+ Load "extmod"
+ Load "record"
+ Load "xtrap"
+ Load "Glcore"
+ Load "glx"
+ Load "dri"
+ Load "xtsol"
+ Load "type1"
+ Load "freetype"
+EndSection
+
+Section "InputDevice"
+ Identifier "Keyboard0"
+ Driver "kbd"
+EndSection
+
+Section "InputDevice"
+ Identifier "Mouse0"
+ Driver "mouse"
+ Option "CorePointer"
+ Option "Device" "/dev/mouse"
+ Option "Protocol" "auto"
+ Option "ZAxisMapping" "4 5"
+EndSection
+
+Section "Device"
+ Identifier "Generic Video Card"
+ Driver "vesa"
+ BusID "0:2:0"
+EndSection
+
+Section "Monitor"
+ Identifier "Monitor0"
+ VendorName "Monitor Vendor"
+ ModelName "Monitor Model"
+ HorizSync 30.0 - 110.0
+ VertRefresh 50.0 - 150.0
+EndSection
+
+Section "Device"
+ Identifier "Card0"
+ Driver "vesa"
+ VendorName "Unknown Vendor"
+ BoardName "Unknown Board"
+ BusID "PCI:0:2:0"
+EndSection
+
+Section "Screen"
+ Identifier "Screen0"
+ Device "Card0"
+ Monitor "Monitor0"
+EndSection
+
diff --git a/src/VBox/Additions/x11/Installer/vboxclient.desktop b/src/VBox/Additions/x11/Installer/vboxclient.desktop
new file mode 100644
index 00000000..b5e4d863
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/vboxclient.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Type=Application
+Encoding=UTF-8
+Version=1.0
+Name=vboxclient
+Name[C]=vboxclient
+Comment[C]=VirtualBox User Session Services
+Comment=VirtualBox User Session Services
+Comment[it]=Servizi di sessione utente di VirtualBox
+Comment[pl]=Usługi sesji użytkownika VirtualBox
+Exec=/usr/bin/VBoxClient-all
+X-GNOME-Autostart-enabled=true
+X-KDE-autostart-after=panel
diff --git a/src/VBox/Additions/x11/Installer/vboxvideo.ids b/src/VBox/Additions/x11/Installer/vboxvideo.ids
new file mode 100644
index 00000000..8286a95e
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/vboxvideo.ids
@@ -0,0 +1 @@
+80eebeef
diff --git a/src/VBox/Additions/x11/Installer/x11config.pl b/src/VBox/Additions/x11/Installer/x11config.pl
new file mode 100755
index 00000000..dcbb6618
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/x11config.pl
@@ -0,0 +1,139 @@
+#!/usr/bin/perl -w
+# $Id: x11config.pl $
+## @file
+# Guest Additions X11 config update script
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+my $temp="/tmp/xorg.conf";
+my $os_type=`uname -s`;
+my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/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");
+my $CFG;
+my $TMP;
+
+my $config_count = 0;
+
+foreach $cfg (@cfg_files)
+{
+
+ if (open(CFG, $cfg))
+ {
+ open(TMP, ">$temp") or die "Can't create $TMP: $!\n";
+
+ my $have_mouse = 0;
+ my $in_section = 0;
+
+ while (defined ($line = <CFG>))
+ {
+ if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i)
+ {
+ my $section = lc($1);
+ if (($section eq "inputdevice") || ($section eq "device"))
+ {
+ $in_section = 1;
+ }
+ if ($section eq "serverlayout")
+ {
+ $in_layout = 1;
+ }
+ } else {
+ if ($line =~ /^\s*EndSection/i)
+ {
+ $in_section = 0;
+ $in_layout = 0;
+ }
+ }
+
+ if ($in_section)
+ {
+ if ($line =~ /^\s*driver\s+\"(?:mouse|vboxmouse)\"/i)
+ {
+ $line = " Driver \"vboxmouse\"\n Option \"CorePointer\"\n";
+ $have_mouse = 1
+ }
+
+ # Other drivers sending events interfere badly with pointer integration
+ if ($line =~ /^\s*option\s+\"(?:alwayscore|sendcoreevents|corepointer)\"/i)
+ {
+ $line = "";
+ }
+
+ # Solaris specific: /dev/kdmouse for PS/2 and not /dev/mouse
+ if ($os_type =~ 'SunOS')
+ {
+ if ($line =~ /^\s*option\s+\"(?:device)\"\s+\"(?:\/dev\/mouse)\"/i)
+ {
+ $line = " Option \"Device\" \"\/dev\/kdmouse\"\n"
+ }
+ }
+
+ if ($line =~ /^\s*driver\s+\"(?:fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i)
+ {
+ $line = " Driver \"vboxvideo\"\n";
+ }
+ }
+ if ($in_layout)
+ {
+ # Other drivers sending events interfere badly with pointer integration
+ if ( $line =~ /^\s*inputdevice.*\"(?:alwayscore|sendcoreevents)\"/i)
+ {
+ $line = "";
+ }
+ }
+ print TMP $line;
+ }
+
+ if (!$have_mouse) {
+ print TMP "\n";
+ print TMP "Section \"InputDevice\"\n";
+ print TMP " Identifier \"VBoxMouse\"\n";
+ print TMP " Driver \"vboxmouse\"\n";
+ if ($os_type eq 'SunOS')
+ {
+ print TMP " Option \"Device\" \"\/dev\/kdmouse\"\n";
+ }
+ print TMP " Option \"CorePointer\"\n";
+ print TMP "EndSection\n";
+ }
+ close(TMP);
+
+ rename $cfg, $cfg.".bak";
+ system("cp $temp $cfg");
+ unlink $temp;
+
+ # Solaris specific: Rename our modified .xorg.conf to xorg.conf for it to be used
+ if (($os_type =~ 'SunOS') && ($cfg =~ '/etc/X11/.xorg.conf'))
+ {
+ system("mv -f $cfg /etc/X11/xorg.conf");
+ }
+
+ $config_count++;
+ }
+}
+
+$config_count != 0 or die "Could not find any X11 configuration files";
diff --git a/src/VBox/Additions/x11/Installer/x11config.sh b/src/VBox/Additions/x11/Installer/x11config.sh
new file mode 100755
index 00000000..a17bf47f
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/x11config.sh
@@ -0,0 +1,171 @@
+#!/bin/sh
+# $Id: x11config.sh $
+## @file
+# Guest Additions X11 config update script
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+auto_mouse=""
+auto_keyboard=""
+no_bak=""
+old_mouse_dev="/dev/psaux"
+video_driver="vboxvideo"
+
+tab=`printf '\t'`
+
+ALL_SECTIONS=\
+'^[ '$tab']*[Ss][Ee][Cc][Tt][Ii][Oo][Nn][ '$tab']*'\
+'"\([Ii][Nn][Pp][Uu][Tt][Dd][Ee][Vv][Ii][Cc][Ee]\|'\
+'[Dd][Ee][Vv][Ii][Cc][Ee]\|'\
+'[Ss][Ee][Rr][Vv][Ee][Rr][Ll][Aa][Yy][Oo][Uu][Tt]\|'\
+'[Ss][Cc][Rr][Ee][Ee][Nn]\|'\
+'[Mm][Oo][Nn][Ii][Tt][Oo][Rr]\|'\
+'[Kk][Ee][Yy][Bb][Oo][Aa][Rr][Dd]\|'\
+'[Pp][Oo][Ii][Nn][Tt][Ee][Rr]\)"'
+# ^\s*Section\s*"(InputDevice|Device|ServerLayout|Screen|Monitor|Keyboard|Pointer)"
+
+KBD_SECTION='^[ '$tab']*[Ss][Ee][Cc][Tt][Ii][Oo][Nn][ '$tab']*"'\
+'[Ii][Nn][Pp][Uu][Tt][Dd][Ee][Vv][Ii][Cc][Ee]"' # ^\s*section\s*\"inputdevice\"
+
+END_SECTION='[Ee][Nn][Dd][Ss][Ee][Cc][Tt][Ii][Oo][Nn]' # EndSection
+
+OPT_XKB='^[ '$tab']*option[ '$tab'][ '$tab']*"xkb'
+
+DRIVER_KBD='^[ '$tab']*[Dd][Rr][Ii][Vv][Ee][Rr][ '$tab'][ '$tab']*'\
+'"\(kbd\|keyboard\)"'
+# ^\s*driver\s+\"(kbd|keyboard)\"
+
+reconfigure()
+{
+ cfg="$1"
+ tmp="$cfg.vbox.tmp"
+ test -w "$cfg" || { echo "$cfg does not exist"; return; }
+ rm -f "$tmp"
+ test ! -e "$tmp" || { echo "Failed to delete $tmp"; return; }
+ touch "$tmp"
+ test -w "$tmp" || { echo "Failed to create $tmp"; return; }
+ xkb_opts="`cat "$cfg" | sed -n -e "/$KBD_SECTION/,/$END_SECTION/p" |
+ grep -i "$OPT_XKB"`"
+ kbd_drv="`cat "$cfg" | sed -n -e "/$KBD_SECTION/,/$END_SECTION/p" |
+ sed -n -e "0,/$DRIVER_KBD/s/$DRIVER_KBD/\\1/p"`"
+ test -z "${kbd_drv}" && test -z "${auto_keyboard}" && kbd_drv=keyboard
+ cat > "$tmp" << EOF
+# VirtualBox generated configuration file
+# based on $cfg.
+EOF
+ cat "$cfg" | sed -e "/$ALL_SECTIONS/,/$END_SECTION/s/\\(.*\\)/# \\1/" >> "$tmp"
+ test -n "$kbd_drv" && cat >> "$tmp" << EOF
+Section "InputDevice"
+ Identifier "Keyboard[0]"
+ Driver "$kbd_drv"
+$xkb_opts
+ Option "Protocol" "Standard"
+ Option "CoreKeyboard"
+EndSection
+EOF
+ kbd_line=""
+ test -n "$kbd_drv" && kbd_line=' InputDevice "Keyboard[0]" "CoreKeyboard"'
+ test -z "$auto_mouse" &&
+ cat >> "$tmp" << EOF
+
+Section "InputDevice"
+ Driver "mouse"
+ Identifier "Mouse[1]"
+ Option "Buttons" "9"
+ Option "Device" "$old_mouse_dev"
+ Option "Name" "VirtualBox Mouse Buttons"
+ Option "Protocol" "explorerps/2"
+ Option "Vendor" "Oracle Corporation"
+ Option "ZAxisMapping" "4 5"
+ Option "CorePointer"
+EndSection
+
+Section "InputDevice"
+ Driver "vboxmouse"
+ Identifier "Mouse[2]"
+ Option "Device" "/dev/vboxguest"
+ Option "Name" "VirtualBox Mouse"
+ Option "Vendor" "Oracle Corporation"
+ Option "SendCoreEvents"
+EndSection
+
+Section "ServerLayout"
+ Identifier "Layout[all]"
+${kbd_line}
+ InputDevice "Mouse[1]" "CorePointer"
+ InputDevice "Mouse[2]" "SendCoreEvents"
+ Option "Clone" "off"
+ Option "Xinerama" "off"
+ Screen "Screen[0]"
+EndSection
+EOF
+
+ cat >> "$tmp" << EOF
+
+Section "Monitor"
+ Identifier "Monitor[0]"
+ ModelName "VirtualBox Virtual Output"
+ VendorName "Oracle Corporation"
+EndSection
+
+Section "Device"
+ BoardName "VirtualBox Graphics"
+ Driver "${video_driver}"
+ Identifier "Device[0]"
+ VendorName "Oracle Corporation"
+EndSection
+
+Section "Screen"
+ SubSection "Display"
+ Depth 24
+ EndSubSection
+ Device "Device[0]"
+ Identifier "Screen[0]"
+ Monitor "Monitor[0]"
+EndSection
+EOF
+
+ test -n "$no_bak" -o -f "$cfg.vbox" || cp "$cfg" "$cfg.vbox"
+ test -n "$no_bak" || mv "$cfg" "$cfg.bak"
+ mv "$tmp" "$cfg"
+}
+
+while test -n "$1"
+do
+ case "$1" in
+ --autoMouse)
+ auto_mouse=1 ;;
+ --autoKeyboard)
+ auto_keyboard=1 ;;
+ --noBak)
+ no_bak=1 ;;
+ --nopsaux)
+ old_mouse_dev="/dev/input/mice" ;;
+ --vmsvga)
+ video_driver="vmware" ;;
+ *)
+ reconfigure "$1" ;;
+ esac
+ shift
+done
diff --git a/src/VBox/Additions/x11/Installer/x11config15.pl b/src/VBox/Additions/x11/Installer/x11config15.pl
new file mode 100755
index 00000000..7f404c95
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/x11config15.pl
@@ -0,0 +1,97 @@
+#!/usr/bin/perl -w
+# $Id: x11config15.pl $
+## @file
+# Guest Additions X11 config update script for X.org 1.5
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# What this script does: X.org 1.5 introduces full hardware autodetection
+# and no longer requires the user to provide an X.org configuration file.
+# However, if such a file is provided, it will override autodetection of
+# the graphics card (not of vboxmouse as far as I can see). Although this
+# would normally be the user's business, at least Fedora 9 still generates
+# a configuration file by default, so we have to rewrite it if we want
+# the additions to work on a default guest installation. So we simply go
+# through any configuration files we may find on the system and replace
+# references to VESA or framebuffer drivers (which might be autodetected
+# for use on a VirtualBox guest) and replace them with vboxvideo.
+
+use File::Copy;
+
+my $temp="/tmp/xorg.conf";
+# The list of possible names of X.org configuration files
+my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/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");
+my $CFG;
+my $TMP;
+
+# Subroutine to roll back after a partial installation
+sub do_fail {
+ foreach $cfg (@cfg_files) {
+ move $cfg.".vbox", $cfg;
+ unlink $cfg.".vbox";
+ }
+ die $1;
+}
+
+# Perform the substitution on any configuration file we may find.
+foreach $cfg (@cfg_files) {
+
+ if (open(CFG, $cfg)) {
+ open(TMP, ">$temp")
+ or &do_fail("Can't create $TMP: $!\n");
+
+ while (defined ($line = <CFG>)) {
+ if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i) {
+ my $section = lc($1);
+ if ($section eq "device") {
+ $in_section = 1;
+ }
+ } else {
+ if ($line =~ /^\s*EndSection/i) {
+ $in_section = 0;
+ }
+ }
+
+ if ($in_section) {
+ if ($line =~ /^\s*driver\s+\"(fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i) {
+ $line =~ s/(fbdev|vga|vesa|vboxvideo|ChangeMe)/vboxvideo/i;
+ }
+ }
+ print TMP $line;
+ }
+ close(TMP);
+
+ # We do not overwrite existing $cfg.".vbox" files because that will
+ # likely ruin any future attempts to uninstall the additions
+ copy $cfg, $cfg.".bak";
+ if (! -e $cfg.".vbox") {
+ rename $cfg, $cfg.".vbox";
+ }
+ copy $temp, $cfg
+ or &do_fail("Could not overwrite configuration file $cfg! Exiting...");
+ unlink $temp;
+ }
+}
diff --git a/src/VBox/Additions/x11/Installer/x11config15sol.pl b/src/VBox/Additions/x11/Installer/x11config15sol.pl
new file mode 100755
index 00000000..a4bcedf8
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/x11config15sol.pl
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+# $Id: x11config15sol.pl $
+## @file
+# Guest Additions X11 config update script
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+use strict;
+use warnings;
+
+my $temp="/tmp/xorg.conf";
+my $os_type=`uname -s`;
+my @cfg_files = ("/etc/X11/xorg.conf", "/etc/X11/.xorg.conf", "/etc/X11/xorg.conf-4", "/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");
+
+## @todo: r=ramshankar: Hmm, why do we use the same variable name with upper/lower case for different variables?
+my $cfg;
+my $CFG;
+my $TMP;
+my $line;
+my $config_count = 0;
+
+# Command line options
+if ($#ARGV < 0)
+{
+ die "x11config15sol.pl: Missing driver name argument to configure for X.org";
+}
+my $driver_name = $ARGV[0];
+
+# Loop through all possible config files and change them. It's done this wasy for hysterical raisins
+# as we didn't know what the correct config file is so we update all of them. However, for Solaris it's
+# most likely -only- one of the 2 config files (/etc/X11/xorg.conf, /etc/X11/.xorg.conf).
+foreach $cfg (@cfg_files)
+{
+
+ if (($os_type =~ 'SunOS') && (defined $ENV{PKG_INSTALL_ROOT}))
+ {
+ $cfg = $ENV{PKG_INSTALL_ROOT}.$cfg;
+ }
+ if (open(CFG, $cfg))
+ {
+ open(TMP, ">$temp") or die "Can't create $TMP: $!\n";
+
+ my $in_section = 0;
+
+ while (defined ($line = <CFG>))
+ {
+ if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i)
+ {
+ my $section = lc($1);
+ if ($section eq "device")
+ {
+ $in_section = 1;
+ }
+ }
+ else
+ {
+ if ($line =~ /^\s*EndSection/i)
+ {
+ $in_section = 0;
+ }
+ }
+
+ if ($in_section)
+ {
+ if ($line =~ /^\s*driver\s+\"(?:fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i)
+ {
+ $line = " Driver \"$driver_name\"\n";
+ }
+ }
+ print TMP $line;
+ }
+
+ close(TMP);
+
+ rename $cfg, $cfg.".bak";
+ system("cp $temp $cfg");
+ unlink $temp;
+
+ # Solaris specific: Rename our modified .xorg.conf to xorg.conf for it to be used
+ if ((defined $ENV{PKG_INSTALL_ROOT}) &&
+ ($os_type =~ 'SunOS') && ($cfg =~ "$ENV{PKG_INSTALL_ROOT}/etc/X11/.xorg.conf"))
+ {
+ system("mv -f $cfg $ENV{PKG_INSTALL_ROOT}/etc/X11/xorg.conf");
+ }
+ else
+ {
+ if (($os_type =~ 'SunOS') && ($cfg =~ '/etc/X11/.xorg.conf'))
+ {
+ system("mv -f $cfg /etc/X11/xorg.conf");
+ }
+ }
+
+ $config_count++;
+ }
+}
+
+$config_count != 0 or die "Could not find any X11 configuration files.";
+
diff --git a/src/VBox/Additions/x11/Installer/x11config15suse.pl b/src/VBox/Additions/x11/Installer/x11config15suse.pl
new file mode 100755
index 00000000..d221a785
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/x11config15suse.pl
@@ -0,0 +1,166 @@
+#!/usr/bin/perl -w
+# $Id: x11config15suse.pl $
+## @file
+# Guest Additions X11 config update script
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+# Versions of (open)SUSE which ship X.Org Server 1.5 still do not enable
+# mouse autodetection, so on these systems we have to enable vboxmouse in the
+# X.Org configuration file as well as vboxvideo. When uninstalling, we enable
+# the fbdev driver, which SUSE prefers over vesa, and we leave the references
+# to vboxmouse in place, as without the driver they are harmless.
+
+use File::Copy;
+
+# This is the file name for the temporary file we write the new configuration
+# to.
+# @todo: perl must have an API for generating this
+my $temp="/tmp/xorg.conf";
+# The list of possible names of X.org configuration files
+my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/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");
+# File descriptor of the old configuration file
+my $CFG;
+# File descriptor of the temporary file
+my $TMP;
+
+# The name of the mouse driver we are enabling
+my $mousedrv = 'vboxmouse';
+# The name of the video driver we are enabling
+my $videodrv= 'vboxvideo';
+
+# If we are uninstalling, restore the old video driver
+if (@ARGV && "$ARGV[0]" eq 'uninstall')
+{
+ $videodrv = 'fbdev' # SUSE prefers this one
+}
+
+# How many different configuration files have we found?
+my $config_count = 0;
+
+# Subroutine to roll back after a partial installation
+sub do_fail {
+ foreach $cfg (@cfg_files) {
+ move "$cfg.vbox", $cfg;
+ unlink "$cfg.vbox";
+ }
+ die $_[0];
+}
+
+# Perform the substitution on any configuration file we may find.
+foreach $cfg (@cfg_files) {
+
+ if (open(CFG, $cfg)) {
+ open(TMP, ">$temp")
+ or &do_fail("Can't create $TMP: $!\n");
+
+ my $have_mouse = 0;
+ my $in_section = 0;
+ my $in_layout = 0;
+
+ # Go through the configuration file line by line
+ while (defined ($line = <CFG>)) {
+ # Look for the start of sections
+ if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i) {
+ my $section = lc($1);
+ # And see if they are device or input device sections
+ if (($section eq "inputdevice") || $section eq "device") {
+ $in_section = 1;
+ }
+ # Or server layout sections
+ if ($section eq "serverlayout")
+ {
+ $in_section = 1;
+ $in_layout = 1;
+ }
+ } else {
+ if ($line =~ /^\s*EndSection/i && $in_layout) {
+ # We always add this to the end of the server layout.
+ print TMP " InputDevice \"VBoxMouse\"\n"
+ }
+ if ($line =~ /^\s*EndSection/i) {
+ $in_section = 0;
+ $in_layout = 0;
+ }
+ }
+
+ if ($in_section) {
+ # Inside sections, look for any graphics drivers and replace
+ # them with our one.
+ if ($line =~ /^\s*driver\s+\"(fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i) {
+ $line =~ s/(fbdev|vga|vesa|vboxvideo|ChangeMe)/$videodrv/i;
+ }
+ # Also keep track of whether this configuration file contains
+ # an input device section for vboxmouse. If it does, we don't
+ # need to add one later.
+ if ($line =~ /^\s*driver\s+\"(?:vboxmouse)\"/i)
+ {
+ $have_mouse = 1
+ }
+
+ # We add vboxmouse to the server layout section ourselves, so
+ # remove any existing references to it.
+ if ( $line =~ /^\s*inputdevice.*\"vboxmouse\"/i)
+ {
+ $line = "";
+ }
+ }
+ print TMP $line;
+ }
+
+ # We always add a vboxmouse section at the end for SUSE guests using
+ # X.Org 1.5 if vboxmouse is not referenced anywhere else in the file,
+ # and we do not remove it when we uninstall the additions, as it will
+ # not do any harm if it is left.
+ if (!$have_mouse) {
+ print TMP "\n";
+ print TMP "Section \"InputDevice\"\n";
+ print TMP " Identifier \"VBoxMouse\"\n";
+ print TMP " Driver \"$mousedrv\"\n";
+ print TMP " Option \"Device\" \"\/dev\/vboxguest\"\n";
+ print TMP " Option \"SendCoreEvents\" \"on\"\n";
+ print TMP "EndSection\n";
+ }
+ close(TMP);
+
+ # We do not overwrite existing "$cfg.vbox" files in order to keep a
+ # record of what the configuration looked like before the very first
+ # installation of the additions.
+ copy $cfg, "$cfg.bak";
+ if (! -e "$cfg.vbox") {
+ rename $cfg, "$cfg.vbox";
+ }
+ copy $temp, $cfg
+ or &do_fail("Could not overwrite configuration file $cfg! Exiting...");
+ unlink $temp;
+
+ $config_count++;
+ }
+}
+
+# Warn if we did not find any configuration files
+$config_count != 0 or die "Could not find any X11 configuration files";
+
diff --git a/src/VBox/Additions/x11/Installer/x11restore.pl b/src/VBox/Additions/x11/Installer/x11restore.pl
new file mode 100755
index 00000000..f1c485fc
--- /dev/null
+++ b/src/VBox/Additions/x11/Installer/x11restore.pl
@@ -0,0 +1,79 @@
+#!/usr/bin/perl -w
+# $Id: x11restore.pl $
+## @file
+# Restore xorg.conf while removing Guest Additions.
+#
+
+#
+# Copyright (C) 2008-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+
+my $os_type=`uname -s`;
+my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/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");
+my $CFG;
+my $BAK;
+
+my $config_count = 0;
+my $vboxpresent = "vboxvideo";
+
+foreach $cfg (@cfg_files)
+{
+ if (($os_type =~ 'SunOS') && (defined $ENV{PKG_INSTALL_ROOT}))
+ {
+ $cfg = $ENV{PKG_INSTALL_ROOT}.$cfg;
+ }
+ if (open(CFG, $cfg))
+ {
+ @array=<CFG>;
+ close(CFG);
+
+ foreach $line (@array)
+ {
+ if ($line =~ /$vboxpresent/)
+ {
+ if (open(BAK, $cfg.".bak"))
+ {
+ close(BAK);
+ print("Restoring $cfg.back to $cfg.\n");
+ rename $cfg.".bak", $cfg;
+ }
+ else
+ {
+ # On Solaris just delete existing conf if backup is not found (Possible on distros like Indiana)
+ if ($os_type =~ 'SunOS')
+ {
+ unlink $cfg
+ }
+ else
+ {
+ die "Failed to restore xorg.conf! Your existing config. still uses VirtualBox drivers!!";
+ }
+ }
+ }
+ }
+ $config_count++;
+ }
+}
diff --git a/src/VBox/Additions/x11/Makefile.kmk b/src/VBox/Additions/x11/Makefile.kmk
new file mode 100644
index 00000000..2e8619d8
--- /dev/null
+++ b/src/VBox/Additions/x11/Makefile.kmk
@@ -0,0 +1,43 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the X11 Guest Additions.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Include sub-makefiles.
+if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris)
+ include $(PATH_SUB_CURRENT)/VBoxClient/Makefile.kmk
+ ifndef VBOX_NO_LEGACY_XORG_X11
+ include $(PATH_SUB_CURRENT)/vboxvideo/Makefile.kmk
+ ifn1of ($(KBUILD_TARGET), netbsd solaris)
+ include $(PATH_SUB_CURRENT)/vboxmouse/Makefile.kmk
+ endif
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/x11/VBoxClient/Makefile.kmk b/src/VBox/Additions/x11/VBoxClient/Makefile.kmk
new file mode 100644
index 00000000..1687b59b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/Makefile.kmk
@@ -0,0 +1,236 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox Guest Addition X11 Client.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Use header files from our tree for randr and xinerama. and don't link but rather dlopen libXrand
+# This is mostly because the GA build boxes can have a very old xrandr lib, so instead of linking we dlopen.
+VBOX_WITH_DISTRO_XRAND_XINERAMA =
+
+# We don't yet have a seamless mode compilation flag, so define it here unconditionally.
+VBOX_WITH_SEAMLESS := 1
+
+#
+# VBoxClient - clipboard and seamless.
+#
+PROGRAMS += VBoxClient
+#
+# Please make sure that you grep the source tree and modify all occurences accordingly
+# if you rename the following program name.
+#
+PROGRAMS.linux += VBoxDRMClient
+
+VBoxClient_TEMPLATE = VBoxGuestR3Exe
+VBoxClient_DEFS += VBOX_X11_CLIPBOARD VBOX_WITH_HGCM
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxClient_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ VBoxClient_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+ifdef VBOX_WITH_DBUS
+ VBoxClient_DEFS += VBOX_WITH_DBUS
+endif
+
+VBoxClient_DEFS.linux += _GNU_SOURCE
+VBoxClient_INCS = $(VBOX_GRAPHICS_INCS)
+VBoxClient_INCS += ../x11include/panoramiXproto-1.1
+VBoxClient_INCS += ../x11include/libXrandr-1.5.2
+VBoxClient_INCS += ../x11include/randrproto-1.5.0
+VBoxClient_SOURCES = \
+ main.cpp \
+ logging.cpp
+
+VBoxDRMClient_TEMPLATE = VBoxGuestR3Exe
+ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ VBoxDRMClient_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+else
+ VBoxDRMClient_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+endif
+VBoxDRMClient_SOURCES = \
+ display-drm.cpp \
+ display-ipc.cpp \
+ logging.cpp
+
+
+VBoxClient_SOURCES.linux = \
+ chk_stubs.c
+VBoxClient_LIBPATH = \
+ $(VBOX_LIBPATH32_X11)
+VBoxClient_LIBS.freebsd = \
+ iconv
+VBoxClient_LIBS.linux = \
+ dl
+VBoxClient_LIBS.netbsd = \
+ crypt
+VBoxClient_LIBS.solaris = \
+ dl
+VBoxClient_LIBS = \
+ X11 Xt Xext Xmu
+ifdef VBOX_WITH_DISTRO_XRAND_XINERAMA
+ VBoxClient_DEFS += WITH_DISTRO_XRAND_XINERAMA
+ VBoxClient_LIBS += Xrandr
+endif
+
+# XXX: -L comes from the template, but not rpath
+VBoxClient_LDFLAGS.netbsd = \
+ -Wl,-rpath /usr/X11R7/lib
+
+ifdef VBOX_WITH_DRAG_AND_DROP
+ ifdef VBOX_DND_WITH_XTEST
+ VBoxClient_DEFS += VBOX_DND_WITH_XTEST
+ VBoxClient_LIBS += \
+ Xtst
+ endif
+endif
+
+# This forces the memcpy references in the static libraries to go to
+# __wrap_memcpy, which we can wrap around memcpy@GLIBC_2.2.5. I do not know
+# how else to do that without recompiling or implementing our own memcpy.
+ifeq ($(KBUILD_TARGET),linux)
+ VBoxClient_LDFLAGS.amd64 += \
+ -Wl,--wrap=memcpy
+endif
+
+ifdef VBOX_WITH_GUEST_PROPS
+ VBoxClient_DEFS += VBOX_WITH_GUEST_PROPS
+ VBoxClient_SOURCES += \
+ hostversion.cpp
+ VBoxDRMClient_DEFS += VBOX_WITH_GUEST_PROPS
+endif
+
+ifdef VBOX_WITH_DRAG_AND_DROP
+ VBoxClient_DEFS += \
+ VBOX_WITH_DRAG_AND_DROP \
+ $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,)
+ VBoxClient_SOURCES += \
+ draganddrop.cpp
+ VBoxClient_LIBS += \
+ $(VBOX_LIB_VBGL_R3) \
+ $(PATH_STAGE_LIB)/additions/VBoxDnDGuestR3Lib$(VBOX_SUFF_LIB)
+endif
+
+ifdef VBOX_WITH_SEAMLESS
+ VBoxClient_DEFS += VBOX_WITH_SEAMLESS
+ VBoxClient_SOURCES += \
+ seamless.cpp \
+ seamless-x11.cpp
+endif
+
+ifdef VBOX_WITH_VMSVGA
+ VBoxClient_DEFS += VBOX_WITH_VMSVGA
+ VBoxClient_SOURCES += \
+ display.cpp \
+ display-svga-x11.cpp \
+ display-svga-xf86cvt.cpp
+ VBoxClient_SOURCES.linux += \
+ display-svga-session.cpp \
+ display-ipc.cpp \
+ display-helper-gnome3.cpp \
+ display-helper-generic.cpp
+
+ ### include $(PATH_SUB_CURRENT)/helpers/Makefile.kmk
+endif
+
+ifdef VBOX_WITH_SHARED_CLIPBOARD
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \
+ clipboard.cpp
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS VBOX_WITH_SHARED_CLIPBOARD_GUEST
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp
+ endif
+ ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ # @todo Make this dynamic loading more generic.
+ VBoxClient_SOURCES += \
+ $(PATH_ROOT)/src/VBox/ImageMounter/vboximg-mount/fuse.cpp \
+ clipboard-fuse.cpp
+ # @todo Ditto.
+ VBoxClient_INCS += \
+ $(PATH_ROOT)/src/VBox/ImageMounter/vboximg-mount
+ endif
+ endif
+endif
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris)
+
+ # Set this in LocalConfig.kmk if you are working on the X11 clipboard service
+ # to automatically run the unit test at build time.
+ # OTHERS += $(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto.run
+
+ PROGRAMS += tstSeamlessX11-auto
+ tstSeamlessX11-auto_TEMPLATE = VBoxR3TstExe
+ tstSeamlessX11-auto_SOURCES = \
+ testcase/tstSeamlessX11-auto.cpp \
+ seamless-x11.cpp
+ tstSeamlessX11-auto_DEFS = TESTCASE
+ tstSeamlessX11-auto_LIBS = \
+ $(LIB_RUNTIME)
+
+ TESTING += $(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto
+ $$(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto.run: \
+ $$(tstSeamlessX11-auto_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstSeamlessX11-auto_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ #
+ # Additional testcase designed to be run manually, which initiates and starts the Linux
+ # guest client part of the seamless additions in the host, faking seamless events from
+ # the host and writing information about guest events to standard output.
+ #
+ PROGRAMS += tstSeamlessX11
+ tstSeamlessX11_TEMPLATE = VBoxR3TstExe
+ tstSeamlessX11_SOURCES = \
+ testcase/tstSeamlessX11.cpp \
+ seamless.cpp \
+ seamless-x11.cpp
+ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING
+ tstSeamlessX11_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)"
+ else
+ tstSeamlessX11_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\"
+ endif
+ tstSeamlessX11_LIBPATH = \
+ $(VBOX_LIBPATH_X11)
+ tstSeamlessX11_LIBS = \
+ $(LIB_RUNTIME) \
+ Xext \
+ Xmu \
+ X11
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/x11/VBoxClient/VBoxClient.h b/src/VBox/Additions/x11/VBoxClient/VBoxClient.h
new file mode 100644
index 00000000..2bbf88bf
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/VBoxClient.h
@@ -0,0 +1,148 @@
+/* $Id: VBoxClient.h $ */
+/** @file
+ *
+ * VirtualBox additions user session daemon.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/log.h>
+#include <iprt/cpp/utils.h>
+#include <iprt/string.h>
+
+/** Environment variable which is exported when in Wayland Desktop Environment. */
+#define VBCL_ENV_WAYLAND_DISPLAY "WAYLAND_DISPLAY"
+/** Environment variable which contains information about currently running Desktop Environment. */
+#define VBCL_ENV_XDG_CURRENT_DESKTOP "XDG_CURRENT_DESKTOP"
+/** Environment variable which contains information about currently running session (X11, Wayland, etc). */
+#define VBCL_ENV_XDG_SESSION_TYPE "XDG_SESSION_TYPE"
+
+int VBClShowNotify(const char *pszHeader, const char *pszBody);
+
+void VBClLogInfo(const char *pszFormat, ...);
+void VBClLogError(const char *pszFormat, ...);
+void VBClLogFatalError(const char *pszFormat, ...);
+void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...);
+
+int VBClLogCreate(const char *pszLogFile);
+void VBClLogSetLogPrefix(const char *pszPrefix);
+void VBClLogDestroy(void);
+
+/**
+ * Detect if user is running on Wayland by checking corresponding environment variable.
+ *
+ * @returns True if Wayland has been detected, False otherwise.
+ */
+extern bool VBClHasWayland(void);
+
+/** Call clean-up for the current service and exit. */
+extern void VBClShutdown(bool fExit = true);
+
+/**
+ * A service descriptor.
+ */
+typedef struct
+{
+ /** The short service name. 16 chars maximum (RTTHREAD_NAME_LEN). */
+ const char *pszName;
+ /** The longer service name. */
+ const char *pszDesc;
+ /** Get the services default path to pidfile, relative to $HOME */
+ /** @todo Should this also have a component relative to the X server number?
+ */
+ const char *pszPidFilePathTemplate;
+ /** The usage options stuff for the --help screen. */
+ const char *pszUsage;
+ /** The option descriptions for the --help screen. */
+ const char *pszOptions;
+
+ /**
+ * Tries to parse the given command line option.
+ *
+ * @returns 0 if we parsed, -1 if it didn't and anything else means exit.
+ * @param ppszShort If not NULL it points to the short option iterator. a short argument.
+ * If NULL examine argv[*pi].
+ * @param argc The argument count.
+ * @param argv The argument vector.
+ * @param pi The argument vector index. Update if any value(s) are eaten.
+ */
+ DECLCALLBACKMEMBER(int, pfnOption,(const char **ppszShort, int argc, char **argv, int *pi));
+
+ /**
+ * Called before parsing arguments.
+ * @returns VBox status code, or
+ * VERR_NOT_AVAILABLE if service is supported on this platform in general but not available at the moment.
+ * VERR_NOT_SUPPORTED if service is not supported on this platform. */
+ DECLCALLBACKMEMBER(int, pfnInit,(void));
+
+ /** Called from the worker thread.
+ *
+ * @returns VBox status code.
+ * @retval VINF_SUCCESS if exitting because *pfShutdown was set.
+ * @param pfShutdown Pointer to a per service termination flag to check
+ * before and after blocking.
+ */
+ DECLCALLBACKMEMBER(int, pfnWorker,(bool volatile *pfShutdown));
+
+ /**
+ * Asks the service to stop.
+ *
+ * @remarks Will be called from the signal handler.
+ */
+ DECLCALLBACKMEMBER(void, pfnStop,(void));
+
+ /**
+ * Does termination cleanups.
+ *
+ * @remarks This will be called even if pfnInit hasn't been called or pfnStop failed!
+ */
+ DECLCALLBACKMEMBER(int, pfnTerm,(void));
+} VBCLSERVICE;
+/** Pointer to a VBCLSERVICE. */
+typedef VBCLSERVICE *PVBCLSERVICE;
+/** Pointer to a const VBCLSERVICE. */
+typedef VBCLSERVICE const *PCVBCLSERVICE;
+
+RT_C_DECLS_BEGIN
+extern VBCLSERVICE g_SvcClipboard;
+extern VBCLSERVICE g_SvcDisplayDRM;
+extern VBCLSERVICE g_SvcDisplaySVGA;
+extern VBCLSERVICE g_SvcDisplayLegacy;
+# ifdef RT_OS_LINUX
+extern VBCLSERVICE g_SvcDisplaySVGASession;
+# endif
+extern VBCLSERVICE g_SvcDragAndDrop;
+extern VBCLSERVICE g_SvcHostVersion;
+extern VBCLSERVICE g_SvcSeamless;
+
+extern unsigned g_cVerbosity;
+extern bool g_fDaemonized;
+RT_C_DECLS_END
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/chk_stubs.c b/src/VBox/Additions/x11/VBoxClient/chk_stubs.c
new file mode 100644
index 00000000..9b5093eb
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/chk_stubs.c
@@ -0,0 +1,69 @@
+/* $Id: chk_stubs.c $ */
+/** @file
+ * glibc stubs for the VirtualBox Guest Addition X11 Client.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* If we want the binary to be usable with glibc 2.3, we have to prevent
+ VBoxClient from containing later symbols. This includes resolution of
+ symbols from supc++ and gcc_eh. */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+extern int __sprintf_chk(char *psz, int fFlags, size_t cb, const char *pszFormat, ...);
+int __sprintf_chk(char *psz, int fFlags, size_t cb, const char *pszFormat, ...)
+{
+ int rc;
+ va_list va;
+
+ (void)fFlags;
+ va_start(va, pszFormat);
+ rc = vsnprintf(psz, cb, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+extern void __stack_chk_fail(void);
+void __stack_chk_fail(void)
+{
+ fprintf(stderr, "Stack check failed!\n");
+ _exit(1);
+}
+
+#ifdef __x86_64
+/* Furthermore, wrap references to memcpy to force them to go to the right
+ * version. We are forced to do it this way because the shared libraries
+ * supc++ and gcc_eh contain references which we cannot change. */
+
+extern void *__wrap_memcpy(void *dest, const void *src, size_t n);
+
+asm (".symver memcpy, memcpy@GLIBC_2.2.5");
+void *__wrap_memcpy(void *dest, const void *src, size_t n)
+{
+ return memcpy(dest, src, n);
+}
+#endif
diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp
new file mode 100644
index 00000000..13b1fbc2
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp
@@ -0,0 +1,440 @@
+/** $Id: clipboard.cpp $ */
+/** @file
+ * Guest Additions - X11 Shared Clipboard.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/alloc.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+# include <iprt/dir.h>
+#endif
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+
+#include "VBoxClient.h"
+
+#include "clipboard.h"
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+# include "clipboard-fuse.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Only one context is supported at a time for now. */
+SHCLCONTEXT g_Ctx;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+SHCLFUSECTX g_FuseCtx;
+#endif
+
+
+static DECLCALLBACK(int) vbclOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx,
+ SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt));
+
+ int rc = VINF_SUCCESS;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ if (uFmt == VBOX_SHCL_FMT_URI_LIST)
+ {
+ //rc = VbglR3ClipboardRootListRead()
+ rc = VERR_NO_DATA;
+ }
+ else
+#endif
+ {
+ uint32_t cbRead = 0;
+
+ uint32_t cbData = _4K; /** @todo Make this dynamic. */
+ void *pvData = RTMemAlloc(cbData);
+ if (pvData)
+ {
+ rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /*
+ * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a
+ * larger buffer. The size of the buffer needed is placed in *pcb.
+ * So we start all over again.
+ */
+ if (rc == VINF_BUFFER_OVERFLOW)
+ {
+ /* cbRead contains the size required. */
+
+ cbData = cbRead;
+ pvData = RTMemRealloc(pvData, cbRead);
+ if (pvData)
+ {
+ rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead);
+ if (rc == VINF_BUFFER_OVERFLOW)
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (!cbRead)
+ rc = VERR_NO_DATA;
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcb = cbRead; /* Actual bytes read. */
+ *ppv = pvData;
+ }
+ else
+ {
+ /*
+ * Catch other errors. This also catches the case in which the buffer was
+ * too small a second time, possibly because the clipboard contents
+ * changed half-way through the operation. Since we can't say whether or
+ * not this is actually an error, we just return size 0.
+ */
+ RTMemFree(pvData);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opaque data structure describing a request from the host for clipboard
+ * data, passed in when the request is forwarded to the X11 backend so that
+ * it can be completed correctly.
+ */
+struct CLIPREADCBREQ
+{
+ /** The data format that was requested. */
+ SHCLFORMAT uFmt;
+};
+
+static DECLCALLBACK(int) vbclReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ LogFlowFunc(("fFormats=%#x\n", fFormats));
+
+ int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats);
+ LogFlowFuncLeaveRC(rc);
+
+ return rc;
+}
+
+static DECLCALLBACK(int) vbclOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser)
+{
+ PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser;
+ AssertPtrReturn(pData, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("rcCompletion=%Rrc, Format=0x%x, pv=%p, cb=%RU32\n", pData->rcCompletion, pData->pReq->uFmt, pv, cb));
+
+ Assert((cb == 0 && pv == NULL) || (cb != 0 && pv != NULL));
+ pData->rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pData->pReq->uFmt, pv, cb);
+
+ RTMemFree(pData->pReq);
+
+ LogFlowFuncLeaveRC(pData->rcCompletion);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Connect the guest clipboard to the host.
+ *
+ * @returns VBox status code.
+ */
+static int vboxClipboardConnect(void)
+{
+ LogFlowFuncEnter();
+
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = vbclReportFormatsCallback;
+ Callbacks.pfnOnRequestDataFromSource = vbclOnRequestDataFromSourceCallback;
+ Callbacks.pfnOnSendDataToDest = vbclOnSendDataToDestCallback;
+
+ int rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID);
+ if (RT_FAILURE(rc))
+ ShClX11ThreadStop(&g_Ctx.X11);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error connecting to host service, rc=%Rrc\n", rc);
+
+ VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx);
+ ShClX11Destroy(&g_Ctx.X11);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * The main loop of our clipboard reader.
+ */
+int vboxClipboardMain(void)
+{
+ int rc;
+
+ PSHCLCONTEXT pCtx = &g_Ctx;
+
+ bool fShutdown = false;
+
+ /* The thread waits for incoming messages from the host. */
+ for (;;)
+ {
+ PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT));
+ AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY);
+
+ LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n",
+ pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures));
+
+ uint32_t idMsg = 0;
+ uint32_t cParms = 0;
+ rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent);
+#else
+ rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent);
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogFlowFunc(("Getting next event failed with %Rrc\n", rc));
+
+ VbglR3ClipboardEventFree(pEvent);
+ pEvent = NULL;
+
+ if (fShutdown)
+ break;
+
+ /* Wait a bit before retrying. */
+ RTThreadSleep(1000);
+ continue;
+ }
+ else
+ {
+ AssertPtr(pEvent);
+ LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType));
+
+ switch (pEvent->enmType)
+ {
+ case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS:
+ {
+ ShClX11ReportFormatsToX11(&g_Ctx.X11, pEvent->u.fReportedFormats);
+ break;
+ }
+
+ case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA:
+ {
+ /* The host needs data in the specified format. */
+ CLIPREADCBREQ *pReq;
+ pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ));
+ if (pReq)
+ {
+ pReq->uFmt = pEvent->u.fReadData;
+ ShClX11ReadDataFromX11(&g_Ctx.X11, pReq->uFmt, pReq);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ case VBGLR3CLIPBOARDEVENTTYPE_QUIT:
+ {
+ VBClLogVerbose(2, "Host requested termination\n");
+ fShutdown = true;
+ break;
+ }
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS:
+ {
+ /* Nothing to do here. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+#endif
+ case VBGLR3CLIPBOARDEVENTTYPE_NONE:
+ {
+ /* Nothing to do here. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED);
+ }
+ }
+
+ if (pEvent)
+ {
+ VbglR3ClipboardEventFree(pEvent);
+ pEvent = NULL;
+ }
+ }
+
+ if (fShutdown)
+ break;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclShClInit(void)
+{
+ int rc;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ rc = ShClTransferCtxInit(&g_Ctx.TransferCtx);
+#else
+ rc = VINF_SUCCESS;
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclShClWorker(bool volatile *pfShutdown)
+{
+ RT_NOREF(pfShutdown);
+
+ /* Initialise the guest library. */
+ int rc = vboxClipboardConnect();
+ if (RT_SUCCESS(rc))
+ {
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ rc = VbClShClFUSEInit(&g_FuseCtx, &g_Ctx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VbClShClFUSEStart(&g_FuseCtx);
+ if (RT_SUCCESS(rc))
+ {
+#endif
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ rc = vboxClipboardMain();
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE
+ int rc2 = VbClShClFUSEStop(&g_FuseCtx);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ }
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Service terminated abnormally with %Rrc\n", rc);
+
+ if (rc == VERR_HGCM_SERVICE_NOT_FOUND)
+ rc = VINF_SUCCESS; /* Prevent automatic restart by daemon script if host service not available. */
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclShClStop(void)
+{
+ /* Disconnect from the host service.
+ * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */
+ VbglR3ClipboardDisconnect(g_Ctx.CmdCtx.idClient);
+ g_Ctx.CmdCtx.idClient = 0;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclShClTerm(void)
+{
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ ShClTransferCtxDestroy(&g_Ctx.TransferCtx);
+#endif
+
+ return VINF_SUCCESS;
+}
+
+VBCLSERVICE g_SvcClipboard =
+{
+ "shcl", /* szName */
+ "Shared Clipboard", /* pszDescription */
+ ".vboxclient-clipboard", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclShClInit, /* pfnInit */
+ vbclShClWorker, /* pfnWorker */
+ vbclShClStop, /* pfnStop*/
+ vbclShClTerm /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.h b/src/VBox/Additions/x11/VBoxClient/clipboard.h
new file mode 100644
index 00000000..bc98487b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/clipboard.h
@@ -0,0 +1,49 @@
+/** $Id: clipboard.h $ */
+/** @file
+ * Guest Additions - X11 Shared Clipboard - Main header.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/**
+ * Struct keeping a Shared Clipboard context.
+ */
+struct SHCLCONTEXT
+{
+ /** Client command context */
+ VBGLR3SHCLCMDCTX CmdCtx;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /** Associated transfer data. */
+ SHCLTRANSFERCTX TransferCtx;
+#endif
+ /** X11 clipboard context. */
+ SHCLX11CTX X11;
+};
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/display-drm.cpp b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp
new file mode 100644
index 00000000..fa5d56d2
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp
@@ -0,0 +1,1369 @@
+/* $Id: display-drm.cpp $ */
+/** @file
+ * Guest Additions - VMSVGA guest screen resize service.
+ *
+ * A user space daemon which communicates with VirtualBox host interface
+ * and performs VMSVGA-specific guest screen resize and communicates with
+ * Desktop Environment helper daemon over IPC.
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_vboxdrmcliet VBoxDRMClient - The VMSVGA Guest Screen Resize Service
+ *
+ * The VMSVGA Guest Screen Resize Service is a service which communicates with a
+ * guest VMSVGA driver and triggers it to perform screen resize on a guest side.
+ *
+ * This service supposed to be started on early boot. On start it will try to find
+ * compatible VMSVGA graphics card and terminate immediately if not found.
+ * VMSVGA functionality implemented here is only supported starting from vmgfx
+ * driver version 2.10 which was introduced in Linux kernel 4.6. When compatible
+ * graphics card is found, service will start a worker loop in order to receive screen
+ * update data from host and apply it to local DRM stack.
+ *
+ * In addition, it will start a local IPC server in order to communicate with Desktop
+ * Environment specific service(s). Currently, it will propagate to IPC client information regarding to
+ * which display should be set as primary on Desktop Environment level. As well as
+ * receive screen layout change events obtained on Desktop Environment level and send it
+ * back to host, so host and guest will have the same screen layout representation.
+ *
+ * By default, access to IPC server socket is granted to all users. It can be restricted to
+ * only root and users from group 'vboxdrmipc' if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest
+ * property is set and READ-ONLY for guest. User group 'vboxdrmipc' is created during Guest
+ * Additions installation. If this group is removed (or not found due to any reason) prior to
+ * service start, access to IPC server socket will be granted to root only regardless
+ * if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property is set or not. If guest property
+ * is set, but is not READ-ONLY for guest, property is ignored and IPC socket access is granted
+ * to all users.
+ *
+ * Logging is implemented in a way that errors are always printed out, VBClLogVerbose(1) and
+ * VBClLogVerbose(2) are used for debugging purposes. Verbosity level 1 is for messages related
+ * to service itself (excluding IPC), level 2 is for IPC communication debugging. In order to see
+ * logging on a host side it is enough to do:
+ *
+ * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host.
+ *
+ *
+ * Service is running the following threads:
+ *
+ * DrmResizeThread - this thread listens for display layout update events from host.
+ * Once event is received, it either injects new screen layout data into DRM stack,
+ * and/or asks IPC client(s) to set primary display. This thread is accessing IPC
+ * client connection list when it needs to sent new primary display data to all the
+ * connected clients.
+ *
+ * DrmIpcSRV - this thread is a main loop for IPC server. It accepts new connection(s),
+ * authenticates it and starts new client thread IpcCLT-XXX for processing client
+ * requests. This thread is accessing IPC client connection list by adding a new
+ * connection data into it.
+ *
+ * IpcCLT-%u - this thread processes all the client data. Suffix '-%u' in thread name is PID
+ * of a remote client process. Typical name for client thread would be IpcCLT-1234. This
+ * thread is accessing IPC client connection list when it removes connection data from it
+ * when actual IPC connection is closed. Due to IPRT thread name limitation, actual thread
+ * name will be cropped by 15 characters.
+ *
+ *
+ * The following locks are utilized:
+ *
+ * #g_ipcClientConnectionsListCritSect - protects access to list of IPC client connections.
+ * It is used by each thread - DrmResizeThread, DrmIpcSRV and IpcCLT-XXX.
+ *
+ * #g_monitorPositionsCritSect - protects access to display layout data cache and vmwgfx driver
+ * handle, serializes access to host interface and vmwgfx driver handle between
+ * DrmResizeThread and IpcCLT-%u.
+ */
+
+#include "VBoxClient.h"
+#include "display-ipc.h"
+
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/HostServices/GuestPropertySvc.h>
+
+#include <iprt/getopt.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/thread.h>
+#include <iprt/asm.h>
+#include <iprt/localipc.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <limits.h>
+#include <signal.h>
+#include <grp.h>
+#include <errno.h>
+
+#ifdef RT_OS_LINUX
+# include <sys/ioctl.h>
+#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */
+# include <sys/ioccom.h>
+#endif
+
+/** Ioctl command to query vmwgfx version information. */
+#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION)
+/** Ioctl command to set new screen layout. */
+#define DRM_IOCTL_VMW_UPDATE_LAYOUT _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT)
+/** A driver name which identifies VMWare driver. */
+#define DRM_DRIVER_NAME "vmwgfx"
+/** VMWare driver compatible version number. On previous versions resizing does not seem work. */
+#define DRM_DRIVER_VERSION_MAJOR_MIN (2)
+#define DRM_DRIVER_VERSION_MINOR_MIN (10)
+
+/** VMWare char device driver minor numbers range. */
+#define VMW_CONTROL_DEVICE_MINOR_START (64)
+#define VMW_RENDER_DEVICE_MINOR_START (128)
+#define VMW_RENDER_DEVICE_MINOR_END (192)
+
+/** Name of DRM resize thread. */
+#define DRM_RESIZE_THREAD_NAME "DrmResizeThread"
+
+/** Name of DRM IPC server thread. */
+#define DRM_IPC_SERVER_THREAD_NAME "DrmIpcSRV"
+/** Maximum length of thread name. */
+#define DRM_IPC_THREAD_NAME_MAX (16)
+/** Name pattern of DRM IPC client thread. */
+#define DRM_IPC_CLIENT_THREAD_NAME_PTR "IpcCLT-%u"
+/** Maximum number of simultaneous IPC client connections. */
+#define DRM_IPC_SERVER_CONNECTIONS_MAX (16)
+
+/** IPC client connections counter. */
+static volatile uint32_t g_cDrmIpcConnections = 0;
+/* A flag which indicates whether access to IPC socket should be restricted.
+ * This flag caches '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property
+ * in order to prevent its retrieving from the host side each time a new IPC
+ * client connects to server. This flag is updated each time when property is
+ * changed on the host side. */
+static volatile bool g_fDrmIpcRestricted;
+
+/** Global handle to vmwgfx file descriptor (protected by #g_monitorPositionsCritSect). */
+static RTFILE g_hDevice = NIL_RTFILE;
+
+/** DRM version structure. */
+struct DRMVERSION
+{
+ int cMajor;
+ int cMinor;
+ int cPatchLevel;
+ size_t cbName;
+ char *pszName;
+ size_t cbDate;
+ char *pszDate;
+ size_t cbDescription;
+ char *pszDescription;
+};
+AssertCompileSize(struct DRMVERSION, 8 + 7 * sizeof(void *));
+
+/** Preferred screen layout information for DRM_VMW_UPDATE_LAYOUT IoCtl. The
+ * rects argument is a cast pointer to an array of drm_vmw_rect. */
+struct DRMVMWUPDATELAYOUT
+{
+ uint32_t cOutputs;
+ uint32_t u32Pad;
+ uint64_t ptrRects;
+};
+AssertCompileSize(struct DRMVMWUPDATELAYOUT, 16);
+
+/** A node of IPC client connections list. */
+typedef struct VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE
+{
+ /** The list node. */
+ RTLISTNODE Node;
+ /** List node payload. */
+ PVBOX_DRMIPC_CLIENT pClient;
+} VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE;
+
+/* Pointer to VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE. */
+typedef VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE *PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE;
+
+/** IPC client connections list. */
+static VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE g_ipcClientConnectionsList;
+
+/** IPC client connections list critical section. */
+static RTCRITSECT g_ipcClientConnectionsListCritSect;
+
+/** Critical section used for reporting monitors position back to host. */
+static RTCRITSECT g_monitorPositionsCritSect;
+
+/** Counter of how often our daemon has been re-spawned. */
+unsigned g_cRespawn = 0;
+/** Logging verbosity level. */
+unsigned g_cVerbosity = 0;
+
+/** Path to the PID file. */
+static const char *g_pszPidFile = "/var/run/VBoxDRMClient";
+
+/** Global flag which is triggered when service requested to shutdown. */
+static bool volatile g_fShutdown;
+
+/**
+ * Go over all existing IPC client connection and put set-primary-screen request
+ * data into TX queue of each of them .
+ *
+ * @return IPRT status code.
+ * @param u32PrimaryDisplay Primary display ID.
+ */
+static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay);
+
+/**
+ * Attempts to open DRM device by given path and check if it is
+ * capable for screen resize.
+ *
+ * @return DRM device handle on success, NIL_RTFILE otherwise.
+ * @param szPathPattern Path name pattern to the DRM device.
+ * @param uInstance Driver / device instance.
+ */
+static RTFILE vbDrmTryDevice(const char *szPathPattern, uint8_t uInstance)
+{
+ int rc = VERR_NOT_FOUND;
+ char szPath[PATH_MAX];
+ struct DRMVERSION vmwgfxVersion;
+ RTFILE hDevice = NIL_RTFILE;
+
+ RT_ZERO(szPath);
+ RT_ZERO(vmwgfxVersion);
+
+ rc = RTStrPrintf(szPath, sizeof(szPath), szPathPattern, uInstance);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ char szVmwgfxDriverName[sizeof(DRM_DRIVER_NAME)];
+ RT_ZERO(szVmwgfxDriverName);
+
+ vmwgfxVersion.cbName = sizeof(szVmwgfxDriverName);
+ vmwgfxVersion.pszName = szVmwgfxDriverName;
+
+ /* Query driver version information and check if it can be used for screen resizing. */
+ rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &vmwgfxVersion, sizeof(vmwgfxVersion), NULL);
+ if ( RT_SUCCESS(rc)
+ && strncmp(szVmwgfxDriverName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1) == 0
+ && ( vmwgfxVersion.cMajor > DRM_DRIVER_VERSION_MAJOR_MIN
+ || ( vmwgfxVersion.cMajor == DRM_DRIVER_VERSION_MAJOR_MIN
+ && vmwgfxVersion.cMinor >= DRM_DRIVER_VERSION_MINOR_MIN)))
+ {
+ VBClLogInfo("found compatible device: %s\n", szPath);
+ }
+ else
+ {
+ RTFileClose(hDevice);
+ hDevice = NIL_RTFILE;
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ }
+ else
+ {
+ VBClLogError("unable to construct path to DRM device: %Rrc\n", rc);
+ }
+
+ return RT_SUCCESS(rc) ? hDevice : NIL_RTFILE;
+}
+
+/**
+ * Attempts to find and open DRM device to be used for screen resize.
+ *
+ * @return DRM device handle on success, NIL_RTFILE otherwise.
+ */
+static RTFILE vbDrmOpenVmwgfx(void)
+{
+ /* Control devices for drm graphics driver control devices go from
+ * controlD64 to controlD127. Render node devices go from renderD128
+ * to renderD192. The driver takes resize hints via the control device
+ * on pre-4.10 (???) kernels and on the render device on newer ones.
+ * At first, try to find control device and render one if not found.
+ */
+ uint8_t i;
+ RTFILE hDevice = NIL_RTFILE;
+
+ /* Lookup control device. */
+ for (i = VMW_CONTROL_DEVICE_MINOR_START; i < VMW_RENDER_DEVICE_MINOR_START; i++)
+ {
+ hDevice = vbDrmTryDevice("/dev/dri/controlD%u", i);
+ if (hDevice != NIL_RTFILE)
+ return hDevice;
+ }
+
+ /* Lookup render device. */
+ for (i = VMW_RENDER_DEVICE_MINOR_START; i <= VMW_RENDER_DEVICE_MINOR_END; i++)
+ {
+ hDevice = vbDrmTryDevice("/dev/dri/renderD%u", i);
+ if (hDevice != NIL_RTFILE)
+ return hDevice;
+ }
+
+ VBClLogError("unable to find DRM device\n");
+
+ return hDevice;
+}
+
+/**
+ * This function converts input monitors layout array passed from DevVMM
+ * into monitors layout array to be passed to DRM stack. Last validation
+ * request is cached.
+ *
+ * @return VINF_SUCCESS on success, VERR_DUPLICATE if monitors layout was not changed, IPRT error code otherwise.
+ * @param aDisplaysIn Input displays array.
+ * @param cDisplaysIn Number of elements in input displays array.
+ * @param aDisplaysOut Output displays array.
+ * @param cDisplaysOutMax Number of elements in output displays array.
+ * @param pu32PrimaryDisplay ID of a display which marked as primary.
+ * @param pcActualDisplays Number of displays to report to DRM stack (number of enabled displays).
+ * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not.
+ * When layout is reported by Desktop Environment helper, aDisplaysIn does not have
+ * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them).
+ */
+static int vbDrmValidateLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn,
+ struct VBOX_DRMIPC_VMWRECT *aDisplaysOut, uint32_t *pu32PrimaryDisplay,
+ uint32_t cDisplaysOutMax, uint32_t *pcActualDisplays, bool fPartialLayout)
+{
+ /* This array is a cache of what was received from DevVMM so far.
+ * DevVMM may send to us partial information bout scree layout. This
+ * cache remembers entire picture. */
+ static struct VMMDevDisplayDef aVmMonitorsCache[VBOX_DRMIPC_MONITORS_MAX];
+ /* Number of valid (enabled) displays in output array. */
+ uint32_t cDisplaysOut = 0;
+ /* Flag indicates that current layout cache is consistent and can be passed to DRM stack. */
+ bool fValid = true;
+
+ /* Make sure input array fits cache size. */
+ if (cDisplaysIn > VBOX_DRMIPC_MONITORS_MAX)
+ {
+ VBClLogError("unable to validate screen layout: input (%u) array does not fit to cache size (%u)\n",
+ cDisplaysIn, VBOX_DRMIPC_MONITORS_MAX);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Make sure there is enough space in output array. */
+ if (cDisplaysIn > cDisplaysOutMax)
+ {
+ VBClLogError("unable to validate screen layout: input array (%u) is bigger than output one (%u)\n",
+ cDisplaysIn, cDisplaysOut);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Make sure input and output arrays are of non-zero size. */
+ if (!(cDisplaysIn > 0 && cDisplaysOutMax > 0))
+ {
+ VBClLogError("unable to validate screen layout: invalid size of either input (%u) or output display array\n",
+ cDisplaysIn, cDisplaysOutMax);
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Update cache. */
+ for (uint32_t i = 0; i < cDisplaysIn; i++)
+ {
+ uint32_t idDisplay = !fPartialLayout ? aDisplaysIn[i].idDisplay : i;
+ if (idDisplay < VBOX_DRMIPC_MONITORS_MAX)
+ {
+ if (!fPartialLayout)
+ {
+ aVmMonitorsCache[idDisplay].idDisplay = idDisplay;
+ aVmMonitorsCache[idDisplay].fDisplayFlags = aDisplaysIn[i].fDisplayFlags;
+ aVmMonitorsCache[idDisplay].cBitsPerPixel = aDisplaysIn[i].cBitsPerPixel;
+ }
+
+ aVmMonitorsCache[idDisplay].cx = aDisplaysIn[i].cx;
+ aVmMonitorsCache[idDisplay].cy = aDisplaysIn[i].cy;
+ aVmMonitorsCache[idDisplay].xOrigin = aDisplaysIn[i].xOrigin;
+ aVmMonitorsCache[idDisplay].yOrigin = aDisplaysIn[i].yOrigin;
+ }
+ else
+ {
+ VBClLogError("received display ID (0x%x, position %u) is invalid\n", idDisplay, i);
+ /* If monitor configuration cannot be placed into cache, consider entire cache is invalid. */
+ fValid = false;
+ }
+ }
+
+ /* Now, go though complete cache and check if it is valid. */
+ for (uint32_t i = 0; i < VBOX_DRMIPC_MONITORS_MAX; i++)
+ {
+ if (i == 0)
+ {
+ if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
+ {
+ VBClLogError("unable to validate screen layout: first monitor is not allowed to be disabled\n");
+ fValid = false;
+ }
+ else
+ cDisplaysOut++;
+ }
+ else
+ {
+ /* Check if there is no hole in between monitors (i.e., if current monitor is enabled, but previous one does not). */
+ if ( !(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
+ && aVmMonitorsCache[i - 1].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)
+ {
+ VBClLogError("unable to validate screen layout: there is a hole in displays layout config, "
+ "monitor (%u) is ENABLED while (%u) does not\n", i, i - 1);
+ fValid = false;
+ }
+ else
+ {
+ /* Always align screens since unaligned layout will result in disaster. */
+ aVmMonitorsCache[i].xOrigin = aVmMonitorsCache[i - 1].xOrigin + aVmMonitorsCache[i - 1].cx;
+ aVmMonitorsCache[i].yOrigin = aVmMonitorsCache[i - 1].yOrigin;
+
+ /* Only count enabled monitors. */
+ if (!(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
+ cDisplaysOut++;
+ }
+ }
+ }
+
+ /* Copy out layout data. */
+ if (fValid)
+ {
+ /* Start with invalid display ID. */
+ uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX;
+
+ for (uint32_t i = 0; i < cDisplaysOut; i++)
+ {
+ aDisplaysOut[i].x = aVmMonitorsCache[i].xOrigin;
+ aDisplaysOut[i].y = aVmMonitorsCache[i].yOrigin;
+ aDisplaysOut[i].w = aVmMonitorsCache[i].cx;
+ aDisplaysOut[i].h = aVmMonitorsCache[i].cy;
+
+ if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY)
+ {
+ /* Make sure display layout has only one primary display
+ * set (for display 0, host side sets primary flag, so exclude it). */
+ Assert(u32PrimaryDisplay == 0 || u32PrimaryDisplay == VBOX_DRMIPC_MONITORS_MAX);
+ u32PrimaryDisplay = i;
+ }
+
+ VBClLogVerbose(1, "update monitor %u parameters: %dx%d, (%d, %d)\n",
+ i, aDisplaysOut[i].w, aDisplaysOut[i].h, aDisplaysOut[i].x, aDisplaysOut[i].y);
+ }
+
+ *pu32PrimaryDisplay = u32PrimaryDisplay;
+ *pcActualDisplays = cDisplaysOut;
+ }
+
+ return (fValid && cDisplaysOut > 0) ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+}
+
+/**
+ * This function sends screen layout data to DRM stack.
+ *
+ * Helper function for vbDrmPushScreenLayout(). Should be called
+ * under g_monitorPositionsCritSect lock.
+ *
+ * @return VINF_SUCCESS on success, IPRT error code otherwise.
+ * @param hDevice Handle to opened DRM device.
+ * @param paRects Array of screen configuration data.
+ * @param cRects Number of elements in screen configuration array.
+ */
+static int vbDrmSendHints(RTFILE hDevice, struct VBOX_DRMIPC_VMWRECT *paRects, uint32_t cRects)
+{
+ int rc = 0;
+ uid_t curuid;
+
+ /* Store real user id. */
+ curuid = getuid();
+
+ /* Change effective user id. */
+ if (setreuid(0, 0) == 0)
+ {
+ struct DRMVMWUPDATELAYOUT ioctlLayout;
+
+ RT_ZERO(ioctlLayout);
+ ioctlLayout.cOutputs = cRects;
+ ioctlLayout.ptrRects = (uint64_t)paRects;
+
+ rc = RTFileIoCtl(hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT,
+ &ioctlLayout, sizeof(ioctlLayout), NULL);
+
+ if (setreuid(curuid, 0) != 0)
+ {
+ VBClLogError("reset of setreuid failed after drm ioctl");
+ rc = VERR_ACCESS_DENIED;
+ }
+ }
+ else
+ {
+ VBClLogError("setreuid failed during drm ioctl\n");
+ rc = VERR_ACCESS_DENIED;
+ }
+
+ return rc;
+}
+
+/**
+ * This function converts vmwgfx monitors layout data into an array of monitor offsets
+ * and sends it back to the host in order to ensure that host and guest have the same
+ * monitors layout representation.
+ *
+ * @return IPRT status code.
+ * @param cDisplays Number of displays (elements in pDisplays).
+ * @param pDisplays Displays parameters as it was sent to vmwgfx driver.
+ */
+static int drmSendMonitorPositions(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pDisplays)
+{
+ static RTPOINT aPositions[VBOX_DRMIPC_MONITORS_MAX];
+
+ if (!pDisplays || !cDisplays || cDisplays > VBOX_DRMIPC_MONITORS_MAX)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Prepare monitor offsets list to be sent to the host. */
+ for (uint32_t i = 0; i < cDisplays; i++)
+ {
+ aPositions[i].x = pDisplays[i].x;
+ aPositions[i].y = pDisplays[i].y;
+ }
+
+ return VbglR3SeamlessSendMonitorPositions(cDisplays, aPositions);
+}
+
+/**
+ * Validate and apply screen layout data.
+ *
+ * @return IPRT status code.
+ * @param aDisplaysIn An array with screen layout data.
+ * @param cDisplaysIn Number of elements in aDisplaysIn.
+ * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not.
+ * When layout is reported by Desktop Environment helper, aDisplaysIn does not have
+ * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them).
+ * @param fApply Whether to apply provided display layout data to the DRM stack or send display offsets only.
+ */
+static int vbDrmPushScreenLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn, bool fPartialLayout, bool fApply)
+{
+ int rc;
+
+ struct VBOX_DRMIPC_VMWRECT aDisplaysOut[VBOX_DRMIPC_MONITORS_MAX];
+ uint32_t cDisplaysOut = 0;
+
+ uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX;
+
+ rc = RTCritSectEnter(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to lock monitor data cache, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ static uint32_t u32PrimaryDisplayLast = VBOX_DRMIPC_MONITORS_MAX;
+
+ RT_ZERO(aDisplaysOut);
+
+ /* Validate displays layout and push it to DRM stack if valid. */
+ rc = vbDrmValidateLayout(aDisplaysIn, cDisplaysIn, aDisplaysOut, &u32PrimaryDisplay,
+ sizeof(aDisplaysOut), &cDisplaysOut, fPartialLayout);
+ if (RT_SUCCESS(rc))
+ {
+ if (fApply)
+ {
+ rc = vbDrmSendHints(g_hDevice, aDisplaysOut, cDisplaysOut);
+ VBClLogInfo("push screen layout data of %u display(s) to DRM stack, fPartialLayout=%RTbool, rc=%Rrc\n",
+ cDisplaysOut, fPartialLayout, rc);
+ }
+
+ /* In addition, notify host that configuration was successfully applied to the guest vmwgfx driver. */
+ if (RT_SUCCESS(rc))
+ {
+ rc = drmSendMonitorPositions(cDisplaysOut, aDisplaysOut);
+ if (RT_FAILURE(rc))
+ VBClLogError("cannot send host notification: %Rrc\n", rc);
+
+ /* If information about primary display is present in display layout, send it to DE over IPC. */
+ if (u32PrimaryDisplay != VBOX_DRMIPC_MONITORS_MAX
+ && u32PrimaryDisplayLast != u32PrimaryDisplay)
+ {
+ rc = vbDrmIpcBroadcastPrimaryDisplay(u32PrimaryDisplay);
+
+ /* Cache last value in order to avoid sending duplicate data over IPC. */
+ u32PrimaryDisplayLast = u32PrimaryDisplay;
+
+ VBClLogVerbose(2, "DE was notified that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc);
+ }
+ else
+ VBClLogVerbose(2, "do not notify DE second time that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc);
+ }
+ }
+ else if (rc == VERR_DUPLICATE)
+ VBClLogVerbose(2, "do not notify DRM stack about monitors layout change twice, rc=%Rrc\n", rc);
+ else
+ VBClLogError("displays layout is invalid, will not notify guest driver, rc=%Rrc\n", rc);
+
+ int rc2 = RTCritSectLeave(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to unlock monitor data cache, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/** Worker thread for resize task. */
+static DECLCALLBACK(int) vbDrmResizeWorker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ RT_NOREF(ThreadSelf);
+ RT_NOREF(pvUser);
+
+ for (;;)
+ {
+ /* Do not acknowledge the first event we query for to pick up old events,
+ * e.g. from before a guest reboot. */
+ bool fAck = false;
+
+ uint32_t events;
+
+ VMMDevDisplayDef aDisplaysIn[VBOX_DRMIPC_MONITORS_MAX];
+ uint32_t cDisplaysIn = 0;
+
+ RT_ZERO(aDisplaysIn);
+
+ /* Query the first size without waiting. This lets us e.g. pick up
+ * the last event before a guest reboot when we start again after. */
+ rc = VbglR3GetDisplayChangeRequestMulti(VBOX_DRMIPC_MONITORS_MAX, &cDisplaysIn, aDisplaysIn, fAck);
+ fAck = true;
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmPushScreenLayout(aDisplaysIn, cDisplaysIn, false, true);
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to push display change as requested by host, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("Failed to get display change request, rc=%Rrc\n", rc);
+
+ do
+ {
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_DRMIPC_RX_TIMEOUT_MS, &events);
+ } while ((rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED) && !ASMAtomicReadBool(&g_fShutdown));
+
+ if (ASMAtomicReadBool(&g_fShutdown))
+ {
+ VBClLogInfo("exiting resize thread: shutdown requested\n");
+ /* This is a case when we should return positive status. */
+ rc = (rc == VERR_TIMEOUT) ? VINF_SUCCESS : rc;
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: resize thread: failure waiting for event, rc=%Rrc\n", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * Go over all existing IPC client connection and put set-primary-screen request
+ * data into TX queue of each of them .
+ *
+ * @return IPRT status code.
+ * @param u32PrimaryDisplay Primary display ID.
+ */
+static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay)
+{
+ int rc;
+
+ rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node))
+ {
+ PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry;
+ RTListForEach(&g_ipcClientConnectionsList.Node, pEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node)
+ {
+ AssertReturn(pEntry, VERR_INVALID_PARAMETER);
+ AssertReturn(pEntry->pClient, VERR_INVALID_PARAMETER);
+ AssertReturn(pEntry->pClient->hThread, VERR_INVALID_PARAMETER);
+
+ rc = vbDrmIpcSetPrimaryDisplay(pEntry->pClient, u32PrimaryDisplay);
+
+ VBClLogInfo("thread %s notified IPC Client that display %u is now primary, rc=%Rrc\n",
+ RTThreadGetName(pEntry->pClient->hThread), u32PrimaryDisplay, rc);
+ }
+ }
+
+ int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("notify DE: unable to leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("notify DE: unable to enter critical section, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Main loop for IPC client connection handling.
+ *
+ * @return IPRT status code.
+ * @param pClient Pointer to IPC client data.
+ */
+static int vbDrmIpcConnectionProc(PVBOX_DRMIPC_CLIENT pClient)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+
+ /* This loop handles incoming messages. */
+ for (;;)
+ {
+ rc = vbDrmIpcConnectionHandler(pClient);
+
+ /* Try to detect if we should shutdown as early as we can. */
+ if (ASMAtomicReadBool(&g_fShutdown))
+ break;
+
+ /* Normal case. No data received within short interval. */
+ if (rc == VERR_TIMEOUT)
+ {
+ continue;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ /* Terminate connection handling in case of error. */
+ VBClLogError("unable to handle IPC session, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Add IPC client connection data into list of connections.
+ *
+ * List size is limited indirectly by DRM_IPC_SERVER_CONNECTIONS_MAX value.
+ * This function should only be invoked from client thread context
+ * (from vbDrmIpcClientWorker() in particular).
+ *
+ * @return IPRT status code.
+ * @param pClientNode Client connection information to add to the list.
+ */
+static int vbDrmIpcClientsListAdd(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode)
+{
+ int rc;
+
+ AssertReturn(pClientNode, VERR_INVALID_PARAMETER);
+
+ rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListAppend(&g_ipcClientConnectionsList.Node, &pClientNode->Node);
+
+ int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("add client connection: unable to leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("add client connection: unable to enter critical section, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Remove IPC client connection data from list of connections.
+ *
+ * This function should only be invoked from client thread context
+ * (from vbDrmIpcClientWorker() in particular).
+ *
+ * @return IPRT status code.
+ * @param pClientNode Client connection information to remove from the list.
+ */
+static int vbDrmIpcClientsListRemove(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode)
+{
+ int rc;
+ PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry, pNextEntry, pFound = NULL;
+
+ AssertReturn(pClientNode, VERR_INVALID_PARAMETER);
+
+ rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect);
+ if (RT_SUCCESS(rc))
+ {
+
+ if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node))
+ {
+ RTListForEachSafe(&g_ipcClientConnectionsList.Node, pEntry, pNextEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node)
+ {
+ if (pEntry == pClientNode)
+ pFound = (PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE)RTListNodeRemoveRet(&pEntry->Node);
+ }
+ }
+ else
+ VBClLogError("remove client connection: connections list empty, node %p not there\n", pClientNode);
+
+ int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("remove client connection: unable to leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("remove client connection: unable to enter critical section, rc=%Rrc\n", rc);
+
+ if (!pFound)
+ VBClLogError("remove client connection: node not found\n");
+
+ return !rc && pFound ? VINF_SUCCESS : VERR_INVALID_PARAMETER;
+}
+
+/**
+ * Convert VBOX_DRMIPC_VMWRECT into VMMDevDisplayDef and check layout correctness.
+ *
+ * VBOX_DRMIPC_VMWRECT does not represent enough information needed for
+ * VMMDevDisplayDef. Missing fields (fDisplayFlags, idDisplay, cBitsPerPixel)
+ * are initialized with default (invalid) values due to this.
+ *
+ * @return True if given screen layout is correct (i.e., has no displays which overlap), False
+ * if it needs to be adjusted before injecting into DRM stack.
+ * @param cDisplays Number of displays in configuration data.
+ * @param pIn A pointer to display configuration data array in form of VBOX_DRMIPC_VMWRECT (input).
+ * @param pOut A pointer to display configuration data array in form of VMMDevDisplayDef (output).
+ */
+static bool vbDrmVmwRectToDisplayDef(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pIn, VMMDevDisplayDef *pOut)
+{
+ bool fCorrect = true;
+
+ for (uint32_t i = 0; i < cDisplays; i++)
+ {
+ /* VBOX_DRMIPC_VMWRECT has no information about this fields. */
+ pOut[i].fDisplayFlags = 0;
+ pOut[i].idDisplay = VBOX_DRMIPC_MONITORS_MAX;
+ pOut[i].cBitsPerPixel = 0;
+
+ pOut[i].xOrigin = pIn[i].x;
+ pOut[i].yOrigin = pIn[i].y;
+ pOut[i].cx = pIn[i].w;
+ pOut[i].cy = pIn[i].h;
+
+ /* Make sure that displays do not overlap within reported screen layout. Ask IPC server to fix layout otherwise. */
+ fCorrect = i > 0
+ && pIn[i].x != (int32_t)pIn[i - 1].w + pIn[i - 1].x
+ ? false
+ : fCorrect;
+ }
+
+ return fCorrect;
+}
+
+/**
+ * @interface_method_impl{VBOX_DRMIPC_CLIENT,pfnRxCb}
+ */
+static DECLCALLBACK(int) vbDrmIpcClientRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
+{
+ int rc = VERR_INVALID_PARAMETER;
+
+ AssertReturn(pvData, VERR_INVALID_PARAMETER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ switch (idCmd)
+ {
+ case VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS:
+ {
+ VMMDevDisplayDef aDisplays[VBOX_DRMIPC_MONITORS_MAX];
+ bool fCorrect;
+
+ PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)pvData;
+ AssertReturn(cbData == sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS), VERR_INVALID_PARAMETER);
+ AssertReturn(pCmd->cDisplays < VBOX_DRMIPC_MONITORS_MAX, VERR_INVALID_PARAMETER);
+
+ /* Convert input display config into VMMDevDisplayDef representation. */
+ RT_ZERO(aDisplays);
+ fCorrect = vbDrmVmwRectToDisplayDef(pCmd->cDisplays, pCmd->aDisplays, aDisplays);
+
+ rc = vbDrmPushScreenLayout(aDisplays, pCmd->cDisplays, true, !fCorrect);
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to push display change as requested by Desktop Environment helper, rc=%Rrc\n", rc);
+
+ break;
+ }
+
+ default:
+ {
+ VBClLogError("received unknown IPC command 0x%x\n", idCmd);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/** Worker thread for IPC client task. */
+static DECLCALLBACK(int) vbDrmIpcClientWorker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ VBOX_DRMIPC_CLIENT hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
+ RTLOCALIPCSESSION hSession = (RTLOCALIPCSESSION)pvUser;
+ int rc;
+
+ AssertReturn(RT_VALID_PTR(hSession), VERR_INVALID_PARAMETER);
+
+ /* Initialize client session resources. */
+ rc = vbDrmIpcClientInit(&hClient, ThreadSelf, hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbDrmIpcClientRxCallBack);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add IPC client connection data into clients list. */
+ VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE hClientNode = { { 0, 0 } , &hClient };
+
+ rc = vbDrmIpcClientsListAdd(&hClientNode);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserSignal(ThreadSelf);
+ if (RT_SUCCESS(rc))
+ {
+ /* Start spinning the connection. */
+ VBClLogInfo("IPC client connection started\n", rc);
+ rc = vbDrmIpcConnectionProc(&hClient);
+ VBClLogInfo("IPC client connection ended, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to report IPC client connection handler start, rc=%Rrc\n", rc);
+
+ /* Remove IPC client connection data from clients list. */
+ rc = vbDrmIpcClientsListRemove(&hClientNode);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to remove IPC client session from list of connections, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to add IPC client connection to the list, rc=%Rrc\n");
+
+ /* Disconnect remote peer if still connected. */
+ if (RT_VALID_PTR(hSession))
+ {
+ rc = RTLocalIpcSessionClose(hSession);
+ VBClLogInfo("IPC session closed, rc=%Rrc\n", rc);
+ }
+
+ /* Connection handler loop has ended, release session resources. */
+ rc = vbDrmIpcClientReleaseResources(&hClient);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC client session, rc=%Rrc\n", rc);
+
+ ASMAtomicDecU32(&g_cDrmIpcConnections);
+ }
+ else
+ VBClLogError("unable to initialize IPC client session, rc=%Rrc\n", rc);
+
+ VBClLogInfo("closing IPC client session, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Start processing thread for IPC client requests handling.
+ *
+ * @returns IPRT status code.
+ * @param hSession IPC client connection handle.
+ */
+static int vbDrmIpcClientStart(RTLOCALIPCSESSION hSession)
+{
+ int rc;
+ RTTHREAD hThread = 0;
+ RTPROCESS hProcess = 0;
+
+ rc = RTLocalIpcSessionQueryProcess(hSession, &hProcess);
+ if (RT_SUCCESS(rc))
+ {
+ char pszThreadName[DRM_IPC_THREAD_NAME_MAX];
+ RT_ZERO(pszThreadName);
+
+ RTStrPrintf2(pszThreadName, DRM_IPC_THREAD_NAME_MAX, DRM_IPC_CLIENT_THREAD_NAME_PTR, hProcess);
+
+ /* Attempt to start IPC client connection handler task. */
+ rc = RTThreadCreate(&hThread, vbDrmIpcClientWorker, (void *)hSession, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, pszThreadName);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserWait(hThread, RT_MS_5SEC);
+ }
+ }
+
+ return rc;
+}
+
+/** Worker thread for IPC server task. */
+static DECLCALLBACK(int) vbDrmIpcServerWorker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ int rc = VERR_GENERAL_FAILURE;
+ RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser;
+
+ RT_NOREF1(ThreadSelf);
+
+ AssertReturn(hIpcServer, VERR_INVALID_PARAMETER);
+
+ /* This loop accepts incoming connections. */
+ for (;;)
+ {
+ RTLOCALIPCSESSION hClientSession;
+
+ /* Wait for incoming connection. */
+ rc = RTLocalIpcServerListen(hIpcServer, &hClientSession);
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogVerbose(2, "new IPC session\n");
+
+ if (ASMAtomicIncU32(&g_cDrmIpcConnections) <= DRM_IPC_SERVER_CONNECTIONS_MAX)
+ {
+ /* Authenticate remote peer. */
+ if (ASMAtomicReadBool(&g_fDrmIpcRestricted))
+ rc = vbDrmIpcAuth(hClientSession);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Start incoming connection handler thread. */
+ rc = vbDrmIpcClientStart(hClientSession);
+ VBClLogVerbose(2, "connection processing ended, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("IPC authentication failed, rc=%Rrc\n", rc);
+ }
+ else
+ rc = VERR_RESOURCE_BUSY;
+
+ /* Release resources in case of error. */
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("maximum amount of IPC client connections reached, dropping connection\n");
+
+ int rc2 = RTLocalIpcSessionClose(hClientSession);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to close IPC session, rc=%Rrc\n", rc2);
+
+ ASMAtomicDecU32(&g_cDrmIpcConnections);
+ }
+ }
+ else
+ VBClLogError("IPC authentication failed, rc=%Rrc\n", rc);
+
+ /* Check shutdown was requested. */
+ if (ASMAtomicReadBool(&g_fShutdown))
+ {
+ VBClLogInfo("exiting IPC thread: shutdown requested\n");
+ break;
+ }
+
+ /* Wait a bit before spinning a loop if something went wrong. */
+ if (RT_FAILURE(rc))
+ RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
+ }
+
+ return rc;
+}
+
+/** A signal handler. */
+static void vbDrmRequestShutdown(int sig)
+{
+ RT_NOREF(sig);
+ ASMAtomicWriteBool(&g_fShutdown, true);
+}
+
+/**
+ * Grant access to DRM IPC server socket depending on VM configuration.
+ *
+ * If VM has '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property set
+ * and this property is READ-ONLY for the guest side, access will be
+ * granted to root and users from 'vboxdrmipc' group only. If group does
+ * not exists, only root will have access to the socket. When property is
+ * not set or not READ-ONLY, all users will have access to the socket.
+ *
+ * @param hIpcServer IPC server handle.
+ * @param fRestrict Whether to restrict access to socket or not.
+ */
+static void vbDrmSetIpcServerAccessPermissions(RTLOCALIPCSERVER hIpcServer, bool fRestrict)
+{
+ int rc;
+
+ if (fRestrict)
+ {
+ struct group *pGrp;
+ pGrp = getgrnam(VBOX_DRMIPC_USER_GROUP);
+ if (pGrp)
+ {
+ rc = RTLocalIpcServerGrantGroupAccess(hIpcServer, pGrp->gr_gid);
+ if (RT_SUCCESS(rc))
+ VBClLogInfo("IPC server socket access granted to '" VBOX_DRMIPC_USER_GROUP "' users\n");
+ else
+ VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "' users, rc=%Rrc\n", rc);
+
+ }
+ else
+ VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "', group does not exist\n");
+ }
+ else
+ {
+ rc = RTLocalIpcServerSetAccessMode(hIpcServer,
+ RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR |
+ RTFS_UNIX_IRGRP | RTFS_UNIX_IWGRP |
+ RTFS_UNIX_IROTH | RTFS_UNIX_IWOTH);
+ if (RT_SUCCESS(rc))
+ VBClLogInfo("IPC server socket access granted to all users\n");
+ else
+ VBClLogError("unable to grant IPC server socket access to all users, rc=%Rrc\n", rc);
+ }
+
+ /* Set flag for the thread which serves incomming IPC connections. */
+ ASMAtomicWriteBool(&g_fDrmIpcRestricted, fRestrict);
+}
+
+/**
+ * Wait and handle '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property change.
+ *
+ * This function is executed in context of main().
+ *
+ * @param hIpcServer IPC server handle.
+ */
+static void vbDrmPollIpcServerAccessMode(RTLOCALIPCSERVER hIpcServer)
+{
+ HGCMCLIENTID idClient;
+ int rc;
+
+ rc = VbglR3GuestPropConnect(&idClient);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ /* Buffer should be big enough to fit guest property data layout: Name\0Value\0Flags\0fWasDeleted\0. */
+ static char achBuf[GUEST_PROP_MAX_NAME_LEN];
+ char *pszName = NULL;
+ char *pszValue = NULL;
+ char *pszFlags = NULL;
+ bool fWasDeleted = false;
+ uint64_t u64Timestamp = 0;
+
+ rc = VbglR3GuestPropWait(idClient, VBGLR3DRMPROPPTR, achBuf, sizeof(achBuf), u64Timestamp,
+ VBOX_DRMIPC_RX_TIMEOUT_MS, &pszName, &pszValue, &u64Timestamp,
+ &pszFlags, NULL, &fWasDeleted);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fFlags = 0;
+
+ VBClLogVerbose(1, "guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool\n",
+ pszName, pszValue, pszFlags, fWasDeleted);
+
+ if (RT_SUCCESS(GuestPropValidateFlags(pszFlags, &fFlags)))
+ {
+ if (RTStrNCmp(pszName, VBGLR3DRMIPCPROPRESTRICT, GUEST_PROP_MAX_NAME_LEN) == 0)
+ {
+ /* Enforce restricted socket access until guest property exist and READ-ONLY for the guest. */
+ vbDrmSetIpcServerAccessPermissions(hIpcServer, !fWasDeleted && fFlags & GUEST_PROP_F_RDONLYGUEST);
+ }
+
+ } else
+ VBClLogError("guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool: bad flags\n",
+ pszName, pszValue, pszFlags, fWasDeleted);
+
+ } else if ( rc != VERR_TIMEOUT
+ && rc != VERR_INTERRUPTED)
+ {
+ VBClLogError("error on waiting guest property notification, rc=%Rrc\n", rc);
+ RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
+ }
+
+ } while (!ASMAtomicReadBool(&g_fShutdown));
+
+ VbglR3GuestPropDisconnect(idClient);
+ }
+ else
+ VBClLogError("cannot connect to VM guest properties service, rc=%Rrc\n", rc);
+}
+
+int main(int argc, char *argv[])
+{
+ /** Custom log prefix to be used for logger instance of this process. */
+ static const char *pszLogPrefix = "VBoxDRMClient:";
+
+ static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, };
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ int ch;
+
+ RTFILE hPidFile;
+
+ RTLOCALIPCSERVER hIpcServer;
+ RTTHREAD vbDrmIpcThread;
+ int rcDrmIpcThread = 0;
+
+ RTTHREAD drmResizeThread;
+ int rcDrmResizeThread = 0;
+ int rc, rc2 = 0;
+
+ rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ rc = VbglR3InitUser();
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: VbglR3InitUser failed: %Rrc", rc);
+
+ /* Process command line options. */
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: unable to process command line options, rc=%Rrc\n", rc);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (ch)
+ {
+ case 'v':
+ {
+ g_cVerbosity++;
+ break;
+ }
+
+ case VERR_GETOPT_UNKNOWN_OPTION:
+ {
+ VBClLogFatalError("unknown command line option '%s'\n", ValueUnion.psz);
+ return RTEXITCODE_SYNTAX;
+
+ }
+
+ default:
+ break;
+ }
+ }
+
+ rc = VBClLogCreate("");
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("VBoxDRMClient: failed to setup logging, rc=%Rrc\n", rc);
+ VBClLogSetLogPrefix(pszLogPrefix);
+
+ /* Check PID file before attempting to initialize anything. */
+ rc = VbglR3PidFile(g_pszPidFile, &hPidFile);
+ if (rc == VERR_FILE_LOCK_VIOLATION)
+ {
+ VBClLogInfo("already running, exiting\n");
+ return RTEXITCODE_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to lock PID file (%Rrc), exiting\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ g_hDevice = vbDrmOpenVmwgfx();
+ if (g_hDevice == NIL_RTFILE)
+ return RTEXITCODE_FAILURE;
+
+ rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+ rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Setup signals: gracefully terminate on SIGINT, SIGTERM. */
+ if ( signal(SIGINT, vbDrmRequestShutdown) == SIG_ERR
+ || signal(SIGTERM, vbDrmRequestShutdown) == SIG_ERR)
+ {
+ VBClLogError("unable to setup signals\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Init IPC client connection list. */
+ RTListInit(&g_ipcClientConnectionsList.Node);
+ rc = RTCritSectInit(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to initialize IPC client connection list critical section\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Init critical section which is used for reporting monitors offset back to host. */
+ rc = RTCritSectInit(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to initialize monitors position critical section\n");
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Instantiate IPC server for VBoxClient service communication. */
+ rc = RTLocalIpcServerCreate(&hIpcServer, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to setup IPC server, rc=%Rrc\n", rc);
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Set IPC server socket access permissions according to VM configuration. */
+ vbDrmSetIpcServerAccessPermissions(hIpcServer, VbglR3DrmRestrictedIpcAccessIsNeeded());
+
+ /* Attempt to start DRM resize task. */
+ rc = RTThreadCreate(&drmResizeThread, vbDrmResizeWorker, NULL, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_RESIZE_THREAD_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ /* Attempt to start IPC task. */
+ rc = RTThreadCreate(&vbDrmIpcThread, vbDrmIpcServerWorker, (void *)hIpcServer, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_IPC_SERVER_THREAD_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ /* Poll for host notification about IPC server socket access mode change. */
+ vbDrmPollIpcServerAccessMode(hIpcServer);
+
+ /* HACK ALERT!
+ * The sequence of RTThreadWait(drmResizeThread) -> RTLocalIpcServerDestroy() -> RTThreadWait(vbDrmIpcThread)
+ * is intentional! Once process received a signal, it will pull g_fShutdown flag, which in turn will cause
+ * drmResizeThread to quit. The vbDrmIpcThread might hang on accept() call, so we terminate IPC server to
+ * release it and then wait for its termination. */
+
+ rc = RTThreadWait(drmResizeThread, RT_INDEFINITE_WAIT, &rcDrmResizeThread);
+ VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_RESIZE_THREAD_NAME, rcDrmResizeThread);
+
+ rc = RTLocalIpcServerCancel(hIpcServer);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to notify IPC server about shutdown, rc=%Rrc\n", rc);
+
+ /* Wait for threads to terminate gracefully. */
+ rc = RTThreadWait(vbDrmIpcThread, RT_INDEFINITE_WAIT, &rcDrmIpcThread);
+ VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_IPC_SERVER_THREAD_NAME, rcDrmResizeThread);
+
+ }
+ else
+ VBClLogError("unable to start IPC thread, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to start resize thread, rc=%Rrc\n", rc);
+
+ rc = RTLocalIpcServerDestroy(hIpcServer);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to stop IPC server, rc=%Rrc\n", rc);
+
+ rc2 = RTCritSectDelete(&g_monitorPositionsCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to destroy g_monitorPositionsCritSect critsect, rc=%Rrc\n", rc2);
+
+ rc2 = RTCritSectDelete(&g_ipcClientConnectionsListCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to destroy g_ipcClientConnectionsListCritSect critsect, rc=%Rrc\n", rc2);
+
+ RTFileClose(g_hDevice);
+
+ VBClLogInfo("releasing PID file lock\n");
+ VbglR3ClosePidFile(g_pszPidFile, hPidFile);
+
+ VBClLogDestroy();
+
+ return rc == 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp b/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp
new file mode 100644
index 00000000..8e88e669
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp
@@ -0,0 +1,421 @@
+/* $Id: display-helper-generic.cpp $ */
+/** @file
+ * Guest Additions - Generic Desktop Environment helper.
+ *
+ * A generic helper for X11 Client which performs Desktop Environment
+ * specific actions utilizing libXrandr.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "VBoxClient.h"
+#include "display-helper.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <VBox/log.h>
+#include <VBox/xrandr.h>
+
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/thread.h>
+#include <iprt/mem.h>
+#include <iprt/list.h>
+
+/** Load libxrandr symbols needed for us. */
+#include <VBox/xrandr.h>
+/* Declarations of the functions that we need from libXrandr. */
+#define VBOX_XRANDR_GENERATE_BODY
+#include <VBox/xrandr-calls.h>
+
+#include <X11/Xlibint.h>
+
+/** Name of Display Change Monitor thread. */
+#define VBCL_HLP_DCM_THREAD_NAME "dcm-task"
+
+/** Display Change Monitor thread. */
+static RTTHREAD g_vbclHlpGenericDcmThread = NIL_RTTHREAD;
+
+/** Global flag which is triggered when service requested to shutdown. */
+static bool volatile g_fShutdown;
+
+/** Node of monitors info list. */
+typedef struct vbcl_hlp_generic_monitor_list_t
+{
+ /** List node. */
+ RTLISTNODE Node;
+ /** Pointer to xRandr monitor info. */
+ XRRMonitorInfo *pMonitorInfo;
+} vbcl_hlp_generic_monitor_list_t;
+
+/** Pointer to display change event notification callback (set by external function call). */
+static FNDISPLAYOFFSETCHANGE *g_pfnDisplayOffsetChangeCb;
+
+/**
+ * Determine monitor name strings order in a list of monitors which is sorted in ascending way.
+ *
+ * @return TRUE if first name should go first in a list, FALSE otherwise.
+ * @param pszName1 First monitor name.
+ * @param pszName2 Second monitor name.
+ */
+static bool vbcl_hlp_generic_order_names(char *pszName1, char *pszName2)
+{
+ AssertReturn(pszName1, false);
+ AssertReturn(pszName2, false);
+
+ char *pszFirst = pszName1;
+ char *pszSecond = pszName2;
+
+ while (*pszFirst && *pszSecond)
+ {
+ if (*pszFirst < *pszSecond)
+ return true;
+
+ pszFirst++;
+ pszSecond++;
+ }
+
+ return false;
+}
+
+/**
+ * Insert monitor info into the list sorted ascending.
+ *
+ * @return IPRT status code.
+ * @param pDisplay X11 display handle to fetch monitor name string from.
+ * @param pListHead Head of monitors info list.
+ * @param pMonitorInfo Monitor info ti be inserted into the list.
+ */
+static int vbcl_hlp_generic_monitor_list_insert_sorted(
+ Display *pDisplay, vbcl_hlp_generic_monitor_list_t *pListHead, XRRMonitorInfo *pMonitorInfo)
+{
+ vbcl_hlp_generic_monitor_list_t *pNode = (vbcl_hlp_generic_monitor_list_t *)RTMemAllocZ(sizeof(vbcl_hlp_generic_monitor_list_t));
+ vbcl_hlp_generic_monitor_list_t *pNodeIter;
+ char *pszMonitorName;
+
+ AssertReturn(pNode, VERR_NO_MEMORY);
+
+ pNode->pMonitorInfo = pMonitorInfo;
+
+ if (RTListIsEmpty(&pListHead->Node))
+ {
+ RTListNodeInsertAfter(&pListHead->Node, &pNode->Node);
+ return VINF_SUCCESS;
+ }
+
+ pszMonitorName = XGetAtomName(pDisplay, pMonitorInfo->name);
+ AssertReturn(pszMonitorName, VERR_NO_MEMORY);
+
+ RTListForEach(&pListHead->Node, pNodeIter, vbcl_hlp_generic_monitor_list_t, Node)
+ {
+ char *pszIterMonitorName = XGetAtomName(pDisplay, pNodeIter->pMonitorInfo->name);
+
+ if (vbcl_hlp_generic_order_names(pszMonitorName, pszIterMonitorName))
+ {
+ RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
+ XFree((void *)pszIterMonitorName);
+ XFree((void *)pszMonitorName);
+ return VINF_SUCCESS;
+ }
+
+ XFree((void *)pszIterMonitorName);
+ }
+
+ XFree((void *)pszMonitorName);
+
+ /* If we reached the end of the list, it means that monitor
+ * should be placed in the end (according to alphabetical sorting). */
+ RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Release monitors info list resources.
+ *
+ * @param pListHead List head.
+ */
+static void vbcl_hlp_generic_free_monitor_list(vbcl_hlp_generic_monitor_list_t *pListHead)
+{
+ vbcl_hlp_generic_monitor_list_t *pEntry, *pNextEntry;
+
+ RTListForEachSafe(&pListHead->Node, pEntry, pNextEntry, vbcl_hlp_generic_monitor_list_t, Node)
+ {
+ RTListNodeRemove(&pEntry->Node);
+ RTMemFree(pEntry);
+ }
+}
+
+/**
+ * Handle received RRScreenChangeNotify event.
+ *
+ * @param pDisplay X11 display handle.
+ */
+static void vbcl_hlp_generic_process_display_change_event(Display *pDisplay)
+{
+ int iCount;
+ uint32_t idxDisplay = 0;
+ XRRMonitorInfo *pMonitorsInfo = XRRGetMonitors(pDisplay, DefaultRootWindow(pDisplay), true, &iCount);
+ if (pMonitorsInfo && iCount > 0 && iCount < VBOX_DRMIPC_MONITORS_MAX)
+ {
+ int rc;
+ vbcl_hlp_generic_monitor_list_t pMonitorsInfoList, *pIter;
+ struct VBOX_DRMIPC_VMWRECT aDisplays[VBOX_DRMIPC_MONITORS_MAX];
+
+ RTListInit(&pMonitorsInfoList.Node);
+
+ /* Put monitors info into sorted (by monitor name) list. */
+ for (int i = 0; i < iCount; i++)
+ {
+ rc = vbcl_hlp_generic_monitor_list_insert_sorted(pDisplay, &pMonitorsInfoList, &pMonitorsInfo[i]);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to fill monitors info list, rc=%Rrc\n", rc);
+ break;
+ }
+ }
+
+ /* Now iterate over sorted list of monitor configurations. */
+ RTListForEach(&pMonitorsInfoList.Node, pIter, vbcl_hlp_generic_monitor_list_t, Node)
+ {
+ char *pszMonitorName = XGetAtomName(pDisplay, pIter->pMonitorInfo->name);
+
+ VBClLogVerbose(1, "reporting monitor %s offset: (%d, %d)\n",
+ pszMonitorName, pIter->pMonitorInfo->x, pIter->pMonitorInfo->y);
+
+ XFree((void *)pszMonitorName);
+
+ aDisplays[idxDisplay].x = pIter->pMonitorInfo->x;
+ aDisplays[idxDisplay].y = pIter->pMonitorInfo->y;
+ aDisplays[idxDisplay].w = pIter->pMonitorInfo->width;
+ aDisplays[idxDisplay].h = pIter->pMonitorInfo->height;
+
+ idxDisplay++;
+ }
+
+ vbcl_hlp_generic_free_monitor_list(&pMonitorsInfoList);
+
+ XRRFreeMonitors(pMonitorsInfo);
+
+ if (g_pfnDisplayOffsetChangeCb)
+ {
+ rc = g_pfnDisplayOffsetChangeCb(idxDisplay, aDisplays);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to notify subscriber about monitors info change, rc=%Rrc\n", rc);
+ }
+ }
+ else
+ VBClLogError("cannot get monitors info\n");
+}
+
+/** Worker thread for display change events monitoring. */
+static DECLCALLBACK(int) vbcl_hlp_generic_display_change_event_monitor_worker(RTTHREAD ThreadSelf, void *pvUser)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ RT_NOREF(pvUser);
+
+ VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker started\n");
+
+ Display *pDisplay = XOpenDisplay(NULL);
+ if (pDisplay)
+ {
+ bool fSuccess;
+ int iEventBase, iErrorBase /* unused */, iMajor, iMinor;
+
+ fSuccess = XRRQueryExtension(pDisplay, &iEventBase, &iErrorBase);
+ fSuccess &= XRRQueryVersion(pDisplay, &iMajor, &iMinor);
+
+ if (fSuccess && iMajor >= 1 && iMinor > 3)
+ {
+ /* All required checks are now passed. Notify parent thread that we started. */
+ RTThreadUserSignal(ThreadSelf);
+
+ /* Only receive events we need. */
+ XRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask);
+
+ /* Monitor main loop. */
+ while (!ASMAtomicReadBool(&g_fShutdown))
+ {
+ XEvent Event;
+
+ if (XPending(pDisplay) > 0)
+ {
+ XNextEvent(pDisplay, &Event);
+ switch (Event.type - iEventBase)
+ {
+ case RRScreenChangeNotify:
+ {
+ vbcl_hlp_generic_process_display_change_event(pDisplay);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ else
+ RTThreadSleep(RT_MS_1SEC / 2);
+ }
+ }
+ else
+ {
+ VBClLogError("dcm monitor cannot find XRandr 1.3+ extension\n");
+ rc = VERR_NOT_AVAILABLE;
+ }
+
+ XCloseDisplay(pDisplay);
+ }
+ else
+ {
+ VBClLogError("dcm monitor cannot open X Display\n");
+ rc = VERR_NOT_AVAILABLE;
+ }
+
+ VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker ended\n");
+
+ return rc;
+}
+
+static void vbcl_hlp_generic_start_display_change_monitor()
+{
+ int rc;
+
+ rc = RTXrandrLoadLib();
+ if (RT_SUCCESS(rc))
+ {
+ /* Start thread which will monitor display change events. */
+ rc = RTThreadCreate(&g_vbclHlpGenericDcmThread, vbcl_hlp_generic_display_change_event_monitor_worker, (void *)NULL, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, VBCL_HLP_DCM_THREAD_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC);
+ }
+ else
+ g_vbclHlpGenericDcmThread = NIL_RTTHREAD;
+
+ VBClLogInfo("attempt to start display change monitor thread, rc=%Rrc\n", rc);
+
+ }
+ else
+ VBClLogInfo("libXrandr not available, will not monitor display change events, rc=%Rrc\n", rc);
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnSetPrimaryDisplay}
+ */
+static DECLCALLBACK(int) vbcl_hlp_generic_set_primary_display(uint32_t idDisplay)
+{
+ XRRScreenResources *pScreenResources;
+ Display *pDisplay;
+
+ int rc = VERR_INVALID_PARAMETER;
+
+ pDisplay = XOpenDisplay(NULL);
+ if (pDisplay)
+ {
+ pScreenResources = XRRGetScreenResources(pDisplay, DefaultRootWindow(pDisplay));
+ if (pScreenResources)
+ {
+ if ((int)idDisplay < pScreenResources->noutput)
+ {
+ XRRSetOutputPrimary(pDisplay, DefaultRootWindow(pDisplay), pScreenResources->outputs[idDisplay]);
+ VBClLogInfo("display %u has been set as primary\n", idDisplay);
+ rc = VINF_SUCCESS;
+ }
+ else
+ VBClLogError("cannot set display %u as primary: index out of range\n", idDisplay);
+
+ XRRFreeScreenResources(pScreenResources);
+ }
+ else
+ VBClLogError("cannot set display %u as primary: libXrandr can not get screen resources\n", idDisplay);
+
+ XCloseDisplay(pDisplay);
+ }
+ else
+ VBClLogError("cannot set display %u as primary: cannot connect to X11\n", idDisplay);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
+ */
+static DECLCALLBACK(int) vbcl_hlp_generic_probe(void)
+{
+ /* Generic helper always supposed to return positive status on probe(). This
+ * helper is a fallback one in case all the other helpers were failed to detect
+ * their environments. */
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) vbcl_hlp_generic_init(void)
+{
+ ASMAtomicWriteBool(&g_fShutdown, false);
+
+ /* Attempt to start display change events monitor. */
+ vbcl_hlp_generic_start_display_change_monitor();
+
+ /* Always return positive status for generic (fallback, last resort) helper. */
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) vbcl_hlp_generic_term(void)
+{
+ int rc = VINF_SUCCESS;
+
+ if (g_vbclHlpGenericDcmThread != NIL_RTTHREAD)
+ {
+ /* Signal thread we are going to shutdown. */
+ ASMAtomicWriteBool(&g_fShutdown, true);
+
+ /* Wait for thread to terminate gracefully. */
+ rc = RTThreadWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC, NULL);
+ }
+
+ return rc;
+}
+
+RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb)
+{
+ g_pfnDisplayOffsetChangeCb = pfnCb;
+}
+
+RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void)
+{
+ g_pfnDisplayOffsetChangeCb = NULL;
+}
+
+/* Helper callbacks. */
+const VBCLDISPLAYHELPER g_DisplayHelperGeneric =
+{
+ "GENERIC", /* .pszName */
+ vbcl_hlp_generic_probe, /* .pfnProbe */
+ vbcl_hlp_generic_init, /* .pfnInit */
+ vbcl_hlp_generic_term, /* .pfnTerm */
+ vbcl_hlp_generic_set_primary_display, /* .pfnSetPrimaryDisplay */
+ vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */
+ vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */
+};
diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp b/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp
new file mode 100644
index 00000000..d85f6f46
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp
@@ -0,0 +1,1019 @@
+/* $Id: display-helper-gnome3.cpp $ */
+/** @file
+ * Guest Additions - Gnome3 Desktop Environment helper.
+ *
+ * A helper for X11/Wayland Client which performs Gnome Desktop
+ * Environment specific actions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/**
+ * This helper implements communication protocol between gnome-settings-daemon
+ * and itself using interface defined in (revision e88467f9):
+ *
+ * https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/org.gnome.Mutter.DisplayConfig.xml
+ */
+
+#include "VBoxClient.h"
+#include "display-helper.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/env.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+/** Load libDbus symbols needed for us. */
+#include <VBox/dbus.h>
+/* Declarations of the functions that we need from libXrandr. */
+#define VBOX_DBUS_GENERATE_BODY
+#include <VBox/dbus-calls.h>
+
+/** D-bus parameters for connecting to Gnome display service. */
+#define VBOXCLIENT_HELPER_DBUS_DESTINATION "org.gnome.Mutter.DisplayConfig"
+#define VBOXCLIENT_HELPER_DBUS_PATH "/org/gnome/Mutter/DisplayConfig"
+#define VBOXCLIENT_HELPER_DBUS_IFACE "org.gnome.Mutter.DisplayConfig"
+#define VBOXCLIENT_HELPER_DBUS_GET_METHOD "GetCurrentState"
+#define VBOXCLIENT_HELPER_DBUS_APPLY_METHOD "ApplyMonitorsConfig"
+
+/** D-bus communication timeout value, milliseconds.*/
+#define VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS (1 * 1000)
+
+/** gnome-settings-daemon ApplyMonitorsConfig method:
+ * 0: verify - test if configuration can be applied and do not change anything,
+ * 1: temporary - apply configuration temporary, all will be reverted after re-login,
+ * 2: persistent - apply configuration permanently (asks for user confirmation).
+ */
+#define VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD (1)
+
+/**
+ * Helper macro which is used in order to simplify code when batch of
+ * values needed to be parsed out of D-bus. Macro prevents execution
+ * of the 'next' command if 'previous' one was failed (tracked via
+ * local variable _ret). It is required that '_ret' should be initialized
+ * to TRUE before batch started.
+ *
+ * @param _ret Local variable which is used in order to track execution flow.
+ * @param _call A function (with full arguments) which returns 'dbus_bool_t'.
+ */
+#define VBCL_HLP_GNOME3_NEXT(_ret, _call) \
+ { _ret &= _ret ? _call : _ret; if (!ret) VBClLogError(__FILE__ ":%d: check fail here!\n", __LINE__); }
+
+/**
+ * This structure describes sub-part of physical monitor state
+ * required to compose a payload for calling ApplyMonitorsConfig method. */
+struct vbcl_hlp_gnome3_physical_display_state
+{
+ /** Physical display connector name string. */
+ char *connector;
+ /** Current mode name string for physical display. */
+ char *mode;
+};
+
+/**
+ * Verify if data represented by D-bus message iteration corresponds to given data type.
+ *
+ * @return True if D-bus message iteration corresponds to given data type, False otherwise.
+ * @param iter D-bus message iteration.
+ * @param type D-bus data type.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_verify_data_type(DBusMessageIter *iter, int type)
+{
+ if (!iter)
+ return false;
+
+ if (dbus_message_iter_get_arg_type(iter) != type)
+ return false;
+
+ return true;
+}
+
+/**
+ * Verifies D-bus iterator signature.
+ *
+ * @return True if iterator signature matches to given one.
+ * @param iter D-bus iterator to check.
+ * @param signature Expected iterator signature.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_check_iter_signature(DBusMessageIter *iter, const char *signature)
+{
+ char *iter_signature;
+ dbus_bool_t match;
+
+ if ( !iter
+ || !signature)
+ {
+ return false;
+ }
+
+ /* In case of dbus_message_iter_get_signature() returned memory should be freed by us. */
+ iter_signature = dbus_message_iter_get_signature(iter);
+ match = (strcmp(iter_signature, signature) == 0);
+
+ if (!match)
+ VBClLogError("iter signature mismatch: '%s' vs. '%s'\n", signature, iter_signature);
+
+ if (iter_signature)
+ dbus_free(iter_signature);
+
+ return match;
+}
+
+/**
+ * Verifies D-bus message signature.
+ *
+ * @return True if message signature matches to given one.
+ * @param message D-bus message to check.
+ * @param signature Expected message signature.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_check_message_signature(DBusMessage *message, const char *signature)
+{
+ char *message_signature;
+ dbus_bool_t match;
+
+ if ( !message
+ || !signature)
+ {
+ return false;
+ }
+
+ /* In case of dbus_message_get_signature() returned memory need NOT be freed by us. */
+ message_signature = dbus_message_get_signature(message);
+ match = (strcmp(message_signature, signature) == 0);
+
+ if (!match)
+ VBClLogError("message signature mismatch: '%s' vs. '%s'\n", signature, message_signature);
+
+ return match;
+}
+
+/**
+ * Jump into DBUS_TYPE_ARRAY iter container and initialize sub-iterator
+ * aimed to traverse container child nodes.
+ *
+ * @return True if operation was successful, False otherwise.
+ * @param iter D-bus iter of type DBUS_TYPE_ARRAY.
+ * @param array Returned sub-iterator.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_iter_get_array(DBusMessageIter *iter, DBusMessageIter *array)
+{
+ if (!iter || !array)
+ return false;
+
+ if (vbcl_hlp_gnome3_verify_data_type(iter, DBUS_TYPE_ARRAY))
+ {
+ dbus_message_iter_recurse(iter, array);
+ /* Move to the next iter, returned value not important. */
+ dbus_message_iter_next(iter);
+ return true;
+ }
+ else
+ {
+ VBClLogError(
+ "cannot get array: argument signature '%s' does not match to type of array\n",
+ dbus_message_iter_get_signature(iter));
+ }
+
+ return false;
+}
+
+/**
+ * Get value of D-bus iter of specified simple type (numerals, strings).
+ *
+ * @return True if operation was successful, False otherwise.
+ * @param iter D-bus iter of type simple type.
+ * @param type D-bus data type.
+ * @param value Returned value.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_iter_get_basic(DBusMessageIter *iter, int type, void *value)
+{
+ if (!iter || !value)
+ return false;
+
+ if (vbcl_hlp_gnome3_verify_data_type(iter, type))
+ {
+ dbus_message_iter_get_basic(iter, value);
+ /* Move to the next iter, returned value not important. */
+ dbus_message_iter_next(iter);
+ return true;
+ }
+ else
+ {
+ VBClLogError(
+ "cannot get value: argument signature '%s' does not match to specified type\n",
+ dbus_message_iter_get_signature(iter));
+ }
+
+ return false;
+}
+
+/**
+ * Lookup simple value (numeral, string, bool etc) in D-bus dictionary
+ * by given key and type.
+ *
+ * @return True value is found, False otherwise.
+ * @param dict D-bus iterator which represents dictionary.
+ * @param key_match Dictionary key.
+ * @param type Type of value.
+ * @param value Returning value.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_lookup_dict(DBusMessageIter *dict, const char *key_match, int type, void *value)
+{
+ dbus_bool_t found = false;
+
+ if (!dict || !key_match)
+ return false;
+
+ if (!vbcl_hlp_gnome3_check_iter_signature(dict, "{sv}"))
+ return false;
+
+ do
+ {
+ dbus_bool_t ret = true;
+ DBusMessageIter iter;
+ char *key = NULL;
+
+ /* Proceed to part a{ > sv < } of a{sv}. */
+ dbus_message_iter_recurse(dict, &iter);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ /* Proceed to part a{ > s < v} of a{sv}. */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_STRING, &key));
+
+ /* Check if key matches. */
+ if (strcmp(key_match, key) == 0)
+ {
+ DBusMessageIter value_iter;
+
+ /* Proceed to part a{s > v < } of a{sv}. */
+ dbus_message_iter_recurse(&iter, &value_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&value_iter, type, value));
+
+ /* Make sure there are no more arguments. */
+ VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&value_iter));
+
+ if (ret)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ while (dbus_message_iter_next(dict));
+
+ return found;
+}
+
+/**
+ * Go through available modes and pick up the one which has property 'is-current' set.
+ * See GetCurrentState interface documentation for more details. Returned string memory
+ * must be freed by calling function.
+ *
+ * @return Mode name as a string if found, NULL otherwise.
+ * @param modes List of monitor modes.
+ */
+static char *vbcl_hlp_gnome3_lookup_monitor_current_mode(DBusMessageIter *modes)
+{
+ char *szCurrentMode = NULL;
+ DBusMessageIter modes_iter;
+
+ /* De-serialization parameters for 'modes': (siiddada{sv}). */
+ char *id = NULL;
+ int32_t width = 0;
+ int32_t height = 0;
+ double refresh_rate = 0;
+ double preferred_scale = 0;
+ DBusMessageIter supported_scales;
+ DBusMessageIter properties;
+
+ if (!modes)
+ return NULL;
+
+ if(!vbcl_hlp_gnome3_check_iter_signature(modes, "(siiddada{sv})"))
+ return NULL;
+
+ do
+ {
+ static const char *key_match = "is-current";
+ dbus_bool_t default_mode_found = false;
+ dbus_bool_t ret = true;
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, NULL);
+
+ /* Proceed to part a( > siiddada{sv} < ) of a(siiddada{sv}). */
+ dbus_message_iter_recurse(modes, &modes_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_STRING, &id));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &width));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &height));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &refresh_rate));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &preferred_scale));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &supported_scales));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &properties));
+
+ ret = vbcl_hlp_gnome3_lookup_dict(&properties, key_match, DBUS_TYPE_BOOLEAN, &default_mode_found);
+ if (ret && default_mode_found)
+ {
+ szCurrentMode = strdup(id);
+ break;
+ }
+ }
+ while (dbus_message_iter_next(modes));
+
+ return szCurrentMode;
+}
+
+/**
+ * Parse physical monitors list entry. See GetCurrentState interface documentation for more details.
+ *
+ * @return True if monitors list entry has been successfully parsed, False otherwise.
+ * @param physical_monitors_in D-bus iterator representing list of physical monitors.
+ * @param connector Connector name (out).
+ * @param vendor Vendor name (out).
+ * @param product Product name (out).
+ * @param physical_monitor_serial Serial number (out).
+ * @param modes List of monitor modes (out).
+ * @param physical_monitor_properties A D-bus dictionary containing monitor properties (out).
+ */
+static dbus_bool_t vbcl_hlp_gnome3_parse_physical_monitor_record(
+ DBusMessageIter *physical_monitors_in,
+ char **connector,
+ char **vendor,
+ char **product,
+ char **physical_monitor_serial,
+ DBusMessageIter *modes,
+ DBusMessageIter *physical_monitor_properties)
+{
+ dbus_bool_t ret = true;
+
+ DBusMessageIter physical_monitors_in_iter;
+ DBusMessageIter physical_monitors_in_description_iter;
+
+ if ( !physical_monitors_in
+ || !connector
+ || !vendor
+ || !product
+ || !physical_monitor_serial
+ || !modes
+ || !physical_monitor_properties)
+ {
+ return false;
+ }
+
+ /* Validate signature. */
+ if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})"))
+ return false;
+
+ /* Proceed to part ( > (ssss)a(siiddada{sv})a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */
+ dbus_message_iter_recurse(physical_monitors_in, &physical_monitors_in_iter);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ /* Proceed to part ( > (ssss) < a(siiddada{sv})a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */
+ dbus_message_iter_recurse(&physical_monitors_in_iter, &physical_monitors_in_description_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, connector));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, vendor));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, product));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, physical_monitor_serial));
+
+ /* Proceed to part ((ssss) > a(siiddada{sv}) < a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */
+ if (ret)
+ dbus_message_iter_next(&physical_monitors_in_iter);
+
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, modes));
+
+ /* Proceed to part ((ssss)a(siiddada{sv}) > a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, physical_monitor_properties));
+
+ /* Make sure there are no more arguments. */
+ VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&physical_monitors_in_iter));
+
+ return ret;
+}
+
+/**
+ * Parse logical monitors list entry. See GetCurrentState interface documentation for more details.
+ *
+ * @return True if monitors list entry has been successfully parsed, False otherwise.
+ * @param logical_monitors_in D-bus iterator representing list of logical monitors.
+ * @param x Monitor X position (out).
+ * @param y Monitor Y position (out).
+ * @param scale Monitor scale factor (out).
+ * @param transform Current monitor transform (rotation) (out).
+ * @param primary A flag which indicates if monitor is set as primary (out).
+ * @param monitors List of physical monitors which are displaying this logical monitor (out).
+ * @param properties List of monitor properties (out).
+ */
+static dbus_bool_t vbcl_hlp_gnome3_parse_logical_monitor_record(
+ DBusMessageIter *logical_monitors_in,
+ int32_t *x,
+ int32_t *y,
+ double *scale,
+ uint32_t *transform,
+ dbus_bool_t *primary,
+ DBusMessageIter *monitors,
+ DBusMessageIter *properties)
+{
+ dbus_bool_t ret = true;
+
+ /* Iter used to traverse logical monitor parameters: @a(iiduba(ssss)a{sv}). */
+ DBusMessageIter logical_monitors_in_iter;
+
+ if ( !logical_monitors_in
+ || !x
+ || !y
+ || !scale
+ || !transform
+ || !primary
+ || !monitors
+ || !properties)
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ /* Proceed to part @a( > iiduba(ssss)a{sv} < ) of @a(iiduba(ssss)a{sv}). */
+ dbus_message_iter_recurse(logical_monitors_in, &logical_monitors_in_iter);
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, x));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, y));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_DOUBLE, scale));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_UINT32, transform));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_BOOLEAN, primary));
+ /* Proceed to part @a(iidub > a(ssss) < a{sv}) of @a(iiduba(ssss)a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, monitors));
+ /* Proceed to part @a(iiduba(ssss) > a{sv} < ) of @a(iiduba(ssss)a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, properties));
+
+ /* Make sure there are no more arguments. */
+ VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&logical_monitors_in_iter));
+
+ return ret;
+}
+
+/**
+ * Get list of physical monitors parameters from D-bus iterator.
+ *
+ * Once this function was traversed 'physical_monitors_in' iterator, we are in the
+ * end of the list of physical monitors parameters. So, it is important to do it once.
+ *
+ * @return True if monitors parameters were successfully discovered, False otherwise.
+ * @param physical_monitors_in D-bus iterator representing list of physical monitors.
+ * @param state Storage to put monitors state to.
+ * @param state_records_max Size of state storage.
+ * @param cPhysicalMonitors Actual number of physical displays parsed.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_get_physical_monitors_state(
+ DBusMessageIter *physical_monitors_in,
+ vbcl_hlp_gnome3_physical_display_state *state,
+ uint32_t state_records_max,
+ uint32_t *cPhysicalMonitors)
+{
+ dbus_bool_t ret = true;
+ uint32_t iMonitor = 0;
+
+ if ( !physical_monitors_in
+ || !state
+ || !cPhysicalMonitors)
+ {
+ return false;
+ }
+
+ /* Validate signature. */
+ if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})"))
+ return false;
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ do
+ {
+ char *connector = NULL;
+ char *vendor = NULL;
+ char *product = NULL;
+ char *physical_monitor_serial = NULL;
+ DBusMessageIter modes;
+ DBusMessageIter physical_monitor_properties;
+
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_physical_monitor_record(
+ physical_monitors_in, &connector, &vendor, &product, &physical_monitor_serial,
+ &modes, &physical_monitor_properties));
+
+ if (iMonitor < state_records_max)
+ {
+ state[iMonitor].connector = connector;
+ state[iMonitor].mode = vbcl_hlp_gnome3_lookup_monitor_current_mode(&modes);
+
+ /* Check if both parameters were discovered successfully. */
+ VBCL_HLP_GNOME3_NEXT(ret, state[iMonitor].connector && state[iMonitor].mode);
+ }
+
+ iMonitor++;
+
+ }
+ while (ret && dbus_message_iter_next(physical_monitors_in));
+
+ if (iMonitor >= state_records_max)
+ {
+ VBClLogError("physical monitors list is too big (%u)\n", iMonitor);
+ ret = false;
+ }
+
+ *cPhysicalMonitors = iMonitor;
+
+ return ret;
+}
+
+/**
+ * Release monitors state resources.
+ *
+ * @param state Array of monitor states.
+ * @param cPhysicalMonitors Number of elements in array.
+ */
+static void vbcl_hlp_gnome3_free_physical_monitors_state(
+ vbcl_hlp_gnome3_physical_display_state *state,
+ uint32_t cPhysicalMonitors)
+{
+ if (!state || !cPhysicalMonitors)
+ return;
+
+ for (uint32_t i = 0; i < cPhysicalMonitors; i++)
+ {
+ /* Only free() what we allocated ourselves. */
+ if (state[i].mode)
+ free(state[i].mode);
+ }
+}
+
+/**
+ * Add dictionary element with boolean value into an array.
+ *
+ * @return True on success, False otherwise.
+ * @param parent_iter Array to add dictionary element into.
+ * @param key Dictionary key.
+ * @param value Boolean value for given key.
+ */
+static dbus_bool_t vbcl_hlp_gnome3_add_dict_bool_entry(
+ DBusMessageIter *parent_iter, const char *key, const dbus_bool_t value)
+{
+ dbus_bool_t ret = true;
+
+ DBusMessageIter sub_iter_key;
+ DBusMessageIter sub_iter_value;
+
+ RT_ZERO(sub_iter_key);
+ RT_ZERO(sub_iter_value);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, false);
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(parent_iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub_iter_key));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_key, DBUS_TYPE_STRING, &key));
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter_key, ((int) 'v'), "b", &sub_iter_value));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_value, DBUS_TYPE_BOOLEAN, &value));
+ }
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter_key, &sub_iter_value));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(parent_iter, &sub_iter_key));
+
+ return ret;
+}
+
+/**
+ * This function is responsible for gathering current display
+ * information (via its helper functions), compose a payload
+ * for ApplyMonitorsConfig method and finally send configuration
+ * change to gnome-settings-daemon over D-bus.
+ *
+ * @return IPRT status code.
+ * @param connection Handle to D-bus connection.
+ * @param serial Serial number obtained from GetCurrentState interface,
+ * needs to be passed to ApplyMonitorsConfig.
+ * @param physical_monitors_in List of physical monitors (see GetCurrentState).
+ * @param logical_monitors_in List of logical monitors (see GetCurrentState).
+ * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary.
+ */
+static int vbcl_hlp_gnome3_convert_and_apply_display_settings(
+ DBusConnection *connection,
+ uint32_t serial,
+ DBusMessageIter *physical_monitors_in,
+ DBusMessageIter *logical_monitors_in,
+ uint32_t idPrimaryDisplay)
+{
+ int rc = VERR_INVALID_PARAMETER;
+ uint32_t iLogicalMonitor = 0;
+ uint32_t cPhysicalMonitors = 0;
+ int32_t method = VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD;
+
+ dbus_bool_t ret = true;
+ DBusError error;
+ DBusMessage *reply = NULL;;
+ DBusMessage *message = NULL;
+ DBusMessageIter message_iter;
+ DBusMessageIter logical_monitors_out_iter;
+ DBusMessageIter properties_out_iter;
+
+ struct vbcl_hlp_gnome3_physical_display_state
+ physical_monitors_state[VBOX_DRMIPC_MONITORS_MAX];
+
+ if ( !connection
+ || !physical_monitors_in
+ || !logical_monitors_in)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */
+ RT_ZERO(message_iter);
+ RT_ZERO(logical_monitors_out_iter);
+ RT_ZERO(properties_out_iter);
+
+ message = dbus_message_new_method_call(
+ VBOXCLIENT_HELPER_DBUS_DESTINATION,
+ VBOXCLIENT_HELPER_DBUS_PATH,
+ VBOXCLIENT_HELPER_DBUS_IFACE,
+ VBOXCLIENT_HELPER_DBUS_APPLY_METHOD);
+ if (!message)
+ {
+ VBClLogError("unable to apply monitors config: no memory\n");
+ return VERR_NO_MEMORY;
+ }
+
+ /* Start composing payload for ApplyMonitorsConfig method: (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ dbus_message_iter_init_append(message, &message_iter);
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, VERR_INVALID_PARAMETER);
+
+ /* Get list of physical monitors parameters. */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_get_physical_monitors_state(
+ physical_monitors_in, physical_monitors_state, VBOX_DRMIPC_MONITORS_MAX, &cPhysicalMonitors));
+
+ /* ( >u< u@a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &serial));
+ /* (u >u< @a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &method));
+
+ /* Parameter "monitors" of method ApplyMonitorsConfig.
+ * Part (uu >@a(iiduba(ssa{sv}))< @a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "(iiduba(ssa{sv}))", &logical_monitors_out_iter));
+
+ /* Iterate over current configuration monitors (@logical_monitors
+ * parameter of GetCurrentState interface) and compose the rest part of message. */
+ do
+ {
+ /* De-serialization parameters for @logical_monitors data (see GetCurrentState interface documentation). */
+ int32_t x = 0;
+ int32_t y = 0;
+ double scale = 0;
+ uint32_t transform = 0;
+ dbus_bool_t primary = false;
+ dbus_bool_t isPrimary = false;
+ DBusMessageIter monitors;
+ DBusMessageIter properties;
+
+ /* These iterators are used in order to compose sub-containers of the message. */
+ DBusMessageIter sub_iter0;
+ DBusMessageIter sub_iter1;
+ DBusMessageIter sub_iter2;
+ DBusMessageIter sub_iter3;
+ /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */
+ RT_ZERO(sub_iter0);
+ RT_ZERO(sub_iter1);
+ RT_ZERO(sub_iter2);
+ RT_ZERO(sub_iter3);
+
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_logical_monitor_record(
+ logical_monitors_in, &x, &y, &scale, &transform, &primary, &monitors, &properties));
+
+ if (ret)
+ {
+ /* Whether current display supposed to be set as primary. */
+ isPrimary = (iLogicalMonitor == idPrimaryDisplay);
+
+ /* Compose part (uu@a( > iiduba(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&logical_monitors_out_iter, DBUS_TYPE_STRUCT, NULL, &sub_iter0));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &x));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &y));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_DOUBLE, &scale));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_UINT32, &transform));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_BOOLEAN, &isPrimary));
+
+ /* Compose part (uu@a(iidub > a(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter0, DBUS_TYPE_ARRAY, "(ssa{sv})", &sub_iter1));
+ {
+ /* Compose part (uu@a(iiduba > (ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter1, DBUS_TYPE_STRUCT, NULL, &sub_iter2));
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].connector));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].mode));
+
+ /* Compose part (uu@a(iiduba(ss > a{sv} < ))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter2, DBUS_TYPE_ARRAY, "{sv}", &sub_iter3));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-current", true));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-preferred", true));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter2, &sub_iter3));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter1, &sub_iter2));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter0, &sub_iter1));
+ }
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&logical_monitors_out_iter, &sub_iter0));
+
+ iLogicalMonitor++;
+
+ if (!ret)
+ {
+ dbus_message_iter_abandon_container_if_open(&sub_iter2, &sub_iter3);
+ dbus_message_iter_abandon_container_if_open(&sub_iter1, &sub_iter2);
+ dbus_message_iter_abandon_container_if_open(&sub_iter0, &sub_iter1);
+ dbus_message_iter_abandon_container_if_open(&logical_monitors_out_iter, &sub_iter0);
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ }
+ while (ret && dbus_message_iter_next(logical_monitors_in));
+
+ /* Finish with parameter "monitors" of method ApplyMonitorsConfig. */
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &logical_monitors_out_iter));
+
+ /* Parameter "properties" of method ApplyMonitorsConfig (empty dict).
+ * Part (uu@a(iiduba(ssa{sv})) >@a{sv}< ) of (uu@a(iiduba(ssa{sv}))@a{sv}).*/
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "{sv}", &properties_out_iter));
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &properties_out_iter));
+
+ if (ret)
+ {
+ dbus_error_init(&error);
+
+ reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error);
+ if (reply)
+ {
+ VBClLogInfo("display %d has been set as primary\n", idPrimaryDisplay);
+ dbus_message_unref(reply);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ VBClLogError("unable to apply monitors config: %s\n",
+ dbus_error_is_set(&error) ? error.message : "unknown error");
+ dbus_error_free(&error);
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ VBClLogError("unable to apply monitors config: cannot compose monitors config\n");
+
+ dbus_message_iter_abandon_container_if_open(&message_iter, &logical_monitors_out_iter);
+ dbus_message_iter_abandon_container_if_open(&message_iter, &properties_out_iter);
+
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ /* Clean physical monitors state. */
+ vbcl_hlp_gnome3_free_physical_monitors_state(physical_monitors_state, cPhysicalMonitors);
+
+ dbus_message_unref(message);
+
+ return rc;
+}
+
+/**
+ * This function parses GetCurrentState interface call reply and passes it for further processing.
+ *
+ * @return IPRT status code.
+ * @param connection Handle to D-bus connection.
+ * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary.
+ * @param reply Reply message of GetCurrentState call.
+ */
+static int vbcl_hlp_gnome3_process_current_display_layout(
+ DBusConnection *connection, uint32_t idPrimaryDisplay, DBusMessage *reply)
+{
+ static const char *expected_signature = "ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv}";
+
+ dbus_bool_t ret = true;
+ DBusMessageIter iter;
+ int rc = VERR_GENERAL_FAILURE;
+
+ uint32_t serial = 0;
+ DBusMessageIter monitors;
+ DBusMessageIter logical_monitors_in;
+ DBusMessageIter properties;
+
+ if (!reply)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* Parse VBOXCLIENT_HELPER_DBUS_GET_METHOD reply payload:
+ *
+ * (u@a((ssss)a(siiddada{sv})a{sv})@a(iiduba(ssss)a{sv})@a{sv}).
+ *
+ * Method return the following arguments: monitors, logical_monitors, properties.
+ */
+
+ /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */
+ AssertReturn(ret, VERR_INVALID_PARAMETER);
+
+ /* Important: in order to avoid libdbus asserts during parsing, its signature should be verified at first. */
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_check_message_signature(reply, expected_signature));
+
+ VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_init(reply, &iter));
+ if (ret)
+ {
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_UINT32, &serial));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &monitors));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &logical_monitors_in));
+ VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &properties));
+
+ /* Make sure there are no more arguments. */
+ if (ret && !dbus_message_iter_has_next(&iter))
+ {
+ rc = vbcl_hlp_gnome3_convert_and_apply_display_settings(
+ connection, serial, &monitors, &logical_monitors_in, idPrimaryDisplay);
+ }
+ else
+ {
+ VBClLogError("cannot fetch current displays configuration: incorrect number of arguments\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ VBClLogError("cannot fetch current displays configuration: no data\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+/**
+ * This function establishes D-bus connection, requests gnome-settings-daemon
+ * to provide current display configuration via GetCurrentState interface call
+ * and passes this information further to helper functions in order to set
+ * requested display as primary.
+ *
+ * @return IPRT status code.
+ * @param idPrimaryDisplay A display ID which is requested to be set as primary.
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_set_primary_display(uint32_t idPrimaryDisplay)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ DBusConnection *connection = NULL;
+ DBusMessage *message = NULL;
+ DBusError error;
+
+ rc = RTDBusLoadLib();
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to load D-bus library\n");
+ return VERR_SYMBOL_NOT_FOUND;
+ }
+
+ dbus_error_init(&error);
+ connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
+ if (!dbus_error_is_set(&error))
+ {
+ message = dbus_message_new_method_call(
+ VBOXCLIENT_HELPER_DBUS_DESTINATION,
+ VBOXCLIENT_HELPER_DBUS_PATH,
+ VBOXCLIENT_HELPER_DBUS_IFACE,
+ VBOXCLIENT_HELPER_DBUS_GET_METHOD);
+
+ if (message)
+ {
+ DBusMessage *reply;
+
+ reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error);
+ if (!dbus_error_is_set(&error))
+ {
+ rc = vbcl_hlp_gnome3_process_current_display_layout(connection, idPrimaryDisplay, reply);
+ dbus_message_unref(reply);
+ }
+ else
+ {
+ VBClLogError("unable to get current display configuration: %s\n", error.message);
+ dbus_error_free(&error);
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ dbus_message_unref(message);
+ }
+ else
+ {
+ VBClLogError("unable to get current display configuration: no memory\n");
+ rc = VERR_NO_MEMORY;
+ }
+
+ dbus_connection_flush(connection);
+ }
+ else
+ {
+ VBClLogError("unable to establish dbus connection: %s\n", error.message);
+ dbus_error_free(&error);
+ rc = VERR_INVALID_HANDLE;
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe}
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_probe(void)
+{
+ const char *pszCurrentDesktop = RTEnvGet(VBCL_ENV_XDG_CURRENT_DESKTOP);
+
+ /* GNOME3 identifies itself by XDG_CURRENT_DESKTOP environment variable.
+ * It can slightly vary for different distributions, but we assume that this
+ * variable should at least contain sub-string 'GNOME' in its value. */
+ if (pszCurrentDesktop && RTStrStr(pszCurrentDesktop, "GNOME"))
+ return VINF_SUCCESS;
+
+ return VERR_NOT_FOUND;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnInit}
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_init(void)
+{
+ int rc;
+
+ if (!VBClHasWayland())
+ {
+ rc = vbcl_hlp_generic_init();
+ VBClLogInfo("attempt to start generic helper routines, rc=%Rrc\n", rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBCLDISPLAYHELPER,pfnTerm}
+ */
+static DECLCALLBACK(int) vbcl_hlp_gnome3_term(void)
+{
+ int rc;
+
+ if (!VBClHasWayland())
+ {
+ rc = vbcl_hlp_generic_term();
+ VBClLogInfo("attempt to stop generic helper routines, rc=%Rrc\n", rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* Helper callbacks. */
+const VBCLDISPLAYHELPER g_DisplayHelperGnome3 =
+{
+ "GNOME3", /* .pszName */
+ vbcl_hlp_gnome3_probe, /* .pfnProbe */
+ vbcl_hlp_gnome3_init, /* .pfnInit */
+ vbcl_hlp_gnome3_term, /* .pfnTerm */
+ vbcl_hlp_gnome3_set_primary_display, /* .pfnSetPrimaryDisplay */
+ vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */
+ vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */
+};
diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper.h b/src/VBox/Additions/x11/VBoxClient/display-helper.h
new file mode 100644
index 00000000..81fae51c
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-helper.h
@@ -0,0 +1,130 @@
+/* $Id: display-helper.h $ */
+/** @file
+ * Guest Additions - Definitions for Desktop Environment helpers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "display-ipc.h"
+
+/**
+ * Display offsets change notification callback.
+ *
+ * @returns IPRT status code.
+ * @param cDisplays Number of displays which have changed their offset.
+ * @param aDisplays Displays offset data.
+ */
+typedef DECLCALLBACKTYPE(int, FNDISPLAYOFFSETCHANGE, (uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays));
+
+/**
+ * Desktop Environment helper definition structure.
+ */
+typedef struct
+{
+ /** A short helper name. 16 chars maximum (RTTHREAD_NAME_LEN). */
+ const char *pszName;
+
+ /**
+ * Probing callback.
+ *
+ * Called in attempt to detect if user is currently running Desktop Environment
+ * which is compatible with the helper.
+ *
+ * @returns IPRT status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnProbe, (void));
+
+ /**
+ * Initialization callback.
+ *
+ * @returns IPRT status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnInit, (void));
+
+ /**
+ * Termination callback.
+ *
+ * @returns IPRT status code.
+ */
+ DECLCALLBACKMEMBER(int, pfnTerm, (void));
+
+ /**
+ * Set primary display in Desktop Environment specific way.
+ *
+ * @returns IPRT status code.
+ * @param idDisplay Display ID which should be set as primary.
+ */
+ DECLCALLBACKMEMBER(int, pfnSetPrimaryDisplay, (uint32_t idDisplay));
+
+ /**
+ * Register notification callback for display offsets change event.
+ *
+ * @param pfnCb Notification callback.
+ */
+ DECLCALLBACKMEMBER(void, pfnSubscribeDisplayOffsetChangeNotification, (FNDISPLAYOFFSETCHANGE *pfnCb));
+
+ /**
+ * Unregister notification callback for display offsets change event.
+ */
+ DECLCALLBACKMEMBER(void, pfnUnsubscribeDisplayOffsetChangeNotification, (void));
+
+} VBCLDISPLAYHELPER;
+
+/**
+ * Initialization callback for generic Desktop Environment helper.
+ *
+ * @returns IPRT status code.
+ */
+RTDECL(int) vbcl_hlp_generic_init(void);
+
+/**
+ * Termination callback for generic Desktop Environment helper.
+ *
+ * @returns IPRT status code.
+ */
+RTDECL(int) vbcl_hlp_generic_term(void);
+
+/**
+ * Subscribe to display offset change notifications emitted by Generic Desktop Environment helper.
+ *
+ * @param pfnCb A pointer to callback function which will be triggered when event arrives.
+ */
+RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb);
+
+/**
+ * Unsubscribe from display offset change notifications emitted by Generic Desktop Environment helper.
+ */
+RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void);
+
+/** GNOME3 helper private data. */
+extern const VBCLDISPLAYHELPER g_DisplayHelperGnome3;
+/** Generic helper private data. */
+extern const VBCLDISPLAYHELPER g_DisplayHelperGeneric;
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp b/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp
new file mode 100644
index 00000000..5f5d5c8b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp
@@ -0,0 +1,451 @@
+/* $Id: display-ipc.cpp $ */
+/** @file
+ * Guest Additions - DRM IPC communication core functions.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This module implements connection handling routine which is common for
+ * both IPC server and client (see vbDrmIpcConnectionHandler()). This function
+ * at first tries to read incoming command from IPC socket and if no data has
+ * arrived within VBOX_DRMIPC_RX_TIMEOUT_MS, it checks is there is some data in
+ * TX queue and sends it. TX queue and IPC connection handle is unique per IPC
+ * client and handled in a separate thread of either server or client process.
+ *
+ * Logging is implemented in a way that errors are always printed out,
+ * VBClLogVerbose(2) is used for debugging purposes and reflects what is related to
+ * IPC communication. In order to see logging on a host side it is enough to do:
+ *
+ * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host.
+ */
+
+#include "VBoxClient.h"
+#include "display-ipc.h"
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/localipc.h>
+#include <iprt/err.h>
+#include <iprt/crc.h>
+#include <iprt/mem.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/assert.h>
+
+#include <grp.h>
+#include <pwd.h>
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+
+/**
+ * Calculate size of TX list entry.
+ *
+ * TX list entry consists of RTLISTNODE, DRM IPC message header and message payload.
+ * Given IpcCmd already includes message header and payload. So, TX list entry size
+ * equals to size of IpcCmd plus size of RTLISTNODE.
+ *
+ * @param IpcCmd A structure which represents DRM IPC command.
+ */
+#define DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(IpcCmd) (sizeof(IpcCmd) + RT_UOFFSETOF(VBOX_DRMIPC_TX_LIST_ENTRY, Hdr))
+
+/**
+ * Initialize IPC client private data.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC client private data to be initialized.
+ * @param hThread A thread which server IPC client connection.
+ * @param hClientSession IPC session handle obtained from RTLocalIpcSessionXXX().
+ * @param cTxListCapacity Maximum number of messages which can be queued for TX for this IPC session.
+ * @param pfnRxCb IPC RX callback function pointer.
+ */
+RTDECL(int) vbDrmIpcClientInit(PVBOX_DRMIPC_CLIENT pClient, RTTHREAD hThread, RTLOCALIPCSESSION hClientSession,
+ uint32_t cTxListCapacity, PFNDRMIPCRXCB pfnRxCb)
+{
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+ AssertReturn(hThread, VERR_INVALID_PARAMETER);
+ AssertReturn(hClientSession, VERR_INVALID_PARAMETER);
+ AssertReturn(cTxListCapacity, VERR_INVALID_PARAMETER);
+ AssertReturn(pfnRxCb, VERR_INVALID_PARAMETER);
+
+ pClient->hThread = hThread;
+ pClient->hClientSession = hClientSession;
+
+ RT_ZERO(pClient->TxList);
+ RTListInit(&pClient->TxList.Node);
+
+ pClient->cTxListCapacity = cTxListCapacity;
+ ASMAtomicWriteU32(&pClient->cTxListSize, 0);
+
+ pClient->pfnRxCb = pfnRxCb;
+
+ return RTCritSectInit(&pClient->CritSect);
+}
+
+/**
+ * Releases IPC client private data resources.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data to be initialized.
+ */
+RTDECL(int) vbDrmIpcClientReleaseResources(PVBOX_DRMIPC_CLIENT pClient)
+{
+ PVBOX_DRMIPC_TX_LIST_ENTRY pEntry, pNextEntry;
+ int rc;
+
+ pClient->hClientSession = 0;
+
+ rc = RTCritSectEnter(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTListIsEmpty(&pClient->TxList.Node))
+ {
+ RTListForEachSafe(&pClient->TxList.Node, pEntry, pNextEntry, VBOX_DRMIPC_TX_LIST_ENTRY, Node)
+ {
+ RTListNodeRemove(&pEntry->Node);
+ RTMemFree(pEntry);
+ ASMAtomicDecU32(&pClient->cTxListSize);
+ }
+ }
+
+ rc = RTCritSectLeave(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectDelete(&pClient->CritSect);
+ if (RT_FAILURE(rc))
+ VBClLogError("vbDrmIpcClientReleaseResources: unable to delete critical section, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("vbDrmIpcClientReleaseResources: unable to leave critical section, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("vbDrmIpcClientReleaseResources: unable to enter critical section, rc=%Rrc\n", rc);
+
+ Assert(ASMAtomicReadU32(&pClient->cTxListSize) == 0);
+
+ RT_ZERO(*pClient);
+
+ return rc;
+}
+
+/**
+ * Add message to IPC session TX queue.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param pEntry Pointer to the message.
+ */
+static int vbDrmIpcSessionScheduleTx(PVBOX_DRMIPC_CLIENT pClient, PVBOX_DRMIPC_TX_LIST_ENTRY pEntry)
+{
+ int rc;
+
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+ AssertReturn(pEntry, VERR_INVALID_PARAMETER);
+
+ rc = RTCritSectEnter(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (pClient->cTxListSize < pClient->cTxListCapacity)
+ {
+ RTListAppend(&pClient->TxList.Node, &pEntry->Node);
+ pClient->cTxListSize++;
+ }
+ else
+ VBClLogError("vbDrmIpcSessionScheduleTx: TX queue is full\n");
+
+ int rc2 = RTCritSectLeave(&pClient->CritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("vbDrmIpcSessionScheduleTx: cannot leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("vbDrmIpcSessionScheduleTx: cannot enter critical section, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Pick up message from TX queue if available.
+ *
+ * @return Pointer to list entry or NULL if queue is empty.
+ */
+static PVBOX_DRMIPC_TX_LIST_ENTRY vbDrmIpcSessionPickupTxMessage(PVBOX_DRMIPC_CLIENT pClient)
+{
+ PVBOX_DRMIPC_TX_LIST_ENTRY pEntry = NULL;
+ int rc;
+
+ AssertReturn(pClient, NULL);
+
+ rc = RTCritSectEnter(&pClient->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTListIsEmpty(&pClient->TxList.Node))
+ {
+ pEntry = (PVBOX_DRMIPC_TX_LIST_ENTRY)RTListRemoveFirst(&pClient->TxList.Node, VBOX_DRMIPC_TX_LIST_ENTRY, Node);
+ pClient->cTxListSize--;
+ Assert(pEntry);
+ }
+
+ int rc2 = RTCritSectLeave(&pClient->CritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot leave critical section, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot enter critical section, rc=%Rrc\n", rc);
+
+ return pEntry;
+}
+
+RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession)
+{
+ int rc = VERR_ACCESS_DENIED;
+ RTUID uUid;
+ struct group *pAllowedGroup;
+
+ AssertReturn(hClientSession, VERR_INVALID_PARAMETER);
+
+ /* Get DRM IPC user group entry from system database. */
+ pAllowedGroup = getgrnam(VBOX_DRMIPC_USER_GROUP);
+ if (!pAllowedGroup)
+ return RTErrConvertFromErrno(errno);
+
+ /* Get remote user ID and check if it is in allowed user group. */
+ rc = RTLocalIpcSessionQueryUserId(hClientSession, &uUid);
+ if (RT_SUCCESS(rc))
+ {
+ /* Get user record from system database and look for it in group's members list. */
+ struct passwd *UserRecord = getpwuid(uUid);
+
+ if (UserRecord && UserRecord->pw_name)
+ {
+ while (*pAllowedGroup->gr_mem)
+ {
+ if (RTStrNCmp(*pAllowedGroup->gr_mem, UserRecord->pw_name, LOGIN_NAME_MAX) == 0)
+ return VINF_SUCCESS;
+
+ pAllowedGroup->gr_mem++;
+ }
+ }
+ }
+
+ return rc;
+}
+
+RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
+ (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY));
+
+ if (pTxListEntry)
+ {
+ PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)(&pTxListEntry->Hdr);
+
+ pCmd->Hdr.idCmd = VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY;
+ pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY);
+ pCmd->idDisplay = idDisplay;
+ pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
+ Assert(pCmd->Hdr.u64Crc);
+
+ /* Put command into queue and trigger TX. */
+ rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogVerbose(2, "vbDrmIpcSetPrimaryDisplay: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
+ }
+ else
+ {
+ RTMemFree(pTxListEntry);
+ VBClLogError("vbDrmIpcSetPrimaryDisplay: unable to schedule TX, rc=%Rrc\n", rc);
+ }
+ }
+ else
+ {
+ VBClLogInfo("cannot allocate SET_PRIMARY_DISPLAY command\n");
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Report to IPC server that display layout offsets have been changed (called by IPC client).
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param cDisplays Number of monitors which have offsets changed.
+ * @param aDisplays Offsets data.
+ */
+RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry =
+ (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(
+ DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS));
+
+ if (pTxListEntry)
+ {
+ PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)(&pTxListEntry->Hdr);
+
+ pCmd->Hdr.idCmd = VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS;
+ pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS);
+ pCmd->cDisplays = cDisplays;
+ memcpy(pCmd->aDisplays, aDisplays, cDisplays * sizeof(struct VBOX_DRMIPC_VMWRECT));
+ pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData);
+ Assert(pCmd->Hdr.u64Crc);
+
+ /* Put command into queue and trigger TX. */
+ rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry);
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogVerbose(2, "vbDrmIpcReportDisplayOffsets: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc);
+ }
+ else
+ {
+ RTMemFree(pTxListEntry);
+ VBClLogError("vbDrmIpcReportDisplayOffsets: unable to schedule TX, rc=%Rrc\n", rc);
+ }
+ }
+ else
+ {
+ VBClLogInfo("cannot allocate REPORT_DISPLAY_OFFSETS command\n");
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Common function for both IPC server and client which is responsible
+ * for handling IPC communication flow.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC connection private data.
+ */
+RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient)
+{
+ int rc;
+ static uint8_t aInputBuf[VBOX_DRMIPC_RX_BUFFER_SIZE];
+ size_t cbRead = 0;
+ PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry;
+
+ AssertReturn(pClient, VERR_INVALID_PARAMETER);
+
+ /* Make sure we are still connected to IPC server. */
+ if (!pClient->hClientSession)
+ {
+ VBClLogVerbose(2, "connection to IPC server lost\n");
+ return VERR_NET_CONNECTION_RESET_BY_PEER;
+ }
+
+ AssertReturn(pClient->pfnRxCb, VERR_INVALID_PARAMETER);
+
+ /* Make sure we have valid connection handle. By reporting VERR_BROKEN_PIPE,
+ * we trigger reconnect to IPC server. */
+ if (!RT_VALID_PTR(pClient->hClientSession))
+ return VERR_BROKEN_PIPE;
+
+ rc = RTLocalIpcSessionWaitForData(pClient->hClientSession, VBOX_DRMIPC_RX_TIMEOUT_MS);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read IPC message header. */
+ rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf, sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbRead == sizeof(VBOX_DRMIPC_COMMAND_HEADER))
+ {
+ PVBOX_DRMIPC_COMMAND_HEADER pHdr = (PVBOX_DRMIPC_COMMAND_HEADER)aInputBuf;
+ if (pHdr)
+ {
+ AssertReturn(pHdr->cbData <= sizeof(aInputBuf) - sizeof(VBOX_DRMIPC_COMMAND_HEADER), VERR_INVALID_PARAMETER);
+
+ /* Read the rest of a message. */
+ rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf + sizeof(VBOX_DRMIPC_COMMAND_HEADER), pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead);
+ AssertRCReturn(rc, rc);
+ AssertReturn(cbRead == (pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER)), VERR_INVALID_PARAMETER);
+
+ uint64_t u64Crc = pHdr->u64Crc;
+
+ /* Verify checksum. */
+ pHdr->u64Crc = 0;
+ if (u64Crc != 0 && RTCrc64(pHdr, pHdr->cbData) == u64Crc)
+ {
+ /* Restore original CRC. */
+ pHdr->u64Crc = u64Crc;
+
+ /* Trigger RX callback. */
+ rc = pClient->pfnRxCb(pHdr->idCmd, (void *)pHdr, pHdr->cbData);
+ VBClLogVerbose(2, "command 0x%X executed, rc=%Rrc\n", pHdr->idCmd, rc);
+ }
+ else
+ {
+ VBClLogError("unable to read from IPC: CRC mismatch, provided crc=0x%X, cmd=0x%X\n", u64Crc, pHdr->idCmd);
+ rc = VERR_NOT_EQUAL;
+ }
+ }
+ else
+ {
+ VBClLogError("unable to read from IPC: zero data received\n");
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ VBClLogError("received partial IPC message header (%u bytes)\n", cbRead);
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ VBClLogVerbose(2, "received %u bytes from IPC\n", cbRead);
+ }
+ else
+ {
+ VBClLogError("unable to read from IPC, rc=%Rrc\n", rc);
+ }
+ }
+
+ /* Check if TX queue has some messages to transfer. */
+ while ((pTxListEntry = vbDrmIpcSessionPickupTxMessage(pClient)) != NULL)
+ {
+ PVBOX_DRMIPC_COMMAND_HEADER pMessageHdr = (PVBOX_DRMIPC_COMMAND_HEADER)(&pTxListEntry->Hdr);
+ Assert(pMessageHdr);
+
+ rc = RTLocalIpcSessionWrite(
+ pClient->hClientSession, (void *)(&pTxListEntry->Hdr), pMessageHdr->cbData);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLocalIpcSessionFlush(pClient->hClientSession);
+ if (RT_SUCCESS(rc))
+ VBClLogVerbose(2, "vbDrmIpcConnectionHandler: transferred %u bytes\n", pMessageHdr->cbData);
+ else
+ VBClLogError("vbDrmIpcConnectionHandler: cannot flush IPC connection, transfer of %u bytes failed\n", pMessageHdr->cbData);
+ }
+ else
+ VBClLogError("vbDrmIpcConnectionHandler: cannot TX, rc=%Rrc\n", rc);
+
+ RTMemFree(pTxListEntry);
+ }
+
+ return rc;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/display-ipc.h b/src/VBox/Additions/x11/VBoxClient/display-ipc.h
new file mode 100644
index 00000000..39e9760e
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-ipc.h
@@ -0,0 +1,242 @@
+/* $Id: display-ipc.h $ */
+/** @file
+ * Guest Additions - DRM IPC communication core function definitions.
+ *
+ * Definitions for IPC communication in between VBoxDRMClient and VBoxClient.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+# include <iprt/assert.h>
+# include <iprt/localipc.h>
+# include <iprt/critsect.h>
+# include <iprt/list.h>
+
+/** Name of DRM IPC server.*/
+# define VBOX_DRMIPC_SERVER_NAME "DRMIpcServer"
+/** A user group which is allowed to connect to IPC server. */
+#define VBOX_DRMIPC_USER_GROUP "vboxdrmipc"
+/** Time in milliseconds to wait for host events. */
+#define VBOX_DRMIPC_RX_TIMEOUT_MS (500)
+/** Time in milliseconds to relax in between unsuccessful connect attempts. */
+#define VBOX_DRMIPC_RX_RELAX_MS (500)
+/** Size of RX buffer for IPC communication. */
+#define VBOX_DRMIPC_RX_BUFFER_SIZE (1024)
+/** Maximum amount of TX messages which can be queued. */
+#define VBOX_DRMIPC_TX_QUEUE_SIZE (64)
+/** Maximum number of physical monitor configurations we can process. */
+#define VBOX_DRMIPC_MONITORS_MAX (32)
+
+/** Rectangle structure for geometry of a single screen. */
+struct VBOX_DRMIPC_VMWRECT
+{
+ /** Monitor X offset. */
+ int32_t x;
+ /** Monitor Y offset. */
+ int32_t y;
+ /** Monitor width. */
+ uint32_t w;
+ /** Monitor height. */
+ uint32_t h;
+};
+AssertCompileSize(struct VBOX_DRMIPC_VMWRECT, 16);
+
+/** List of IPC commands issued by client to server. */
+typedef enum VBOXDRMIPCSRVCMD
+{
+ /** Separate server and client commands by starting index. */
+ VBOXDRMIPCSRVCMD_INVALID = 0x00,
+ /** Client reports list of current display offsets. */
+ VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS,
+ /** Termination of commands list. */
+ VBOXDRMIPCSRVCMD_MAX
+} VBOXDRMIPCSRVCMD;
+
+/** List of IPC commands issued by server to client. */
+typedef enum VBOXDRMIPCCLTCMD
+{
+ /** Separate server and client commands by starting index. */
+ VBOXDRMIPCCLTCMD_INVALID = 0x7F,
+ /** Server requests client to set primary screen. */
+ VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY,
+ /** Termination of commands list. */
+ VBOXDRMIPCCLTCMD_MAX
+} VBOXDRMIPCCLTCMD;
+
+/** IPC command header. */
+typedef struct VBOX_DRMIPC_COMMAND_HEADER
+{
+ /** IPC command structure checksum, includes header and payload. */
+ uint64_t u64Crc;
+ /** IPC command identificator (opaque). */
+ uint8_t idCmd;
+ /** Size of payload data. */
+ uint64_t cbData;
+
+} VBOX_DRMIPC_COMMAND_HEADER;
+
+/** Pointer to IPC command header. */
+typedef VBOX_DRMIPC_COMMAND_HEADER *PVBOX_DRMIPC_COMMAND_HEADER;
+
+/** IPC command VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY payload. */
+typedef struct VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY
+{
+ /* IPC command header. */
+ VBOX_DRMIPC_COMMAND_HEADER Hdr;
+ /** ID of display to be set as primary. */
+ uint32_t idDisplay;
+
+} VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY;
+
+/** Pointer to IPC command DRMIPCCOMMAND_SET_PRIMARY_DISPLAY payload. */
+typedef VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY *PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY;
+
+/** IPC command VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS payload. */
+typedef struct VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS
+{
+ /* IPC command header. */
+ VBOX_DRMIPC_COMMAND_HEADER Hdr;
+ /** Number of displays which have changed offsets. */
+ uint32_t cDisplays;
+ /** Offsets data. */
+ struct VBOX_DRMIPC_VMWRECT aDisplays[VBOX_DRMIPC_MONITORS_MAX];
+} VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS;
+
+/** Pointer to IPC command DRMIPCCOMMAND_SET_PRIMARY_DISPLAY payload. */
+typedef VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS *PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS;
+
+/** DRM IPC TX list entry. */
+typedef struct VBOX_DRMIPC_TX_LIST_ENTRY
+{
+ /** The list node. */
+ RTLISTNODE Node;
+ /* IPC command header. */
+ VBOX_DRMIPC_COMMAND_HEADER Hdr;
+} VBOX_DRMIPC_TX_LIST_ENTRY;
+
+/** Pointer to DRM IPC TX list entry. */
+typedef VBOX_DRMIPC_TX_LIST_ENTRY *PVBOX_DRMIPC_TX_LIST_ENTRY;
+
+/**
+ * A callback function which is called by IPC client session thread when new message arrives.
+ *
+ * @returns IPRT status code.
+ * @param idCmd Command ID to be executed (opaque).
+ * @param pvData Command specific argument data.
+ * @param cbData Size of command argument data as received over IPC.
+ */
+typedef DECLCALLBACKTYPE(int, FNDRMIPCRXCB, (uint8_t idCmd, void *pvData, uint32_t cbData));
+
+/** Pointer to FNDRMIPCRXCB. */
+typedef FNDRMIPCRXCB *PFNDRMIPCRXCB;
+
+/** IPC session private data. */
+typedef struct VBOX_DRMIPC_CLIENT
+{
+ /** Thread handle which dispatches this IPC client session. */
+ RTTHREAD hThread;
+ /** IPC session handle. */
+ RTLOCALIPCSESSION hClientSession;
+ /** TX message queue mutex. */
+ RTCRITSECT CritSect;
+ /** TX message queue (accessed under critsect). */
+ VBOX_DRMIPC_TX_LIST_ENTRY TxList;
+ /** Maximum number of messages which can be queued to TX message queue. */
+ uint32_t cTxListCapacity;
+ /** Actual number of messages currently queued to TX message queue (accessed under critsect). */
+ uint32_t cTxListSize;
+ /** IPC RX callback. */
+ PFNDRMIPCRXCB pfnRxCb;
+} VBOX_DRMIPC_CLIENT;
+
+/** Pointer to IPC session private data. */
+typedef VBOX_DRMIPC_CLIENT *PVBOX_DRMIPC_CLIENT;
+
+/** Static initializer for VBOX_DRMIPC_CLIENT. */
+#define VBOX_DRMIPC_CLIENT_INITIALIZER { NIL_RTTHREAD, 0, { 0 }, { { NULL, NULL }, {0, 0, 0} }, 0, 0, NULL }
+
+/**
+ * Initialize IPC client private data.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC client private data to be initialized.
+ * @param hThread A thread which server IPC client connection.
+ * @param hClientSession IPC session handle obtained from RTLocalIpcSessionXXX().
+ * @param cTxListCapacity Maximum number of messages which can be queued for TX for this IPC session.
+ * @param pfnRxCb IPC RX callback function pointer.
+ */
+RTDECL(int) vbDrmIpcClientInit(PVBOX_DRMIPC_CLIENT pClient, RTTHREAD hThread, RTLOCALIPCSESSION hClientSession,
+ uint32_t cTxListCapacity, PFNDRMIPCRXCB pfnRxCb);
+
+/**
+ * Releases IPC client private data resources.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data to be initialized.
+ */
+RTDECL(int) vbDrmIpcClientReleaseResources(PVBOX_DRMIPC_CLIENT pClient);
+
+/**
+ * Verify if remote IPC peer corresponds to a process which is running
+ * from allowed user.
+ *
+ * @return IPRT status code.
+ * @param hClientSession IPC session handle.
+ */
+RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession);
+
+/**
+ * Common function for both IPC server and client which is responsible
+ * for handling IPC communication flow.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC connection private data.
+ */
+RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient);
+
+/**
+ * Request remote IPC peer to set primary display.
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param idDisplay ID of display to be set as primary.
+ */
+RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay);
+
+/**
+ * Report to IPC server that display layout offsets have been changed (called by IPC client).
+ *
+ * @return IPRT status code.
+ * @param pClient IPC session private data.
+ * @param cDisplays Number of monitors which have offsets changed.
+ * @param aDisplays Offsets data.
+ */
+RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays);
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp
new file mode 100644
index 00000000..168adb9b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp
@@ -0,0 +1,460 @@
+/* $Id: display-svga-session.cpp $ */
+/** @file
+ * Guest Additions - VMSVGA Desktop Environment user session assistant.
+ *
+ * This service connects to VBoxDRMClient IPC server, listens for
+ * its commands and reports current display offsets to it. If IPC
+ * server is not available, it forks legacy 'VBoxClient --vmsvga
+ * service and terminates.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This service is an IPC client for VBoxDRMClient daemon. It is also
+ * a proxy bridge to a Desktop Environment specific code (so called
+ * Desktop Environment helpers).
+ *
+ * Once started, it will try to enumerate and probe all the registered
+ * helpers and if appropriate helper found, it will forward incoming IPC
+ * commands to it as well as send helper's commands back to VBoxDRMClient.
+ * Generic helper is a special one. It will be used by default if all the
+ * other helpers are failed on probe. Moreover, generic helper provides
+ * helper functions that can be used by other helpers as well. For example,
+ * once Gnome3 Desktop Environment is running on X11, it will be also use
+ * display offsets change notification monitor of a generic helper.
+ *
+ * Multiple instances of this daemon are allowed to run in parallel
+ * with the following limitations.
+ * A single user cannot run multiple daemon instances per single TTY device,
+ * however, multiple instances are allowed for the user on different
+ * TTY devices (i.e. in case if user runs multiple X servers on different
+ * terminals). On multiple TTY devices multiple users can run multiple
+ * daemon instances (i.e. in case of "switch user" DE configuration when
+ * multiple X/Wayland servers are running on separate TTY devices).
+ */
+
+#include "VBoxClient.h"
+#include "display-ipc.h"
+#include "display-helper.h"
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/localipc.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+#include <iprt/linux/sysfs.h>
+
+
+/** Handle to IPC client connection. */
+VBOX_DRMIPC_CLIENT g_hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
+
+/** IPC client handle critical section. */
+static RTCRITSECT g_hClientCritSect;
+
+/** List of available Desktop Environment specific display helpers. */
+static const VBCLDISPLAYHELPER *g_apDisplayHelpers[] =
+{
+ &g_DisplayHelperGnome3, /* GNOME3 helper. */
+ &g_DisplayHelperGeneric, /* Generic helper. */
+ NULL, /* Terminate list. */
+};
+
+/** Selected Desktop Environment specific display helper. */
+static const VBCLDISPLAYHELPER *g_pDisplayHelper = NULL;
+
+/** IPC connection session handle. */
+static RTLOCALIPCSESSION g_hSession = 0;
+
+/**
+ * Callback for display offsets change events provided by Desktop Environment specific display helper.
+ *
+ * @returns IPRT status code.
+ * @param cDisplays Number of displays which have changed offset.
+ * @param aDisplays Display data.
+ */
+static DECLCALLBACK(int) vbclSVGASessionDisplayOffsetChanged(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
+{
+ int rc = RTCritSectEnter(&g_hClientCritSect);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmIpcReportDisplayOffsets(&g_hClient, cDisplays, aDisplays);
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to leave critical session, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to enter critical session, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclSVGASessionInit(void)
+{
+ int rc;
+ RTLOCALIPCSESSION hSession;
+ int idxDisplayHelper = 0;
+
+ /** Custom log prefix to be used for logger instance of this process. */
+ static const char *pszLogPrefix = "VBoxClient VMSVGA:";
+
+ VBClLogSetLogPrefix(pszLogPrefix);
+
+ rc = RTCritSectInit(&g_hClientCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to init locking, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ /* Go through list of available Desktop Environment specific helpers and try to pick up one. */
+ while (g_apDisplayHelpers[idxDisplayHelper])
+ {
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnProbe)
+ {
+ VBClLogInfo("probing Desktop Environment helper '%s'\n",
+ g_apDisplayHelpers[idxDisplayHelper]->pszName);
+
+ rc = g_apDisplayHelpers[idxDisplayHelper]->pfnProbe();
+
+ /* Found compatible helper. */
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize it. */
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnInit)
+ {
+ rc = g_apDisplayHelpers[idxDisplayHelper]->pfnInit();
+ }
+
+ /* Some helpers might have no .pfnInit(), that's ok. */
+ if (RT_SUCCESS(rc))
+ {
+ /* Subscribe to display offsets change event. */
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnSubscribeDisplayOffsetChangeNotification)
+ {
+ g_apDisplayHelpers[idxDisplayHelper]->
+ pfnSubscribeDisplayOffsetChangeNotification(
+ vbclSVGASessionDisplayOffsetChanged);
+ }
+
+ g_pDisplayHelper = g_apDisplayHelpers[idxDisplayHelper];
+ break;
+ }
+ else
+ VBClLogError("compatible Desktop Environment "
+ "helper has been found, but it cannot be initialized, rc=%Rrc\n", rc);
+ }
+ }
+
+ idxDisplayHelper++;
+ }
+
+ /* Make sure we found compatible Desktop Environment specific helper. */
+ if (g_pDisplayHelper)
+ {
+ VBClLogInfo("using Desktop Environment specific display helper '%s'\n",
+ g_pDisplayHelper->pszName);
+ }
+ else
+ {
+ VBClLogError("unable to find Desktop Environment specific display helper\n");
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ /* Attempt to connect to VBoxDRMClient IPC server. */
+ rc = RTLocalIpcSessionConnect(&hSession, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_SUCCESS(rc))
+ {
+ g_hSession = hSession;
+ }
+ else
+ VBClLogError("unable to connect to IPC server, rc=%Rrc\n", rc);
+
+ /* We cannot initialize ourselves, start legacy service and terminate. */
+ if (RT_FAILURE(rc))
+ {
+ /* Free helper resources. */
+ if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
+ g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
+
+ if (g_pDisplayHelper->pfnTerm)
+ {
+ rc = g_pDisplayHelper->pfnTerm();
+ VBClLogInfo("helper service terminated, rc=%Rrc\n", rc);
+ }
+
+ rc = VbglR3DrmLegacyClientStart();
+ VBClLogInfo("starting legacy service, rc=%Rrc\n", rc);
+ /* Force return status, so parent thread wont be trying to start worker thread. */
+ rc = VERR_NOT_AVAILABLE;
+ }
+
+ return rc;
+}
+
+/**
+ * A callback function which is triggered on IPC data receive.
+ *
+ * @returns IPRT status code.
+ * @param idCmd DRM IPC command ID.
+ * @param pvData DRM IPC command payload.
+ * @param cbData Size of DRM IPC command payload.
+ */
+static DECLCALLBACK(int) vbclSVGASessionRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
+{
+ VBOXDRMIPCCLTCMD enmCmd =
+ (idCmd > VBOXDRMIPCCLTCMD_INVALID && idCmd < VBOXDRMIPCCLTCMD_MAX) ?
+ (VBOXDRMIPCCLTCMD)idCmd : VBOXDRMIPCCLTCMD_INVALID;
+
+ int rc = VERR_INVALID_PARAMETER;
+
+ AssertReturn(pvData, VERR_INVALID_PARAMETER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertReturn(g_pDisplayHelper, VERR_INVALID_PARAMETER);
+
+ switch (enmCmd)
+ {
+ case VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY:
+ {
+ if (g_pDisplayHelper->pfnSetPrimaryDisplay)
+ {
+ PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)pvData;
+ static uint32_t idPrimaryDisplayCached = VBOX_DRMIPC_MONITORS_MAX;
+
+ if ( pCmd->idDisplay < VBOX_DRMIPC_MONITORS_MAX
+ && idPrimaryDisplayCached != pCmd->idDisplay)
+ {
+ rc = g_pDisplayHelper->pfnSetPrimaryDisplay(pCmd->idDisplay);
+ /* Update cache. */
+ idPrimaryDisplayCached = pCmd->idDisplay;
+ }
+ else
+ VBClLogVerbose(1, "do not set %u as a primary display\n", pCmd->idDisplay);
+ }
+
+ break;
+ }
+ default:
+ {
+ VBClLogError("received unknown IPC command 0x%x\n", idCmd);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Reconnect to DRM IPC server.
+ */
+static int vbclSVGASessionReconnect(void)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ rc = RTCritSectEnter(&g_hClientCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to enter critical section on reconnect, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ /* Check if session was not closed before. */
+ if (RT_VALID_PTR(g_hSession))
+ {
+ rc = RTLocalIpcSessionClose(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC connection on reconnect, rc=%Rrc\n", rc);
+
+ rc = vbDrmIpcClientReleaseResources(&g_hClient);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC session resources, rc=%Rrc\n", rc);
+ }
+
+ rc = RTLocalIpcSessionConnect(&g_hSession, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to re-initialize IPC session, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to reconnect to IPC server, rc=%Rrc\n", rc);
+
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on reconnect, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclSVGASessionWorker(bool volatile *pfShutdown)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Notify parent thread that we started successfully. */
+ rc = RTThreadUserSignal(RTThreadSelf());
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to notify parent thread about successful start\n");
+
+ rc = RTCritSectEnter(&g_hClientCritSect);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to enter critical section on worker start, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on worker start, rc=%Rrc\n", rc);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("cannot initialize IPC session, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ for (;;)
+ {
+ rc = vbDrmIpcConnectionHandler(&g_hClient);
+
+ /* Try to shutdown thread as soon as possible. */
+ if (ASMAtomicReadBool(pfShutdown))
+ {
+ /* Shutdown requested. */
+ break;
+ }
+
+ /* Normal case, there was no incoming messages for a while. */
+ if (rc == VERR_TIMEOUT)
+ {
+ continue;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to handle IPC connection, rc=%Rrc\n", rc);
+
+ /* Relax a bit before spinning the loop. */
+ RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
+ /* Try to reconnect to server. */
+ rc = vbclSVGASessionReconnect();
+ }
+ }
+
+ /* Check if session was not closed before. */
+ if (RT_VALID_PTR(g_hSession))
+ {
+ rc2 = RTCritSectEnter(&g_hClientCritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = vbDrmIpcClientReleaseResources(&g_hClient);
+ if (RT_FAILURE(rc2))
+ VBClLogError("cannot release IPC session resources, rc=%Rrc\n", rc2);
+
+ rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on worker end, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to enter critical section on worker end, rc=%Rrc\n", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclSVGASessionStop(void)
+{
+ int rc;
+
+ /* Check if session was not closed before. */
+ if (!RT_VALID_PTR(g_hSession))
+ return;
+
+ /* Attempt to release any waiting syscall related to RTLocalIpcSessionXXX(). */
+ rc = RTLocalIpcSessionFlush(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to flush data to IPC connection, rc=%Rrc\n", rc);
+
+ rc = RTLocalIpcSessionCancel(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to cancel IPC session, rc=%Rrc\n", rc);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclSVGASessionTerm(void)
+{
+ int rc = VINF_SUCCESS;
+
+ if (g_hSession)
+ {
+ rc = RTLocalIpcSessionClose(g_hSession);
+ g_hSession = 0;
+
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to close IPC connection, rc=%Rrc\n", rc);
+ }
+
+ if (g_pDisplayHelper)
+ {
+ if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
+ g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
+
+ if (g_pDisplayHelper->pfnTerm)
+ {
+ rc = g_pDisplayHelper->pfnTerm();
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to terminate Desktop Environment helper '%s', rc=%Rrc\n",
+ rc, g_pDisplayHelper->pszName);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+VBCLSERVICE g_SvcDisplaySVGASession =
+{
+ "vmsvga-session", /* szName */
+ "VMSVGA display assistant", /* pszDescription */
+ ".vboxclient-vmsvga-session", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclSVGASessionInit, /* pfnInit */
+ vbclSVGASessionWorker, /* pfnWorker */
+ vbclSVGASessionStop, /* pfnStop */
+ vbclSVGASessionTerm, /* pfnTerm */
+};
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp
new file mode 100644
index 00000000..90005ab1
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp
@@ -0,0 +1,1402 @@
+/* $Id: display-svga-x11.cpp $ */
+/** @file
+ * X11 guest client - VMSVGA emulation resize event pass-through to X.Org
+ * guest driver.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * Known things to test when changing this code. All assume a guest with VMSVGA
+ * active and controlled by X11 or Wayland, and Guest Additions installed and
+ * running, unless otherwise stated.
+ * - On Linux 4.6 and later guests, VBoxClient --vmsvga should be running as
+ * root and not as the logged-in user. Dynamic resizing should work for all
+ * screens in any environment which handles kernel resize notifications,
+ * including at log-in screens. Test GNOME Shell Wayland and GNOME Shell
+ * under X.Org or Unity or KDE at the log-in screen and after log-in.
+ * - Linux 4.10 changed the user-kernel-ABI introduced in 4.6: test both.
+ * - On other guests (than Linux 4.6 or later) running X.Org Server 1.3 or
+ * later, VBoxClient --vmsvga should never be running as root, and should run
+ * (and dynamic resizing and screen enable/disable should work for all
+ * screens) whenever a user is logged in to a supported desktop environment.
+ * - On guests running X.Org Server 1.2 or older, VBoxClient --vmsvga should
+ * never run as root and should run whenever a user is logged in to a
+ * supported desktop environment. Dynamic resizing should work for the first
+ * screen, and enabling others should not be possible.
+ * - When VMSVGA is not enabled, VBoxClient --vmsvga should never stay running.
+ * - The following assumptions are done and should be taken into account when reading/chaning the code:
+ * # The order of the outputs (monitors) is assumed to be the same in RANDROUTPUT array and
+ * XRRScreenResources.outputs array.
+ * - This code does 2 related but separate things: 1- It resizes and enables/disables monitors upon host's
+ * requests (see the infinite loop in run()). 2- it listens to RandR events (caused by this or any other X11 client)
+ * on a different thread and notifies host about the new monitor positions. See sendMonitorPositions(...). This is
+ * mainly a work around since we have realized that vmsvga does not convey correct monitor positions thru FIFO.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <stdio.h>
+#include <dlfcn.h>
+/** For sleep(..) */
+#include <unistd.h>
+#include "VBoxClient.h"
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/env.h>
+
+#include <X11/Xlibint.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/panoramiXproto.h>
+
+#include "display-svga-xf86cvt.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MILLIS_PER_INCH (25.4)
+#define DEFAULT_DPI (96.0)
+
+/* Time in milliseconds to relax if no X11 events available. */
+#define VBOX_SVGA_X11_RELAX_TIME_MS (500)
+/* Time in milliseconds to wait for host events. */
+#define VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS (500)
+
+/** Maximum number of supported screens. DRM and X11 both limit this to 32. */
+/** @todo if this ever changes, dynamically allocate resizeable arrays in the
+ * context structure. */
+#define VMW_MAX_HEADS 32
+
+#define checkFunctionPtrReturn(pFunction) \
+ do { \
+ if (pFunction) { } \
+ else \
+ { \
+ VBClLogFatalError("Could not find symbol address (%s)\n", #pFunction); \
+ dlclose(x11Context.pRandLibraryHandle); \
+ x11Context.pRandLibraryHandle = NULL; \
+ return VERR_NOT_FOUND; \
+ } \
+ } while (0)
+
+#define checkFunctionPtr(pFunction) \
+ do { \
+ if (pFunction) {} \
+ else VBClLogError("Could not find symbol address (%s)\n", #pFunction);\
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#define X_VMwareCtrlSetRes 1
+
+typedef struct
+{
+ CARD8 reqType;
+ CARD8 VMwareCtrlReqType;
+ CARD16 length B16;
+ CARD32 screen B32;
+ CARD32 x B32;
+ CARD32 y B32;
+} xVMwareCtrlSetResReq;
+#define sz_xVMwareCtrlSetResReq 16
+
+typedef struct
+{
+ BYTE type;
+ BYTE pad1;
+ CARD16 sequenceNumber B16;
+ CARD32 length B32;
+ CARD32 screen B32;
+ CARD32 x B32;
+ CARD32 y B32;
+ CARD32 pad2 B32;
+ CARD32 pad3 B32;
+ CARD32 pad4 B32;
+} xVMwareCtrlSetResReply;
+#define sz_xVMwareCtrlSetResReply 32
+
+typedef struct {
+ CARD8 reqType; /* always X_VMwareCtrlReqCode */
+ CARD8 VMwareCtrlReqType; /* always X_VMwareCtrlSetTopology */
+ CARD16 length B16;
+ CARD32 screen B32;
+ CARD32 number B32;
+ CARD32 pad1 B32;
+} xVMwareCtrlSetTopologyReq;
+#define sz_xVMwareCtrlSetTopologyReq 16
+
+#define X_VMwareCtrlSetTopology 2
+
+typedef struct {
+ BYTE type; /* X_Reply */
+ BYTE pad1;
+ CARD16 sequenceNumber B16;
+ CARD32 length B32;
+ CARD32 screen B32;
+ CARD32 pad2 B32;
+ CARD32 pad3 B32;
+ CARD32 pad4 B32;
+ CARD32 pad5 B32;
+ CARD32 pad6 B32;
+} xVMwareCtrlSetTopologyReply;
+#define sz_xVMwareCtrlSetTopologyReply 32
+
+struct X11VMWRECT
+{
+ int16_t x;
+ int16_t y;
+ uint16_t w;
+ uint16_t h;
+};
+AssertCompileSize(struct X11VMWRECT, 8);
+
+struct X11CONTEXT
+{
+ Display *pDisplay;
+ /* We use a separate connection for randr event listening since sharing a
+ single display object with resizing (main) and event listening threads ends up having a deadlock.*/
+ Display *pDisplayRandRMonitoring;
+ Window rootWindow;
+ int iDefaultScreen;
+ XRRScreenResources *pScreenResources;
+ int hRandRMajor;
+ int hRandRMinor;
+ int hRandREventBase;
+ int hRandRErrorBase;
+ int hEventMask;
+ bool fMonitorInfoAvailable;
+ /** The number of outputs (monitors, including disconnect ones) xrandr reports. */
+ int hOutputCount;
+ void *pRandLibraryHandle;
+ bool fWmwareCtrlExtention;
+ int hVMWCtrlMajorOpCode;
+ /** Function pointers we used if we dlopen libXrandr instead of linking. */
+ void (*pXRRSelectInput) (Display *, Window, int);
+ Bool (*pXRRQueryExtension) (Display *, int *, int *);
+ Status (*pXRRQueryVersion) (Display *, int *, int*);
+ XRRMonitorInfo* (*pXRRGetMonitors)(Display *, Window, Bool, int *);
+ XRRScreenResources* (*pXRRGetScreenResources)(Display *, Window);
+ Status (*pXRRSetCrtcConfig)(Display *, XRRScreenResources *, RRCrtc,
+ Time, int, int, RRMode, Rotation, RROutput *, int);
+ void (*pXRRFreeMonitors)(XRRMonitorInfo *);
+ void (*pXRRFreeScreenResources)(XRRScreenResources *);
+ void (*pXRRFreeModeInfo)(XRRModeInfo *);
+ void (*pXRRFreeOutputInfo)(XRROutputInfo *);
+ void (*pXRRSetScreenSize)(Display *, Window, int, int, int, int);
+ int (*pXRRUpdateConfiguration)(XEvent *event);
+ XRRModeInfo* (*pXRRAllocModeInfo)(_Xconst char *, int);
+ RRMode (*pXRRCreateMode) (Display *, Window, XRRModeInfo *);
+ XRROutputInfo* (*pXRRGetOutputInfo) (Display *, XRRScreenResources *, RROutput);
+ XRRCrtcInfo* (*pXRRGetCrtcInfo) (Display *, XRRScreenResources *, RRCrtc crtc);
+ void (*pXRRFreeCrtcInfo)(XRRCrtcInfo *);
+ void (*pXRRAddOutputMode)(Display *, RROutput, RRMode);
+ void (*pXRRDeleteOutputMode)(Display *, RROutput, RRMode);
+ void (*pXRRDestroyMode)(Display *, RRMode);
+ void (*pXRRSetOutputPrimary)(Display *, Window, RROutput);
+};
+
+static X11CONTEXT x11Context;
+
+struct RANDROUTPUT
+{
+ int32_t x;
+ int32_t y;
+ uint32_t width;
+ uint32_t height;
+ bool fEnabled;
+ bool fPrimary;
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void x11Connect();
+static int determineOutputCount();
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Monitor positions array. Allocated here and deallocated in the class descructor. */
+RTPOINT *mpMonitorPositions;
+/** Thread to listen to some of the X server events. */
+RTTHREAD mX11MonitorThread = NIL_RTTHREAD;
+/** Shutdown indicator for the monitor thread. */
+static bool g_fMonitorThreadShutdown = false;
+
+
+
+#ifdef RT_OS_SOLARIS
+static bool VMwareCtrlSetRes(
+ Display *dpy, int hExtensionMajorOpcode, int screen, int x, int y)
+{
+ xVMwareCtrlSetResReply rep;
+ xVMwareCtrlSetResReq *pReq;
+ bool fResult = false;
+
+ LockDisplay(dpy);
+
+ GetReq(VMwareCtrlSetRes, pReq);
+ AssertPtrReturn(pReq, false);
+
+ pReq->reqType = hExtensionMajorOpcode;
+ pReq->VMwareCtrlReqType = X_VMwareCtrlSetRes;
+ pReq->screen = screen;
+ pReq->x = x;
+ pReq->y = y;
+
+ fResult = !!_XReply(dpy, (xReply *)&rep, (SIZEOF(xVMwareCtrlSetResReply) - SIZEOF(xReply)) >> 2, xFalse);
+
+ UnlockDisplay(dpy);
+
+ return fResult;
+}
+#endif /* RT_OS_SOLARIS */
+
+/** Makes a call to vmwarectrl extension. This updates the
+ * connection information and possible resolutions (modes)
+ * of each monitor on the driver. Also sets the preferred mode
+ * of each output (monitor) to currently selected one. */
+bool VMwareCtrlSetTopology(Display *dpy, int hExtensionMajorOpcode,
+ int screen, xXineramaScreenInfo extents[], int number)
+{
+ xVMwareCtrlSetTopologyReply rep;
+ xVMwareCtrlSetTopologyReq *req;
+
+ long len;
+
+ LockDisplay(dpy);
+
+ GetReq(VMwareCtrlSetTopology, req);
+ req->reqType = hExtensionMajorOpcode;
+ req->VMwareCtrlReqType = X_VMwareCtrlSetTopology;
+ req->screen = screen;
+ req->number = number;
+
+ len = ((long) number) << 1;
+ SetReqLen(req, len, len);
+ len <<= 2;
+ _XSend(dpy, (char *)extents, len);
+
+ if (!_XReply(dpy, (xReply *)&rep,
+ (SIZEOF(xVMwareCtrlSetTopologyReply) - SIZEOF(xReply)) >> 2,
+ xFalse))
+ {
+ UnlockDisplay(dpy);
+ SyncHandle();
+ return false;
+ }
+ UnlockDisplay(dpy);
+ SyncHandle();
+ return true;
+}
+
+/** This function assumes monitors are named as from Virtual1 to VirtualX. */
+static int getMonitorIdFromName(const char *sMonitorName)
+{
+ if (!sMonitorName)
+ return -1;
+#ifdef RT_OS_SOLARIS
+ if (!strcmp(sMonitorName, "default"))
+ return 1;
+#endif
+ int iLen = strlen(sMonitorName);
+ if (iLen <= 0)
+ return -1;
+ int iBase = 10;
+ int iResult = 0;
+ for (int i = iLen - 1; i >= 0; --i)
+ {
+ /* Stop upon seeing the first non-numeric char. */
+ if (sMonitorName[i] < 48 || sMonitorName[i] > 57)
+ break;
+ iResult += (sMonitorName[i] - 48) * iBase / 10;
+ iBase *= 10;
+ }
+ return iResult;
+}
+
+static void sendMonitorPositions(RTPOINT *pPositions, size_t cPositions)
+{
+ if (cPositions && !pPositions)
+ {
+ VBClLogError(("Monitor position update called with NULL pointer!\n"));
+ return;
+ }
+ int rc = VbglR3SeamlessSendMonitorPositions(cPositions, pPositions);
+ if (RT_SUCCESS(rc))
+ VBClLogInfo("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc);
+ else
+ VBClLogError("Error during sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc);
+}
+
+static void queryMonitorPositions()
+{
+ static const int iSentinelPosition = -1;
+ if (mpMonitorPositions)
+ {
+ free(mpMonitorPositions);
+ mpMonitorPositions = NULL;
+ }
+
+ int iMonitorCount = 0;
+ XRRMonitorInfo *pMonitorInfo = NULL;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pMonitorInfo = XRRGetMonitors(x11Context.pDisplayRandRMonitoring,
+ DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount);
+#else
+ if (x11Context.pXRRGetMonitors)
+ pMonitorInfo = x11Context.pXRRGetMonitors(x11Context.pDisplayRandRMonitoring,
+ DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount);
+#endif
+ if (!pMonitorInfo)
+ return;
+ if (iMonitorCount == -1)
+ VBClLogError("Could not get monitor info\n");
+ else
+ {
+ mpMonitorPositions = (RTPOINT*)malloc(x11Context.hOutputCount * sizeof(RTPOINT));
+ /** @todo memset? */
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ mpMonitorPositions[i].x = iSentinelPosition;
+ mpMonitorPositions[i].y = iSentinelPosition;
+ }
+ for (int i = 0; i < iMonitorCount; ++i)
+ {
+ char *pszMonitorName = XGetAtomName(x11Context.pDisplayRandRMonitoring, pMonitorInfo[i].name);
+ if (!pszMonitorName)
+ {
+ VBClLogError("queryMonitorPositions: skip monitor with unknown name %d\n", i);
+ continue;
+ }
+
+ int iMonitorID = getMonitorIdFromName(pszMonitorName) - 1;
+ XFree((void *)pszMonitorName);
+ pszMonitorName = NULL;
+
+ if (iMonitorID >= x11Context.hOutputCount || iMonitorID == -1)
+ {
+ VBClLogInfo("queryMonitorPositions: skip monitor %d (id %d) (w,h)=(%d,%d) (x,y)=(%d,%d)\n",
+ i, iMonitorID,
+ pMonitorInfo[i].width, pMonitorInfo[i].height,
+ pMonitorInfo[i].x, pMonitorInfo[i].y);
+ continue;
+ }
+ VBClLogInfo("Monitor %d (w,h)=(%d,%d) (x,y)=(%d,%d)\n",
+ i,
+ pMonitorInfo[i].width, pMonitorInfo[i].height,
+ pMonitorInfo[i].x, pMonitorInfo[i].y);
+ mpMonitorPositions[iMonitorID].x = pMonitorInfo[i].x;
+ mpMonitorPositions[iMonitorID].y = pMonitorInfo[i].y;
+ }
+ if (iMonitorCount > 0)
+ sendMonitorPositions(mpMonitorPositions, x11Context.hOutputCount);
+ }
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeMonitors(pMonitorInfo);
+#else
+ if (x11Context.pXRRFreeMonitors)
+ x11Context.pXRRFreeMonitors(pMonitorInfo);
+#endif
+}
+
+static void monitorRandREvents()
+{
+ XEvent event;
+
+ if (XPending(x11Context.pDisplayRandRMonitoring) > 0)
+ {
+ XNextEvent(x11Context.pDisplayRandRMonitoring, &event);
+ int eventTypeOffset = event.type - x11Context.hRandREventBase;
+ VBClLogInfo("received X11 event (%d)\n", event.type);
+ switch (eventTypeOffset)
+ {
+ case RRScreenChangeNotify:
+ VBClLogInfo("RRScreenChangeNotify event received\n");
+ queryMonitorPositions();
+ break;
+ default:
+ break;
+ }
+ } else
+ {
+ RTThreadSleep(VBOX_SVGA_X11_RELAX_TIME_MS);
+ }
+}
+
+/**
+ * @callback_method_impl{FNRTTHREAD}
+ */
+static DECLCALLBACK(int) x11MonitorThreadFunction(RTTHREAD ThreadSelf, void *pvUser)
+{
+ RT_NOREF(ThreadSelf, pvUser);
+ while (!ASMAtomicReadBool(&g_fMonitorThreadShutdown))
+ {
+ monitorRandREvents();
+ }
+
+ VBClLogInfo("X11 thread gracefully terminated\n");
+
+ return 0;
+}
+
+static int startX11MonitorThread()
+{
+ int rc;
+ Assert(g_fMonitorThreadShutdown == false);
+ if (mX11MonitorThread == NIL_RTTHREAD)
+ {
+ rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThreadFunction, NULL /*pvUser*/, 0 /*cbStack*/,
+ RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "X11 events");
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Warning: failed to start X11 monitor thread (VBoxClient) rc=%Rrc!\n", rc);
+ }
+ else
+ rc = VINF_ALREADY_INITIALIZED;
+ return rc;
+}
+
+static int stopX11MonitorThread(void)
+{
+ int rc = VINF_SUCCESS;
+ if (mX11MonitorThread != NIL_RTTHREAD)
+ {
+ ASMAtomicWriteBool(&g_fMonitorThreadShutdown, true);
+ /** @todo Send event to thread to get it out of XNextEvent. */
+ //????????
+ //mX11Monitor.interruptEventWait();
+ rc = RTThreadWait(mX11MonitorThread, RT_MS_1SEC, NULL /*prc*/);
+ if (RT_SUCCESS(rc))
+ {
+ mX11MonitorThread = NIL_RTTHREAD;
+ g_fMonitorThreadShutdown = false;
+ }
+ else
+ VBClLogError("Failed to stop X11 monitor thread, rc=%Rrc!\n", rc);
+ }
+ return rc;
+}
+
+static bool callVMWCTRL(struct RANDROUTPUT *paOutputs)
+{
+ int hHeight = 600;
+ int hWidth = 800;
+ bool fResult = false;
+ int idxDefaultScreen = DefaultScreen(x11Context.pDisplay);
+
+ AssertReturn(idxDefaultScreen >= 0, false);
+ AssertReturn(idxDefaultScreen < x11Context.hOutputCount, false);
+
+ xXineramaScreenInfo *extents = (xXineramaScreenInfo *)malloc(x11Context.hOutputCount * sizeof(xXineramaScreenInfo));
+ if (!extents)
+ return false;
+ int hRunningOffset = 0;
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ if (paOutputs[i].fEnabled)
+ {
+ hHeight = paOutputs[i].height;
+ hWidth = paOutputs[i].width;
+ }
+ else
+ {
+ hHeight = 0;
+ hWidth = 0;
+ }
+ extents[i].x_org = hRunningOffset;
+ extents[i].y_org = 0;
+ extents[i].width = hWidth;
+ extents[i].height = hHeight;
+ hRunningOffset += hWidth;
+ }
+#ifdef RT_OS_SOLARIS
+ fResult = VMwareCtrlSetRes(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode,
+ idxDefaultScreen, extents[idxDefaultScreen].width,
+ extents[idxDefaultScreen].height);
+#else
+ fResult = VMwareCtrlSetTopology(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode,
+ idxDefaultScreen, extents, x11Context.hOutputCount);
+#endif
+ free(extents);
+ return fResult;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclSVGAInit(void)
+{
+ int rc;
+
+ /* In 32-bit guests GAs build on our release machines causes an xserver hang.
+ * So for 32-bit GAs we use our DRM client. */
+#if ARCH_BITS == 32
+ rc = VbglR3DrmClientStart();
+ if (RT_FAILURE(rc))
+ VBClLogError("Starting DRM resizing client (32-bit) failed with %Rrc\n", rc);
+ return VERR_NOT_AVAILABLE; /** @todo r=andy Why ignoring rc here? */
+#endif
+
+ /* If DRM client is already running don't start this service. */
+ if (VbglR3DrmClientIsRunning())
+ {
+ VBClLogInfo("DRM resizing is already running. Exiting this service\n");
+ return VERR_NOT_AVAILABLE;
+ }
+
+ if (VBClHasWayland())
+ {
+ rc = VbglR3DrmClientStart();
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogInfo("VBoxDrmClient has been successfully started, exitting parent process\n");
+ exit(0);
+ }
+ else
+ {
+ VBClLogError("Starting DRM resizing client failed with %Rrc\n", rc);
+ }
+ return rc;
+ }
+
+ x11Connect();
+
+ if (x11Context.pDisplay == NULL)
+ return VERR_NOT_AVAILABLE;
+
+ /* don't start the monitoring thread if related randr functionality is not available. */
+ if (x11Context.fMonitorInfoAvailable)
+ {
+ if (RT_FAILURE(startX11MonitorThread()))
+ return VERR_NOT_AVAILABLE;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclSVGAStop(void)
+{
+ int rc;
+
+ rc = stopX11MonitorThread();
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("cannot stop X11 monitor thread (%Rrc)\n", rc);
+ return;
+ }
+
+ if (mpMonitorPositions)
+ {
+ free(mpMonitorPositions);
+ mpMonitorPositions = NULL;
+ }
+
+ if (x11Context.pDisplayRandRMonitoring)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0);
+#endif
+ }
+
+ if (x11Context.pDisplay)
+ {
+ XCloseDisplay(x11Context.pDisplay);
+ x11Context.pDisplay = NULL;
+ }
+
+ if (x11Context.pDisplayRandRMonitoring)
+ {
+ XCloseDisplay(x11Context.pDisplayRandRMonitoring);
+ x11Context.pDisplayRandRMonitoring = NULL;
+ }
+
+ if (x11Context.pRandLibraryHandle)
+ {
+ dlclose(x11Context.pRandLibraryHandle);
+ x11Context.pRandLibraryHandle = NULL;
+ }
+}
+
+#ifndef WITH_DISTRO_XRAND_XINERAMA
+static int openLibRandR()
+{
+ x11Context.pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */);
+ if (!x11Context.pRandLibraryHandle)
+ x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */);
+ if (!x11Context.pRandLibraryHandle)
+ x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */);
+
+ if (!x11Context.pRandLibraryHandle)
+ {
+ VBClLogFatalError("Could not locate libXrandr for dlopen\n");
+ return VERR_NOT_FOUND;
+ }
+
+ *(void **)(&x11Context.pXRRSelectInput) = dlsym(x11Context.pRandLibraryHandle, "XRRSelectInput");
+ checkFunctionPtrReturn(x11Context.pXRRSelectInput);
+
+ *(void **)(&x11Context.pXRRQueryExtension) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryExtension");
+ checkFunctionPtrReturn(x11Context.pXRRQueryExtension);
+
+ *(void **)(&x11Context.pXRRQueryVersion) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryVersion");
+ checkFunctionPtrReturn(x11Context.pXRRQueryVersion);
+
+ /* Don't bail out when XRRGetMonitors XRRFreeMonitors are missing as in Oracle Solaris 10. It is not crucial esp. for single monitor. */
+ *(void **)(&x11Context.pXRRGetMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRGetMonitors");
+ checkFunctionPtr(x11Context.pXRRGetMonitors);
+
+ *(void **)(&x11Context.pXRRFreeMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeMonitors");
+ checkFunctionPtr(x11Context.pXRRFreeMonitors);
+
+ x11Context.fMonitorInfoAvailable = x11Context.pXRRGetMonitors && x11Context.pXRRFreeMonitors;
+
+ *(void **)(&x11Context.pXRRGetScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRGetScreenResources");
+ checkFunctionPtr(x11Context.pXRRGetScreenResources);
+
+ *(void **)(&x11Context.pXRRSetCrtcConfig) = dlsym(x11Context.pRandLibraryHandle, "XRRSetCrtcConfig");
+ checkFunctionPtr(x11Context.pXRRSetCrtcConfig);
+
+ *(void **)(&x11Context.pXRRFreeScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeScreenResources");
+ checkFunctionPtr(x11Context.pXRRFreeScreenResources);
+
+ *(void **)(&x11Context.pXRRFreeModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeModeInfo");
+ checkFunctionPtr(x11Context.pXRRFreeModeInfo);
+
+ *(void **)(&x11Context.pXRRFreeOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeOutputInfo");
+ checkFunctionPtr(x11Context.pXRRFreeOutputInfo);
+
+ *(void **)(&x11Context.pXRRSetScreenSize) = dlsym(x11Context.pRandLibraryHandle, "XRRSetScreenSize");
+ checkFunctionPtr(x11Context.pXRRSetScreenSize);
+
+ *(void **)(&x11Context.pXRRUpdateConfiguration) = dlsym(x11Context.pRandLibraryHandle, "XRRUpdateConfiguration");
+ checkFunctionPtr(x11Context.pXRRUpdateConfiguration);
+
+ *(void **)(&x11Context.pXRRAllocModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRAllocModeInfo");
+ checkFunctionPtr(x11Context.pXRRAllocModeInfo);
+
+ *(void **)(&x11Context.pXRRCreateMode) = dlsym(x11Context.pRandLibraryHandle, "XRRCreateMode");
+ checkFunctionPtr(x11Context.pXRRCreateMode);
+
+ *(void **)(&x11Context.pXRRGetOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetOutputInfo");
+ checkFunctionPtr(x11Context.pXRRGetOutputInfo);
+
+ *(void **)(&x11Context.pXRRGetCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetCrtcInfo");
+ checkFunctionPtr(x11Context.pXRRGetCrtcInfo);
+
+ *(void **)(&x11Context.pXRRFreeCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeCrtcInfo");
+ checkFunctionPtr(x11Context.pXRRFreeCrtcInfo);
+
+ *(void **)(&x11Context.pXRRAddOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRAddOutputMode");
+ checkFunctionPtr(x11Context.pXRRAddOutputMode);
+
+ *(void **)(&x11Context.pXRRDeleteOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDeleteOutputMode");
+ checkFunctionPtr(x11Context.pXRRDeleteOutputMode);
+
+ *(void **)(&x11Context.pXRRDestroyMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDestroyMode");
+ checkFunctionPtr(x11Context.pXRRDestroyMode);
+
+ *(void **)(&x11Context.pXRRSetOutputPrimary) = dlsym(x11Context.pRandLibraryHandle, "XRRSetOutputPrimary");
+ checkFunctionPtr(x11Context.pXRRSetOutputPrimary);
+
+ return VINF_SUCCESS;
+}
+#endif
+
+static void x11Connect()
+{
+ x11Context.pScreenResources = NULL;
+ x11Context.pXRRSelectInput = NULL;
+ x11Context.pRandLibraryHandle = NULL;
+ x11Context.pXRRQueryExtension = NULL;
+ x11Context.pXRRQueryVersion = NULL;
+ x11Context.pXRRGetMonitors = NULL;
+ x11Context.pXRRGetScreenResources = NULL;
+ x11Context.pXRRSetCrtcConfig = NULL;
+ x11Context.pXRRFreeMonitors = NULL;
+ x11Context.pXRRFreeScreenResources = NULL;
+ x11Context.pXRRFreeOutputInfo = NULL;
+ x11Context.pXRRFreeModeInfo = NULL;
+ x11Context.pXRRSetScreenSize = NULL;
+ x11Context.pXRRUpdateConfiguration = NULL;
+ x11Context.pXRRAllocModeInfo = NULL;
+ x11Context.pXRRCreateMode = NULL;
+ x11Context.pXRRGetOutputInfo = NULL;
+ x11Context.pXRRGetCrtcInfo = NULL;
+ x11Context.pXRRFreeCrtcInfo = NULL;
+ x11Context.pXRRAddOutputMode = NULL;
+ x11Context.pXRRDeleteOutputMode = NULL;
+ x11Context.pXRRDestroyMode = NULL;
+ x11Context.pXRRSetOutputPrimary = NULL;
+ x11Context.fWmwareCtrlExtention = false;
+ x11Context.fMonitorInfoAvailable = false;
+ x11Context.hRandRMajor = 0;
+ x11Context.hRandRMinor = 0;
+
+ int dummy;
+ if (x11Context.pDisplay != NULL)
+ VBClLogFatalError("%s called with bad argument\n", __func__);
+ x11Context.pDisplay = XOpenDisplay(NULL);
+ x11Context.pDisplayRandRMonitoring = XOpenDisplay(NULL);
+ if (x11Context.pDisplay == NULL)
+ return;
+#ifndef WITH_DISTRO_XRAND_XINERAMA
+ if (openLibRandR() != VINF_SUCCESS)
+ {
+ XCloseDisplay(x11Context.pDisplay);
+ XCloseDisplay(x11Context.pDisplayRandRMonitoring);
+ x11Context.pDisplay = NULL;
+ x11Context.pDisplayRandRMonitoring = NULL;
+ return;
+ }
+#endif
+
+ x11Context.fWmwareCtrlExtention = XQueryExtension(x11Context.pDisplay, "VMWARE_CTRL",
+ &x11Context.hVMWCtrlMajorOpCode, &dummy, &dummy);
+ if (!x11Context.fWmwareCtrlExtention)
+ VBClLogError("VMWARE's ctrl extension is not available! Multi monitor management is not possible\n");
+ else
+ VBClLogInfo("VMWARE's ctrl extension is available. Major Opcode is %d.\n", x11Context.hVMWCtrlMajorOpCode);
+
+ /* Check Xrandr stuff. */
+ bool fSuccess = false;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ fSuccess = XRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
+#else
+ if (x11Context.pXRRQueryExtension)
+ fSuccess = x11Context.pXRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase);
+#endif
+ if (fSuccess)
+ {
+ fSuccess = false;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ fSuccess = XRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
+#else
+ if (x11Context.pXRRQueryVersion)
+ fSuccess = x11Context.pXRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor);
+#endif
+ if (!fSuccess)
+ {
+ XCloseDisplay(x11Context.pDisplay);
+ x11Context.pDisplay = NULL;
+ return;
+ }
+ if (x11Context.hRandRMajor < 1 || x11Context.hRandRMinor <= 3)
+ {
+ VBClLogError("Resizing service requires libXrandr Version >= 1.4. Detected version is %d.%d\n", x11Context.hRandRMajor, x11Context.hRandRMinor);
+ XCloseDisplay(x11Context.pDisplay);
+ x11Context.pDisplay = NULL;
+
+ int rc = VbglR3DrmLegacyX11AgentStart();
+ VBClLogInfo("Attempt to start legacy X11 resize agent, rc=%Rrc\n", rc);
+
+ return;
+ }
+ }
+ x11Context.rootWindow = DefaultRootWindow(x11Context.pDisplay);
+ x11Context.hEventMask = RRScreenChangeNotifyMask;
+
+ /* Select the XEvent types we want to listen to. */
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask);
+#endif
+ x11Context.iDefaultScreen = DefaultScreen(x11Context.pDisplay);
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#else
+ if (x11Context.pXRRGetScreenResources)
+ x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#endif
+ x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+}
+
+static int determineOutputCount()
+{
+ if (!x11Context.pScreenResources)
+ return 0;
+ return x11Context.pScreenResources->noutput;
+}
+
+static int findExistingModeIndex(unsigned iXRes, unsigned iYRes)
+{
+ if (!x11Context.pScreenResources)
+ return -1;
+ for (int i = 0; i < x11Context.pScreenResources->nmode; ++i)
+ {
+ if (x11Context.pScreenResources->modes[i].width == iXRes && x11Context.pScreenResources->modes[i].height == iYRes)
+ return i;
+ }
+ return -1;
+}
+
+static bool disableCRTC(RRCrtc crtcID)
+{
+ XRRCrtcInfo *pCrctInfo = NULL;
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pCrctInfo = XRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID);
+#else
+ if (x11Context.pXRRGetCrtcInfo)
+ pCrctInfo = x11Context.pXRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID);
+#endif
+
+ if (!pCrctInfo)
+ return false;
+
+ Status ret = Success;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID,
+ CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0);
+#else
+ if (x11Context.pXRRSetCrtcConfig)
+ ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID,
+ CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0);
+#endif
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeCrtcInfo(pCrctInfo);
+#else
+ if (x11Context.pXRRFreeCrtcInfo)
+ x11Context.pXRRFreeCrtcInfo(pCrctInfo);
+#endif
+
+ /** @todo In case of unsuccesful crtc config set we have to revert frame buffer size and crtc sizes. */
+ if (ret == Success)
+ return true;
+ else
+ return false;
+}
+
+static XRRScreenSize currentSize()
+{
+ XRRScreenSize cSize;
+ cSize.width = DisplayWidth(x11Context.pDisplay, x11Context.iDefaultScreen);
+ cSize.mwidth = DisplayWidthMM(x11Context.pDisplay, x11Context.iDefaultScreen);
+ cSize.height = DisplayHeight(x11Context.pDisplay, x11Context.iDefaultScreen);
+ cSize.mheight = DisplayHeightMM(x11Context.pDisplay, x11Context.iDefaultScreen);
+ return cSize;
+}
+
+static unsigned int computeDpi(unsigned int pixels, unsigned int mm)
+{
+ unsigned int dpi = 0;
+ if (mm > 0)
+ dpi = (unsigned int)((double)pixels * MILLIS_PER_INCH / (double)mm + 0.5);
+ return dpi > 0 ? dpi : (unsigned int)DEFAULT_DPI;
+}
+
+static bool resizeFrameBuffer(struct RANDROUTPUT *paOutputs)
+{
+ unsigned int iXRes = 0;
+ unsigned int iYRes = 0;
+ /* Don't care about the output positions for now. */
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ if (!paOutputs[i].fEnabled)
+ continue;
+ iXRes += paOutputs[i].width;
+ iYRes = iYRes < paOutputs[i].height ? paOutputs[i].height : iYRes;
+ }
+ XRRScreenSize cSize= currentSize();
+ unsigned int xdpi = computeDpi(cSize.width, cSize.mwidth);
+ unsigned int ydpi = computeDpi(cSize.height, cSize.mheight);
+ unsigned int xmm;
+ unsigned int ymm;
+ xmm = (int)(MILLIS_PER_INCH * iXRes / ((double)xdpi) + 0.5);
+ ymm = (int)(MILLIS_PER_INCH * iYRes / ((double)ydpi) + 0.5);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask);
+ XRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask);
+ if (x11Context.pXRRSetScreenSize)
+ x11Context.pXRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm);
+#endif
+ XSync(x11Context.pDisplay, False);
+ XEvent configEvent;
+ bool event = false;
+ while (XCheckTypedEvent(x11Context.pDisplay, RRScreenChangeNotify + x11Context.hRandREventBase, &configEvent))
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRUpdateConfiguration(&configEvent);
+#else
+ if (x11Context.pXRRUpdateConfiguration)
+ x11Context.pXRRUpdateConfiguration(&configEvent);
+#endif
+ event = true;
+ }
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
+#else
+ if (x11Context.pXRRSelectInput)
+ x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0);
+#endif
+ XRRScreenSize newSize = currentSize();
+
+ /* On Solaris guest, new screen size is not reported properly despite
+ * RRScreenChangeNotify event arrives. Hense, only check for event here.
+ * Linux guests do report new size correctly. */
+ if ( !event
+#ifndef RT_OS_SOLARIS
+ || newSize.width != (int)iXRes || newSize.height != (int)iYRes
+#endif
+ )
+ {
+ VBClLogError("Resizing frame buffer to %d %d has failed, current mode %d %d\n",
+ iXRes, iYRes, newSize.width, newSize.height);
+ return false;
+ }
+ return true;
+}
+
+static XRRModeInfo *createMode(int iXRes, int iYRes)
+{
+ XRRModeInfo *pModeInfo = NULL;
+ char sModeName[126];
+ sprintf(sModeName, "%dx%d_vbox", iXRes, iYRes);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pModeInfo = XRRAllocModeInfo(sModeName, strlen(sModeName));
+#else
+ if (x11Context.pXRRAllocModeInfo)
+ pModeInfo = x11Context.pXRRAllocModeInfo(sModeName, strlen(sModeName));
+#endif
+ pModeInfo->width = iXRes;
+ pModeInfo->height = iYRes;
+
+ DisplayModeR const mode = VBoxClient_xf86CVTMode(iXRes, iYRes, 60 /*VRefresh */, true /*Reduced */, false /* Interlaced */);
+
+ /* Convert kHz to Hz: f86CVTMode returns clock value in units of kHz,
+ * XRRCreateMode will expect it in units of Hz. */
+ pModeInfo->dotClock = mode.Clock * 1000;
+
+ pModeInfo->hSyncStart = mode.HSyncStart;
+ pModeInfo->hSyncEnd = mode.HSyncEnd;
+ pModeInfo->hTotal = mode.HTotal;
+ pModeInfo->hSkew = mode.HSkew;
+ pModeInfo->vSyncStart = mode.VSyncStart;
+ pModeInfo->vSyncEnd = mode.VSyncEnd;
+ pModeInfo->vTotal = mode.VTotal;
+
+ RRMode newMode = None;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ newMode = XRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo);
+#else
+ if (x11Context.pXRRCreateMode)
+ newMode = x11Context.pXRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo);
+#endif
+ if (newMode == None)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeModeInfo(pModeInfo);
+#else
+ if (x11Context.pXRRFreeModeInfo)
+ x11Context.pXRRFreeModeInfo(pModeInfo);
+#endif
+ return NULL;
+ }
+ pModeInfo->id = newMode;
+ return pModeInfo;
+}
+
+static bool configureOutput(int iOutputIndex, struct RANDROUTPUT *paOutputs)
+{
+ if (iOutputIndex >= x11Context.hOutputCount)
+ {
+ VBClLogError("Output index %d is greater than # of oputputs %d\n", iOutputIndex, x11Context.hOutputCount);
+ return false;
+ }
+
+ AssertReturn(iOutputIndex >= 0, false);
+ AssertReturn(iOutputIndex < VMW_MAX_HEADS, false);
+
+ /* Remember the last instantiated display mode ID here. This mode will be replaced with the
+ * new one on the next guest screen resize event. */
+ static RRMode aPrevMode[VMW_MAX_HEADS];
+
+ RROutput outputId = x11Context.pScreenResources->outputs[iOutputIndex];
+ XRROutputInfo *pOutputInfo = NULL;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId);
+#else
+ if (x11Context.pXRRGetOutputInfo)
+ pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId);
+#endif
+ if (!pOutputInfo)
+ return false;
+ XRRModeInfo *pModeInfo = NULL;
+ bool fNewMode = false;
+ /* Index of the mode within the XRRScreenResources.modes array. -1 if such a mode with required resolution does not exists*/
+ int iModeIndex = findExistingModeIndex(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
+ if (iModeIndex != -1 && iModeIndex < x11Context.pScreenResources->nmode)
+ pModeInfo = &(x11Context.pScreenResources->modes[iModeIndex]);
+ else
+ {
+ /* A mode with required size was not found. Create a new one. */
+ pModeInfo = createMode(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
+ VBClLogInfo("create mode %s (%u) on output %d\n", pModeInfo->name, pModeInfo->id, iOutputIndex);
+ fNewMode = true;
+ }
+ if (!pModeInfo)
+ {
+ VBClLogError("Could not create mode for the resolution (%d, %d)\n",
+ paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height);
+ return false;
+ }
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id);
+#else
+ if (x11Context.pXRRAddOutputMode)
+ x11Context.pXRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id);
+#endif
+
+ /* If mode has been newly created, destroy and forget mode created on previous guest screen resize event. */
+ if ( aPrevMode[iOutputIndex] > 0
+ && pModeInfo->id != aPrevMode[iOutputIndex]
+ && fNewMode)
+ {
+ VBClLogInfo("removing unused mode %u from output %d\n", aPrevMode[iOutputIndex], iOutputIndex);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]);
+ XRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]);
+#else
+ if (x11Context.pXRRDeleteOutputMode)
+ x11Context.pXRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]);
+ if (x11Context.pXRRDestroyMode)
+ x11Context.pXRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]);
+#endif
+ /* Forget destroyed mode. */
+ aPrevMode[iOutputIndex] = 0;
+ }
+
+ /* Only cache modes created "by us". XRRDestroyMode will complain if provided mode
+ * was not created by XRRCreateMode call. */
+ if (fNewMode)
+ aPrevMode[iOutputIndex] = pModeInfo->id;
+
+ if (paOutputs[iOutputIndex].fPrimary)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId);
+#else
+ if (x11Context.pXRRSetOutputPrimary)
+ x11Context.pXRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId);
+#endif
+ }
+
+ /* Make sure outputs crtc is set. */
+ pOutputInfo->crtc = pOutputInfo->crtcs[0];
+
+ RRCrtc crtcId = pOutputInfo->crtcs[0];
+ Status ret = Success;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime,
+ paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y,
+ pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/);
+#else
+ if (x11Context.pXRRSetCrtcConfig)
+ ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime,
+ paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y,
+ pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/);
+#endif
+ if (ret != Success)
+ VBClLogError("crtc set config failed for output %d\n", iOutputIndex);
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeOutputInfo(pOutputInfo);
+#else
+ if (x11Context.pXRRFreeOutputInfo)
+ x11Context.pXRRFreeOutputInfo(pOutputInfo);
+#endif
+
+ if (fNewMode)
+ {
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeModeInfo(pModeInfo);
+#else
+ if (x11Context.pXRRFreeModeInfo)
+ x11Context.pXRRFreeModeInfo(pModeInfo);
+#endif
+ }
+ return true;
+}
+
+/** Construct the xrandr command which sets the whole monitor topology each time. */
+static void setXrandrTopology(struct RANDROUTPUT *paOutputs)
+{
+ if (!x11Context.pDisplay)
+ {
+ VBClLogInfo("not connected to X11\n");
+ return;
+ }
+
+ XGrabServer(x11Context.pDisplay);
+ if (x11Context.fWmwareCtrlExtention)
+ callVMWCTRL(paOutputs);
+
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#else
+ if (x11Context.pXRRGetScreenResources)
+ x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow);
+#endif
+
+ x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0;
+ if (!x11Context.pScreenResources)
+ {
+ XUngrabServer(x11Context.pDisplay);
+ XFlush(x11Context.pDisplay);
+ return;
+ }
+
+ /* Disable crtcs. */
+ for (int i = 0; i < x11Context.pScreenResources->noutput; ++i)
+ {
+ XRROutputInfo *pOutputInfo = NULL;
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]);
+#else
+ if (x11Context.pXRRGetOutputInfo)
+ pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]);
+#endif
+ if (!pOutputInfo)
+ continue;
+ if (pOutputInfo->crtc == None)
+ continue;
+
+ if (!disableCRTC(pOutputInfo->crtc))
+ {
+ VBClLogFatalError("Crtc disable failed %lu\n", pOutputInfo->crtc);
+ XUngrabServer(x11Context.pDisplay);
+ XSync(x11Context.pDisplay, False);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+ XFlush(x11Context.pDisplay);
+ return;
+ }
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeOutputInfo(pOutputInfo);
+#else
+ if (x11Context.pXRRFreeOutputInfo)
+ x11Context.pXRRFreeOutputInfo(pOutputInfo);
+#endif
+ }
+ /* Resize the frame buffer. */
+ if (!resizeFrameBuffer(paOutputs))
+ {
+ XUngrabServer(x11Context.pDisplay);
+ XSync(x11Context.pDisplay, False);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+ XFlush(x11Context.pDisplay);
+ return;
+ }
+
+ /* Configure the outputs. */
+ for (int i = 0; i < x11Context.hOutputCount; ++i)
+ {
+ /* be paranoid. */
+ if (i >= x11Context.pScreenResources->noutput)
+ break;
+ if (!paOutputs[i].fEnabled)
+ continue;
+ if (configureOutput(i, paOutputs))
+ VBClLogInfo("output[%d] successfully configured\n", i);
+ else
+ VBClLogError("failed to configure output[%d]\n", i);
+ }
+ XSync(x11Context.pDisplay, False);
+#ifdef WITH_DISTRO_XRAND_XINERAMA
+ XRRFreeScreenResources(x11Context.pScreenResources);
+#else
+ if (x11Context.pXRRFreeScreenResources)
+ x11Context.pXRRFreeScreenResources(x11Context.pScreenResources);
+#endif
+ XUngrabServer(x11Context.pDisplay);
+ XFlush(x11Context.pDisplay);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclSVGAWorker(bool volatile *pfShutdown)
+{
+ /* Do not acknowledge the first event we query for to pick up old events,
+ * e.g. from before a guest reboot. */
+ bool fAck = false;
+ bool fFirstRun = true;
+ static struct VMMDevDisplayDef aMonitors[VMW_MAX_HEADS];
+
+ int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc);
+ rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc);
+ if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */
+ return VERR_RESOURCE_BUSY;
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ for (;;)
+ {
+ struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS];
+ uint32_t cDisplaysOut;
+ /* Query the first size without waiting. This lets us e.g. pick up
+ * the last event before a guest reboot when we start again after. */
+ rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck);
+ fAck = true;
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to get display change request, rc=%Rrc\n", rc);
+ if (cDisplaysOut > VMW_MAX_HEADS)
+ VBClLogError("Display change request contained, rc=%Rrc\n", rc);
+ if (cDisplaysOut > 0)
+ {
+ for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i)
+ {
+ uint32_t idDisplay = aDisplays[i].idDisplay;
+ if (idDisplay >= VMW_MAX_HEADS)
+ continue;
+ aMonitors[idDisplay].fDisplayFlags = aDisplays[i].fDisplayFlags;
+ if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED))
+ {
+ if (idDisplay == 0 || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN))
+ {
+ aMonitors[idDisplay].xOrigin = aDisplays[i].xOrigin;
+ aMonitors[idDisplay].yOrigin = aDisplays[i].yOrigin;
+ } else {
+ aMonitors[idDisplay].xOrigin = aMonitors[idDisplay - 1].xOrigin + aMonitors[idDisplay - 1].cx;
+ aMonitors[idDisplay].yOrigin = aMonitors[idDisplay - 1].yOrigin;
+ }
+ aMonitors[idDisplay].cx = aDisplays[i].cx;
+ aMonitors[idDisplay].cy = aDisplays[i].cy;
+ }
+ }
+ /* Create a whole topology and send it to xrandr. */
+ struct RANDROUTPUT aOutputs[VMW_MAX_HEADS];
+ int iRunningX = 0;
+ for (int j = 0; j < x11Context.hOutputCount; ++j)
+ {
+ aOutputs[j].x = iRunningX;
+ aOutputs[j].y = aMonitors[j].yOrigin;
+ aOutputs[j].width = aMonitors[j].cx;
+ aOutputs[j].height = aMonitors[j].cy;
+ aOutputs[j].fEnabled = !(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED);
+ aOutputs[j].fPrimary = (aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY);
+ if (aOutputs[j].fEnabled)
+ iRunningX += aOutputs[j].width;
+ }
+ /* In 32-bit guests GAs build on our release machines causes an xserver lock during vmware_ctrl extention
+ if we do the call withing XGrab. We make the call the said extension only once (to connect the outputs)
+ rather than at each resize iteration. */
+#if ARCH_BITS == 32
+ if (fFirstRun)
+ callVMWCTRL(aOutputs);
+#endif
+ setXrandrTopology(aOutputs);
+ /* Wait for some seconds and set toplogy again after the boot. In some desktop environments (cinnamon) where
+ DE get into our resizing our first resize is reverted by the DE. Sleeping for some secs. helps. Setting
+ topology a 2nd time resolves the black screen I get after resizing.*/
+ if (fFirstRun)
+ {
+ sleep(4);
+ setXrandrTopology(aOutputs);
+ fFirstRun = false;
+ }
+ }
+ uint32_t events;
+ do
+ {
+ rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS, &events);
+ } while (rc == VERR_TIMEOUT && !ASMAtomicReadBool(pfShutdown));
+
+ if (ASMAtomicReadBool(pfShutdown))
+ {
+ /* Shutdown requested. */
+ break;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc);
+ }
+
+ };
+
+ return VINF_SUCCESS;
+}
+
+VBCLSERVICE g_SvcDisplaySVGA =
+{
+ "dp-svga-x11", /* szName */
+ "SVGA X11 display", /* pszDescription */
+ ".vboxclient-display-svga-x11", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclSVGAInit, /* pfnInit */
+ vbclSVGAWorker, /* pfnWorker */
+ vbclSVGAStop, /* pfnStop*/
+ NULL /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp
new file mode 100644
index 00000000..5263ffbf
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp
@@ -0,0 +1,310 @@
+/* $Id: display-svga-xf86cvt.cpp $ */
+/** @file
+ * Guest Additions - Our version of xf86CVTMode.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ * This file is based on x.org server 1.18.0 file xf86cvt.c:
+ *
+ * Copyright 2005-2006 Luc Verhaegen.
+ *
+ * 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.
+ */
+
+#if 0
+/*
+ * The reason for having this function in a file of its own is
+ * so that ../utils/cvt/cvt can link to it, and that xf86CVTMode
+ * code is shared directly.
+ */
+
+#ifdef HAVE_XORG_CONFIG_H
+#include <xorg-config.h>
+#else
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#endif
+
+#include "xf86.h"
+#include "xf86Modes.h"
+
+#include <string.h>
+#else
+# include "VBoxClient.h"
+# include "display-svga-xf86cvt.h"
+#endif
+
+/*
+ * This is a slightly modified version of the xf86CVTMode function from
+ * xf86cvt.c from the xorg xserver source code. Computes several parameters
+ * of a display mode out of horizontal and vertical resolutions. Replicated
+ * here to avoid further dependencies.
+ *
+ *----------------------------------------------------------------------------
+ *
+ * Generate a CVT standard mode from HDisplay, VDisplay and VRefresh.
+ *
+ * These calculations are stolen from the CVT calculation spreadsheet written
+ * by Graham Loveridge. He seems to be claiming no copyright and there seems to
+ * be no license attached to this. He apparently just wants to see his name
+ * mentioned.
+ *
+ * This file can be found at http://www.vesa.org/Public/CVT/CVTd6r1.xls
+ *
+ * Comments and structure corresponds to the comments and structure of the xls.
+ * This should ease importing of future changes to the standard (not very
+ * likely though).
+ *
+ * About margins; i'm sure that they are to be the bit between HDisplay and
+ * HBlankStart, HBlankEnd and HTotal, VDisplay and VBlankStart, VBlankEnd and
+ * VTotal, where the overscan colour is shown. FB seems to call _all_ blanking
+ * outside sync "margin" for some reason. Since we prefer seeing proper
+ * blanking instead of the overscan colour, and since the Crtc* values will
+ * probably get altered after us, we will disable margins altogether. With
+ * these calculations, Margins will plainly expand H/VDisplay, and we don't
+ * want that. -- libv
+ *
+ */
+DisplayModeR VBoxClient_xf86CVTMode(int HDisplay, int VDisplay, float VRefresh /* Herz */, bool Reduced, bool Interlaced)
+{
+ DisplayModeR Mode;
+
+ /* 1) top/bottom margin size (% of height) - default: 1.8 */
+#define CVT_MARGIN_PERCENTAGE 1.8
+
+ /* 2) character cell horizontal granularity (pixels) - default 8 */
+#define CVT_H_GRANULARITY 8
+
+ /* 4) Minimum vertical porch (lines) - default 3 */
+#define CVT_MIN_V_PORCH 3
+
+ /* 4) Minimum number of vertical back porch lines - default 6 */
+#define CVT_MIN_V_BPORCH 6
+
+ /* Pixel Clock step (kHz) */
+#define CVT_CLOCK_STEP 250
+
+ bool Margins = false;
+ float VFieldRate, HPeriod;
+ int HDisplayRnd, HMargin;
+ int VDisplayRnd, VMargin, VSync;
+ float Interlace; /* Please rename this */
+
+ /* CVT default is 60.0Hz */
+ if (!VRefresh)
+ VRefresh = 60.0;
+
+ /* 1. Required field rate */
+ if (Interlaced)
+ VFieldRate = VRefresh * 2;
+ else
+ VFieldRate = VRefresh;
+
+ /* 2. Horizontal pixels */
+ HDisplayRnd = HDisplay - (HDisplay % CVT_H_GRANULARITY);
+
+ /* 3. Determine left and right borders */
+ if (Margins) {
+ /* right margin is actually exactly the same as left */
+ HMargin = (int)((float)HDisplayRnd * CVT_MARGIN_PERCENTAGE / 100.0);
+ HMargin -= HMargin % CVT_H_GRANULARITY;
+ }
+ else
+ HMargin = 0;
+
+ /* 4. Find total active pixels */
+ Mode.HDisplay = HDisplayRnd + 2 * HMargin;
+
+ /* 5. Find number of lines per field */
+ if (Interlaced)
+ VDisplayRnd = VDisplay / 2;
+ else
+ VDisplayRnd = VDisplay;
+
+ /* 6. Find top and bottom margins */
+ /* nope. */
+ if (Margins)
+ /* top and bottom margins are equal again. */
+ VMargin = (int)((float)VDisplayRnd * CVT_MARGIN_PERCENTAGE / 100.0);
+ else
+ VMargin = 0;
+
+ Mode.VDisplay = VDisplay + 2 * VMargin;
+
+ /* 7. Interlace */
+ if (Interlaced)
+ Interlace = 0.5;
+ else
+ Interlace = 0.0;
+
+ /* Determine VSync Width from aspect ratio */
+ if (!(VDisplay % 3) && ((VDisplay * 4 / 3) == HDisplay))
+ VSync = 4;
+ else if (!(VDisplay % 9) && ((VDisplay * 16 / 9) == HDisplay))
+ VSync = 5;
+ else if (!(VDisplay % 10) && ((VDisplay * 16 / 10) == HDisplay))
+ VSync = 6;
+ else if (!(VDisplay % 4) && ((VDisplay * 5 / 4) == HDisplay))
+ VSync = 7;
+ else if (!(VDisplay % 9) && ((VDisplay * 15 / 9) == HDisplay))
+ VSync = 7;
+ else /* Custom */
+ VSync = 10;
+
+ if (!Reduced) { /* simplified GTF calculation */
+
+ /* 4) Minimum time of vertical sync + back porch interval (µs)
+ * default 550.0 */
+#define CVT_MIN_VSYNC_BP 550.0
+
+ /* 3) Nominal HSync width (% of line period) - default 8 */
+#define CVT_HSYNC_PERCENTAGE 8
+
+ float HBlankPercentage;
+ int VSyncAndBackPorch, VBackPorch;
+ int HBlank;
+
+ /* 8. Estimated Horizontal period */
+ HPeriod = ((float)(1000000.0 / VFieldRate - CVT_MIN_VSYNC_BP))
+ / (VDisplayRnd + 2 * VMargin + CVT_MIN_V_PORCH + Interlace);
+
+ /* 9. Find number of lines in sync + backporch */
+ if ((int)(CVT_MIN_VSYNC_BP / HPeriod) + 1 < VSync + CVT_MIN_V_PORCH)
+ VSyncAndBackPorch = VSync + CVT_MIN_V_PORCH;
+ else
+ VSyncAndBackPorch = (int)(CVT_MIN_VSYNC_BP / HPeriod) + 1;
+
+ /* 10. Find number of lines in back porch */
+ VBackPorch = VSyncAndBackPorch - VSync;
+ (void) VBackPorch;
+
+ /* 11. Find total number of lines in vertical field */
+ Mode.VTotal = (int)(VDisplayRnd + 2 * VMargin + VSyncAndBackPorch + Interlace + CVT_MIN_V_PORCH);
+
+ /* 5) Definition of Horizontal blanking time limitation */
+ /* Gradient (%/kHz) - default 600 */
+#define CVT_M_FACTOR 600
+
+ /* Offset (%) - default 40 */
+#define CVT_C_FACTOR 40
+
+ /* Blanking time scaling factor - default 128 */
+#define CVT_K_FACTOR 128
+
+ /* Scaling factor weighting - default 20 */
+#define CVT_J_FACTOR 20
+
+#define CVT_M_PRIME (CVT_M_FACTOR * CVT_K_FACTOR / 256)
+#define CVT_C_PRIME ((CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256 + CVT_J_FACTOR)
+
+ /* 12. Find ideal blanking duty cycle from formula */
+ HBlankPercentage = CVT_C_PRIME - CVT_M_PRIME * HPeriod / 1000.0;
+
+ /* 13. Blanking time */
+ if (HBlankPercentage < 20)
+ HBlankPercentage = 20;
+
+ HBlank = (int)(Mode.HDisplay * HBlankPercentage / (100.0 - HBlankPercentage));
+ HBlank -= HBlank % (2 * CVT_H_GRANULARITY);
+
+ /* 14. Find total number of pixels in a line. */
+ Mode.HTotal = Mode.HDisplay + HBlank;
+
+ /* Fill in HSync values */
+ Mode.HSyncEnd = Mode.HDisplay + HBlank / 2;
+
+ Mode.HSyncStart = Mode.HSyncEnd - (Mode.HTotal * CVT_HSYNC_PERCENTAGE) / 100;
+ Mode.HSyncStart += CVT_H_GRANULARITY - Mode.HSyncStart % CVT_H_GRANULARITY;
+
+ /* Fill in VSync values */
+ Mode.VSyncStart = Mode.VDisplay + CVT_MIN_V_PORCH;
+ Mode.VSyncEnd = Mode.VSyncStart + VSync;
+
+ }
+ else { /* Reduced blanking */
+ /* Minimum vertical blanking interval time (µs) - default 460 */
+#define CVT_RB_MIN_VBLANK 460.0
+
+ /* Fixed number of clocks for horizontal sync */
+#define CVT_RB_H_SYNC 32.0
+
+ /* Fixed number of clocks for horizontal blanking */
+#define CVT_RB_H_BLANK 160.0
+
+ /* Fixed number of lines for vertical front porch - default 3 */
+#define CVT_RB_VFPORCH 3
+
+ int VBILines;
+
+ /* 8. Estimate Horizontal period. */
+ HPeriod = ((float)(1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) / (VDisplayRnd + 2 * VMargin);
+
+ /* 9. Find number of lines in vertical blanking */
+ VBILines = (int)((float)CVT_RB_MIN_VBLANK / HPeriod + 1);
+
+ /* 10. Check if vertical blanking is sufficient */
+ if (VBILines < CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH)
+ VBILines = CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH;
+
+ /* 11. Find total number of lines in vertical field */
+ Mode.VTotal = (int)(VDisplayRnd + 2 * VMargin + Interlace + VBILines);
+
+ /* 12. Find total number of pixels in a line */
+ Mode.HTotal = (int)(Mode.HDisplay + CVT_RB_H_BLANK);
+
+ /* Fill in HSync values */
+ Mode.HSyncEnd = (int)(Mode.HDisplay + CVT_RB_H_BLANK / 2);
+ Mode.HSyncStart = (int)(Mode.HSyncEnd - CVT_RB_H_SYNC);
+
+ /* Fill in VSync values */
+ Mode.VSyncStart = Mode.VDisplay + CVT_RB_VFPORCH;
+ Mode.VSyncEnd = Mode.VSyncStart + VSync;
+ }
+ /* 15/13. Find pixel clock frequency (kHz for xf86) */
+ Mode.Clock = (int)(Mode.HTotal * 1000.0 / HPeriod);
+ Mode.Clock -= Mode.Clock % CVT_CLOCK_STEP;
+
+ /* 16/14. Find actual Horizontal Frequency (kHz) */
+ Mode.HSync = (float)Mode.Clock / (float)Mode.HTotal;
+
+ /* 17/15. Find actual Field rate */
+ Mode.VRefresh = (1000.0 * (float)Mode.Clock) / (float)(Mode.HTotal * Mode.VTotal);
+
+ /* 18/16. Find actual vertical frame frequency */
+ /* ignore - just set the mode flag for interlaced */
+ if (Interlaced)
+ Mode.VTotal *= 2;
+
+#if 0
+ XNFasprintf(&tmp, "%dx%d", HDisplay, VDisplay);
+ Mode->name = tmp;
+
+ if (Reduced)
+ Mode->Flags |= V_PHSYNC | V_NVSYNC;
+ else
+ Mode->Flags |= V_NHSYNC | V_PVSYNC;
+
+ if (Interlaced)
+ Mode->Flags |= V_INTERLACE;
+#endif
+
+ return Mode;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h
new file mode 100644
index 00000000..431f17c7
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h
@@ -0,0 +1,56 @@
+/* $Id: display-svga-xf86cvt.h $ */
+/** @file
+ * Guest Additions - Header for display-svga-xf86ctv.cpp.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+typedef struct DisplayModeR
+{
+ int Clock;
+ int HDisplay;
+ int HSyncStart;
+ int HSyncEnd;
+ int HTotal;
+ int HSkew;
+ int VDisplay;
+ int VSyncStart;
+ int VSyncEnd;
+ int VTotal;
+ int VScan;
+ float HSync;
+ float VRefresh;
+} DisplayModeR;
+
+DisplayModeR VBoxClient_xf86CVTMode(int HDisplay, int VDisplay, float VRefresh /* Herz */, bool Reduced, bool Interlaced);
+
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h */
+
diff --git a/src/VBox/Additions/x11/VBoxClient/display.cpp b/src/VBox/Additions/x11/VBoxClient/display.cpp
new file mode 100644
index 00000000..3edbfd31
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display.cpp
@@ -0,0 +1,315 @@
+/* $Id: display.cpp $ */
+/** @file
+ * X11 guest client - display management.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "VBoxClient.h"
+
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <X11/extensions/Xrandr.h>
+
+/** @todo this should probably be replaced by something IPRT */
+/* For system() and WEXITSTATUS() */
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <time.h>
+#include <dlfcn.h>
+
+/* TESTING: Dynamic resizing and mouse integration toggling should work
+ * correctly with a range of X servers (pre-1.3, 1.3 and later under Linux, 1.3
+ * and later under Solaris) with Guest Additions installed. Switching to a
+ * virtual terminal while a user session is in place should disable dynamic
+ * resizing and cursor integration, switching back should re-enable them. */
+
+/** State information needed for the service. The main VBoxClient code provides
+ * the daemon logic needed by all services. */
+struct DISPLAYSTATE
+{
+ /** Are we initialised yet? */
+ bool mfInit;
+ /** The connection to the server. */
+ Display *pDisplay;
+ /** The RandR extension base event number. */
+ int cRREventBase;
+ /** Can we use version 1.2 or later of the RandR protocol here? */
+ bool fHaveRandR12;
+ /** The command argument to use for the xrandr binary. Currently only
+ * used to support the non-standard location on some Solaris systems -
+ * would it make sense to use absolute paths on all systems? */
+ const char *pcszXrandr;
+ /** Was there a recent mode hint with no following root window resize, and
+ * if so, have we waited for a reasonable time? */
+ time_t timeLastModeHint;
+ /** Handle to libXrandr. */
+ void *pRandLibraryHandle;
+ /** Handle to pXRRSelectInput. */
+ void (*pXRRSelectInput) (Display *, Window, int);
+ /** Handle to pXRRQueryExtension. */
+ Bool (*pXRRQueryExtension) (Display *, int *, int *);
+};
+
+static struct DISPLAYSTATE g_DisplayState;
+
+static unsigned char *getRootProperty(struct DISPLAYSTATE *pState, const char *pszName,
+ long cItems, Atom type)
+{
+ Atom actualType = None;
+ int iFormat = 0;
+ unsigned long cReturned = 0;
+ unsigned long cAfter = 0;
+ unsigned char *pData = 0;
+
+ if (XGetWindowProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay),
+ XInternAtom(pState->pDisplay, pszName, 0), 0, cItems,
+ False /* delete */, type, &actualType, &iFormat,
+ &cReturned, &cAfter, &pData))
+ return NULL;
+ return pData;
+}
+
+static void doResize(struct DISPLAYSTATE *pState)
+{
+ /** @note The xrandr command can fail if something else accesses RandR at
+ * the same time. We just ignore failure for now as we do not know what
+ * someone else is doing. */
+ if (!pState->fHaveRandR12)
+ {
+ char szCommand[256];
+ unsigned char *pData;
+
+ pData = getRootProperty(pState, "VBOXVIDEO_PREFERRED_MODE", 1, XA_INTEGER);
+ if (pData != NULL)
+ {
+ RTStrPrintf(szCommand, sizeof(szCommand), "%s -s %ux%u",
+ pState->pcszXrandr, ((unsigned long *)pData)[0] >> 16, ((unsigned long *)pData)[0] & 0xFFFF);
+ int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc);
+ XFree(pData);
+ }
+ }
+ else
+ {
+ const char szCommandBase[] =
+ "%s --output VGA-0 --auto --output VGA-1 --auto --right-of VGA-0 "
+ "--output VGA-2 --auto --right-of VGA-1 --output VGA-3 --auto --right-of VGA-2 "
+ "--output VGA-4 --auto --right-of VGA-3 --output VGA-5 --auto --right-of VGA-4 "
+ "--output VGA-6 --auto --right-of VGA-5 --output VGA-7 --auto --right-of VGA-6 "
+ "--output VGA-8 --auto --right-of VGA-7 --output VGA-9 --auto --right-of VGA-8 "
+ "--output VGA-10 --auto --right-of VGA-9 --output VGA-11 --auto --right-of VGA-10 "
+ "--output VGA-12 --auto --right-of VGA-11 --output VGA-13 --auto --right-of VGA-12 "
+ "--output VGA-14 --auto --right-of VGA-13 --output VGA-15 --auto --right-of VGA-14 "
+ "--output VGA-16 --auto --right-of VGA-15 --output VGA-17 --auto --right-of VGA-16 "
+ "--output VGA-18 --auto --right-of VGA-17 --output VGA-19 --auto --right-of VGA-18 "
+ "--output VGA-20 --auto --right-of VGA-19 --output VGA-21 --auto --right-of VGA-20 "
+ "--output VGA-22 --auto --right-of VGA-21 --output VGA-23 --auto --right-of VGA-22 "
+ "--output VGA-24 --auto --right-of VGA-23 --output VGA-25 --auto --right-of VGA-24 "
+ "--output VGA-26 --auto --right-of VGA-25 --output VGA-27 --auto --right-of VGA-26 "
+ "--output VGA-28 --auto --right-of VGA-27 --output VGA-29 --auto --right-of VGA-28 "
+ "--output VGA-30 --auto --right-of VGA-29 --output VGA-31 --auto --right-of VGA-30";
+ char szCommand[sizeof(szCommandBase) + 256];
+ RTStrPrintf(szCommand, sizeof(szCommand), szCommandBase, pState->pcszXrandr);
+ int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc);
+ }
+}
+
+/** Main loop: handle display hot-plug events, property updates (which can
+ * signal VT switches hot-plug in old X servers). */
+static void runDisplay(struct DISPLAYSTATE *pState, bool volatile *pfShutdown)
+{
+ Display *pDisplay = pState->pDisplay;
+ long cValue = 1;
+
+ /* One way or another we want the preferred mode at server start-up. */
+ doResize(pState);
+ XSelectInput(pDisplay, DefaultRootWindow(pDisplay), PropertyChangeMask | StructureNotifyMask);
+ if (pState->fHaveRandR12)
+ pState->pXRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask);
+ /* Semantics: when VBOXCLIENT_STARTED is set, pre-1.3 X.Org Server driver
+ * assumes that a client capable of handling mode hints will be present for the
+ * rest of the X session. If we crash things will not work as they should.
+ * I thought that preferable to implementing complex crash-handling logic.
+ */
+ XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOXCLIENT_STARTED", 0),
+ XA_INTEGER, 32, PropModeReplace, (unsigned char *)&cValue, 1);
+ /* Interrupting this cleanly will be more work than making it robust
+ * against spontaneous termination, especially as it will never get
+ * properly tested, so I will go for the second. */
+ while (!ASMAtomicReadBool(pfShutdown))
+ {
+ XEvent event;
+ struct pollfd PollFd;
+ int pollTimeOut = -1;
+ int cFds;
+
+ /* Do not handle overflow. */
+ if (pState->timeLastModeHint > 0 && pState->timeLastModeHint < INT_MAX - 2)
+ pollTimeOut = 2 - (time(0) - pState->timeLastModeHint);
+ PollFd.fd = ConnectionNumber(pDisplay);
+ PollFd.events = POLLIN; /* Hang-up is always reported. */
+ XFlush(pDisplay);
+ cFds = poll(&PollFd, 1, pollTimeOut >= 0 ? pollTimeOut * 1000 : -1);
+ while (XPending(pDisplay))
+ {
+ XNextEvent(pDisplay, &event);
+ /* This property is deleted when the server regains the virtual
+ * terminal. Force the main thread to call xrandr again, as old X
+ * servers could not handle it while switched out. */
+ if ( !pState->fHaveRandR12
+ && event.type == PropertyNotify
+ && event.xproperty.state == PropertyDelete
+ && event.xproperty.window == DefaultRootWindow(pDisplay)
+ && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_NO_VT", False))
+ doResize(pState);
+ if ( !pState->fHaveRandR12
+ && event.type == PropertyNotify
+ && event.xproperty.state == PropertyNewValue
+ && event.xproperty.window == DefaultRootWindow(pDisplay)
+ && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_PREFERRED_MODE", False))
+ doResize(pState);
+ if ( pState->fHaveRandR12
+ && event.type == pState->cRREventBase + RRScreenChangeNotify)
+ pState->timeLastModeHint = time(0);
+ if ( event.type == ConfigureNotify
+ && event.xproperty.window == DefaultRootWindow(pDisplay))
+ pState->timeLastModeHint = 0;
+ }
+ if (cFds == 0 && pState->timeLastModeHint > 0)
+ doResize(pState);
+ }
+}
+
+static int initDisplay(struct DISPLAYSTATE *pState)
+{
+ char szCommand[256];
+ int status;
+
+ pState->pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */);
+ if (!pState->pRandLibraryHandle)
+ pState->pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */);
+ if (!pState->pRandLibraryHandle)
+ pState->pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */);
+
+ if (!RT_VALID_PTR(pState->pRandLibraryHandle))
+ {
+ VBClLogFatalError("Could not locate libXrandr for dlopen\n");
+ return VERR_NOT_FOUND;
+ }
+
+ *(void **)(&pState->pXRRSelectInput) = dlsym(pState->pRandLibraryHandle, "XRRSelectInput");
+ *(void **)(&pState->pXRRQueryExtension) = dlsym(pState->pRandLibraryHandle, "XRRQueryExtension");
+
+ if ( !RT_VALID_PTR(pState->pXRRSelectInput)
+ || !RT_VALID_PTR(pState->pXRRQueryExtension))
+ {
+ VBClLogFatalError("Could not load required libXrandr symbols\n");
+ dlclose(pState->pRandLibraryHandle);
+ pState->pRandLibraryHandle = NULL;
+ return VERR_NOT_FOUND;
+ }
+
+ pState->pDisplay = XOpenDisplay(NULL);
+ if (!pState->pDisplay)
+ return VERR_NOT_FOUND;
+ if (!pState->pXRRQueryExtension(pState->pDisplay, &pState->cRREventBase, &status))
+ return VERR_NOT_FOUND;
+ pState->fHaveRandR12 = false;
+ pState->pcszXrandr = "xrandr";
+ if (RTFileExists("/usr/X11/bin/xrandr"))
+ pState->pcszXrandr = "/usr/X11/bin/xrandr";
+ status = system(pState->pcszXrandr);
+ if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
+ VBClLogFatalError("Failed to execute the xrandr utility\n");
+ RTStrPrintf(szCommand, sizeof(szCommand), "%s --q12", pState->pcszXrandr);
+ status = system(szCommand);
+ if (WEXITSTATUS(status) == 0)
+ pState->fHaveRandR12 = true;
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) init(void)
+{
+ struct DISPLAYSTATE *pSelf = &g_DisplayState;
+ int rc;
+
+ if (pSelf->mfInit)
+ return VERR_WRONG_ORDER;
+ rc = initDisplay(pSelf);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (RT_SUCCESS(rc))
+ pSelf->mfInit = true;
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) run(bool volatile *pfShutdown)
+{
+ struct DISPLAYSTATE *pSelf = &g_DisplayState;
+
+ if (!pSelf->mfInit)
+ return VERR_WRONG_ORDER;
+
+ runDisplay(pSelf, pfShutdown);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) stop(void)
+{
+ /* Nothing to do here. Implement empty callback, so
+ * main thread can set pfShutdown=true on process termination. */
+}
+
+VBCLSERVICE g_SvcDisplayLegacy =
+{
+ "dp-legacy-x11", /* szName */
+ "Legacy display assistant", /* pszDescription */
+ ".vboxclient-display", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ init, /* pfnInit */
+ run, /* pfnWorker */
+ stop, /* pfnStop */
+ NULL, /* pfnTerm */
+};
diff --git a/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp
new file mode 100644
index 00000000..3edb7e98
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp
@@ -0,0 +1,3877 @@
+/* $Id: draganddrop.cpp $ */
+/** @file
+ * X11 guest client - Drag and drop implementation.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#ifdef VBOX_DND_WITH_XTEST
+# include <X11/extensions/XTest.h>
+#endif
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/critsect.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+
+#include <iprt/cpp/mtlist.h>
+#include <iprt/cpp/ministring.h>
+
+#include <limits.h>
+
+#ifdef LOG_GROUP
+# undef LOG_GROUP
+#endif
+#define LOG_GROUP LOG_GROUP_GUEST_DND
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/version.h>
+
+#include "VBox/HostServices/DragAndDropSvc.h"
+#include "VBoxClient.h"
+
+
+/* Enable this to handle drag'n drop "promises".
+ * This is needed for supporting certain applications (i.e. PcManFM on LXDE),
+ * which require the drag'n drop meta data a lot earlier than actually needed.
+ * That behavior is similar to macOS' drag'n drop promises, hence the name.
+ *
+ * Those applications query the data right while dragging over them (see GtkWidget::drag-motion),
+ * instead of when the source dropped the data (GtkWidget::drag-drop).
+ *
+ * This might be entirely implementation-specific, so not being a bug in GTK/GDK. Also see #9820.
+ */
+#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES
+# undef VBOX_WITH_DRAG_AND_DROP_PROMISES
+#endif
+
+/**
+ * For X11 guest Xdnd is used. See http://www.acc.umu.se/~vatten/XDND.html for
+ * a walk trough.
+ *
+ * Also useful pages:
+ * - https://www.freedesktop.org/wiki/Draganddropwarts/
+ * - https://www.freedesktop.org/wiki/Specifications/XDNDRevision/
+ *
+ * Host -> Guest:
+ * For X11 this means mainly forwarding all the events from HGCM to the
+ * appropriate X11 events. There exists a proxy window, which is invisible and
+ * used for all the X11 communication. On a HGCM Enter event, we set our proxy
+ * window as XdndSelection owner with the given mime-types. On every HGCM move
+ * event, we move the X11 mouse cursor to the new position and query for the
+ * window below that position. Depending on if it is XdndAware, a new window or
+ * a known window, we send the appropriate X11 messages to it. On HGCM drop, we
+ * send a XdndDrop message to the current window and wait for a X11
+ * SelectionMessage from the target window. Because we didn't have the data in
+ * the requested mime-type, yet, we save that message and ask the host for the
+ * data. When the data is successfully received from the host, we put the data
+ * as a property to the window and send a X11 SelectionNotify event to the
+ * target window.
+ *
+ * Guest -> Host:
+ * This is a lot more trickery than H->G. When a pending event from HGCM
+ * arrives, we ask if there currently is an owner of the XdndSelection
+ * property. If so, our proxy window is shown (1x1, but without backing store)
+ * and some mouse event is triggered. This should be followed by an XdndEnter
+ * event send to the proxy window. From this event we can fetch the necessary
+ * info of the MIME types and allowed actions and send this back to the host.
+ * On a drop request from the host, we query for the selection and should get
+ * the data in the specified mime-type. This data is send back to the host.
+ * After that we send a XdndLeave event to the source window.
+ *
+ ** @todo Cancelling (e.g. with ESC key) doesn't work.
+ ** @todo INCR (incremental transfers) support.
+ ** @todo Really check for the Xdnd version and the supported features.
+ ** @todo Either get rid of the xHelpers class or properly unify the code with the drag instance class.
+ */
+
+/*********************************************************************************************************************************
+ * Definitions *
+ ********************************************************************************************************************************/
+
+/** The Xdnd protocol version we support. */
+#define VBOX_XDND_VERSION (5)
+
+/** No flags specified. */
+#define VBOX_XDND_STATUS_FLAG_NONE 0
+/** Whether the target window accepts the data being dragged over or not. */
+#define VBOX_XDND_STATUS_FLAG_ACCEPT RT_BIT(0)
+/** Whether the target window wants XdndPosition messages while dragging stuff over it. */
+#define VBOX_XDND_STATUS_FLAG_WANTS_POS RT_BIT(1)
+
+/** Whether the target window accepted the drop data or not. */
+#define VBOX_XDND_FINISHED_FLAG_SUCCEEDED RT_BIT(0)
+
+/** How many X properties our proxy window can hold. */
+#define VBOX_MAX_XPROPERTIES (LONG_MAX-1)
+
+/** The notification header text for VBClShowNotify(). */
+#define VBOX_DND_SHOWNOTIFY_HEADER VBOX_PRODUCT " Drag'n Drop"
+
+/**
+ * Structure for storing new X11 events and HGCM messages
+ * into a single event queue.
+ */
+typedef struct DNDEVENT
+{
+ enum DnDEventType
+ {
+ /** Unknown event, do not use. */
+ DnDEventType_Unknown = 0,
+ /** VBGLR3DNDEVENT event. */
+ DnDEventType_HGCM,
+ /** X11 event. */
+ DnDEventType_X11,
+ /** Blow the type up to 32-bit. */
+ DnDEventType_32BIT_HACK = 0x7fffffff
+ };
+ /** Event type. */
+ DnDEventType enmType;
+ union
+ {
+ PVBGLR3DNDEVENT hgcm;
+ XEvent x11;
+ };
+#ifdef IN_GUEST
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+} DNDEVENT;
+/** Pointer to a DnD event. */
+typedef DNDEVENT *PDNDEVENT;
+
+enum XA_Type
+{
+ /* States */
+ XA_WM_STATE = 0,
+ /* Properties */
+ XA_TARGETS,
+ XA_MULTIPLE,
+ XA_INCR,
+ /* Mime Types */
+ XA_image_bmp,
+ XA_image_jpg,
+ XA_image_tiff,
+ XA_image_png,
+ XA_text_uri_list,
+ XA_text_uri,
+ XA_text_plain,
+ XA_TEXT,
+ /* Xdnd */
+ XA_XdndSelection,
+ XA_XdndAware,
+ XA_XdndEnter,
+ XA_XdndLeave,
+ XA_XdndTypeList,
+ XA_XdndActionList,
+ XA_XdndPosition,
+ XA_XdndActionCopy,
+ XA_XdndActionMove,
+ XA_XdndActionLink,
+ XA_XdndStatus,
+ XA_XdndDrop,
+ XA_XdndFinished,
+ /* Our own stop marker */
+ XA_dndstop,
+ /* End marker */
+ XA_End
+};
+
+/**
+ * Xdnd message value indices, sorted by message type.
+ */
+typedef enum XdndMsg
+{
+ /** XdndEnter. */
+ XdndEnterTypeCount = 3, /* Maximum number of types in XdndEnter message. */
+
+ XdndEnterWindow = 0, /* Source window (sender). */
+ XdndEnterFlags, /* Version in high byte, bit 0 => more data types. */
+ XdndEnterType1, /* First available data type. */
+ XdndEnterType2, /* Second available data type. */
+ XdndEnterType3, /* Third available data type. */
+
+ XdndEnterMoreTypesFlag = 1, /* Set if there are more than XdndEnterTypeCount. */
+ XdndEnterVersionRShift = 24, /* Right shift to position version number. */
+ XdndEnterVersionMask = 0xFF, /* Mask to get version after shifting. */
+
+ /** XdndHere. */
+ XdndHereWindow = 0, /* Source window (sender). */
+ XdndHereFlags, /* Reserved. */
+ XdndHerePt, /* X + Y coordinates of mouse (root window coords). */
+ XdndHereTimeStamp, /* Timestamp for requesting data. */
+ XdndHereAction, /* Action requested by user. */
+
+ /** XdndPosition. */
+ XdndPositionWindow = 0, /* Source window (sender). */
+ XdndPositionFlags, /* Flags. */
+ XdndPositionXY, /* X/Y coordinates of the mouse position relative to the root window. */
+ XdndPositionTimeStamp, /* Time stamp for retrieving the data. */
+ XdndPositionAction, /* Action requested by the user. */
+
+ /** XdndStatus. */
+ XdndStatusWindow = 0, /* Target window (sender).*/
+ XdndStatusFlags, /* Flags returned by target. */
+ XdndStatusNoMsgXY, /* X + Y of "no msg" rectangle (root window coords). */
+ XdndStatusNoMsgWH, /* Width + height of "no msg" rectangle. */
+ XdndStatusAction, /* Action accepted by target. */
+
+ XdndStatusAcceptDropFlag = 1, /* Set if target will accept the drop. */
+ XdndStatusSendHereFlag = 2, /* Set if target wants a stream of XdndPosition. */
+
+ /** XdndLeave. */
+ XdndLeaveWindow = 0, /* Source window (sender). */
+ XdndLeaveFlags, /* Reserved. */
+
+ /** XdndDrop. */
+ XdndDropWindow = 0, /* Source window (sender). */
+ XdndDropFlags, /* Reserved. */
+ XdndDropTimeStamp, /* Timestamp for requesting data. */
+
+ /** XdndFinished. */
+ XdndFinishedWindow = 0, /* Target window (sender). */
+ XdndFinishedFlags, /* Since version 5: Bit 0 is set if the current target accepted the drop. */
+ XdndFinishedAction /* Since version 5: Contains the action performed by the target. */
+
+} XdndMsg;
+
+class DragAndDropService;
+
+/** List of Atoms. */
+#define VBoxDnDAtomList RTCList<Atom>
+
+class xHelpers
+{
+public:
+
+ static xHelpers *getInstance(Display *pDisplay = 0)
+ {
+ if (!m_pInstance)
+ {
+ AssertPtrReturn(pDisplay, NULL);
+ m_pInstance = new xHelpers(pDisplay);
+ }
+
+ return m_pInstance;
+ }
+
+ static void destroyInstance(void)
+ {
+ if (m_pInstance)
+ {
+ delete m_pInstance;
+ m_pInstance = NULL;
+ }
+ }
+
+ inline Display *display() const { return m_pDisplay; }
+ inline Atom xAtom(XA_Type e) const { return m_xAtoms[e]; }
+
+ inline Atom stringToxAtom(const char *pcszString) const
+ {
+ return XInternAtom(m_pDisplay, pcszString, False);
+ }
+ inline RTCString xAtomToString(Atom atom) const
+ {
+ if (atom == None) return "None";
+
+ char* pcsAtom = XGetAtomName(m_pDisplay, atom);
+ RTCString strAtom(pcsAtom);
+ XFree(pcsAtom);
+
+ return strAtom;
+ }
+
+ inline RTCString xAtomListToString(const VBoxDnDAtomList &formatList, const RTCString &strSep = DND_FORMATS_SEPARATOR_STR)
+ {
+ RTCString format;
+ for (size_t i = 0; i < formatList.size(); ++i)
+ format += xAtomToString(formatList.at(i)) + strSep;
+ return format;
+ }
+
+ /**
+ * Returns a filtered X11 atom list.
+ *
+ * @returns Filtered list.
+ * @param formatList Atom list to convert.
+ * @param filterList Atom list to filter out.
+ */
+ inline VBoxDnDAtomList xAtomListFiltered(const VBoxDnDAtomList &formatList, const VBoxDnDAtomList &filterList)
+ {
+ VBoxDnDAtomList tempList = formatList;
+ tempList.filter(filterList);
+ return tempList;
+ }
+
+ RTCString xErrorToString(int xRc) const;
+ Window applicationWindowBelowCursor(Window parentWin) const;
+
+private:
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ xHelpers(Display *pDisplay)
+ : m_pDisplay(pDisplay)
+ {
+ /* Not all x11 atoms we use are defined in the headers. Create the
+ * additional one we need here. */
+ for (int i = 0; i < XA_End; ++i)
+ m_xAtoms[i] = XInternAtom(m_pDisplay, m_xAtomNames[i], False);
+ };
+
+ /* Private member vars */
+ static xHelpers *m_pInstance;
+ Display *m_pDisplay;
+ Atom m_xAtoms[XA_End];
+ static const char *m_xAtomNames[XA_End];
+};
+
+/* Some xHelpers convenience defines. */
+#define gX11 xHelpers::getInstance()
+#define xAtom(xa) xHelpers::getInstance()->xAtom((xa))
+#define xAtomToString(xa) xHelpers::getInstance()->xAtomToString((xa))
+
+/*********************************************************************************************************************************
+ * xHelpers implementation. *
+ ********************************************************************************************************************************/
+
+xHelpers *xHelpers::m_pInstance = NULL;
+
+/* Has to be in sync with the XA_Type enum. */
+const char *xHelpers::m_xAtomNames[] =
+{
+ /* States */
+ "WM_STATE",
+ /* Properties */
+ "TARGETS",
+ "MULTIPLE",
+ "INCR",
+ /* Mime Types */
+ "image/bmp",
+ "image/jpg",
+ "image/tiff",
+ "image/png",
+ "text/uri-list",
+ "text/uri",
+ "text/plain",
+ "TEXT",
+ /* Xdnd */
+ "XdndSelection",
+ "XdndAware",
+ "XdndEnter",
+ "XdndLeave",
+ "XdndTypeList",
+ "XdndActionList",
+ "XdndPosition",
+ "XdndActionCopy",
+ "XdndActionMove",
+ "XdndActionLink",
+ "XdndStatus",
+ "XdndDrop",
+ "XdndFinished",
+ /* Our own stop marker */
+ "dndstop"
+};
+
+RTCString xHelpers::xErrorToString(int xRc) const
+{
+ switch (xRc)
+ {
+ case Success: return RTCStringFmt("%d (Success)", xRc); break;
+ case BadRequest: return RTCStringFmt("%d (BadRequest)", xRc); break;
+ case BadValue: return RTCStringFmt("%d (BadValue)", xRc); break;
+ case BadWindow: return RTCStringFmt("%d (BadWindow)", xRc); break;
+ case BadPixmap: return RTCStringFmt("%d (BadPixmap)", xRc); break;
+ case BadAtom: return RTCStringFmt("%d (BadAtom)", xRc); break;
+ case BadCursor: return RTCStringFmt("%d (BadCursor)", xRc); break;
+ case BadFont: return RTCStringFmt("%d (BadFont)", xRc); break;
+ case BadMatch: return RTCStringFmt("%d (BadMatch)", xRc); break;
+ case BadDrawable: return RTCStringFmt("%d (BadDrawable)", xRc); break;
+ case BadAccess: return RTCStringFmt("%d (BadAccess)", xRc); break;
+ case BadAlloc: return RTCStringFmt("%d (BadAlloc)", xRc); break;
+ case BadColor: return RTCStringFmt("%d (BadColor)", xRc); break;
+ case BadGC: return RTCStringFmt("%d (BadGC)", xRc); break;
+ case BadIDChoice: return RTCStringFmt("%d (BadIDChoice)", xRc); break;
+ case BadName: return RTCStringFmt("%d (BadName)", xRc); break;
+ case BadLength: return RTCStringFmt("%d (BadLength)", xRc); break;
+ case BadImplementation: return RTCStringFmt("%d (BadImplementation)", xRc); break;
+ }
+ return RTCStringFmt("%d (unknown)", xRc);
+}
+
+/** @todo Make this iterative. */
+Window xHelpers::applicationWindowBelowCursor(Window wndParent) const
+{
+ /* No parent, nothing to do. */
+ if(wndParent == 0)
+ return 0;
+
+ Window wndApp = 0;
+ int cProps = -1;
+
+ /* Fetch all x11 window properties of the parent window. */
+ Atom *pProps = XListProperties(m_pDisplay, wndParent, &cProps);
+ if (cProps > 0)
+ {
+ /* We check the window for the WM_STATE property. */
+ for (int i = 0; i < cProps; ++i)
+ {
+ if (pProps[i] == xAtom(XA_WM_STATE))
+ {
+ /* Found it. */
+ wndApp = wndParent;
+ break;
+ }
+ }
+
+ /* Cleanup */
+ XFree(pProps);
+ }
+
+ if (!wndApp)
+ {
+ Window wndChild, wndTemp;
+ int tmp;
+ unsigned int utmp;
+
+ /* Query the next child window of the parent window at the current
+ * mouse position. */
+ XQueryPointer(m_pDisplay, wndParent, &wndTemp, &wndChild, &tmp, &tmp, &tmp, &tmp, &utmp);
+
+ /* Recursive call our self to dive into the child tree. */
+ wndApp = applicationWindowBelowCursor(wndChild);
+ }
+
+ return wndApp;
+}
+
+#ifdef DEBUG
+# define VBOX_DND_FN_DECL_LOG(x) inline x /* For LogFlowXXX logging. */
+#else
+# define VBOX_DND_FN_DECL_LOG(x) x
+#endif
+
+/**
+ * Class which handles a single drag'n drop proxy window.
+ ** @todo Move all proxy window-related stuff into this class! Clean up this mess.
+ */
+class VBoxDnDProxyWnd
+{
+
+public:
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ VBoxDnDProxyWnd(void);
+ virtual ~VBoxDnDProxyWnd(void);
+
+public:
+
+ int init(Display *pDisplay);
+ void destroy();
+
+ int sendFinished(Window hWndSource, VBOXDNDACTION dndAction);
+
+public:
+
+ Display *pDisp;
+ /** Proxy window handle. */
+ Window hWnd;
+ int iX;
+ int iY;
+ int iWidth;
+ int iHeight;
+};
+
+/** This class only serve to avoid dragging in generic new() and delete(). */
+class WrappedXEvent
+{
+public:
+ XEvent m_Event;
+
+public:
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ WrappedXEvent(const XEvent &a_rSrcEvent)
+ {
+ m_Event = a_rSrcEvent;
+ }
+
+ WrappedXEvent()
+ {
+ RT_ZERO(m_Event);
+ }
+
+ WrappedXEvent &operator=(const XEvent &a_rSrcEvent)
+ {
+ m_Event = a_rSrcEvent;
+ return *this;
+ }
+};
+
+/**
+ * Class for handling a single drag and drop operation, that is,
+ * one source and one target at a time.
+ *
+ * For now only one DragInstance will exits when the app is running.
+ */
+class DragInstance
+{
+public:
+
+ enum State
+ {
+ Uninitialized = 0,
+ Initialized,
+ Dragging,
+ Dropped,
+ State_32BIT_Hack = 0x7fffffff
+ };
+
+ enum Mode
+ {
+ Unknown = 0,
+ HG,
+ GH,
+ Mode_32Bit_Hack = 0x7fffffff
+ };
+
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+ DragInstance(Display *pDisplay, DragAndDropService *pParent);
+
+public:
+
+ int init(uint32_t uScreenID);
+ int term(void);
+ void stop(void);
+ void reset(void);
+
+ /* X11 message processing. */
+ int onX11ClientMessage(const XEvent &e);
+ int onX11MotionNotify(const XEvent &e);
+ int onX11SelectionClear(const XEvent &e);
+ int onX11SelectionNotify(const XEvent &e);
+ int onX11SelectionRequest(const XEvent &evReq);
+ int onX11Event(const XEvent &e);
+ int waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS = 30000);
+ bool waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS = 100);
+ bool waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType, RTMSINTERVAL uTimeoutMS = 100);
+
+ /* Session handling. */
+ int checkForSessionChange(void);
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ /* Guest -> Host handling. */
+ int ghIsDnDPending(void);
+ int ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested);
+#endif
+
+ /* Host -> Guest handling. */
+ int hgEnter(const RTCList<RTCString> &formats, VBOXDNDACTIONLIST dndListActionsAllowed);
+ int hgLeave(void);
+ int hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault);
+ int hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault);
+ int hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta);
+
+ /* X11 helpers. */
+ int mouseCursorFakeMove(void);
+ int mouseCursorMove(int iPosX, int iPosY);
+ void mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress);
+ int proxyWinShow(int *piRootX = NULL, int *piRootY = NULL) const;
+ int proxyWinHide(void);
+
+ /* X11 window helpers. */
+ char *wndX11GetNameA(Window wndThis) const;
+
+ /* Xdnd protocol helpers. */
+ void wndXDnDClearActionList(Window wndThis) const;
+ void wndXDnDClearFormatList(Window wndThis) const;
+ int wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const;
+ int wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const;
+ int wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const;
+ int wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const;
+
+ /* Atom / HGCM formatting helpers. */
+ int appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const;
+ int appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const;
+ static Atom toAtomAction(VBOXDNDACTION dndAction);
+ static int toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms);
+ static uint32_t toHGCMAction(Atom atom);
+ static uint32_t toHGCMActions(const VBoxDnDAtomList &actionsList);
+
+protected:
+
+ /** The instance's own DnD context. */
+ VBGLR3GUESTDNDCMDCTX m_dndCtx;
+ /** Pointer to service instance. */
+ DragAndDropService *m_pParent;
+ /** Pointer to X display operating on. */
+ Display *m_pDisplay;
+ /** X screen ID to operate on. */
+ int m_screenID;
+ /** Pointer to X screen operating on. */
+ Screen *m_pScreen;
+ /** Root window handle. */
+ Window m_wndRoot;
+ /** Proxy window. */
+ VBoxDnDProxyWnd m_wndProxy;
+ /** Current source/target window handle. */
+ Window m_wndCur;
+ /** The XDnD protocol version the current source/target window is using.
+ * Set to 0 if not available / not set yet. */
+ uint8_t m_uXdndVer;
+ /** Last mouse X position (in pixels, absolute to root window).
+ * Set to -1 if not set yet. */
+ int m_lastMouseX;
+ /** Last mouse Y position (in pixels, absolute to root window).
+ * Set to -1 if not set yet. */
+ int m_lastMouseY;
+ /** List of default (Atom) formats required for X11 Xdnd handling.
+ * This list will be included by \a m_lstAtomFormats. */
+ VBoxDnDAtomList m_lstAtomFormatsX11;
+ /** List of (Atom) formats the current source/target window supports. */
+ VBoxDnDAtomList m_lstAtomFormats;
+ /** List of (Atom) actions the current source/target window supports. */
+ VBoxDnDAtomList m_lstAtomActions;
+ /** Buffer for answering the target window's selection request. */
+ void *m_pvSelReqData;
+ /** Size (in bytes) of selection request data buffer. */
+ uint32_t m_cbSelReqData;
+ /** Current operation mode. */
+ volatile uint32_t m_enmMode;
+ /** Current state of operation mode. */
+ volatile uint32_t m_enmState;
+ /** The instance's own X event queue. */
+ RTCMTList<WrappedXEvent> m_eventQueueList;
+ /** Critical section for providing serialized access to list event queue's contents. */
+ RTCRITSECT m_eventQueueCS;
+ /** Event for notifying this instance in case of a new event. */
+ RTSEMEVENT m_eventQueueEvent;
+ /** Critical section for data access. */
+ RTCRITSECT m_dataCS;
+ /** List of allowed formats. */
+ RTCList<RTCString> m_lstAllowedFormats;
+ /** Number of failed attempts by the host
+ * to query for an active drag and drop operation on the guest. */
+ uint16_t m_cFailedPendingAttempts;
+};
+
+/**
+ * Service class which implements drag'n drop.
+ */
+class DragAndDropService
+{
+public:
+ DragAndDropService(void)
+ : m_pDisplay(NULL)
+ , m_hHGCMThread(NIL_RTTHREAD)
+ , m_hX11Thread(NIL_RTTHREAD)
+ , m_hEventSem(NIL_RTSEMEVENT)
+ , m_pCurDnD(NULL)
+ , m_fStop(false)
+ {
+ RT_ZERO(m_dndCtx);
+ }
+
+ int init(void);
+ int worker(bool volatile *pfShutdown);
+ void reset(void);
+ void stop(void);
+ int term(void);
+
+private:
+
+ static DECLCALLBACK(int) hgcmEventThread(RTTHREAD hThread, void *pvUser);
+ static DECLCALLBACK(int) x11EventThread(RTTHREAD hThread, void *pvUser);
+
+ /* Private member vars */
+ Display *m_pDisplay;
+ /** Our (thread-safe) event queue with mixed events (DnD HGCM / X11). */
+ RTCMTList<DNDEVENT> m_eventQueue;
+ /** Critical section for providing serialized access to list
+ * event queue's contents. */
+ RTCRITSECT m_eventQueueCS;
+ /** Thread handle for the HGCM message pumping thread. */
+ RTTHREAD m_hHGCMThread;
+ /** Thread handle for the X11 message pumping thread. */
+ RTTHREAD m_hX11Thread;
+ /** This service' DnD command context. */
+ VBGLR3GUESTDNDCMDCTX m_dndCtx;
+ /** Event semaphore for new DnD events. */
+ RTSEMEVENT m_hEventSem;
+ /** Pointer to the allocated DnD instance.
+ Currently we only support and handle one instance at a time. */
+ DragInstance *m_pCurDnD;
+ /** Stop indicator flag to signal the thread that it should shut down. */
+ bool m_fStop;
+
+ friend class DragInstance;
+} g_Svc;
+
+/*********************************************************************************************************************************
+ * DragInstanc implementation. *
+ ********************************************************************************************************************************/
+
+DragInstance::DragInstance(Display *pDisplay, DragAndDropService *pParent)
+ : m_pParent(pParent)
+ , m_pDisplay(pDisplay)
+ , m_pScreen(0)
+ , m_wndRoot(0)
+ , m_wndCur(0)
+ , m_uXdndVer(0)
+ , m_pvSelReqData(NULL)
+ , m_cbSelReqData(0)
+ , m_enmMode(Unknown)
+ , m_enmState(Uninitialized)
+{
+ /* Append default targets we support.
+ * Note: The order is sorted by preference; be careful when changing this. */
+ m_lstAtomFormatsX11.append(xAtom(XA_TARGETS));
+ m_lstAtomFormatsX11.append(xAtom(XA_MULTIPLE));
+ /** @todo Support INC (incremental transfers). */
+}
+
+/**
+ * Stops this drag instance.
+ */
+void DragInstance::stop(void)
+{
+ LogFlowFuncEnter();
+
+ int rc2 = VbglR3DnDDisconnect(&m_dndCtx);
+ AssertRC(rc2);
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Terminates (destroys) this drag instance.
+ *
+ * @return VBox status code.
+ */
+int DragInstance::term(void)
+{
+ LogFlowFuncEnter();
+
+ if (m_wndProxy.hWnd != 0)
+ XDestroyWindow(m_pDisplay, m_wndProxy.hWnd);
+
+ int rc = VbglR3DnDDisconnect(&m_dndCtx);
+ AssertRCReturn(rc, rc);
+
+ if (m_pvSelReqData)
+ RTMemFree(m_pvSelReqData);
+
+ rc = RTSemEventDestroy(m_eventQueueEvent);
+ AssertRCReturn(rc, rc);
+
+ rc = RTCritSectDelete(&m_eventQueueCS);
+ AssertRCReturn(rc, rc);
+
+ rc = RTCritSectDelete(&m_dataCS);
+ AssertRCReturn(rc, rc);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Resets this drag instance.
+ */
+void DragInstance::reset(void)
+{
+ LogFlowFuncEnter();
+
+ /* Hide the proxy win. */
+ proxyWinHide();
+
+ int rc2 = RTCritSectEnter(&m_dataCS);
+ if (RT_SUCCESS(rc2))
+ {
+ /* If we are currently the Xdnd selection owner, clear that. */
+ Window pWnd = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ if (pWnd == m_wndProxy.hWnd)
+ XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), None, CurrentTime);
+
+ /* Clear any other DnD specific data on the proxy window. */
+ wndXDnDClearFormatList(m_wndProxy.hWnd);
+ wndXDnDClearActionList(m_wndProxy.hWnd);
+
+ m_lstAtomActions.clear();
+
+ /* First, clear the formats list and apply the X11-specific default formats,
+ * required for making Xdnd to work. */
+ m_lstAtomFormats.clear();
+ m_lstAtomFormats.append(m_lstAtomFormatsX11);
+
+ m_wndCur = 0;
+ m_uXdndVer = 0;
+ m_lastMouseX = -1;
+ m_lastMouseY = -1;
+ m_enmState = Initialized;
+ m_enmMode = Unknown;
+ m_cFailedPendingAttempts = 0;
+
+ /* Reset the selection request buffer. */
+ if (m_pvSelReqData)
+ {
+ RTMemFree(m_pvSelReqData);
+ m_pvSelReqData = NULL;
+
+ Assert(m_cbSelReqData);
+ m_cbSelReqData = 0;
+ }
+
+ rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ m_eventQueueList.clear();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ RTCritSectLeave(&m_dataCS);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/**
+ * Initializes this drag instance.
+ *
+ * @return IPRT status code.
+ * @param uScreenID X' screen ID to use.
+ */
+int DragInstance::init(uint32_t uScreenID)
+{
+ int rc = VbglR3DnDConnect(&m_dndCtx);
+ /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if (g_cVerbosity)
+ {
+ RTCString strBody = RTCStringFmt("Connected (screen %RU32, verbosity %u)", uScreenID, g_cVerbosity);
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, strBody.c_str());
+ }
+
+ do
+ {
+ rc = RTSemEventCreate(&m_eventQueueEvent);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTCritSectInit(&m_eventQueueCS);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTCritSectInit(&m_dataCS);
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * Enough screens configured in the x11 server?
+ */
+ if ((int)uScreenID > ScreenCount(m_pDisplay))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+#if 0
+ /* Get the screen number from the x11 server. */
+ pDrag->screen = ScreenOfDisplay(m_pDisplay, uScreenID);
+ if (!pDrag->screen)
+ {
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+#endif
+ m_screenID = uScreenID;
+
+ /* Now query the corresponding root window of this screen. */
+ m_wndRoot = RootWindow(m_pDisplay, m_screenID);
+ if (!m_wndRoot)
+ {
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+
+ /*
+ * Create an invisible window which will act as proxy for the DnD
+ * operation. This window will be used for both the GH and HG
+ * direction.
+ */
+ XSetWindowAttributes attr;
+ RT_ZERO(attr);
+ attr.event_mask = EnterWindowMask | LeaveWindowMask
+ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
+ attr.override_redirect = True;
+ attr.do_not_propagate_mask = NoEventMask;
+
+ if (g_cVerbosity >= 3)
+ {
+ attr.background_pixel = XWhitePixel(m_pDisplay, m_screenID);
+ attr.border_pixel = XBlackPixel(m_pDisplay, m_screenID);
+ m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */,
+ 100, 100, /* Position */
+ 100, 100, /* Width + height */
+ 2, /* Border width */
+ CopyFromParent, /* Depth */
+ InputOutput, /* Class */
+ CopyFromParent, /* Visual */
+ CWBackPixel
+ | CWBorderPixel
+ | CWOverrideRedirect
+ | CWDontPropagate, /* Value mask */
+ &attr); /* Attributes for value mask */
+ }
+
+ m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */,
+ 0, 0, /* Position */
+ 1, 1, /* Width + height */
+ 0, /* Border width */
+ CopyFromParent, /* Depth */
+ InputOnly, /* Class */
+ CopyFromParent, /* Visual */
+ CWOverrideRedirect | CWDontPropagate, /* Value mask */
+ &attr); /* Attributes for value mask */
+
+ if (!m_wndProxy.hWnd)
+ {
+ VBClLogError("Error creating proxy window\n");
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+
+ rc = m_wndProxy.init(m_pDisplay);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error initializing proxy window, rc=%Rrc\n", rc);
+ break;
+ }
+
+ if (g_cVerbosity >= 3) /* Make debug window visible. */
+ {
+ XFlush(m_pDisplay);
+ XMapWindow(m_pDisplay, m_wndProxy.hWnd);
+ XRaiseWindow(m_pDisplay, m_wndProxy.hWnd);
+ XFlush(m_pDisplay);
+ }
+
+ VBClLogInfo("Proxy window=%#x (debug mode: %RTbool), root window=%#x ...\n",
+ m_wndProxy.hWnd, RT_BOOL(g_cVerbosity >= 3), m_wndRoot);
+
+ /* Set the window's name for easier lookup. */
+ XStoreName(m_pDisplay, m_wndProxy.hWnd, "VBoxClientWndDnD");
+
+ /* Make the new window Xdnd aware. */
+ Atom atmVer = VBOX_XDND_VERSION;
+ XChangeProperty(m_pDisplay, m_wndProxy.hWnd, xAtom(XA_XdndAware), XA_ATOM, 32, PropModeReplace,
+ reinterpret_cast<unsigned char*>(&atmVer), 1);
+ } while (0);
+
+ if (RT_SUCCESS(rc))
+ {
+ reset();
+ }
+ else
+ VBClLogError("Initializing drag instance for screen %RU32 failed with rc=%Rrc\n", uScreenID, rc);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Callback handler for a generic client message from a window.
+ *
+ * @return IPRT status code.
+ * @param e X11 event to handle.
+ */
+int DragInstance::onX11ClientMessage(const XEvent &e)
+{
+ AssertReturn(e.type == ClientMessage, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("Event wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str()));
+
+ int rc = VINF_SUCCESS;
+
+ char *pszWndCurName = wndX11GetNameA(m_wndCur);
+ AssertPtrReturn(pszWndCurName, VERR_NO_MEMORY);
+
+ switch (m_enmMode)
+ {
+ case HG:
+ {
+ /*
+ * Client messages are used to inform us about the status of a XdndAware
+ * window, in response of some events we send to them.
+ */
+
+ /* The target window informs us of the current Xdnd status. */
+ if (e.xclient.message_type == xAtom(XA_XdndStatus))
+ {
+ Window wndTgt = static_cast<Window>(e.xclient.data.l[XdndStatusWindow]);
+
+ char *pszWndTgtName = wndX11GetNameA(wndTgt);
+ AssertPtrBreakStmt(pszWndTgtName, VERR_NO_MEMORY);
+
+ /* Does the target accept the drop? */
+ bool const fAcceptDrop = RT_BOOL(e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_ACCEPT);
+ /* Does the target want XdndPosition messages? */
+ bool const fWantsPosition = RT_BOOL(e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_WANTS_POS);
+
+ /*
+ * The XdndStatus message tell us if the window will accept the DnD
+ * event and with which action. We immediately send this info down to
+ * the host as a response of a previous DnD message.
+ */
+ RTCString strActions = xAtomToString(e.xclient.data.l[XdndStatusAction]);
+
+ VBClLogInfo("Target window %#x ('%s')\n", wndTgt, pszWndTgtName);
+ VBClLogInfo(" - %s accept data (actions '%s')\n", fAcceptDrop ? "does" : "does not", strActions.c_str());
+ VBClLogInfo(" - %s want position messages\n", fWantsPosition ? "does" : "does not");
+
+ uint16_t const x = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]);
+ uint16_t const y = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]);
+ uint16_t const cx = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]);
+ uint16_t const cy = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]);
+
+ if (cx && cy)
+ {
+ VBClLogInfo("Target window %#x ('%s') reported dead area at %RU16,%RU16 (%RU16 x %RU16)\n",
+ wndTgt, pszWndTgtName, x, y, cx, cy);
+ /** @todo Save dead area and don't send XdndPosition messages anymore into it. */
+ }
+
+ if (m_wndCur == wndTgt)
+ {
+ VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE; /* Default is ignoring. */
+ /** @todo Compare this with the allowed actions. */
+ if (fAcceptDrop)
+ dndAction = toHGCMAction(static_cast<Atom>(e.xclient.data.l[XdndStatusAction]));
+
+ rc = VbglR3DnDHGSendAckOp(&m_dndCtx, dndAction);
+ }
+ else
+ VBClLogInfo("Target window %#x ('%s') is not our current window, skipping\n", wndTgt, pszWndTgtName);
+
+ RTStrFree(pszWndTgtName);
+ }
+ /* The target window informs us that it finished the Xdnd operation and that we may free all data. */
+ else if (e.xclient.message_type == xAtom(XA_XdndFinished))
+ {
+ Window wndTarget = static_cast<Window>(e.xclient.data.l[XdndFinishedWindow]);
+
+ char *pszWndTgtName = wndX11GetNameA(wndTarget);
+ AssertPtrBreakStmt(pszWndTgtName, VERR_NO_MEMORY);
+
+ if (m_uXdndVer >= 5)
+ {
+ const bool fSucceeded = e.xclient.data.l[XdndFinishedFlags] & VBOX_XDND_FINISHED_FLAG_SUCCEEDED;
+ #if 0 /** @todo Returns garbage -- investigate this! */
+ //const char *pcszAction = fSucceeded ? xAtomToString(e.xclient.data.l[XdndFinishedAction]).c_str() : NULL;
+ #endif
+ VBClLogInfo("Target window %#x ('%s') has %s the data\n",
+ wndTarget, pszWndTgtName, fSucceeded ? "accepted" : "rejected");
+ }
+ else /* Xdnd < version 5 did not have the XdndFinishedFlags / XdndFinishedAction properties. */
+ VBClLogInfo("Target window %#x ('%s') has accepted the data\n", wndTarget, pszWndTgtName);
+
+ RTStrFree(pszWndTgtName);
+
+ reset();
+ }
+ else
+ {
+ LogFlowThisFunc(("Unhandled client message '%s'\n", xAtomToString(e.xclient.message_type).c_str()));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ break;
+ }
+
+ case Unknown: /* Mode not set (yet). */
+ RT_FALL_THROUGH();
+ case GH:
+ {
+ /*
+ * This message marks the beginning of a new drag and drop
+ * operation on the guest.
+ */
+ if (e.xclient.message_type == xAtom(XA_XdndEnter))
+ {
+ /*
+ * Get the window which currently has the XA_XdndSelection
+ * bit set.
+ */
+ Window wndSel = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ char *pszWndSelName = wndX11GetNameA(wndSel);
+ AssertPtrBreakStmt(pszWndSelName, VERR_NO_MEMORY);
+
+ mouseButtonSet(m_wndProxy.hWnd, -1, -1, 1, true /* fPress */);
+
+ /*
+ * Update our state and the window handle to process.
+ */
+ rc = RTCritSectEnter(&m_dataCS);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t const uXdndVer = (uint8_t)e.xclient.data.l[XdndEnterFlags] >> XdndEnterVersionRShift;
+
+ VBClLogInfo("Entered new source window %#x ('%s'), supports Xdnd version %u\n", wndSel, pszWndSelName, uXdndVer);
+#ifdef DEBUG
+ XWindowAttributes xwa;
+ XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
+ LogFlowThisFunc(("wndCur=%#x, x=%d, y=%d, width=%d, height=%d\n", m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
+#endif
+ /*
+ * Retrieve supported formats.
+ */
+
+ /* Check if the MIME types are in the message itself or if we need
+ * to fetch the XdndTypeList property from the window. */
+ bool fMoreTypes = e.xclient.data.l[XdndEnterFlags] & XdndEnterMoreTypesFlag;
+ if (!fMoreTypes)
+ {
+ /* Only up to 3 format types supported. */
+ /* Start with index 2 (first item). */
+ for (int i = 2; i < 5; i++)
+ {
+ LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(e.xclient.data.l[i]).c_str()));
+ m_lstAtomFormats.append(e.xclient.data.l[i]);
+ }
+ }
+ else
+ {
+ /* More than 3 format types supported. */
+ rc = wndXDnDGetFormatList(wndSel, m_lstAtomFormats);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error retrieving supported formats, rc=%Rrc\n", rc);
+ break;
+ }
+
+ /*
+ * Retrieve supported actions.
+ */
+ if (uXdndVer >= 2) /* More than one action allowed since protocol version 2. */
+ {
+ rc = wndXDnDGetActionList(wndSel, m_lstAtomActions);
+ }
+ else /* Only "copy" action allowed on legacy applications. */
+ m_lstAtomActions.append(XA_XdndActionCopy);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Error retrieving supported actions, rc=%Rrc\n", rc);
+ break;
+ }
+
+ VBClLogInfo("Source window %#x ('%s')\n", wndSel, pszWndSelName);
+ VBClLogInfo(" - supports the formats ");
+ for (size_t i = 0; i < m_lstAtomFormats.size(); i++)
+ {
+ if (i > 0)
+ VBClLogInfo(", ");
+ VBClLogInfo("%s", gX11->xAtomToString(m_lstAtomFormats[i]).c_str());
+ }
+ VBClLogInfo("\n");
+ VBClLogInfo(" - supports the actions ");
+ for (size_t i = 0; i < m_lstAtomActions.size(); i++)
+ {
+ if (i > 0)
+ VBClLogInfo(", ");
+ VBClLogInfo("%s", gX11->xAtomToString(m_lstAtomActions[i]).c_str());
+ }
+ VBClLogInfo("\n");
+
+ AssertBreakStmt(wndSel == (Window)e.xclient.data.l[XdndEnterWindow],
+ rc = VERR_INVALID_PARAMETER); /* Source window. */
+
+ m_wndCur = wndSel;
+ m_uXdndVer = uXdndVer;
+ m_enmMode = GH;
+ m_enmState = Dragging;
+
+ RTCritSectLeave(&m_dataCS);
+ }
+
+ RTStrFree(pszWndSelName);
+ }
+ else if ( e.xclient.message_type == xAtom(XA_XdndPosition)
+ && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndPositionWindow]))
+ {
+ if (m_enmState != Dragging) /* Wrong mode? Bail out. */
+ {
+ reset();
+ break;
+ }
+#ifdef LOG_ENABLED
+ int32_t iPos = e.xclient.data.l[XdndPositionXY];
+ Atom atmAction = m_uXdndVer >= 2 /* Actions other than "copy" or only supported since protocol version 2. */
+ ? e.xclient.data.l[XdndPositionAction] : xAtom(XA_XdndActionCopy);
+ LogFlowThisFunc(("XA_XdndPosition: wndProxy=%#x, wndCur=%#x, x=%RI32, y=%RI32, strAction=%s\n",
+ m_wndProxy.hWnd, m_wndCur, RT_HIWORD(iPos), RT_LOWORD(iPos),
+ xAtomToString(atmAction).c_str()));
+#endif
+ bool fAcceptDrop = true;
+
+ /* Reply with a XdndStatus message to tell the source whether
+ * the data can be dropped or not. */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = e.xclient.data.l[XdndPositionWindow];
+ m.message_type = xAtom(XA_XdndStatus);
+ m.format = 32;
+ m.data.l[XdndStatusWindow] = m_wndProxy.hWnd;
+ m.data.l[XdndStatusFlags] = fAcceptDrop ? VBOX_XDND_STATUS_FLAG_ACCEPT : VBOX_XDND_STATUS_FLAG_NONE; /* Whether to accept the drop or not. */
+
+ /* We don't want any new XA_XdndPosition messages while being
+ * in our proxy window. */
+ m.data.l[XdndStatusNoMsgXY] = RT_MAKE_U32(m_wndProxy.iY, m_wndProxy.iX);
+ m.data.l[XdndStatusNoMsgWH] = RT_MAKE_U32(m_wndProxy.iHeight, m_wndProxy.iWidth);
+
+ /** @todo Handle default action! */
+ m.data.l[XdndStatusAction] = fAcceptDrop ? toAtomAction(VBOX_DND_ACTION_COPY) : None;
+
+ int xRc = XSendEvent(m_pDisplay, e.xclient.data.l[XdndPositionWindow],
+ False /* Propagate */, NoEventMask, reinterpret_cast<XEvent *>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending position status event to current window %#x ('%s'): %s\n",
+ m_wndCur, pszWndCurName, gX11->xErrorToString(xRc).c_str());
+ }
+ else if ( e.xclient.message_type == xAtom(XA_XdndLeave)
+ && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndLeaveWindow]))
+ {
+ LogFlowThisFunc(("XA_XdndLeave\n"));
+ VBClLogInfo("Guest to host transfer canceled by the guest source window\n");
+
+ /* Start over. */
+ reset();
+ }
+ else if ( e.xclient.message_type == xAtom(XA_XdndDrop)
+ && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndDropWindow]))
+ {
+ LogFlowThisFunc(("XA_XdndDrop\n"));
+
+ if (m_enmState != Dropped) /* Wrong mode? Bail out. */
+ {
+ /* Can occur when dragging from guest->host, but then back in to the guest again. */
+ VBClLogInfo("Could not drop on own proxy window\n"); /* Not fatal. */
+
+ /* Let the source know. */
+ rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE);
+
+ /* Start over. */
+ reset();
+ break;
+ }
+
+ m_eventQueueList.append(e);
+ rc = RTSemEventSignal(m_eventQueueEvent);
+ }
+ else /* Unhandled event, abort. */
+ {
+ VBClLogInfo("Unhandled event from wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str());
+
+ /* Let the source know. */
+ rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE);
+
+ /* Start over. */
+ reset();
+ }
+ break;
+ }
+
+ default:
+ {
+ AssertMsgFailed(("Drag and drop mode not implemented: %RU32\n", m_enmMode));
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+
+ RTStrFree(pszWndCurName);
+
+ LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
+ return rc;
+}
+
+int DragInstance::onX11MotionNotify(const XEvent &e)
+{
+ RT_NOREF1(e);
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Callback handler for being notified if some other window now
+ * is the owner of the current selection.
+ *
+ * @return IPRT status code.
+ * @param e X11 event to handle.
+ *
+ * @remark
+ */
+int DragInstance::onX11SelectionClear(const XEvent &e)
+{
+ RT_NOREF1(e);
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Callback handler for a XDnD selection notify from a window. This is needed
+ * to let the us know if a certain window has drag'n drop data to share with us,
+ * e.g. our proxy window.
+ *
+ * @return IPRT status code.
+ * @param e X11 event to handle.
+ */
+int DragInstance::onX11SelectionNotify(const XEvent &e)
+{
+ AssertReturn(e.type == SelectionNotify, VERR_INVALID_PARAMETER);
+
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ int rc;
+
+ switch (m_enmMode)
+ {
+ case GH:
+ {
+ if (m_enmState == Dropped)
+ {
+ m_eventQueueList.append(e);
+ rc = RTSemEventSignal(m_eventQueueEvent);
+ }
+ else
+ rc = VERR_WRONG_ORDER;
+ break;
+ }
+
+ default:
+ {
+ LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n",
+ e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str()));
+ rc = VERR_INVALID_STATE;
+ break;
+ }
+ }
+
+ LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Callback handler for a XDnD selection request from a window. This is needed
+ * to retrieve the data required to complete the actual drag'n drop operation.
+ *
+ * @returns IPRT status code.
+ * @param evReq X11 event to handle.
+ */
+int DragInstance::onX11SelectionRequest(const XEvent &evReq)
+{
+ AssertReturn(evReq.type == SelectionRequest, VERR_INVALID_PARAMETER);
+
+ const XSelectionRequestEvent *pEvReq = &evReq.xselectionrequest;
+
+ char *pszWndSrcName = wndX11GetNameA(pEvReq->owner);
+ AssertPtrReturn(pszWndSrcName, VERR_INVALID_POINTER);
+ char *pszWndTgtName = wndX11GetNameA(pEvReq->requestor);
+ AssertPtrReturn(pszWndTgtName, VERR_INVALID_POINTER);
+
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("Event owner=%#x ('%s'), requestor=%#x ('%s'), selection=%s, target=%s, prop=%s, time=%u\n",
+ pEvReq->owner, pszWndSrcName,
+ pEvReq->requestor, pszWndTgtName,
+ xAtomToString(pEvReq->selection).c_str(),
+ xAtomToString(pEvReq->target).c_str(),
+ xAtomToString(pEvReq->property).c_str(),
+ pEvReq->time));
+
+ VBClLogInfo("Window '%s' is asking '%s' for '%s' / '%s'\n",
+ pszWndTgtName, pszWndSrcName, xAtomToString(pEvReq->selection).c_str(), xAtomToString(pEvReq->property).c_str());
+
+ RTStrFree(pszWndSrcName);
+ /* Note: pszWndTgtName will be free'd below. */
+
+ int rc;
+
+ switch (m_enmMode)
+ {
+ case HG:
+ {
+ rc = VINF_SUCCESS;
+
+ /*
+ * Start by creating a refusal selection notify message.
+ * That way we only need to care for the success case.
+ */
+
+ XEvent evResp;
+ RT_ZERO(evResp);
+
+ XSelectionEvent *pEvResp = &evResp.xselection;
+
+ pEvResp->type = SelectionNotify;
+ pEvResp->display = pEvReq->display;
+ pEvResp->requestor = pEvReq->requestor;
+ pEvResp->selection = pEvReq->selection;
+ pEvResp->target = pEvReq->target;
+ pEvResp->property = None; /* "None" means refusal. */
+ pEvResp->time = pEvReq->time;
+
+ if (g_cVerbosity)
+ {
+ VBClLogVerbose(1, "Supported formats by VBoxClient:\n");
+ for (size_t i = 0; i < m_lstAtomFormats.size(); i++)
+ VBClLogVerbose(1, "\t%s\n", xAtomToString(m_lstAtomFormats.at(i)).c_str());
+ }
+
+ /* Is the requestor asking for the possible MIME types? */
+ if (pEvReq->target == xAtom(XA_TARGETS))
+ {
+ VBClLogInfo("Target window %#x ('%s') asking for target list\n", pEvReq->requestor, pszWndTgtName);
+
+ /* If so, set the window property with the formats on the requestor
+ * window. */
+ rc = wndXDnDSetFormatList(pEvReq->requestor, pEvReq->property, m_lstAtomFormats);
+ if (RT_SUCCESS(rc))
+ pEvResp->property = pEvReq->property;
+ }
+ /* Is the requestor asking for a specific MIME type (we support)? */
+ else if (m_lstAtomFormats.contains(pEvReq->target))
+ {
+ VBClLogInfo("Target window %#x ('%s') is asking for data as '%s'\n",
+ pEvReq->requestor, pszWndTgtName, xAtomToString(pEvReq->target).c_str());
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES
+# error "Implement me!"
+#else
+ /* Did we not drop our stuff to the guest yet? Bail out. */
+ if (m_enmState != Dropped)
+ {
+ VBClLogError("Data not dropped by the host on the guest yet (client state %RU32, mode %RU32), refusing selection request by guest\n",
+ m_enmState, m_enmMode);
+ }
+ /* Did we not store the requestor's initial selection request yet? Then do so now. */
+ else
+ {
+#endif /* VBOX_WITH_DRAG_AND_DROP_PROMISES */
+ /* Get the data format the requestor wants from us. */
+ VBClLogInfo("Target window %#x ('%s') requested data from host as '%s', rc=%Rrc\n",
+ pEvReq->requestor, pszWndTgtName, xAtomToString(pEvReq->target).c_str(), rc);
+
+ /* Make a copy of the MIME data to be passed back. The X server will be become
+ * the new owner of that data, so no deletion needed. */
+ /** @todo Do we need to do some more conversion here? XConvertSelection? */
+ AssertMsgBreakStmt(m_pvSelReqData != NULL, ("Selection request data is NULL\n"), rc = VERR_INVALID_PARAMETER);
+ AssertMsgBreakStmt(m_cbSelReqData > 0, ("Selection request data size is 0\n"), rc = VERR_INVALID_PARAMETER);
+
+ void const *pvData = RTMemDup(m_pvSelReqData, m_cbSelReqData);
+ AssertMsgBreakStmt(pvData != NULL, ("Duplicating selection request failed\n"), rc = VERR_NO_MEMORY);
+ uint32_t const cbData = m_cbSelReqData;
+
+ /* Always return the requested property. */
+ evResp.xselection.property = pEvReq->property;
+
+ /* Note: Always seems to return BadRequest. Seems fine. */
+ int xRc = XChangeProperty(pEvResp->display, pEvResp->requestor, pEvResp->property,
+ pEvResp->target, 8, PropModeReplace,
+ reinterpret_cast<const unsigned char*>(pvData), cbData);
+
+ LogFlowFunc(("Changing property '%s' (of type '%s') of window %#x ('%s'): %s\n",
+ xAtomToString(pEvReq->property).c_str(),
+ xAtomToString(pEvReq->target).c_str(),
+ pEvReq->requestor, pszWndTgtName,
+ gX11->xErrorToString(xRc).c_str()));
+ RT_NOREF(xRc);
+#ifndef VBOX_WITH_DRAG_AND_DROP_PROMISES
+ }
+#endif
+ }
+ /* Anything else. */
+ else
+ {
+ VBClLogError("Refusing unknown command/format '%s' of wnd=%#x ('%s')\n",
+ xAtomToString(pEvReq->target).c_str(), pEvReq->requestor, pszWndTgtName);
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ VBClLogVerbose(1, "Offering type '%s', property '%s' to window %#x ('%s') ...\n",
+ xAtomToString(pEvReq->target).c_str(),
+ xAtomToString(pEvReq->property).c_str(), pEvReq->requestor, pszWndTgtName);
+
+ int xRc = XSendEvent(pEvReq->display, pEvReq->requestor, True /* Propagate */, 0, &evResp);
+ if (xRc == 0)
+ VBClLogError("Error sending SelectionNotify(1) event to window %#x ('%s'): %s\n",
+ pEvReq->requestor, pszWndTgtName, gX11->xErrorToString(xRc).c_str());
+
+ XFlush(pEvReq->display);
+ break;
+ }
+
+ default:
+ rc = VERR_INVALID_STATE;
+ break;
+ }
+
+ RTStrFree(pszWndTgtName);
+ pszWndTgtName = NULL;
+
+ LogFlowThisFunc(("Returning rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Handles X11 events, called by x11EventThread.
+ *
+ * @returns IPRT status code.
+ * @param e X11 event to handle.
+ */
+int DragInstance::onX11Event(const XEvent &e)
+{
+ int rc;
+
+ LogFlowThisFunc(("X11 event, type=%d\n", e.type));
+ switch (e.type)
+ {
+ /*
+ * This can happen if a guest->host drag operation
+ * goes back from the host to the guest. This is not what
+ * we want and thus resetting everything.
+ */
+ case ButtonPress:
+ RT_FALL_THROUGH();
+ case ButtonRelease:
+ {
+ VBClLogInfo("Mouse button %s\n", e.type == ButtonPress ? "pressed" : "released");
+
+ reset();
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ case ClientMessage:
+ rc = onX11ClientMessage(e);
+ break;
+
+ case SelectionClear:
+ rc = onX11SelectionClear(e);
+ break;
+
+ case SelectionNotify:
+ rc = onX11SelectionNotify(e);
+ break;
+
+ case SelectionRequest:
+ rc = onX11SelectionRequest(e);
+ break;
+
+ case MotionNotify:
+ rc = onX11MotionNotify(e);
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ LogFlowThisFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+int DragInstance::waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS /* = 30000 */)
+{
+ const uint64_t uiStart = RTTimeMilliTS();
+ volatile uint32_t enmCurState;
+
+ int rc = VERR_TIMEOUT;
+
+ LogFlowFunc(("enmState=%RU32, uTimeoutMS=%RU32\n", enmState, uTimeoutMS));
+
+ do
+ {
+ enmCurState = ASMAtomicReadU32(&m_enmState);
+ if (enmCurState == enmState)
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ while (RTTimeMilliTS() - uiStart < uTimeoutMS);
+
+ LogFlowThisFunc(("Returning %Rrc\n", rc));
+ return rc;
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+/**
+ * Waits for an X11 event of a specific type.
+ *
+ * @returns IPRT status code.
+ * @param evX Reference where to store the event into.
+ * @param iType Event type to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait for the event.
+ */
+bool DragInstance::waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS /* = 100 */)
+{
+ LogFlowThisFunc(("iType=%d, uTimeoutMS=%RU32, cEventQueue=%zu\n", iType, uTimeoutMS, m_eventQueueList.size()));
+
+ bool fFound = false;
+ uint64_t const tsStartMs = RTTimeMilliTS();
+
+ do
+ {
+ /* Check if there is a client message in the queue. */
+ for (size_t i = 0; i < m_eventQueueList.size(); i++)
+ {
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ XEvent e = m_eventQueueList.at(i).m_Event;
+
+ fFound = e.type == iType;
+ if (fFound)
+ {
+ m_eventQueueList.removeAt(i);
+ evX = e;
+ }
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+
+ if (fFound)
+ break;
+ }
+ }
+
+ if (fFound)
+ break;
+
+ int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ {
+ LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
+ break;
+ }
+ }
+ while (RTTimeMilliTS() - tsStartMs < uTimeoutMS);
+
+ LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - tsStartMs));
+ return fFound;
+}
+
+/**
+ * Waits for an X11 client message of a specific type.
+ *
+ * @returns IPRT status code.
+ * @param evMsg Reference where to store the event into.
+ * @param aType Event type to wait for.
+ * @param uTimeoutMS Timeout (in ms) to wait for the event.
+ */
+bool DragInstance::waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType,
+ RTMSINTERVAL uTimeoutMS /* = 100 */)
+{
+ LogFlowThisFunc(("aType=%s, uTimeoutMS=%RU32, cEventQueue=%zu\n",
+ xAtomToString(aType).c_str(), uTimeoutMS, m_eventQueueList.size()));
+
+ bool fFound = false;
+ const uint64_t uiStart = RTTimeMilliTS();
+ do
+ {
+ /* Check if there is a client message in the queue. */
+ for (size_t i = 0; i < m_eventQueueList.size(); i++)
+ {
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ XEvent e = m_eventQueueList.at(i).m_Event;
+ if ( e.type == ClientMessage
+ && e.xclient.message_type == aType)
+ {
+ m_eventQueueList.removeAt(i);
+ evMsg = e.xclient;
+
+ fFound = true;
+ }
+
+ if (e.type == ClientMessage)
+ {
+ LogFlowThisFunc(("Client message: Type=%ld (%s)\n",
+ e.xclient.message_type, xAtomToString(e.xclient.message_type).c_str()));
+ }
+ else
+ LogFlowThisFunc(("X message: Type=%d\n", e.type));
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+
+ if (fFound)
+ break;
+ }
+ }
+
+ if (fFound)
+ break;
+
+ int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */);
+ if ( RT_FAILURE(rc2)
+ && rc2 != VERR_TIMEOUT)
+ {
+ LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2));
+ break;
+ }
+ }
+ while (RTTimeMilliTS() - uiStart < uTimeoutMS);
+
+ LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - uiStart));
+ return fFound;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
+/*
+ * Host -> Guest
+ */
+
+/**
+ * Host -> Guest: Event signalling that the host's (mouse) cursor just entered the VM's (guest's) display
+ * area.
+ *
+ * @returns IPRT status code.
+ * @param lstFormats List of supported formats from the host.
+ * @param dndListActionsAllowed (ORed) List of supported actions from the host.
+ */
+int DragInstance::hgEnter(const RTCList<RTCString> &lstFormats, uint32_t dndListActionsAllowed)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ if (m_enmMode != Unknown)
+ return VERR_INVALID_STATE;
+
+ reset();
+
+#ifdef DEBUG
+ LogFlowThisFunc(("dndListActionsAllowed=0x%x, lstFormats=%zu: ", dndListActionsAllowed, lstFormats.size()));
+ for (size_t i = 0; i < lstFormats.size(); ++i)
+ LogFlow(("'%s' ", lstFormats.at(i).c_str()));
+ LogFlow(("\n"));
+#endif
+
+ int rc;
+
+ do
+ {
+ /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
+ rc = checkForSessionChange();
+ AssertRCBreak(rc);
+
+ /* Append all actual (MIME) formats we support to the list.
+ * These must come last, after the default Atoms above. */
+ rc = appendFormatsToList(lstFormats, m_lstAtomFormats);
+ AssertRCBreak(rc);
+
+ rc = wndXDnDSetFormatList(m_wndProxy.hWnd, xAtom(XA_XdndTypeList), m_lstAtomFormats);
+ AssertRCBreak(rc);
+
+ /* Announce the possible actions. */
+ VBoxDnDAtomList lstActions;
+ rc = toAtomActions(dndListActionsAllowed, lstActions);
+ AssertRCBreak(rc);
+
+ rc = wndXDnDSetActionList(m_wndProxy.hWnd, lstActions);
+ AssertRCBreak(rc);
+
+ /* Set the DnD selection owner to our window. */
+ /** @todo Don't use CurrentTime -- according to ICCCM section 2.1. */
+ XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), m_wndProxy.hWnd, CurrentTime);
+
+ if (g_cVerbosity)
+ {
+ RTCString strMsg("Enter: Host -> Guest\n");
+ strMsg += RTCStringFmt("Allowed actions: ");
+ for (size_t i = 0; i < lstActions.size(); i++)
+ {
+ if (i > 0)
+ strMsg += ", ";
+ strMsg += DnDActionToStr(toHGCMAction(lstActions.at(i)));
+ }
+ strMsg += " - Formats: ";
+ for (size_t i = 0; i < lstFormats.size(); i++)
+ {
+ if (i > 0)
+ strMsg += ", ";
+ strMsg += lstFormats.at(i);
+ }
+
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, strMsg.c_str());
+ }
+
+ m_enmMode = HG;
+ m_enmState = Dragging;
+
+ } while (0);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host's (mouse) cursor has left the VM's (guest's)
+ * display area.
+ */
+int DragInstance::hgLeave(void)
+{
+ if (g_cVerbosity)
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, "Leave: Host -> Guest");
+
+ if (m_enmMode == HG) /* Only reset if in the right operation mode. */
+ reset();
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host's (mouse) cursor has been moved within the VM's
+ * (guest's) display area.
+ *
+ * @returns IPRT status code.
+ * @param uPosX Relative X position within the guest's display area.
+ * @param uPosY Relative Y position within the guest's display area.
+ * @param dndActionDefault Default action the host wants to perform on the guest
+ * as soon as the operation successfully finishes.
+ */
+int DragInstance::hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault));
+
+ if ( m_enmMode != HG
+ || m_enmState != Dragging)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ int rc = VINF_SUCCESS;
+ int xRc = Success;
+
+ /* Move the mouse cursor within the guest. */
+ mouseCursorMove(uPosX, uPosY);
+
+ /* Search for the application window below the cursor. */
+ Window wndBelowCursor = gX11->applicationWindowBelowCursor(m_wndRoot);
+ char *pszWndBelowCursorName = wndX11GetNameA(wndBelowCursor);
+ AssertPtrReturn(pszWndBelowCursorName, VERR_NO_MEMORY);
+
+ uint8_t uBelowCursorXdndVer = 0; /* 0 means the current window is _not_ XdndAware. */
+
+ if (wndBelowCursor != None)
+ {
+ /* Temp stuff for the XGetWindowProperty call. */
+ Atom atmTmp;
+ int fmt;
+ unsigned long cItems, cbRemaining;
+ unsigned char *pcData = NULL;
+
+ /* Query the XdndAware property from the window. We are interested in
+ * the version and if it is XdndAware at all. */
+ xRc = XGetWindowProperty(m_pDisplay, wndBelowCursor, xAtom(XA_XdndAware),
+ 0, 2, False, AnyPropertyType,
+ &atmTmp, &fmt, &cItems, &cbRemaining, &pcData);
+ if (xRc != Success)
+ {
+ VBClLogError("Error getting properties of cursor window=%#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str());
+ }
+ else
+ {
+ if (pcData == NULL || fmt != 32 || cItems != 1)
+ {
+ /** @todo Do we need to deal with this? */
+ VBClLogError("Wrong window properties for window %#x: pcData=%#x, iFmt=%d, cItems=%ul\n",
+ wndBelowCursor, pcData, fmt, cItems);
+ }
+ else
+ {
+ /* Get the current window's Xdnd version. */
+ uBelowCursorXdndVer = (uint8_t)reinterpret_cast<long *>(pcData)[0];
+ }
+
+ XFree(pcData);
+ }
+ }
+
+ char *pszWndCurName = wndX11GetNameA(m_wndCur);
+ AssertPtrReturn(pszWndCurName, VERR_NO_MEMORY);
+
+ LogFlowThisFunc(("wndCursor=%x ('%s', Xdnd version %u), wndCur=%x ('%s', Xdnd version %u)\n",
+ wndBelowCursor, pszWndBelowCursorName, uBelowCursorXdndVer, m_wndCur, pszWndCurName, m_uXdndVer));
+
+ if ( wndBelowCursor != m_wndCur
+ && m_uXdndVer)
+ {
+ VBClLogInfo("Left old window %#x ('%s'), supported Xdnd version %u\n", m_wndCur, pszWndCurName, m_uXdndVer);
+
+ /* We left the current XdndAware window. Announce this to the current indow. */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = m_wndCur;
+ m.message_type = xAtom(XA_XdndLeave);
+ m.format = 32;
+ m.data.l[XdndLeaveWindow] = m_wndProxy.hWnd;
+
+ xRc = XSendEvent(m_pDisplay, m_wndCur, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending leave event to old window %#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str());
+
+ /* Reset our current window. */
+ m_wndCur = 0;
+ m_uXdndVer = 0;
+ }
+
+ /*
+ * Do we have a new Xdnd-aware window which now is under the cursor?
+ */
+ if ( wndBelowCursor != m_wndCur
+ && uBelowCursorXdndVer)
+ {
+ VBClLogInfo("Entered new window %#x ('%s'), supports Xdnd version=%u\n",
+ wndBelowCursor, pszWndBelowCursorName, uBelowCursorXdndVer);
+
+ /*
+ * We enter a new window. Announce the XdndEnter event to the new
+ * window. The first three mime types are attached to the event (the
+ * others could be requested by the XdndTypeList property from the
+ * window itself).
+ */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = wndBelowCursor;
+ m.message_type = xAtom(XA_XdndEnter);
+ m.format = 32;
+ m.data.l[XdndEnterWindow] = m_wndProxy.hWnd;
+ m.data.l[XdndEnterFlags] = RT_MAKE_U32_FROM_U8(
+ /* Bit 0 is set if the source supports more than three data types. */
+ m_lstAtomFormats.size() > 3 ? RT_BIT(0) : 0,
+ /* Reserved for future use. */
+ 0, 0,
+ /* Protocol version to use. */
+ RT_MIN(VBOX_XDND_VERSION, uBelowCursorXdndVer));
+ m.data.l[XdndEnterType1] = m_lstAtomFormats.value(0, None); /* First data type to use. */
+ m.data.l[XdndEnterType2] = m_lstAtomFormats.value(1, None); /* Second data type to use. */
+ m.data.l[XdndEnterType3] = m_lstAtomFormats.value(2, None); /* Third data type to use. */
+
+ xRc = XSendEvent(m_pDisplay, wndBelowCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending enter event to window %#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str());
+ }
+
+ if (uBelowCursorXdndVer)
+ {
+ Assert(wndBelowCursor != None);
+
+ Atom atmAction = toAtomAction(dndActionDefault);
+ LogFlowThisFunc(("strAction=%s\n", xAtomToString(atmAction).c_str()));
+
+ VBClLogInfo("Sent position event (%RU32 x %RU32) to window %#x ('%s') with actions '%s'\n",
+ uPosX, uPosY, wndBelowCursor, pszWndBelowCursorName, xAtomToString(atmAction).c_str());
+
+ /*
+ * Send a XdndPosition event with the proposed action to the guest.
+ */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = wndBelowCursor;
+ m.message_type = xAtom(XA_XdndPosition);
+ m.format = 32;
+ m.data.l[XdndPositionWindow] = m_wndProxy.hWnd; /* X window ID of source window. */
+ m.data.l[XdndPositionFlags] = 0; /* Reserved, set to 0. */
+ m.data.l[XdndPositionXY] = RT_MAKE_U32(uPosY, uPosX); /* Cursor coordinates relative to the root window. */
+ m.data.l[XdndPositionTimeStamp] = CurrentTime; /* Timestamp for retrieving data. */
+ m.data.l[XdndPositionAction] = atmAction; /* Actions requested by the user. */
+
+ xRc = XSendEvent(m_pDisplay, wndBelowCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending position event to current window %#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str());
+ }
+
+ if (uBelowCursorXdndVer == 0)
+ {
+ /* No window to process, so send a ignore ack event to the host. */
+ rc = VbglR3DnDHGSendAckOp(&m_dndCtx, VBOX_DND_ACTION_IGNORE);
+ }
+ else
+ {
+ Assert(wndBelowCursor != None);
+
+ m_wndCur = wndBelowCursor;
+ m_uXdndVer = uBelowCursorXdndVer;
+ }
+
+ RTStrFree(pszWndBelowCursorName);
+ RTStrFree(pszWndCurName);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host has dropped the data over the VM (guest) window.
+ *
+ * @returns IPRT status code.
+ * @param uPosX Relative X position within the guest's display area.
+ * @param uPosY Relative Y position within the guest's display area.
+ * @param dndActionDefault Default action the host wants to perform on the guest
+ * as soon as the operation successfully finishes.
+ */
+int DragInstance::hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault)
+{
+ RT_NOREF3(uPosX, uPosY, dndActionDefault);
+ LogFlowThisFunc(("wndCur=%RU32, wndProxy=%RU32, mode=%RU32, state=%RU32\n", m_wndCur, m_wndProxy.hWnd, m_enmMode, m_enmState));
+ LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault));
+
+ if ( m_enmMode != HG
+ || m_enmState != Dragging)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ /* Set the state accordingly. */
+ m_enmState = Dropped;
+
+ /*
+ * Ask the host to send the raw data, as we don't (yet) know which format
+ * the guest exactly expects. As blocking in a SelectionRequest message turned
+ * out to be very unreliable (e.g. with KDE apps) we request to start transferring
+ * file/directory data (if any) here.
+ */
+ char szFormat[] = { "text/uri-list" };
+
+ int rc = VbglR3DnDHGSendReqData(&m_dndCtx, szFormat);
+ VBClLogInfo("Drop event from host resulted in: %Rrc\n", rc);
+
+ if (g_cVerbosity)
+ VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, "Drop: Host -> Guest");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Host -> Guest: Event signalling that the host has finished sending drag'n drop
+ * data to the guest for further processing.
+ *
+ * @returns IPRT status code.
+ * @param pMeta Pointer to meta data from host.
+ */
+int DragInstance::hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta)
+{
+ LogFlowThisFunc(("enmMode=%RU32, enmState=%RU32\n", m_enmMode, m_enmState));
+ LogFlowThisFunc(("enmMetaType=%RU32\n", pMeta->enmType));
+
+ if ( m_enmMode != HG
+ || m_enmState != Dropped)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ void *pvData = NULL;
+ size_t cbData = 0;
+
+ int rc = VINF_SUCCESS; /* Shut up GCC. */
+
+ switch (pMeta->enmType)
+ {
+ case VBGLR3GUESTDNDMETADATATYPE_RAW:
+ {
+ AssertBreakStmt(pMeta->u.Raw.pvMeta != NULL, rc = VERR_INVALID_POINTER);
+ pvData = pMeta->u.Raw.pvMeta;
+ AssertBreakStmt(pMeta->u.Raw.cbMeta, rc = VERR_INVALID_PARAMETER);
+ cbData = pMeta->u.Raw.cbMeta;
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ case VBGLR3GUESTDNDMETADATATYPE_URI_LIST:
+ {
+ const char *pcszRootPath = DnDTransferListGetRootPathAbs(&pMeta->u.URI.Transfer);
+ AssertPtrBreakStmt(pcszRootPath, VERR_INVALID_POINTER);
+
+ VBClLogInfo("Transfer list root directory is '%s'\n", pcszRootPath);
+
+ /* Note: Use the URI format here, as X' DnD spec says so. */
+ rc = DnDTransferListGetRootsEx(&pMeta->u.URI.Transfer, DNDTRANSFERLISTFMT_URI, pcszRootPath,
+ DND_PATH_SEPARATOR_STR, (char **)&pvData, &cbData);
+ break;
+ }
+
+ default:
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * At this point all data needed (including sent files/directories) should
+ * be on the guest, so proceed working on communicating with the target window.
+ */
+ VBClLogInfo("Received %RU32 bytes of meta data from host\n", cbData);
+
+ /* Destroy any old data. */
+ if (m_pvSelReqData)
+ {
+ Assert(m_cbSelReqData);
+
+ RTMemFree(m_pvSelReqData); /** @todo RTMemRealloc? */
+ m_cbSelReqData = 0;
+ }
+
+ /** @todo Handle incremental transfers. */
+
+ /* Make a copy of the data. This data later then will be used to fill into
+ * the selection request. */
+ if (cbData)
+ {
+ m_pvSelReqData = RTMemAlloc(cbData);
+ if (!m_pvSelReqData)
+ return VERR_NO_MEMORY;
+
+ memcpy(m_pvSelReqData, pvData, cbData);
+ m_cbSelReqData = cbData;
+ }
+
+ /*
+ * Send a drop event to the current window (target).
+ * This window in turn then will raise a SelectionRequest message to our proxy window,
+ * which we will handle in our onX11SelectionRequest handler.
+ *
+ * The SelectionRequest will tell us in which format the target wants the data from the host.
+ */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = m_pDisplay;
+ m.window = m_wndCur;
+ m.message_type = xAtom(XA_XdndDrop);
+ m.format = 32;
+ m.data.l[XdndDropWindow] = m_wndProxy.hWnd; /* Source window. */
+ m.data.l[XdndDropFlags] = 0; /* Reserved for future use. */
+ m.data.l[XdndDropTimeStamp] = CurrentTime; /* Our DnD data does not rely on any timing, so just use the current time. */
+
+ int xRc = XSendEvent(m_pDisplay, m_wndCur, False /* Propagate */, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ VBClLogError("Error sending XA_XdndDrop event to window=%#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str());
+ XFlush(m_pDisplay);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Checks if the VM session has changed (can happen when restoring the VM from a saved state)
+ * and do a reconnect to the DnD HGCM service.
+ *
+ * @returns IPRT status code.
+ */
+int DragInstance::checkForSessionChange(void)
+{
+ uint64_t uSessionID;
+ int rc = VbglR3GetSessionId(&uSessionID);
+ if ( RT_SUCCESS(rc)
+ && uSessionID != m_dndCtx.uSessionID)
+ {
+ LogFlowThisFunc(("VM session has changed to %RU64\n", uSessionID));
+
+ rc = VbglR3DnDDisconnect(&m_dndCtx);
+ AssertRC(rc);
+
+ rc = VbglR3DnDConnect(&m_dndCtx);
+ AssertRC(rc);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+/**
+ * Guest -> Host: Event signalling that the host is asking whether there is a pending
+ * drag event on the guest (to the host).
+ *
+ * @returns IPRT status code.
+ */
+int DragInstance::ghIsDnDPending(void)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState));
+
+ int rc;
+
+ RTCString strFormats = DND_PATH_SEPARATOR_STR; /** @todo If empty, IOCTL fails with VERR_ACCESS_DENIED. */
+ VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE;
+ VBOXDNDACTIONLIST dndActionList = VBOX_DND_ACTION_IGNORE;
+
+ /* Currently in wrong mode? Bail out. */
+ if (m_enmMode == HG)
+ {
+ rc = VERR_INVALID_STATE;
+ }
+ /* Message already processed successfully? */
+ else if ( m_enmMode == GH
+ && ( m_enmState == Dragging
+ || m_enmState == Dropped)
+ )
+ {
+ /* No need to query for the source window again. */
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */
+ rc = checkForSessionChange();
+
+ /* Determine the current window which currently has the XdndSelection set. */
+ Window wndSel = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ LogFlowThisFunc(("wndSel=%#x, wndProxy=%#x, wndCur=%#x\n", wndSel, m_wndProxy.hWnd, m_wndCur));
+
+ /* Is this another window which has a Xdnd selection and not our proxy window? */
+ if ( RT_SUCCESS(rc)
+ && wndSel
+ && wndSel != m_wndCur)
+ {
+ char *pszWndSelName = wndX11GetNameA(wndSel);
+ AssertPtrReturn(pszWndSelName, VERR_NO_MEMORY);
+ VBClLogInfo("New guest source window %#x ('%s')\n", wndSel, pszWndSelName);
+
+ /* Start over. */
+ reset();
+
+ /* Map the window on the current cursor position, which should provoke
+ * an XdndEnter event. */
+ rc = proxyWinShow();
+ if (RT_SUCCESS(rc))
+ {
+ rc = mouseCursorFakeMove();
+ if (RT_SUCCESS(rc))
+ {
+ bool fWaitFailed = false; /* Waiting for status changed failed? */
+
+ /* Wait until we're in "Dragging" state. */
+ rc = waitForStatusChange(Dragging, 100 /* 100ms timeout */);
+
+ /*
+ * Note: Don't wait too long here, as this mostly will make
+ * the drag and drop experience on the host being laggy
+ * and unresponsive.
+ *
+ * Instead, let the host query multiple times with 100ms
+ * timeout each (see above) and only report an error if
+ * the overall querying time has been exceeded.<
+ */
+ if (RT_SUCCESS(rc))
+ {
+ m_enmMode = GH;
+ }
+ else if (rc == VERR_TIMEOUT)
+ {
+ /** @todo Make m_cFailedPendingAttempts configurable. For slower window managers? */
+ if (m_cFailedPendingAttempts++ > 50) /* Tolerate up to 5s total (100ms for each slot). */
+ fWaitFailed = true;
+ else
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ fWaitFailed = true;
+
+ if (fWaitFailed)
+ {
+ VBClLogError("Error mapping proxy window to guest source window %#x ('%s'), rc=%Rrc\n",
+ wndSel, pszWndSelName, rc);
+
+ /* Reset the counter in any case. */
+ m_cFailedPendingAttempts = 0;
+ }
+ }
+ }
+
+ RTStrFree(pszWndSelName);
+ }
+ else
+ VBClLogInfo("No guest source window\n");
+ }
+
+ /*
+ * Acknowledge to the host in any case, regardless
+ * if something failed here or not. Be responsive.
+ */
+
+ int rc2 = RTCritSectEnter(&m_dataCS);
+ if (RT_SUCCESS(rc2))
+ {
+ /* Filter out the default X11-specific formats (required for Xdnd, 'TARGET' / 'MULTIPLE');
+ * those will not be supported by VirtualBox. */
+ VBoxDnDAtomList const lstAtomFormatsFiltered = gX11->xAtomListFiltered(m_lstAtomFormats, m_lstAtomFormatsX11);
+
+ /* Anything left to report to the host? */
+ if (lstAtomFormatsFiltered.size())
+ {
+ strFormats = gX11->xAtomListToString(lstAtomFormatsFiltered);
+ dndActionDefault = VBOX_DND_ACTION_COPY; /** @todo Handle default action! */
+ dndActionList = VBOX_DND_ACTION_COPY; /** @todo Ditto. */
+ dndActionList |= toHGCMActions(m_lstAtomActions);
+ }
+
+ RTCritSectLeave(&m_dataCS);
+ }
+
+ if (g_cVerbosity)
+ {
+ char *pszActions = DnDActionListToStrA(dndActionList);
+ AssertPtrReturn(pszActions, VERR_NO_MEMORY);
+ VBClLogVerbose(1, "Reporting formats '%s' (actions '%s' / %#x, default action is '%s' (%#x)\n",
+ strFormats.c_str(), pszActions, dndActionList, DnDActionToStr(dndActionDefault), dndActionDefault);
+ RTStrFree(pszActions);
+ }
+
+ rc2 = VbglR3DnDGHSendAckPending(&m_dndCtx, dndActionDefault, dndActionList,
+ strFormats.c_str(), strFormats.length() + 1 /* Include termination */);
+ LogFlowThisFunc(("uClientID=%RU32, dndActionDefault=0x%x, dndActionList=0x%x, strFormats=%s, rc=%Rrc\n",
+ m_dndCtx.uClientID, dndActionDefault, dndActionList, strFormats.c_str(), rc2));
+ if (RT_FAILURE(rc2))
+ {
+ switch (rc2)
+ {
+ case VERR_ACCESS_DENIED:
+ {
+ rc = VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER,
+ "Drag and drop to the host either is not supported or disabled. "
+ "Please enable Guest to Host or Bidirectional drag and drop mode "
+ "or re-install the VirtualBox Guest Additions.");
+ AssertRC(rc);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ VBClLogError("Error reporting pending drag and drop operation status to host: %Rrc\n", rc2);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Guest -> Host: Event signalling that the host has dropped the item(s) on the
+ * host side.
+ *
+ * @returns IPRT status code.
+ * @param strFormat Requested format to send to the host.
+ * @param dndActionRequested Requested action to perform on the guest.
+ */
+int DragInstance::ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested)
+{
+ LogFlowThisFunc(("mode=%RU32, state=%RU32, strFormat=%s, dndActionRequested=0x%x\n",
+ m_enmMode, m_enmState, strFormat.c_str(), dndActionRequested));
+
+ /* Currently in wrong mode? Bail out. */
+ if ( m_enmMode == Unknown
+ || m_enmMode == HG)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ if ( m_enmMode == GH
+ && m_enmState != Dragging)
+ {
+ return VERR_INVALID_STATE;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ m_enmState = Dropped;
+
+#ifdef DEBUG
+ XWindowAttributes xwa;
+ XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa);
+ LogFlowThisFunc(("wndProxy=%RU32, wndCur=%RU32, x=%d, y=%d, width=%d, height=%d\n",
+ m_wndProxy.hWnd, m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height));
+
+ Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection));
+ LogFlowThisFunc(("wndSelection=%#x\n", wndSelection));
+#endif
+
+ /* We send a fake mouse move event to the current window, cause
+ * this should have the grab. */
+ mouseCursorFakeMove();
+
+ /**
+ * The fake button release event above should lead to a XdndDrop event from the
+ * source window. Because of showing our proxy window, other Xdnd events can
+ * occur before, e.g. a XdndPosition event. We are not interested
+ * in those, so just try to get the right one.
+ */
+
+ XClientMessageEvent evDnDDrop;
+ bool fDrop = waitForX11ClientMsg(evDnDDrop, xAtom(XA_XdndDrop), 5 * 1000 /* 5s timeout */);
+ if (fDrop)
+ {
+ LogFlowThisFunc(("XA_XdndDrop\n"));
+
+ /* Request to convert the selection in the specific format and
+ * place it to our proxy window as property. */
+ Assert(evDnDDrop.message_type == xAtom(XA_XdndDrop));
+
+ Window wndSource = evDnDDrop.data.l[XdndDropWindow]; /* Source window which has sent the message. */
+ Assert(wndSource == m_wndCur);
+
+ Atom aFormat = gX11->stringToxAtom(strFormat.c_str());
+
+ Time tsDrop;
+ if (m_uXdndVer >= 1)
+ tsDrop = evDnDDrop.data.l[XdndDropTimeStamp];
+ else
+ tsDrop = CurrentTime;
+
+ XConvertSelection(m_pDisplay, xAtom(XA_XdndSelection), aFormat, xAtom(XA_XdndSelection),
+ m_wndProxy.hWnd, tsDrop);
+
+ /* Wait for the selection notify event. */
+ XEvent evSelNotify;
+ RT_ZERO(evSelNotify);
+ if (waitForX11Msg(evSelNotify, SelectionNotify, 5 * 1000 /* 5s timeout */))
+ {
+ bool fCancel = false;
+
+ /* Make some paranoid checks. */
+ if ( evSelNotify.xselection.type == SelectionNotify
+ && evSelNotify.xselection.display == m_pDisplay
+ && evSelNotify.xselection.selection == xAtom(XA_XdndSelection)
+ && evSelNotify.xselection.requestor == m_wndProxy.hWnd
+ && evSelNotify.xselection.target == aFormat)
+ {
+ LogFlowThisFunc(("Selection notfiy (from wnd=%#x)\n", m_wndCur));
+
+ Atom aPropType;
+ int iPropFormat;
+ unsigned long cItems, cbRemaining;
+ unsigned char *pcData = NULL;
+ int xRc = XGetWindowProperty(m_pDisplay, m_wndProxy.hWnd,
+ xAtom(XA_XdndSelection) /* Property */,
+ 0 /* Offset */,
+ VBOX_MAX_XPROPERTIES /* Length of 32-bit multiples */,
+ True /* Delete property? */,
+ AnyPropertyType, /* Property type */
+ &aPropType, &iPropFormat, &cItems, &cbRemaining, &pcData);
+ if (xRc != Success)
+ VBClLogError("Error getting XA_XdndSelection property of proxy window=%#x: %s\n",
+ m_wndProxy.hWnd, gX11->xErrorToString(xRc).c_str());
+
+ LogFlowThisFunc(("strType=%s, iPropFormat=%d, cItems=%RU32, cbRemaining=%RU32\n",
+ gX11->xAtomToString(aPropType).c_str(), iPropFormat, cItems, cbRemaining));
+
+ if ( aPropType != None
+ && pcData != NULL
+ && iPropFormat >= 8
+ && cItems > 0
+ && cbRemaining == 0)
+ {
+ size_t cbData = cItems * (iPropFormat / 8);
+ LogFlowThisFunc(("cbData=%zu\n", cbData));
+
+ /* For whatever reason some of the string MIME types are not
+ * zero terminated. Check that and correct it when necessary,
+ * because the guest side wants this in any case. */
+ if ( m_lstAllowedFormats.contains(strFormat)
+ && pcData[cbData - 1] != '\0')
+ {
+ unsigned char *pvDataTmp = static_cast<unsigned char*>(RTMemAlloc(cbData + 1));
+ if (pvDataTmp)
+ {
+ memcpy(pvDataTmp, pcData, cbData);
+ pvDataTmp[cbData++] = '\0';
+
+ rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pvDataTmp, cbData);
+ RTMemFree(pvDataTmp);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Send the raw data to the host. */
+ rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pcData, cbData);
+ LogFlowThisFunc(("Sent strFormat=%s, rc=%Rrc\n", strFormat.c_str(), rc));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = m_wndProxy.sendFinished(wndSource, dndActionRequested);
+ }
+ else
+ fCancel = true;
+ }
+ else
+ {
+ if (aPropType == xAtom(XA_INCR))
+ {
+ /** @todo Support incremental transfers. */
+ AssertMsgFailed(("Incremental transfers are not supported yet\n"));
+
+ VBClLogError("Incremental transfers are not supported yet\n");
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ else
+ {
+ VBClLogError("Not supported data type: %s\n", gX11->xAtomToString(aPropType).c_str());
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ fCancel = true;
+ }
+
+ if (fCancel)
+ {
+ VBClLogInfo("Cancelling dropping to host\n");
+
+ /* Cancel the operation -- inform the source window by
+ * sending a XdndFinished message so that the source can toss the required data. */
+ rc = m_wndProxy.sendFinished(wndSource, VBOX_DND_ACTION_IGNORE);
+ }
+
+ /* Cleanup. */
+ if (pcData)
+ XFree(pcData);
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_TIMEOUT;
+ }
+ else
+ rc = VERR_TIMEOUT;
+
+ /* Inform the host on error. */
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = VbglR3DnDSendError(&m_dndCtx, rc);
+ LogFlowThisFunc(("Sending error %Rrc to host resulted in %Rrc\n", rc, rc2)); RT_NOREF(rc2);
+ /* This is not fatal for us, just ignore. */
+ }
+
+ /* At this point, we have either successfully transfered any data or not.
+ * So reset our internal state because we are done here for the current (ongoing)
+ * drag and drop operation. */
+ reset();
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+#endif /* VBOX_WITH_DRAG_AND_DROP_GH */
+
+/*
+ * Helpers
+ */
+
+/**
+ * Fakes moving the mouse cursor to provoke various drag and drop
+ * events such as entering a target window or moving within a
+ * source window.
+ *
+ * Not the most elegant and probably correct function, but does
+ * the work for now.
+ *
+ * @returns IPRT status code.
+ */
+int DragInstance::mouseCursorFakeMove(void)
+{
+ int iScreenID = XDefaultScreen(m_pDisplay);
+ /** @todo What about multiple screens? Test this! */
+
+ const int iScrX = XDisplayWidth(m_pDisplay, iScreenID);
+ const int iScrY = XDisplayHeight(m_pDisplay, iScreenID);
+
+ int fx, fy, rx, ry;
+ Window wndTemp, wndChild;
+ int wx, wy; unsigned int mask;
+ XQueryPointer(m_pDisplay, m_wndRoot, &wndTemp, &wndChild, &rx, &ry, &wx, &wy, &mask);
+
+ /*
+ * Apply some simple clipping and change the position slightly.
+ */
+
+ /* FakeX */
+ if (rx == 0) fx = 1;
+ else if (rx == iScrX) fx = iScrX - 1;
+ else fx = rx + 1;
+
+ /* FakeY */
+ if (ry == 0) fy = 1;
+ else if (ry == iScrY) fy = iScrY - 1;
+ else fy = ry + 1;
+
+ /*
+ * Move the cursor to trigger the wanted events.
+ */
+ LogFlowThisFunc(("cursorRootX=%d, cursorRootY=%d\n", fx, fy));
+ int rc = mouseCursorMove(fx, fy);
+ if (RT_SUCCESS(rc))
+ {
+ /* Move the cursor back to its original position. */
+ rc = mouseCursorMove(rx, ry);
+ }
+
+ return rc;
+}
+
+/**
+ * Moves the mouse pointer to a specific position.
+ *
+ * @returns IPRT status code.
+ * @param iPosX Absolute X coordinate.
+ * @param iPosY Absolute Y coordinate.
+ */
+int DragInstance::mouseCursorMove(int iPosX, int iPosY)
+{
+ int const iScreenID = XDefaultScreen(m_pDisplay);
+ /** @todo What about multiple screens? Test this! */
+
+ int const iScreenWidth = XDisplayWidth (m_pDisplay, iScreenID);
+ int const iScreenHeight = XDisplayHeight(m_pDisplay, iScreenID);
+
+ iPosX = RT_CLAMP(iPosX, 0, iScreenWidth);
+ iPosY = RT_CLAMP(iPosY, 0, iScreenHeight);
+
+ /* Same mouse position as before? No need to do anything. */
+ if ( m_lastMouseX == iPosX
+ && m_lastMouseY == iPosY)
+ {
+ return VINF_SUCCESS;
+ }
+
+ LogFlowThisFunc(("iPosX=%d, iPosY=%d, m_wndRoot=%#x\n", iPosX, iPosY, m_wndRoot));
+
+ /* Move the guest pointer to the DnD position, so we can find the window
+ * below that position. */
+ int xRc = XWarpPointer(m_pDisplay, None, m_wndRoot, 0, 0, 0, 0, iPosX, iPosY);
+ if (xRc == Success)
+ {
+ XFlush(m_pDisplay);
+
+ m_lastMouseX = iPosX;
+ m_lastMouseY = iPosY;
+ }
+ else
+ VBClLogError("Moving mouse cursor failed: %s", gX11->xErrorToString(xRc).c_str());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sends a mouse button event to a specific window.
+ *
+ * @param wndDest Window to send the mouse button event to.
+ * @param rx X coordinate relative to the root window's origin.
+ * @param ry Y coordinate relative to the root window's origin.
+ * @param iButton Mouse button to press/release.
+ * @param fPress Whether to press or release the mouse button.
+ */
+void DragInstance::mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress)
+{
+ LogFlowThisFunc(("wndDest=%#x, rx=%d, ry=%d, iBtn=%d, fPress=%RTbool\n",
+ wndDest, rx, ry, iButton, fPress));
+
+#ifdef VBOX_DND_WITH_XTEST
+ /** @todo Make this check run only once. */
+ int ev, er, ma, mi;
+ if (XTestQueryExtension(m_pDisplay, &ev, &er, &ma, &mi))
+ {
+ LogFlowThisFunc(("XText extension available\n"));
+
+ int xRc = XTestFakeButtonEvent(m_pDisplay, 1, fPress ? True : False, CurrentTime);
+ if (Rc == 0)
+ VBClLogError("Error sending XTestFakeButtonEvent event: %s\n", gX11->xErrorToString(xRc).c_str());
+ XFlush(m_pDisplay);
+ }
+ else
+ {
+#endif
+ LogFlowThisFunc(("Note: XText extension not available or disabled\n"));
+
+ unsigned int mask = 0;
+
+ if ( rx == -1
+ && ry == -1)
+ {
+ Window wndRoot, wndChild;
+ int wx, wy;
+ XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild, &rx, &ry, &wx, &wy, &mask);
+ LogFlowThisFunc(("Mouse pointer is at root x=%d, y=%d\n", rx, ry));
+ }
+
+ XButtonEvent eBtn;
+ RT_ZERO(eBtn);
+
+ eBtn.display = m_pDisplay;
+ eBtn.root = m_wndRoot;
+ eBtn.window = wndDest;
+ eBtn.subwindow = None;
+ eBtn.same_screen = True;
+ eBtn.time = CurrentTime;
+ eBtn.button = iButton;
+ eBtn.state = mask | (iButton == 1 ? Button1MotionMask :
+ iButton == 2 ? Button2MotionMask :
+ iButton == 3 ? Button3MotionMask :
+ iButton == 4 ? Button4MotionMask :
+ iButton == 5 ? Button5MotionMask : 0);
+ eBtn.type = fPress ? ButtonPress : ButtonRelease;
+ eBtn.send_event = False;
+ eBtn.x_root = rx;
+ eBtn.y_root = ry;
+
+ XTranslateCoordinates(m_pDisplay, eBtn.root, eBtn.window, eBtn.x_root, eBtn.y_root, &eBtn.x, &eBtn.y, &eBtn.subwindow);
+ LogFlowThisFunc(("state=0x%x, x=%d, y=%d\n", eBtn.state, eBtn.x, eBtn.y));
+
+ int xRc = XSendEvent(m_pDisplay, wndDest, True /* fPropagate */,
+ ButtonPressMask,
+ reinterpret_cast<XEvent*>(&eBtn));
+ if (xRc == 0)
+ VBClLogError("Error sending XButtonEvent event to window=%#x: %s\n", wndDest, gX11->xErrorToString(xRc).c_str());
+
+ XFlush(m_pDisplay);
+
+#ifdef VBOX_DND_WITH_XTEST
+ }
+#endif
+}
+
+/**
+ * Shows the (invisible) proxy window. The proxy window is needed for intercepting
+ * drags from the host to the guest or from the guest to the host. It acts as a proxy
+ * between the host and the actual (UI) element on the guest OS.
+ *
+ * To not make it miss any actions this window gets spawned across the entire guest
+ * screen (think of an umbrella) to (hopefully) capture everything. A proxy window
+ * which follows the cursor would be far too slow here.
+ *
+ * @returns IPRT status code.
+ * @param piRootX X coordinate relative to the root window's origin. Optional.
+ * @param piRootY Y coordinate relative to the root window's origin. Optional.
+ */
+int DragInstance::proxyWinShow(int *piRootX /* = NULL */, int *piRootY /* = NULL */) const
+{
+ /* piRootX is optional. */
+ /* piRootY is optional. */
+
+ LogFlowThisFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+#if 0
+# ifdef VBOX_DND_WITH_XTEST
+ XTestGrabControl(m_pDisplay, False);
+# endif
+#endif
+
+ /* Get the mouse pointer position and determine if we're on the same screen as the root window
+ * and return the current child window beneath our mouse pointer, if any. */
+ int iRootX, iRootY;
+ int iChildX, iChildY;
+ unsigned int iMask;
+ Window wndRoot, wndChild;
+ Bool fInRootWnd = XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild,
+ &iRootX, &iRootY, &iChildX, &iChildY, &iMask);
+
+ LogFlowThisFunc(("fInRootWnd=%RTbool, wndRoot=%RU32, wndChild=%RU32, iRootX=%d, iRootY=%d\n",
+ RT_BOOL(fInRootWnd), wndRoot, wndChild, iRootX, iRootY)); RT_NOREF(fInRootWnd);
+
+ if (piRootX)
+ *piRootX = iRootX;
+ if (piRootY)
+ *piRootY = iRootY;
+
+ XSynchronize(m_pDisplay, True /* Enable sync */);
+
+ /* Bring our proxy window into foreground. */
+ XMapWindow(m_pDisplay, m_wndProxy.hWnd);
+ XRaiseWindow(m_pDisplay, m_wndProxy.hWnd);
+
+ /* Spawn our proxy window over the entire screen, making it an easy drop target for the host's cursor. */
+ LogFlowThisFunc(("Proxy window x=%d, y=%d, width=%d, height=%d\n",
+ m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight));
+ XMoveResizeWindow(m_pDisplay, m_wndProxy.hWnd, m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight);
+
+ XFlush(m_pDisplay);
+
+ XSynchronize(m_pDisplay, False /* Disable sync */);
+
+#if 0
+# ifdef VBOX_DND_WITH_XTEST
+ XTestGrabControl(m_pDisplay, True);
+# endif
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Hides the (invisible) proxy window.
+ */
+int DragInstance::proxyWinHide(void)
+{
+ LogFlowFuncEnter();
+
+ XUnmapWindow(m_pDisplay, m_wndProxy.hWnd);
+ XFlush(m_pDisplay);
+
+ return VINF_SUCCESS; /** @todo Add error checking. */
+}
+
+/**
+ * Allocates the name (title) of an X window.
+ * The returned pointer must be freed using RTStrFree().
+ *
+ * @returns Pointer to the allocated window name.
+ * @retval NULL on allocation failure.
+ * @retval "<No name>" if window name was not found / invalid window handle.
+ * @param wndThis Window to retrieve name for.
+ */
+char *DragInstance::wndX11GetNameA(Window wndThis) const
+{
+ char *pszName = NULL;
+
+ XTextProperty propName;
+ if ( wndThis != None
+ && XGetWMName(m_pDisplay, wndThis, &propName))
+ {
+ if (propName.value)
+ pszName = RTStrDup((char *)propName.value); /** @todo UTF8? */
+ XFree(propName.value);
+ }
+
+ if (!pszName) /* No window name found? */
+ pszName = RTStrDup("<No name>");
+
+ return pszName;
+}
+
+/**
+ * Clear a window's supported/accepted actions list.
+ *
+ * @param wndThis Window to clear the list for.
+ */
+void DragInstance::wndXDnDClearActionList(Window wndThis) const
+{
+ XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndActionList));
+}
+
+/**
+ * Clear a window's supported/accepted formats list.
+ *
+ * @param wndThis Window to clear the list for.
+ */
+void DragInstance::wndXDnDClearFormatList(Window wndThis) const
+{
+ XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndTypeList));
+}
+
+/**
+ * Retrieves a window's supported/accepted XDnD actions.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to retrieve the XDnD actions for.
+ * @param lstActions Reference to VBoxDnDAtomList to store the action into.
+ */
+int DragInstance::wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const
+{
+ Atom iActType = None;
+ int iActFmt;
+ unsigned long cItems, cbData;
+ unsigned char *pcbData = NULL;
+
+ /* Fetch the possible list of actions, if this property is set. */
+ int xRc = XGetWindowProperty(m_pDisplay, wndThis,
+ xAtom(XA_XdndActionList),
+ 0, VBOX_MAX_XPROPERTIES,
+ False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData);
+ if (xRc != Success)
+ {
+ LogFlowThisFunc(("Error getting XA_XdndActionList atoms from window=%#x: %s\n",
+ wndThis, gX11->xErrorToString(xRc).c_str()));
+ return VERR_NOT_FOUND;
+ }
+
+ LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData));
+
+ if (cItems > 0)
+ {
+ AssertPtr(pcbData);
+ Atom *paData = reinterpret_cast<Atom *>(pcbData);
+
+ for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++)
+ {
+ LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str()));
+ lstActions.append(paData[i]);
+ }
+
+ XFree(pcbData);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Retrieves a window's supported/accepted XDnD formats.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to retrieve the XDnD formats for.
+ * @param lstTypes Reference to VBoxDnDAtomList to store the formats into.
+ */
+int DragInstance::wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const
+{
+ Atom iActType = None;
+ int iActFmt;
+ unsigned long cItems, cbData;
+ unsigned char *pcbData = NULL;
+
+ int xRc = XGetWindowProperty(m_pDisplay, wndThis,
+ xAtom(XA_XdndTypeList),
+ 0, VBOX_MAX_XPROPERTIES,
+ False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData);
+ if (xRc != Success)
+ {
+ LogFlowThisFunc(("Error getting XA_XdndTypeList atoms from window=%#x: %s\n",
+ wndThis, gX11->xErrorToString(xRc).c_str()));
+ return VERR_NOT_FOUND;
+ }
+
+ LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData));
+
+ if (cItems > 0)
+ {
+ AssertPtr(pcbData);
+ Atom *paData = reinterpret_cast<Atom *>(pcbData);
+
+ for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++)
+ {
+ LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str()));
+ lstTypes.append(paData[i]);
+ }
+
+ XFree(pcbData);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets (replaces) a window's XDnD accepted/allowed actions.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to set the format list for.
+ * @param lstActions Reference to list of XDnD actions to set.
+ */
+int DragInstance::wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const
+{
+ if (lstActions.isEmpty())
+ return VINF_SUCCESS;
+
+ XChangeProperty(m_pDisplay, wndThis,
+ xAtom(XA_XdndActionList),
+ XA_ATOM, 32, PropModeReplace,
+ reinterpret_cast<const unsigned char*>(lstActions.raw()),
+ lstActions.size());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Sets (replaces) a window's XDnD accepted format list.
+ *
+ * @returns IPRT status code.
+ * @param wndThis Window to set the format list for.
+ * @param atmProp Property to set.
+ * @param lstFormats Reference to list of XDnD formats to set.
+ */
+int DragInstance::wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const
+{
+ if (lstFormats.isEmpty())
+ return VERR_INVALID_PARAMETER;
+
+ /* Add the property with the property data to the window. */
+ XChangeProperty(m_pDisplay, wndThis, atmProp,
+ XA_ATOM, 32, PropModeReplace,
+ reinterpret_cast<const unsigned char*>(lstFormats.raw()),
+ lstFormats.size());
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Appends a RTCString list to VBoxDnDAtomList list.
+ *
+ * @returns IPRT status code.
+ * @param lstFormats Reference to RTCString list to convert.
+ * @param lstAtoms Reference to VBoxDnDAtomList list to store results in.
+ */
+int DragInstance::appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const
+{
+ for (size_t i = 0; i < lstFormats.size(); ++i)
+ lstAtoms.append(XInternAtom(m_pDisplay, lstFormats.at(i).c_str(), False));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Appends a raw-data string list to VBoxDnDAtomList list.
+ *
+ * @returns IPRT status code.
+ * @param pvData Pointer to string data to convert.
+ * @param cbData Size (in bytes) to convert.
+ * @param lstAtoms Reference to VBoxDnDAtomList list to store results in.
+ */
+int DragInstance::appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const
+{
+ RT_NOREF1(lstAtoms);
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+ const char *pszStr = (char *)pvData;
+ uint32_t cbStr = cbData;
+
+ int rc = VINF_SUCCESS;
+
+ VBoxDnDAtomList lstAtom;
+ while (cbStr)
+ {
+ size_t cbSize = RTStrNLen(pszStr, cbStr);
+
+ /* Create a copy with max N chars, so that we are on the save side,
+ * even if the data isn't zero terminated. */
+ char *pszTmp = RTStrDupN(pszStr, cbSize);
+ if (!pszTmp)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ lstAtom.append(XInternAtom(m_pDisplay, pszTmp, False));
+ RTStrFree(pszTmp);
+
+ pszStr += cbSize + 1;
+ cbStr -= cbSize + 1;
+ }
+
+ return rc;
+}
+
+/**
+ * Converts a HGCM-based drag'n drop action to a Atom-based drag'n drop action.
+ *
+ * @returns Converted Atom-based drag'n drop action.
+ * @param dndAction HGCM drag'n drop actions to convert.
+ */
+/* static */
+Atom DragInstance::toAtomAction(VBOXDNDACTION dndAction)
+{
+ /* Ignore is None. */
+ return (isDnDCopyAction(dndAction) ? xAtom(XA_XdndActionCopy) :
+ isDnDMoveAction(dndAction) ? xAtom(XA_XdndActionMove) :
+ isDnDLinkAction(dndAction) ? xAtom(XA_XdndActionLink) :
+ None);
+}
+
+/**
+ * Converts HGCM-based drag'n drop actions to a VBoxDnDAtomList list.
+ *
+ * @returns IPRT status code.
+ * @param dndActionList HGCM drag'n drop actions to convert.
+ * @param lstAtoms Reference to VBoxDnDAtomList to store actions in.
+ */
+/* static */
+int DragInstance::toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms)
+{
+ if (hasDnDCopyAction(dndActionList))
+ lstAtoms.append(xAtom(XA_XdndActionCopy));
+ if (hasDnDMoveAction(dndActionList))
+ lstAtoms.append(xAtom(XA_XdndActionMove));
+ if (hasDnDLinkAction(dndActionList))
+ lstAtoms.append(xAtom(XA_XdndActionLink));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Converts an Atom-based drag'n drop action to a HGCM drag'n drop action.
+ *
+ * @returns HGCM drag'n drop action.
+ * @param atom Atom-based drag'n drop action to convert.
+ */
+/* static */
+uint32_t DragInstance::toHGCMAction(Atom atom)
+{
+ uint32_t uAction = VBOX_DND_ACTION_IGNORE;
+
+ if (atom == xAtom(XA_XdndActionCopy))
+ uAction = VBOX_DND_ACTION_COPY;
+ else if (atom == xAtom(XA_XdndActionMove))
+ uAction = VBOX_DND_ACTION_MOVE;
+ else if (atom == xAtom(XA_XdndActionLink))
+ uAction = VBOX_DND_ACTION_LINK;
+
+ return uAction;
+}
+
+/**
+ * Converts an VBoxDnDAtomList list to an HGCM action list.
+ *
+ * @returns ORed HGCM action list.
+ * @param lstActions List of Atom-based actions to convert.
+ */
+/* static */
+uint32_t DragInstance::toHGCMActions(const VBoxDnDAtomList &lstActions)
+{
+ uint32_t uActions = VBOX_DND_ACTION_IGNORE;
+
+ for (size_t i = 0; i < lstActions.size(); i++)
+ uActions |= toHGCMAction(lstActions.at(i));
+
+ return uActions;
+}
+
+/*********************************************************************************************************************************
+ * VBoxDnDProxyWnd implementation. *
+ ********************************************************************************************************************************/
+
+VBoxDnDProxyWnd::VBoxDnDProxyWnd(void)
+ : pDisp(NULL)
+ , hWnd(0)
+ , iX(0)
+ , iY(0)
+ , iWidth(0)
+ , iHeight(0)
+{
+
+}
+
+VBoxDnDProxyWnd::~VBoxDnDProxyWnd(void)
+{
+ destroy();
+}
+
+int VBoxDnDProxyWnd::init(Display *pDisplay)
+{
+ /** @todo What about multiple screens? Test this! */
+ int iScreenID = XDefaultScreen(pDisplay);
+
+ iWidth = XDisplayWidth(pDisplay, iScreenID);
+ iHeight = XDisplayHeight(pDisplay, iScreenID);
+ pDisp = pDisplay;
+
+ return VINF_SUCCESS;
+}
+
+void VBoxDnDProxyWnd::destroy(void)
+{
+
+}
+
+int VBoxDnDProxyWnd::sendFinished(Window hWndSource, VBOXDNDACTION dndAction)
+{
+ /* Was the drop accepted by the host? That is, anything than ignoring. */
+ bool fDropAccepted = dndAction > VBOX_DND_ACTION_IGNORE;
+
+ LogFlowFunc(("dndAction=0x%x\n", dndAction));
+
+ /* Confirm the result of the transfer to the target window. */
+ XClientMessageEvent m;
+ RT_ZERO(m);
+ m.type = ClientMessage;
+ m.display = pDisp;
+ m.window = hWnd;
+ m.message_type = xAtom(XA_XdndFinished);
+ m.format = 32;
+ m.data.l[XdndFinishedWindow] = hWnd; /* Target window. */
+ m.data.l[XdndFinishedFlags] = fDropAccepted ? RT_BIT(0) : 0; /* Was the drop accepted? */
+ m.data.l[XdndFinishedAction] = fDropAccepted ? DragInstance::toAtomAction(dndAction) : None; /* Action used on accept. */
+
+ int xRc = XSendEvent(pDisp, hWndSource, True, NoEventMask, reinterpret_cast<XEvent*>(&m));
+ if (xRc == 0)
+ {
+ VBClLogError("Error sending finished event to source window=%#x: %s\n",
+ hWndSource, gX11->xErrorToString(xRc).c_str());
+
+ return VERR_GENERAL_FAILURE; /** @todo Fudge. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+/*********************************************************************************************************************************
+ * DragAndDropService implementation. *
+ ********************************************************************************************************************************/
+
+/** @copydoc VBCLSERVICE::pfnInit */
+int DragAndDropService::init(void)
+{
+ LogFlowFuncEnter();
+
+ /* Connect to the x11 server. */
+ m_pDisplay = XOpenDisplay(NULL);
+ if (!m_pDisplay)
+ {
+ VBClLogFatalError("Unable to connect to X server -- running in a terminal session?\n");
+ return VERR_NOT_FOUND;
+ }
+
+ xHelpers *pHelpers = xHelpers::getInstance(m_pDisplay);
+ if (!pHelpers)
+ return VERR_NO_MEMORY;
+
+ int rc;
+
+ do
+ {
+ rc = RTSemEventCreate(&m_hEventSem);
+ AssertRCBreak(rc);
+
+ rc = RTCritSectInit(&m_eventQueueCS);
+ AssertRCBreak(rc);
+
+ rc = VbglR3DnDConnect(&m_dndCtx);
+ AssertRCBreak(rc);
+
+ /* Event thread for events coming from the HGCM device. */
+ rc = RTThreadCreate(&m_hHGCMThread, hgcmEventThread, this,
+ 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndHGCM");
+ AssertRCBreak(rc);
+
+ rc = RTThreadUserWait(m_hHGCMThread, RT_MS_30SEC);
+ AssertRCBreak(rc);
+
+ if (ASMAtomicReadBool(&m_fStop))
+ break;
+
+ /* Event thread for events coming from the x11 system. */
+ rc = RTThreadCreate(&m_hX11Thread, x11EventThread, this,
+ 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndX11");
+ AssertRCBreak(rc);
+
+ rc = RTThreadUserWait(m_hX11Thread, RT_MS_30SEC);
+ AssertRCBreak(rc);
+
+ if (ASMAtomicReadBool(&m_fStop))
+ break;
+
+ } while (0);
+
+ if (m_fStop)
+ rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to initialize, rc=%Rrc\n", rc);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc VBCLSERVICE::pfnWorker */
+int DragAndDropService::worker(bool volatile *pfShutdown)
+{
+ int rc;
+ do
+ {
+ m_pCurDnD = new DragInstance(m_pDisplay, this);
+ if (!m_pCurDnD)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ /* Note: For multiple screen support in VBox it is not necessary to use
+ * another screen number than zero. Maybe in the future it will become
+ * necessary if VBox supports multiple X11 screens. */
+ rc = m_pCurDnD->init(0 /* uScreenID */);
+ /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */
+ if (rc != VINF_SUCCESS)
+ {
+ if (RT_FAILURE(rc))
+ VBClLogError("Unable to connect to drag and drop service, rc=%Rrc\n", rc);
+ else if (rc == VINF_PERMISSION_DENIED) /* No error, DnD might be just disabled. */
+ VBClLogInfo("Not available on host, terminating\n");
+ break;
+ }
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* Enter the main event processing loop. */
+ do
+ {
+ DNDEVENT e;
+ RT_ZERO(e);
+
+ LogFlowFunc(("Waiting for new events ...\n"));
+ rc = RTSemEventWait(m_hEventSem, RT_INDEFINITE_WAIT);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t cEvents = 0;
+
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ cEvents = m_eventQueue.size();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ while (cEvents)
+ {
+ rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ if (m_eventQueue.isEmpty())
+ {
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ break;
+ }
+
+ e = m_eventQueue.first();
+ m_eventQueue.removeFirst();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ if (e.enmType == DNDEVENT::DnDEventType_HGCM)
+ {
+ PVBGLR3DNDEVENT pVbglR3Event = e.hgcm;
+ AssertPtrBreak(pVbglR3Event);
+
+ LogFlowThisFunc(("HGCM event enmType=%RU32\n", pVbglR3Event->enmType));
+ switch (pVbglR3Event->enmType)
+ {
+ case VBGLR3DNDEVENTTYPE_HG_ENTER:
+ {
+ if (pVbglR3Event->u.HG_Enter.cbFormats)
+ {
+ RTCList<RTCString> lstFormats =
+ RTCString(pVbglR3Event->u.HG_Enter.pszFormats, pVbglR3Event->u.HG_Enter.cbFormats - 1).split(DND_PATH_SEPARATOR_STR);
+ rc = m_pCurDnD->hgEnter(lstFormats, pVbglR3Event->u.HG_Enter.dndLstActionsAllowed);
+ if (RT_FAILURE(rc))
+ break;
+ /* Enter is always followed by a move event. */
+ }
+ else
+ {
+ AssertMsgFailed(("cbFormats is 0\n"));
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ /* Note: After HOST_DND_FN_HG_EVT_ENTER there immediately is a move
+ * event, so fall through is intentional here. */
+ RT_FALL_THROUGH();
+ }
+
+ case VBGLR3DNDEVENTTYPE_HG_MOVE:
+ {
+ rc = m_pCurDnD->hgMove(pVbglR3Event->u.HG_Move.uXpos, pVbglR3Event->u.HG_Move.uYpos,
+ pVbglR3Event->u.HG_Move.dndActionDefault);
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_HG_LEAVE:
+ {
+ rc = m_pCurDnD->hgLeave();
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_HG_DROP:
+ {
+ rc = m_pCurDnD->hgDrop(pVbglR3Event->u.HG_Drop.uXpos, pVbglR3Event->u.HG_Drop.uYpos,
+ pVbglR3Event->u.HG_Drop.dndActionDefault);
+ break;
+ }
+
+ /* Note: VbglR3DnDRecvNextMsg() will return HOST_DND_FN_HG_SND_DATA_HDR when
+ * the host has finished copying over all the data to the guest.
+ *
+ * The actual data transfer (and message processing for it) will be done
+ * internally by VbglR3DnDRecvNextMsg() to not duplicate any code for different
+ * platforms.
+ *
+ * The data header now will contain all the (meta) data the guest needs in
+ * order to complete the DnD operation. */
+ case VBGLR3DNDEVENTTYPE_HG_RECEIVE:
+ {
+ rc = m_pCurDnD->hgDataReceive(&pVbglR3Event->u.HG_Received.Meta);
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_CANCEL:
+ {
+ m_pCurDnD->reset();
+ break;
+ }
+
+#ifdef VBOX_WITH_DRAG_AND_DROP_GH
+ case VBGLR3DNDEVENTTYPE_GH_ERROR:
+ {
+ m_pCurDnD->reset();
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_GH_REQ_PENDING:
+ {
+ rc = m_pCurDnD->ghIsDnDPending();
+ break;
+ }
+
+ case VBGLR3DNDEVENTTYPE_GH_DROP:
+ {
+ rc = m_pCurDnD->ghDropped(pVbglR3Event->u.GH_Drop.pszFormat, pVbglR3Event->u.GH_Drop.dndActionRequested);
+ break;
+ }
+#endif
+ case VBGLR3DNDEVENTTYPE_QUIT:
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ default:
+ {
+ VBClLogError("Received unsupported message type %RU32\n", pVbglR3Event->enmType);
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ LogFlowFunc(("Message %RU32 processed with %Rrc\n", pVbglR3Event->enmType, rc));
+ if (RT_FAILURE(rc))
+ {
+ /* Tell the user. */
+ VBClLogError("Processing message %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc);
+
+ /* If anything went wrong, do a reset and start over. */
+ reset();
+ }
+
+ const bool fQuit = pVbglR3Event->enmType == VBGLR3DNDEVENTTYPE_QUIT;
+
+ VbglR3DnDEventFree(e.hgcm);
+ e.hgcm = NULL;
+
+ if (fQuit)
+ break;
+ }
+ else if (e.enmType == DNDEVENT::DnDEventType_X11)
+ {
+ LogFlowThisFunc(("X11 event (type %#x)\n", e.x11.type));
+ m_pCurDnD->onX11Event(e.x11);
+ }
+ else
+ AssertMsgFailed(("Unknown event queue type %RU32\n", e.enmType));
+
+ --cEvents;
+
+ } /* for */
+
+ /*
+ * Make sure that any X11 requests have actually been sent to the
+ * server, since we are waiting for responses using poll() on
+ * another thread which will not automatically trigger flushing.
+ */
+ XFlush(m_pDisplay);
+
+ if (m_fStop)
+ break;
+
+ } while (!ASMAtomicReadBool(pfShutdown));
+
+ } while (0);
+
+ if (m_pCurDnD)
+ {
+ delete m_pCurDnD;
+ m_pCurDnD = NULL;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Resets the DnD service' data.
+ */
+void DragAndDropService::reset(void)
+{
+ LogFlowFuncEnter();
+
+ if (m_pCurDnD)
+ m_pCurDnD->reset();
+
+ /*
+ * Clear the event queue.
+ */
+ int rc2 = RTCritSectEnter(&m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ for (size_t i = 0; i < m_eventQueue.size(); i++)
+ {
+ switch (m_eventQueue[i].enmType)
+ {
+ case DNDEVENT::DnDEventType_HGCM:
+ {
+ VbglR3DnDEventFree(m_eventQueue[i].hgcm);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ }
+
+ m_eventQueue.clear();
+
+ rc2 = RTCritSectLeave(&m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/** @copydoc VBCLSERVICE::pfnStop */
+void DragAndDropService::stop(void)
+{
+ LogFlowFuncEnter();
+
+ /* Set stop flag first. */
+ ASMAtomicXchgBool(&m_fStop, true);
+
+ /* First, disconnect any instances. */
+ if (m_pCurDnD)
+ m_pCurDnD->stop();
+
+ /* Second, disconnect the service's DnD connection. */
+ VbglR3DnDDisconnect(&m_dndCtx);
+
+ LogFlowFuncLeave();
+}
+
+/** @copydoc VBCLSERVICE::pfnTerm */
+int DragAndDropService::term(void)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Wait for threads to terminate.
+ */
+ int rcThread;
+
+ if (m_hX11Thread != NIL_RTTHREAD)
+ {
+ VBClLogVerbose(2, "Terminating X11 thread ...\n");
+
+ int rc2 = RTThreadWait(m_hX11Thread, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc2))
+ rc2 = rcThread;
+
+ if (RT_FAILURE(rc2))
+ VBClLogError("Error waiting for X11 thread to terminate: %Rrc\n", rc2);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ m_hX11Thread = NIL_RTTHREAD;
+
+ VBClLogVerbose(2, "X11 thread terminated\n");
+ }
+
+ if (m_hHGCMThread != NIL_RTTHREAD)
+ {
+ VBClLogVerbose(2, "Terminating HGCM thread ...\n");
+
+ int rc2 = RTThreadWait(m_hHGCMThread, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc2))
+ rc2 = rcThread;
+
+ if (RT_FAILURE(rc2))
+ VBClLogError("Error waiting for HGCM thread to terminate: %Rrc\n", rc2);
+
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ m_hHGCMThread = NIL_RTTHREAD;
+
+ VBClLogVerbose(2, "HGCM thread terminated\n");
+ }
+
+ reset();
+
+ if (m_pCurDnD)
+ {
+ delete m_pCurDnD;
+ m_pCurDnD = NULL;
+ }
+
+ xHelpers::destroyInstance();
+
+ return rc;
+}
+
+/**
+ * Static callback function for HGCM message processing thread. An internal
+ * message queue will be filled which then will be processed by the according
+ * drag'n drop instance.
+ *
+ * @returns IPRT status code.
+ * @param hThread Thread handle to use.
+ * @param pvUser Pointer to DragAndDropService instance to use.
+ */
+/* static */
+DECLCALLBACK(int) DragAndDropService::hgcmEventThread(RTTHREAD hThread, void *pvUser)
+{
+ AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
+ DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
+
+ /* Let the service instance know in any case. */
+ int rc = RTThreadUserSignal(hThread);
+ AssertRCReturn(rc, rc);
+
+ VBClLogVerbose(2, "HGCM thread started\n");
+
+ /* Number of invalid messages skipped in a row. */
+ int cMsgSkippedInvalid = 0;
+ DNDEVENT e;
+
+ do
+ {
+ RT_ZERO(e);
+ e.enmType = DNDEVENT::DnDEventType_HGCM;
+
+ /* Wait for new events. */
+ rc = VbglR3DnDEventGetNext(&pThis->m_dndCtx, &e.hgcm);
+ if (RT_SUCCESS(rc))
+ {
+ cMsgSkippedInvalid = 0; /* Reset skipped messages count. */
+
+ int rc2 = RTCritSectEnter(&pThis->m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ VBClLogVerbose(2, "Received new HGCM message (type %#x)\n", e.hgcm->enmType);
+
+ pThis->m_eventQueue.append(e);
+
+ rc2 = RTCritSectLeave(&pThis->m_eventQueueCS);
+ AssertRC(rc2);
+ }
+
+ rc = RTSemEventSignal(pThis->m_hEventSem);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ {
+ VBClLogError("Processing next message failed with rc=%Rrc\n", rc);
+
+ /* Old(er) hosts either are broken regarding DnD support or otherwise
+ * don't support the stuff we do on the guest side, so make sure we
+ * don't process invalid messages forever. */
+
+ if (cMsgSkippedInvalid++ > 32)
+ {
+ VBClLogError("Too many invalid/skipped messages from host, exiting ...\n");
+ break;
+ }
+ }
+
+ } while (!ASMAtomicReadBool(&pThis->m_fStop));
+
+ VBClLogVerbose(2, "HGCM thread ended\n");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Static callback function for X11 message processing thread. All X11 messages
+ * will be directly routed to the according drag'n drop instance.
+ *
+ * @returns IPRT status code.
+ * @param hThread Thread handle to use.
+ * @param pvUser Pointer to DragAndDropService instance to use.
+ */
+/* static */
+DECLCALLBACK(int) DragAndDropService::x11EventThread(RTTHREAD hThread, void *pvUser)
+{
+ AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER);
+ DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser);
+ AssertPtr(pThis);
+
+ int rc = VINF_SUCCESS;
+
+ /* Note: Nothing to initialize here (yet). */
+
+ /* Let the service instance know in any case. */
+ int rc2 = RTThreadUserSignal(hThread);
+ AssertRC(rc2);
+
+ VBClLogVerbose(2, "X11 thread started\n");
+
+ DNDEVENT e;
+ RT_ZERO(e);
+ e.enmType = DNDEVENT::DnDEventType_X11;
+
+ do
+ {
+ /*
+ * Wait for new events. We can't use XIfEvent here, cause this locks
+ * the window connection with a mutex and if no X11 events occurs this
+ * blocks any other calls we made to X11. So instead check for new
+ * events and if there are not any new one, sleep for a certain amount
+ * of time.
+ */
+ unsigned cNewEvents = 0;
+ unsigned cQueued = XEventsQueued(pThis->m_pDisplay, QueuedAfterFlush);
+ while (cQueued)
+ {
+ /* XNextEvent will block until a new X event becomes available. */
+ XNextEvent(pThis->m_pDisplay, &e.x11);
+ {
+ rc2 = RTCritSectEnter(&pThis->m_eventQueueCS);
+ if (RT_SUCCESS(rc2))
+ {
+ LogFlowFunc(("Added new X11 event, type=%d\n", e.x11.type));
+
+ pThis->m_eventQueue.append(e);
+ cNewEvents++;
+
+ rc2 = RTCritSectLeave(&pThis->m_eventQueueCS);
+ AssertRC(rc2);
+ }
+ }
+
+ cQueued--;
+ }
+
+ if (cNewEvents)
+ {
+ rc = RTSemEventSignal(pThis->m_hEventSem);
+ if (RT_FAILURE(rc))
+ break;
+
+ continue;
+ }
+
+ /* No new events; wait a bit. */
+ RTThreadSleep(25 /* ms */);
+
+ } while (!ASMAtomicReadBool(&pThis->m_fStop));
+
+ VBClLogVerbose(2, "X11 thread ended\n");
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclDnDInit(void)
+{
+ return g_Svc.init();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclDnDWorker(bool volatile *pfShutdown)
+{
+ return g_Svc.worker(pfShutdown);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclDnDStop(void)
+{
+ g_Svc.stop();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclDnDTerm(void)
+{
+ return g_Svc.term();
+}
+
+VBCLSERVICE g_SvcDragAndDrop =
+{
+ "dnd", /* szName */
+ "Drag'n'Drop", /* pszDescription */
+ ".vboxclient-draganddrop", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclDnDInit, /* pfnInit */
+ vbclDnDWorker, /* pfnWorker */
+ vbclDnDStop, /* pfnStop*/
+ vbclDnDTerm /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/hostversion.cpp b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp
new file mode 100644
index 00000000..d2da9094
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp
@@ -0,0 +1,130 @@
+/* $Id: hostversion.cpp $ */
+/** @file
+ * X11 guest client - Host version check.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#include <stdio.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/ldr.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+#ifdef VBOX_OSE
+# include <VBox/version.h>
+#endif
+
+#include "VBoxClient.h"
+
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclHostVerWorker(bool volatile *pfShutdown)
+{
+ /** @todo Move this part in VbglR3 and just provide a callback for the platform-specific
+ notification stuff, since this is very similar to the VBoxTray code. */
+
+ RT_NOREF(pfShutdown);
+
+ LogFlowFuncEnter();
+
+ int rc;
+#ifdef VBOX_WITH_GUEST_PROPS
+ uint32_t uGuestPropSvcClientID;
+ rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Cannot connect to guest property service while chcking for host version, rc = %Rrc\n", rc);
+ return rc;
+ }
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* Because we need desktop notifications to be displayed, wait
+ * some time to make the desktop environment load (as a work around). */
+ if (g_fDaemonized)
+ RTThreadSleep(RT_MS_30SEC);
+
+ char *pszHostVersion;
+ char *pszGuestVersion;
+ bool fUpdate;
+
+ rc = VbglR3HostVersionCheckForUpdate(uGuestPropSvcClientID, &fUpdate, &pszHostVersion, &pszGuestVersion);
+ if (RT_SUCCESS(rc))
+ {
+ if (fUpdate)
+ {
+ char szMsg[1024];
+ char szTitle[64];
+
+ /** @todo add some translation macros here */
+ RTStrPrintf(szTitle, sizeof(szTitle), "VirtualBox Guest Additions update available!");
+# ifndef VBOX_OSE
+ RTStrPrintf(szMsg, sizeof(szMsg), "Your guest is currently running the Guest Additions version %s. "
+ "We recommend updating to the latest version (%s) by choosing the "
+ "install option from the Devices menu.", pszGuestVersion, pszHostVersion);
+# else
+/* This is the message which appears for non-Oracle builds of the
+* Guest Additions. Distributors are encouraged to customise this. */
+ RTStrPrintf(szMsg, sizeof(szMsg), "Your virtual machine is currently running the Guest Additions version %s. Since you are running a version of the Guest Additions provided by the operating system you installed in the virtual machine we recommend that you update it to at least version %s using that system's update features, or alternatively that you remove this version and then install the " VBOX_VENDOR_SHORT " Guest Additions package using the install option from the Devices menu. Please consult the documentation for the operating system you are running to find out how to update or remove the current Guest Additions package.", pszGuestVersion, pszHostVersion);
+# endif /* VBOX_OSE */
+ rc = VBClShowNotify(szTitle, szMsg);
+ }
+
+ /* Store host version to not notify again */
+ int rc2 = VbglR3HostVersionLastCheckedStore(uGuestPropSvcClientID, pszHostVersion);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ VbglR3GuestPropReadValueFree(pszHostVersion);
+ VbglR3GuestPropReadValueFree(pszGuestVersion);
+ }
+
+ VbglR3GuestPropDisconnect(uGuestPropSvcClientID);
+#else /* !VBOX_WITH_GUEST_PROPS */
+ rc = VERR_NOT_SUPPORTED;
+#endif /* VBOX_WITH_GUEST_PROPS */
+
+ return rc;
+}
+
+VBCLSERVICE g_SvcHostVersion =
+{
+ "hostversion", /* szName */
+ "VirtualBox host version check", /* pszDescription */
+ ".vboxclient-hostversion", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ NULL, /* pfnInit */
+ vbclHostVerWorker, /* pfnWorker */
+ NULL, /* pfnStop*/
+ NULL /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/logging.cpp b/src/VBox/Additions/x11/VBoxClient/logging.cpp
new file mode 100644
index 00000000..6591e2fc
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/logging.cpp
@@ -0,0 +1,458 @@
+/* $Id: logging.cpp $ */
+/** @file
+ * VirtualBox Guest Additions - X11 Client.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+#include <sys/wait.h>
+#include <stdlib.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/file.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/system.h>
+
+#ifdef VBOX_WITH_DBUS
+# include <VBox/dbus.h>
+#endif
+#include <VBox/VBoxGuestLib.h>
+
+#include <package-generated.h>
+#include "VBoxClient.h"
+
+/** 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. */
+
+/** Custom log prefix (to be set externally). */
+static char *g_pszCustomLogPrefix;
+
+extern unsigned g_cRespawn;
+
+
+/**
+ * Fallback notification helper using 'notify-send'.
+ *
+ * @returns VBox status code.
+ * @returns VERR_NOT_SUPPORTED if 'notify-send' is not available, or there was an error while running 'notify-send'.
+ * @param pszMessage Message to notify desktop environment with.
+ */
+int vbclNotifyFallbackNotifySend(const char *pszMessage)
+{
+ AssertPtrReturn(pszMessage, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (g_cRespawn == 0)
+ {
+ char *pszCommand = RTStrAPrintf2("notify-send \"VBoxClient: %s\"", pszMessage);
+ if (pszCommand)
+ {
+ int status = system(pszCommand);
+
+ RTStrFree(pszCommand);
+
+ if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
+ {
+ pszCommand = RTStrAPrintf2("xmessage -buttons OK:0 -center \"VBoxClient: %s\"",
+ pszMessage);
+ if (pszCommand)
+ {
+ status = system(pszCommand);
+ if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */
+ rc = VERR_NOT_SUPPORTED;
+
+ RTStrFree(pszCommand);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Shows a notification on the desktop.
+ *
+ * @returns VBox status code.
+ * @returns VERR_NOT_SUPPORTED if the current desktop environment is not supported.
+ * @param pszHeader Header text to show.
+ * @param pszBody Body text to show.
+ *
+ * @note How this notification will look like depends on the actual desktop environment implementing
+ * the actual notification service. Currently only D-BUS-compatible environments are supported.
+ *
+ * Most notification implementations have length limits on their header / body texts, so keep
+ * the text(s) short.
+ */
+int VBClShowNotify(const char *pszHeader, const char *pszBody)
+{
+ AssertPtrReturn(pszHeader, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszBody, VERR_INVALID_POINTER);
+
+ int rc;
+# ifdef VBOX_WITH_DBUS
+ rc = RTDBusLoadLib(); /** @todo Does this init / load the lib only once? Need to check this. */
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("D-Bus seems not to be installed; no desktop notifications available\n");
+ return rc;
+ }
+
+ DBusConnection *conn;
+ DBusMessage* msg = NULL;
+ conn = dbus_bus_get(DBUS_BUS_SESSION, NULL);
+ if (conn == NULL)
+ {
+ VBClLogError("Could not retrieve D-BUS session bus\n");
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ {
+ msg = dbus_message_new_method_call("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "Notify");
+ if (msg == NULL)
+ {
+ VBClLogError("Could not create D-BUS message!\n");
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t msg_replace_id = 0;
+ const char *msg_app = "VBoxClient";
+ const char *msg_icon = "";
+ const char *msg_summary = pszHeader;
+ const char *msg_body = pszBody;
+ int32_t msg_timeout = -1; /* Let the notification server decide */
+
+ DBusMessageIter iter;
+ DBusMessageIter array;
+ /*DBusMessageIter dict; - unused */
+ /*DBusMessageIter value; - unused */
+ /*DBusMessageIter variant; - unused */
+ /*DBusMessageIter data; - unused */
+
+ /* Format: UINT32 org.freedesktop.Notifications.Notify
+ * (STRING app_name, UINT32 replaces_id, STRING app_icon, STRING summary, STRING body,
+ * ARRAY actions, DICT hints, INT32 expire_timeout)
+ */
+ dbus_message_iter_init_append(msg,&iter);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_app);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_UINT32,&msg_replace_id);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_icon);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_summary);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_body);
+ dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING,&array);
+ dbus_message_iter_close_container(&iter,&array);
+ dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,"{sv}",&array);
+ dbus_message_iter_close_container(&iter,&array);
+ dbus_message_iter_append_basic(&iter,DBUS_TYPE_INT32,&msg_timeout);
+
+ DBusError err;
+ dbus_error_init(&err);
+
+ DBusMessage *reply;
+ reply = dbus_connection_send_with_reply_and_block(conn, msg, 30 * 1000 /* 30 seconds timeout */, &err);
+ if (dbus_error_is_set(&err))
+ VBClLogError("D-BUS returned an error while sending the notification: %s", err.message);
+ else if (reply)
+ {
+ dbus_connection_flush(conn);
+ dbus_message_unref(reply);
+ }
+ if (dbus_error_is_set(&err))
+ dbus_error_free(&err);
+ }
+ if (msg != NULL)
+ dbus_message_unref(msg);
+# else
+ /** @todo Implement me */
+ RT_NOREF(pszHeader, pszBody);
+ rc = VERR_NOT_SUPPORTED;
+# endif /* VBOX_WITH_DBUS */
+
+ /* Try to use a fallback if the stuff above fails or is not available. */
+ if (RT_FAILURE(rc))
+ rc = vbclNotifyFallbackNotifySend(pszBody);
+
+ /* If everything fails, still print out our notification to stdout, in the hope
+ * someone still gets aware of it. */
+ if (RT_FAILURE(rc))
+ VBClLogInfo("*** Notification: %s - %s ***\n", pszHeader, pszBody);
+
+ return rc;
+}
+
+
+
+/**
+ * Logs a verbose message.
+ *
+ * @param pszFormat The message text.
+ * @param va Format arguments.
+ */
+static void vbClLogV(const char *pszFormat, va_list va)
+{
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, va);
+ AssertPtrReturnVoid(psz);
+ LogRel(("%s", psz));
+ RTStrFree(psz);
+}
+
+/**
+ * Logs a fatal error, notifies the desktop environment via a message and
+ * exits the application immediately.
+ *
+ * @param pszFormat Format string to log.
+ * @param ... Variable arguments for format string. Optional.
+ */
+void VBClLogFatalError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtrReturnVoid(psz);
+ LogFunc(("Fatal Error: %s", psz));
+ LogRel(("Fatal Error: %s", psz));
+
+ VBClShowNotify("VBoxClient - Fatal Error", psz);
+
+ RTStrFree(psz);
+}
+
+/**
+ * Logs an error message to the (release) logging instance.
+ *
+ * @param pszFormat Format string to log.
+ */
+void VBClLogError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtrReturnVoid(psz);
+ LogFunc(("Error: %s", psz));
+ LogRel(("Error: %s", psz));
+
+ RTStrFree(psz);
+}
+
+/**
+ * Logs an info message to the (release) logging instance.
+ *
+ * @param pszFormat Format string to log.
+ */
+void VBClLogInfo(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ vbClLogV(pszFormat, args);
+ va_end(args);
+}
+
+/**
+ * Displays a verbose message based on the currently
+ * set global verbosity level.
+ *
+ * @param iLevel Minimum log level required to display this message.
+ * @param pszFormat The message text.
+ * @param ... Format arguments.
+ */
+void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...)
+{
+ if (iLevel <= g_cVerbosity)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ vbClLogV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+/**
+ * @callback_method_impl{FNRTLOGPHASE, Release logger callback}
+ */
+static DECLCALLBACK(void) vbClLogHeaderFooter(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,
+ "VBoxClient %s r%s (verbosity: %u) %s (%s %s) release log\n"
+ "Log opened %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, 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);
+ vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(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 */
+ break;
+ }
+}
+
+static DECLCALLBACK(size_t) vbClLogPrefixCb(PRTLOGGER pLogger, char *pchBuf, size_t cchBuf, void *pvUser)
+{
+ size_t cbPrefix = 0;
+
+ RT_NOREF(pLogger);
+ RT_NOREF(pvUser);
+
+ if (g_pszCustomLogPrefix)
+ {
+ cbPrefix = RT_MIN(strlen(g_pszCustomLogPrefix), cchBuf);
+ memcpy(pchBuf, g_pszCustomLogPrefix, cbPrefix);
+ }
+
+ return cbPrefix;
+}
+
+/**
+ * Creates the default release logger outputting to the specified file.
+ *
+ * Pass NULL to disabled logging.
+ *
+ * @return IPRT status code.
+ * @param pszLogFile Filename for log output. NULL disables custom handling.
+ */
+int VBClLogCreate(const char *pszLogFile)
+{
+ if (!pszLogFile)
+ return VINF_SUCCESS;
+
+ /* Create release logger (stdout + file). */
+ static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES;
+ RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME | RTLOGFLAGS_PREFIX_CUSTOM;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ fFlags |= RTLOGFLAGS_USECRLF;
+#endif
+ int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXCLIENT_RELEASE_LOG", fFlags, "all",
+ RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER,
+ vbClLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : "");
+ if (RT_SUCCESS(rc))
+ {
+ /* register this logger as the release logger */
+ RTLogRelSetDefaultInstance(g_pLoggerRelease);
+
+ rc = RTLogSetCustomPrefixCallback(g_pLoggerRelease, vbClLogPrefixCb, NULL);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to register custom log prefix callback\n");
+
+ /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */
+ RTLogFlush(g_pLoggerRelease);
+ }
+
+ return rc;
+}
+
+/**
+ * Set custom log prefix.
+ *
+ * @param pszPrefix Custom log prefix string.
+ */
+void VBClLogSetLogPrefix(const char *pszPrefix)
+{
+ g_pszCustomLogPrefix = (char *)pszPrefix;
+}
+
+/**
+ * Destroys the currently active logging instance.
+ */
+void VBClLogDestroy(void)
+{
+ RTLogDestroy(RTLogRelSetDefaultInstance(NULL));
+}
+
diff --git a/src/VBox/Additions/x11/VBoxClient/main.cpp b/src/VBox/Additions/x11/VBoxClient/main.cpp
new file mode 100644
index 00000000..f276223a
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/main.cpp
@@ -0,0 +1,885 @@
+/* $Id: main.cpp $ */
+/** @file
+ * VirtualBox Guest Additions - X11 Client.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <sys/wait.h>
+#include <stdlib.h> /* For exit */
+#include <signal.h>
+#include <X11/Xlib.h>
+#include "product-generated.h"
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/critsect.h>
+#include <iprt/errno.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/env.h>
+#include <iprt/process.h>
+#include <iprt/linux/sysfs.h>
+#include <VBox/VBoxGuestLib.h>
+#include <VBox/err.h>
+#include <VBox/version.h>
+#include "VBoxClient.h"
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define VBOXCLIENT_OPT_SERVICES 980
+#define VBOXCLIENT_OPT_CHECKHOSTVERSION VBOXCLIENT_OPT_SERVICES
+#define VBOXCLIENT_OPT_CLIPBOARD VBOXCLIENT_OPT_SERVICES + 1
+#define VBOXCLIENT_OPT_DRAGANDDROP VBOXCLIENT_OPT_SERVICES + 2
+#define VBOXCLIENT_OPT_SEAMLESS VBOXCLIENT_OPT_SERVICES + 3
+#define VBOXCLIENT_OPT_VMSVGA VBOXCLIENT_OPT_SERVICES + 4
+#define VBOXCLIENT_OPT_VMSVGA_SESSION VBOXCLIENT_OPT_SERVICES + 5
+#define VBOXCLIENT_OPT_DISPLAY VBOXCLIENT_OPT_SERVICES + 6
+
+
+/*********************************************************************************************************************************
+* Local structures *
+*********************************************************************************************************************************/
+/**
+ * The global service state.
+ */
+typedef struct VBCLSERVICESTATE
+{
+ /** Pointer to the service descriptor. */
+ PVBCLSERVICE pDesc;
+ /** The worker thread. NIL_RTTHREAD if it's the main thread. */
+ RTTHREAD Thread;
+ /** Whether Pre-init was called. */
+ bool fPreInited;
+ /** Shutdown indicator. */
+ bool volatile fShutdown;
+ /** Indicator set by the service thread exiting. */
+ bool volatile fStopped;
+ /** Whether the service was started or not. */
+ bool fStarted;
+} VBCLSERVICESTATE;
+/** Pointer to a service state. */
+typedef VBCLSERVICESTATE *PVBCLSERVICESTATE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The global service state. */
+VBCLSERVICESTATE g_Service = { 0 };
+
+/** Set by the signal handler when being called. */
+static volatile bool g_fSignalHandlerCalled = false;
+/** Critical section for the signal handler. */
+static RTCRITSECT g_csSignalHandler;
+/** Flag indicating Whether the service starts in daemonized mode or not. */
+bool g_fDaemonized = false;
+/** The name of our pidfile. It is global for the benefit of the cleanup
+ * routine. */
+static char g_szPidFile[RTPATH_MAX] = "";
+/** The file handle of our pidfile. It is global for the benefit of the
+ * cleanup routine. */
+static RTFILE g_hPidFile;
+/** The name of pidfile for parent (control) process. */
+static char g_szControlPidFile[RTPATH_MAX] = "";
+/** The file handle of parent process pidfile. */
+static RTFILE g_hControlPidFile;
+
+/** Global critical section held during the clean-up routine (to prevent it
+ * being called on multiple threads at once) or things which may not happen
+ * during clean-up (e.g. pausing and resuming the service).
+ */
+static RTCRITSECT g_critSect;
+/** Counter of how often our daemon has been respawned. */
+unsigned g_cRespawn = 0;
+/** Logging verbosity level. */
+unsigned g_cVerbosity = 0;
+/** Absolute path to log file, if any. */
+static char g_szLogFile[RTPATH_MAX + 128] = "";
+/** Set by the signal handler when SIGUSR1 received. */
+static volatile bool g_fProcessReloadRequested = false;
+
+/**
+ * Tries to determine if the session parenting this process is of Xwayland.
+ * NB: XDG_SESSION_TYPE is a systemd(1) environment variable and is unlikely
+ * set in non-systemd environments or remote logins.
+ * Therefore we check the Wayland specific display environment variable first.
+ */
+bool VBClHasWayland(void)
+{
+ const char *const pDisplayType = RTEnvGet(VBCL_ENV_WAYLAND_DISPLAY);
+ const char *pSessionType;
+
+ if (pDisplayType != NULL)
+ return true;
+
+ pSessionType = RTEnvGet(VBCL_ENV_XDG_SESSION_TYPE);
+ if ((pSessionType != NULL) && (RTStrIStartsWith(pSessionType, "wayland")))
+ return true;
+
+ return false;
+}
+
+/**
+ * Shut down if we get a signal or something.
+ *
+ * This is extern so that we can call it from other compilation units.
+ */
+void VBClShutdown(bool fExit /*=true*/)
+{
+ /* We never release this, as we end up with a call to exit(3) which is not
+ * async-safe. Unless we fix this application properly, we should be sure
+ * never to exit from anywhere except from this method. */
+ int rc = RTCritSectEnter(&g_critSect);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failure while acquiring the global critical section, rc=%Rrc\n", rc);
+
+ /* Ask service to stop. */
+ if (g_Service.pDesc &&
+ g_Service.pDesc->pfnStop)
+ {
+ ASMAtomicWriteBool(&g_Service.fShutdown, true);
+ g_Service.pDesc->pfnStop();
+
+ }
+
+ if (g_szPidFile[0] && g_hPidFile)
+ VbglR3ClosePidFile(g_szPidFile, g_hPidFile);
+
+ VBClLogDestroy();
+
+ if (fExit)
+ exit(RTEXITCODE_SUCCESS);
+}
+
+/**
+ * Xlib error handler for certain errors that we can't avoid.
+ */
+static int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError)
+{
+ char errorText[1024];
+
+ XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText));
+ VBClLogError("An X Window protocol error occurred: %s (error code %d). Request code: %d, minor code: %d, serial number: %d\n", errorText, pError->error_code, pError->request_code, pError->minor_code, pError->serial);
+ return 0;
+}
+
+/**
+ * Xlib error handler for fatal errors. This often means that the programme is still running
+ * when X exits.
+ */
+static int vboxClientXLibIOErrorHandler(Display *pDisplay)
+{
+ RT_NOREF1(pDisplay);
+ VBClLogError("A fatal guest X Window error occurred. This may just mean that the Window system was shut down while the client was still running\n");
+ VBClShutdown();
+ return 0; /* We should never reach this. */
+}
+
+/**
+ * A standard signal handler which cleans up and exits.
+ */
+static void vboxClientSignalHandler(int iSignal)
+{
+ int rc = RTCritSectEnter(&g_csSignalHandler);
+ if (RT_SUCCESS(rc))
+ {
+ if (g_fSignalHandlerCalled)
+ {
+ RTCritSectLeave(&g_csSignalHandler);
+ return;
+ }
+
+ VBClLogVerbose(2, "Received signal %d\n", iSignal);
+ g_fSignalHandlerCalled = true;
+
+ /* In our internal convention, when VBoxClient process receives SIGUSR1,
+ * this is a trigger for restarting a process with exec() call. Usually
+ * happens as a result of Guest Additions update in order to seamlessly
+ * restart newly installed binaries. */
+ if (iSignal == SIGUSR1)
+ g_fProcessReloadRequested = true;
+
+ /* Leave critical section before stopping the service. */
+ RTCritSectLeave(&g_csSignalHandler);
+
+ if ( g_Service.pDesc
+ && g_Service.pDesc->pfnStop)
+ {
+ VBClLogVerbose(2, "Notifying service to stop ...\n");
+
+ /* Signal the service to stop. */
+ ASMAtomicWriteBool(&g_Service.fShutdown, true);
+
+ g_Service.pDesc->pfnStop();
+
+ VBClLogVerbose(2, "Service notified to stop, waiting on worker thread to stop ...\n");
+ }
+ }
+}
+
+/**
+ * Reset all standard termination signals to call our signal handler.
+ */
+static int vboxClientSignalHandlerInstall(void)
+{
+ struct sigaction sigAction;
+ sigAction.sa_handler = vboxClientSignalHandler;
+ sigemptyset(&sigAction.sa_mask);
+ sigAction.sa_flags = 0;
+ sigaction(SIGHUP, &sigAction, NULL);
+ sigaction(SIGINT, &sigAction, NULL);
+ sigaction(SIGQUIT, &sigAction, NULL);
+ sigaction(SIGPIPE, &sigAction, NULL);
+ sigaction(SIGALRM, &sigAction, NULL);
+ sigaction(SIGTERM, &sigAction, NULL);
+ sigaction(SIGUSR1, &sigAction, NULL);
+ sigaction(SIGUSR2, &sigAction, NULL);
+
+ return RTCritSectInit(&g_csSignalHandler);
+}
+
+/**
+ * Uninstalls a previously installed signal handler.
+ */
+static int vboxClientSignalHandlerUninstall(void)
+{
+ signal(SIGTERM, SIG_DFL);
+#ifdef SIGBREAK
+ signal(SIGBREAK, SIG_DFL);
+#endif
+
+ return RTCritSectDelete(&g_csSignalHandler);
+}
+
+/**
+ * Print out a usage message and exit with success.
+ */
+static void vboxClientUsage(const char *pcszFileName)
+{
+ RTPrintf(VBOX_PRODUCT " VBoxClient "
+ VBOX_VERSION_STRING "\n"
+ "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n");
+
+ RTPrintf("Usage: %s "
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ "--clipboard|"
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ "--draganddrop|"
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+ "--checkhostversion|"
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ "--seamless|"
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ "--vmsvga|"
+ "--vmsvga-session"
+#endif
+ "\n[-d|--nodaemon]\n", pcszFileName);
+ RTPrintf("\n");
+ RTPrintf("Options:\n");
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ RTPrintf(" --clipboard starts the shared clipboard service\n");
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ RTPrintf(" --draganddrop starts the drag and drop service\n");
+#endif
+#ifdef VBOX_WITH_GUEST_PROPS
+ RTPrintf(" --checkhostversion starts the host version notifier service\n");
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ RTPrintf(" --seamless starts the seamless windows service\n");
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ RTPrintf(" --vmsvga starts VMSVGA dynamic resizing for X11/Wayland guests\n");
+#ifdef RT_OS_LINUX
+ RTPrintf(" --vmsvga-session starts Desktop Environment specific screen assistant for X11/Wayland guests\n"
+ " (VMSVGA graphics adapter only)\n");
+#else
+ RTPrintf(" --vmsvga-session an alias for --vmsvga\n");
+#endif
+ RTPrintf(" --display starts VMSVGA dynamic resizing for legacy guests\n");
+#endif
+ RTPrintf(" -f, --foreground run in the foreground (no daemonizing)\n");
+ RTPrintf(" -d, --nodaemon continues running as a system service\n");
+ RTPrintf(" -h, --help shows this help text\n");
+ RTPrintf(" -l, --logfile <path> enables logging to a file\n");
+ RTPrintf(" -v, --verbose increases logging verbosity level\n");
+ RTPrintf(" -V, --version shows version information\n");
+ RTPrintf("\n");
+}
+
+/**
+ * Complains about seeing more than one service specification.
+ *
+ * @returns RTEXITCODE_SYNTAX.
+ */
+static int vbclSyntaxOnlyOneService(void)
+{
+ RTMsgError("More than one service specified! Only one, please.");
+ return RTEXITCODE_SYNTAX;
+}
+
+/**
+ * The service thread.
+ *
+ * @returns Whatever the worker function returns.
+ * @param ThreadSelf My thread handle.
+ * @param pvUser The service index.
+ */
+static DECLCALLBACK(int) vbclThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ PVBCLSERVICESTATE pState = (PVBCLSERVICESTATE)pvUser;
+ AssertPtrReturn(pState, VERR_INVALID_POINTER);
+
+#ifndef RT_OS_WINDOWS
+ /*
+ * Block all signals for this thread. Only the main thread will handle signals.
+ */
+ sigset_t signalMask;
+ sigfillset(&signalMask);
+ pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+#endif
+
+ AssertPtrReturn(pState->pDesc->pfnWorker, VERR_INVALID_POINTER);
+ int rc = pState->pDesc->pfnWorker(&pState->fShutdown);
+
+ VBClLogVerbose(2, "Worker loop ended with %Rrc\n", rc);
+
+ ASMAtomicXchgBool(&pState->fShutdown, true);
+ RTThreadUserSignal(ThreadSelf);
+ return rc;
+}
+
+/**
+ * Wait for SIGUSR1 and re-exec.
+ */
+static void vbclHandleUpdateStarted(char *const argv[])
+{
+ /* Context of parent process */
+ sigset_t signalMask;
+ int iSignal;
+ int rc;
+
+ /* Release reference to guest driver. */
+ VbglR3Term();
+
+ sigemptyset(&signalMask);
+ sigaddset(&signalMask, SIGUSR1);
+ rc = pthread_sigmask(SIG_BLOCK, &signalMask, NULL);
+
+ if (rc == 0)
+ {
+ LogRel(("%s: waiting for Guest Additions installation to be completed\n",
+ g_Service.pDesc->pszDesc));
+
+ /* Wait for SIGUSR1. */
+ rc = sigwait(&signalMask, &iSignal);
+ if (rc == 0)
+ {
+ LogRel(("%s: Guest Additions installation to be completed, reloading service\n",
+ g_Service.pDesc->pszDesc));
+
+ /* Release pidfile, otherwise new VBoxClient instance won't be able to quire it. */
+ VBClShutdown(false);
+
+ rc = RTProcCreate(argv[0], argv, RTENV_DEFAULT,
+ RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL);
+ if (RT_SUCCESS(rc))
+ LogRel(("%s: service restarted\n", g_Service.pDesc->pszDesc));
+ else
+ LogRel(("%s: cannot replace running image with %s (%s), automatic service reloading has failed\n",
+ g_Service.pDesc->pszDesc, argv[0], strerror(errno)));
+ }
+ else
+ LogRel(("%s: cannot wait for signal (%s), automatic service reloading has failed\n",
+ g_Service.pDesc->pszDesc, strerror(errno)));
+ }
+ else
+ LogRel(("%s: failed to setup signal handler, automatic service reloading has failed\n",
+ g_Service.pDesc->pszDesc));
+
+ exit(RT_BOOL(rc != 0));
+}
+
+/**
+ * Compose pidfile name.
+ *
+ * @returns IPRT status code.
+ * @param szBuf Buffer to store pidfile name into.
+ * @param cbBuf Size of buffer.
+ * @param szTemplate Null-terminated string which contains pidfile name.
+ * @param fParentProcess Whether pidfile path should be composed for
+ * parent (control) process or for a child (actual
+ * service) process.
+ */
+static int vbclGetPidfileName(char *szBuf, size_t cbBuf, const char *szTemplate,
+ bool fParentProcess)
+{
+ int rc;
+ char pszActiveTTY[128];
+ size_t cchRead;
+
+ RT_ZERO(pszActiveTTY);
+
+ AssertPtrReturn(szBuf, VERR_INVALID_PARAMETER);
+ AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(szTemplate, VERR_INVALID_PARAMETER);
+
+ rc = RTPathUserHome(szBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("%s: getting home directory failed: %Rrc\n",
+ g_Service.pDesc->pszDesc, rc);
+
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szBuf, cbBuf, szTemplate);
+
+#ifdef RT_OS_LINUX
+ if (RT_SUCCESS(rc))
+ rc = RTLinuxSysFsReadStrFile(pszActiveTTY, sizeof(pszActiveTTY) - 1 /* reserve last byte for string termination */,
+ &cchRead, "class/tty/tty0/active");
+ if (RT_SUCCESS(rc))
+ {
+ RTStrCat(szBuf, cbBuf, "-");
+ RTStrCat(szBuf, cbBuf, pszActiveTTY);
+ }
+ else
+ VBClLogInfo("%s: cannot detect currently active tty device, "
+ "multiple service instances for a single user will not be allowed, rc=%Rrc",
+ g_Service.pDesc->pszDesc, rc);
+#endif /* RT_OS_LINUX */
+
+ if (RT_SUCCESS(rc))
+ RTStrCat(szBuf, cbBuf, fParentProcess ? "-control.pid" : "-service.pid");
+
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("%s: reating PID file path failed: %Rrc\n",
+ g_Service.pDesc->pszDesc, rc);
+
+ return rc;
+}
+
+/**
+ * The main loop for the VBoxClient daemon.
+ */
+int main(int argc, char *argv[])
+{
+ /* Note: No VBClLogXXX calls before actually creating the log. */
+
+ /* Initialize our runtime before all else. */
+ int rc = RTR3InitExe(argc, &argv, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgInitFailure(rc);
+
+ /* A flag which is returned to the parent process when Guest Additions update started. */
+ bool fUpdateStarted = false;
+
+ /* This should never be called twice in one process - in fact one Display
+ * object should probably never be used from multiple threads anyway. */
+ if (!XInitThreads())
+ return RTMsgErrorExitFailure("Failed to initialize X11 threads\n");
+
+ /* Get our file name for usage info and hints. */
+ const char *pcszFileName = RTPathFilename(argv[0]);
+ if (!pcszFileName)
+ pcszFileName = "VBoxClient";
+
+ /* Parse our option(s). */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--nodaemon", 'd', RTGETOPT_REQ_NOTHING },
+ { "--foreground", 'f', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--logfile", 'l', RTGETOPT_REQ_STRING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+
+ /* Services */
+#ifdef VBOX_WITH_GUEST_PROPS
+ { "--checkhostversion", VBOXCLIENT_OPT_CHECKHOSTVERSION, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ { "--clipboard", VBOXCLIENT_OPT_CLIPBOARD, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ { "--draganddrop", VBOXCLIENT_OPT_DRAGANDDROP, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ { "--seamless", VBOXCLIENT_OPT_SEAMLESS, RTGETOPT_REQ_NOTHING },
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ { "--vmsvga", VBOXCLIENT_OPT_VMSVGA, RTGETOPT_REQ_NOTHING },
+ { "--vmsvga-session", VBOXCLIENT_OPT_VMSVGA_SESSION, RTGETOPT_REQ_NOTHING },
+ { "--display", VBOXCLIENT_OPT_DISPLAY, RTGETOPT_REQ_NOTHING },
+#endif
+ };
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to parse command line options, rc=%Rrc\n", rc);
+
+ AssertRC(rc);
+
+ bool fDaemonise = true;
+ bool fRespawn = true;
+
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ /* For options that require an argument, ValueUnion has received the value. */
+ switch (ch)
+ {
+ case 'd':
+ {
+ fDaemonise = false;
+ break;
+ }
+
+ case 'h':
+ {
+ vboxClientUsage(pcszFileName);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case 'f':
+ {
+ fDaemonise = false;
+ fRespawn = false;
+ break;
+ }
+
+ case 'l':
+ {
+ rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Unable to set log file path, rc=%Rrc\n", rc);
+ break;
+ }
+
+ case 'n':
+ {
+ fRespawn = false;
+ break;
+ }
+
+ case 'v':
+ {
+ g_cVerbosity++;
+ break;
+ }
+
+ case 'V':
+ {
+ RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ return RTEXITCODE_SUCCESS;
+ }
+
+ /* Services */
+#ifdef VBOX_WITH_GUEST_PROPS
+ case VBOXCLIENT_OPT_CHECKHOSTVERSION:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcHostVersion;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_SHARED_CLIPBOARD
+ case VBOXCLIENT_OPT_CLIPBOARD:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcClipboard;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_DRAG_AND_DROP
+ case VBOXCLIENT_OPT_DRAGANDDROP:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcDragAndDrop;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_SEAMLESS
+ case VBOXCLIENT_OPT_SEAMLESS:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcSeamless;
+ break;
+ }
+#endif
+#ifdef VBOX_WITH_VMSVGA
+ case VBOXCLIENT_OPT_VMSVGA:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcDisplaySVGA;
+ break;
+ }
+
+ case VBOXCLIENT_OPT_VMSVGA_SESSION:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+# ifdef RT_OS_LINUX
+ g_Service.pDesc = &g_SvcDisplaySVGASession;
+# else
+ g_Service.pDesc = &g_SvcDisplaySVGA;
+# endif
+ break;
+ }
+
+ case VBOXCLIENT_OPT_DISPLAY:
+ {
+ if (g_Service.pDesc)
+ return vbclSyntaxOnlyOneService();
+ g_Service.pDesc = &g_SvcDisplayLegacy;
+ break;
+ }
+#endif
+ case VINF_GETOPT_NOT_OPTION:
+ break;
+
+ case VERR_GETOPT_UNKNOWN_OPTION:
+ RT_FALL_THROUGH();
+ default:
+ {
+ if ( g_Service.pDesc
+ && g_Service.pDesc->pfnOption)
+ {
+ rc = g_Service.pDesc->pfnOption(NULL, argc, argv, &GetState.iNext);
+ }
+ else /* No service specified yet. */
+ rc = VERR_NOT_FOUND;
+
+ if (RT_FAILURE(rc))
+ {
+ RTMsgError("unrecognized option '%s'", ValueUnion.psz);
+ RTMsgInfo("Try '%s --help' for more information", pcszFileName);
+ return RTEXITCODE_SYNTAX;
+ }
+ break;
+ }
+
+ } /* switch */
+ } /* while RTGetOpt */
+
+ if (!g_Service.pDesc)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No service specified. Quitting because nothing to do!");
+
+ /* Initialize VbglR3 before we do anything else with the logger. */
+ rc = VbglR3InitUser();
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("VbglR3InitUser failed: %Rrc", rc);
+
+ rc = VBClLogCreate(g_szLogFile[0] ? g_szLogFile : "");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to create release log '%s', rc=%Rrc\n",
+ g_szLogFile[0] ? g_szLogFile : "<None>", rc);
+
+ if (!fDaemonise)
+ {
+ /* If the user is running in "no daemon" mode, send critical logging to stdout as well. */
+ PRTLOGGER pReleaseLog = RTLogRelGetDefaultInstance();
+ if (pReleaseLog)
+ {
+ rc = RTLogDestinations(pReleaseLog, "stdout");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to redivert error output, rc=%Rrc", rc);
+ }
+ }
+
+ VBClLogInfo("VBoxClient %s r%s started. Verbose level = %d. Wayland environment detected: %s\n",
+ RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBClHasWayland() ? "yes" : "no");
+ VBClLogInfo("Service: %s\n", g_Service.pDesc->pszDesc);
+
+ rc = RTCritSectInit(&g_critSect);
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Initializing critical section failed: %Rrc\n", rc);
+ if (g_Service.pDesc->pszPidFilePathTemplate)
+ {
+ /* Get pidfile name for parent (control) process. */
+ rc = vbclGetPidfileName(g_szControlPidFile, sizeof(g_szControlPidFile), g_Service.pDesc->pszPidFilePathTemplate, true);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+
+ /* Get pidfile name for service process. */
+ rc = vbclGetPidfileName(g_szPidFile, sizeof(g_szPidFile), g_Service.pDesc->pszPidFilePathTemplate, false);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (fDaemonise)
+ {
+ rc = VbglR3DaemonizeEx(false /* fNoChDir */, false /* fNoClose */, fRespawn, &g_cRespawn,
+ true /* fReturnOnUpdate */, &fUpdateStarted, g_szControlPidFile, &g_hControlPidFile);
+ /* This combination only works in context of parent process. */
+ if (RT_SUCCESS(rc) && fUpdateStarted)
+ vbclHandleUpdateStarted(argv);
+ }
+
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Daemonizing service failed: %Rrc\n", rc);
+
+ if (g_szPidFile[0])
+ {
+ rc = VbglR3PidFile(g_szPidFile, &g_hPidFile);
+ if (rc == VERR_FILE_LOCK_VIOLATION) /* Already running. */
+ {
+ VBClLogInfo("%s: service already running, exitting\n",
+ g_Service.pDesc->pszDesc);
+ return RTEXITCODE_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ {
+ VBClLogFatalError("Creating PID file %s failed: %Rrc\n", g_szPidFile, rc);
+ return RTEXITCODE_FAILURE;
+ }
+ }
+
+#ifndef VBOXCLIENT_WITHOUT_X11
+ /* Set an X11 error handler, so that we don't die when we get unavoidable
+ * errors. */
+ XSetErrorHandler(vboxClientXLibErrorHandler);
+ /* Set an X11 I/O error handler, so that we can shutdown properly on
+ * fatal errors. */
+ XSetIOErrorHandler(vboxClientXLibIOErrorHandler);
+#endif
+
+ bool fSignalHandlerInstalled = false;
+ if (RT_SUCCESS(rc))
+ {
+ rc = vboxClientSignalHandlerInstall();
+ if (RT_SUCCESS(rc))
+ fSignalHandlerInstalled = true;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && g_Service.pDesc->pfnInit)
+ {
+ VBClLogInfo("Initializing service ...\n");
+ rc = g_Service.pDesc->pfnInit();
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogInfo("Creating worker thread ...\n");
+ rc = RTThreadCreate(&g_Service.Thread, vbclThread, (void *)&g_Service, 0,
+ RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_Service.pDesc->pszName);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("Creating worker thread failed, rc=%Rrc\n", rc);
+ }
+ else
+ {
+ g_Service.fStarted = true;
+
+ /* Wait for the thread to initialize. */
+ /** @todo There is a race between waiting and checking
+ * the fShutdown flag of a thread here and processing
+ * the thread's actual worker loop. If the thread decides
+ * to exit the loop before we skipped the fShutdown check
+ * below the service will fail to start! */
+ /** @todo This presumably means either a one-shot service or that
+ * something has gone wrong. In the second case treating it as failure
+ * to start is probably right, so we need a way to signal the first
+ * rather than leaving the idle thread hanging around. A flag in the
+ * service description? */
+ RTThreadUserWait(g_Service.Thread, RT_MS_1MIN);
+ if (g_Service.fShutdown)
+ {
+ VBClLogError("Service failed to start!\n");
+ rc = VERR_GENERAL_FAILURE;
+ }
+ else
+ {
+ VBClLogInfo("Service started\n");
+
+ int rcThread;
+ rc = RTThreadWait(g_Service.Thread, RT_INDEFINITE_WAIT, &rcThread);
+ if (RT_SUCCESS(rc))
+ rc = rcThread;
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Waiting on worker thread to stop failed, rc=%Rrc\n", rc);
+
+ if (g_Service.pDesc->pfnTerm)
+ {
+ VBClLogInfo("Terminating service\n");
+
+ int rc2 = g_Service.pDesc->pfnTerm();
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ if (RT_SUCCESS(rc))
+ {
+ VBClLogInfo("Service terminated\n");
+ }
+ else
+ VBClLogError("Service failed to terminate, rc=%Rrc\n", rc);
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NOT_AVAILABLE)
+ VBClLogInfo("Service is not availabe, skipping\n");
+ else if (rc == VERR_NOT_SUPPORTED)
+ VBClLogInfo("Service is not supported on this platform, skipping\n");
+ else
+ VBClLogError("Service ended with error %Rrc\n", rc);
+ }
+ else
+ VBClLogVerbose(2, "Service ended\n");
+
+ if (fSignalHandlerInstalled)
+ {
+ int rc2 = vboxClientSignalHandlerUninstall();
+ AssertRC(rc2);
+ }
+
+ VBClShutdown(false /*fExit*/);
+
+ /** @todo r=andy Should we return an appropriate exit code if the service failed to init?
+ * Must be tested carefully with our init scripts first. */
+ return g_fProcessReloadRequested ? VBGLR3EXITCODERELOAD : RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp b/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp
new file mode 100644
index 00000000..e4a88e7d
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp
@@ -0,0 +1,568 @@
+/* $Id: seamless-x11.cpp $ */
+/** @file
+ * X11 Seamless mode.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header files *
+*********************************************************************************************************************************/
+
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include <iprt/vector.h>
+#include <iprt/thread.h>
+#include <VBox/log.h>
+
+#include "seamless-x11.h"
+#include "VBoxClient.h"
+
+#include <X11/Xatom.h>
+#include <X11/Xmu/WinUtil.h>
+
+#include <limits.h>
+
+#ifdef TESTCASE
+#undef DefaultRootWindow
+#define DefaultRootWindow XDefaultRootWindow
+#endif
+
+/*****************************************************************************
+* Static functions *
+*****************************************************************************/
+
+static unsigned char *XXGetProperty(Display *aDpy, Window aWnd, Atom aPropType,
+ const char *aPropName, unsigned long *nItems)
+{
+ LogRelFlowFuncEnter();
+ Atom propNameAtom = XInternAtom (aDpy, aPropName,
+ True /* only_if_exists */);
+ if (propNameAtom == None)
+ {
+ return NULL;
+ }
+
+ Atom actTypeAtom = None;
+ int actFmt = 0;
+ unsigned long nBytesAfter = 0;
+ unsigned char *propVal = 0;
+ int rc = XGetWindowProperty (aDpy, aWnd, propNameAtom,
+ 0, LONG_MAX, False /* delete */,
+ aPropType, &actTypeAtom, &actFmt,
+ nItems, &nBytesAfter, &propVal);
+ if (rc != Success)
+ return NULL;
+
+ LogRelFlowFuncLeave();
+ return propVal;
+}
+
+int SeamlessX11::init(PFNSENDREGIONUPDATE pHostCallback)
+{
+ int rc = VINF_SUCCESS;
+
+ LogRelFlowFuncEnter();
+ if (mHostCallback != NULL) /* Assertion */
+ {
+ VBClLogError("Attempting to initialise seamless guest object twice!\n");
+ return VERR_INTERNAL_ERROR;
+ }
+ if (!(mDisplay = XOpenDisplay(NULL)))
+ {
+ VBClLogError("Seamless guest object failed to acquire a connection to the display\n");
+ return VERR_ACCESS_DENIED;
+ }
+ mHostCallback = pHostCallback;
+ mEnabled = false;
+ unmonitorClientList();
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Shutdown seamless event monitoring.
+ */
+void SeamlessX11::uninit(void)
+{
+ if (mHostCallback)
+ stop();
+ mHostCallback = NULL;
+
+ /* Before closing a Display, make sure X11 is still running. The indicator
+ * that is when XOpenDisplay() returns non NULL. If it is not a
+ * case, XCloseDisplay() will hang on internal X11 mutex forever. */
+ Display *pDisplay = XOpenDisplay(NULL);
+ if (pDisplay)
+ {
+ XCloseDisplay(pDisplay);
+ if (mDisplay)
+ {
+ XCloseDisplay(mDisplay);
+ mDisplay = NULL;
+ }
+ }
+
+ if (mpRects)
+ {
+ RTMemFree(mpRects);
+ mpRects = NULL;
+ }
+}
+
+/**
+ * Read information about currently visible windows in the guest and subscribe to X11
+ * events about changes to this information.
+ *
+ * @note This class does not contain its own event thread, so an external thread must
+ * call nextConfigurationEvent() for as long as events are wished.
+ * @todo This function should switch the guest to fullscreen mode.
+ */
+int SeamlessX11::start(void)
+{
+ int rc = VINF_SUCCESS;
+ /** Dummy values for XShapeQueryExtension */
+ int error, event;
+
+ LogRelFlowFuncEnter();
+ if (mEnabled)
+ return VINF_SUCCESS;
+ mSupportsShape = XShapeQueryExtension(mDisplay, &event, &error);
+ mEnabled = true;
+ monitorClientList();
+ rebuildWindowTree();
+ LogRelFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** Stop reporting seamless events to the host. Free information about guest windows
+ and stop requesting updates. */
+void SeamlessX11::stop(void)
+{
+ LogRelFlowFuncEnter();
+ if (!mEnabled)
+ return;
+ mEnabled = false;
+ unmonitorClientList();
+ freeWindowTree();
+ LogRelFlowFuncLeave();
+}
+
+void SeamlessX11::monitorClientList(void)
+{
+ LogRelFlowFuncEnter();
+ XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask | SubstructureNotifyMask);
+}
+
+void SeamlessX11::unmonitorClientList(void)
+{
+ LogRelFlowFuncEnter();
+ XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask);
+}
+
+/**
+ * Recreate the table of toplevel windows of clients on the default root window of the
+ * X server.
+ */
+void SeamlessX11::rebuildWindowTree(void)
+{
+ LogRelFlowFuncEnter();
+ freeWindowTree();
+ addClients(DefaultRootWindow(mDisplay));
+ mChanged = true;
+}
+
+
+/**
+ * Look at the list of children of a virtual root window and add them to the list of clients
+ * if they belong to a client which is not a virtual root.
+ *
+ * @param hRoot the virtual root window to be examined
+ */
+void SeamlessX11::addClients(const Window hRoot)
+{
+ /** Unused out parameters of XQueryTree */
+ Window hRealRoot, hParent;
+ /** The list of children of the root supplied, raw pointer */
+ Window *phChildrenRaw = NULL;
+ /** The list of children of the root supplied, auto-pointer */
+ Window *phChildren;
+ /** The number of children of the root supplied */
+ unsigned cChildren;
+
+ LogRelFlowFuncEnter();
+ if (!XQueryTree(mDisplay, hRoot, &hRealRoot, &hParent, &phChildrenRaw, &cChildren))
+ return;
+ phChildren = phChildrenRaw;
+ for (unsigned i = 0; i < cChildren; ++i)
+ addClientWindow(phChildren[i]);
+ XFree(phChildrenRaw);
+ LogRelFlowFuncLeave();
+}
+
+
+void SeamlessX11::addClientWindow(const Window hWin)
+{
+ LogRelFlowFuncEnter();
+ XWindowAttributes winAttrib;
+ bool fAddWin = true;
+ Window hClient = XmuClientWindow(mDisplay, hWin);
+
+ if (isVirtualRoot(hClient))
+ fAddWin = false;
+ if (fAddWin && !XGetWindowAttributes(mDisplay, hWin, &winAttrib))
+ {
+ VBClLogError("Failed to get the window attributes for window %d\n", hWin);
+ fAddWin = false;
+ }
+ if (fAddWin && (winAttrib.map_state == IsUnmapped))
+ fAddWin = false;
+ XSizeHints dummyHints;
+ long dummyLong;
+ /* Apparently (?) some old kwin versions had unwanted client windows
+ * without normal hints. */
+ if (fAddWin && (!XGetWMNormalHints(mDisplay, hClient, &dummyHints,
+ &dummyLong)))
+ {
+ LogRelFlowFunc(("window %lu, client window %lu has no size hints\n", hWin, hClient));
+ fAddWin = false;
+ }
+ if (fAddWin)
+ {
+ XRectangle *pRects = NULL;
+ int cRects = 0, iOrdering;
+ bool hasShape = false;
+
+ LogRelFlowFunc(("adding window %lu, client window %lu\n", hWin,
+ hClient));
+ if (mSupportsShape)
+ {
+ XShapeSelectInput(mDisplay, hWin, ShapeNotifyMask);
+ pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects, &iOrdering);
+ if (!pRects)
+ cRects = 0;
+ else
+ {
+ if ( (cRects > 1)
+ || (pRects[0].x != 0)
+ || (pRects[0].y != 0)
+ || (pRects[0].width != winAttrib.width)
+ || (pRects[0].height != winAttrib.height)
+ )
+ hasShape = true;
+ }
+ }
+ mGuestWindows.addWindow(hWin, hasShape, winAttrib.x, winAttrib.y,
+ winAttrib.width, winAttrib.height, cRects,
+ pRects);
+ }
+ LogRelFlowFuncLeave();
+}
+
+
+/**
+ * Checks whether a window is a virtual root.
+ * @returns true if it is, false otherwise
+ * @param hWin the window to be examined
+ */
+bool SeamlessX11::isVirtualRoot(Window hWin)
+{
+ unsigned char *windowTypeRaw = NULL;
+ Atom *windowType;
+ unsigned long ulCount;
+ bool rc = false;
+
+ LogRelFlowFuncEnter();
+ windowTypeRaw = XXGetProperty(mDisplay, hWin, XA_ATOM, WM_TYPE_PROP, &ulCount);
+ if (windowTypeRaw != NULL)
+ {
+ windowType = (Atom *)(windowTypeRaw);
+ if ( (ulCount != 0)
+ && (*windowType == XInternAtom(mDisplay, WM_TYPE_DESKTOP_PROP, True)))
+ rc = true;
+ }
+ if (windowTypeRaw)
+ XFree(windowTypeRaw);
+ LogRelFlowFunc(("returning %RTbool\n", rc));
+ return rc;
+}
+
+DECLCALLBACK(int) VBoxGuestWinFree(VBoxGuestWinInfo *pInfo, void *pvParam)
+{
+ Display *pDisplay = (Display *)pvParam;
+
+ XShapeSelectInput(pDisplay, pInfo->Core.Key, 0);
+ delete pInfo;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Free all information in the tree of visible windows
+ */
+void SeamlessX11::freeWindowTree(void)
+{
+ /* We use post-increment in the operation to prevent the iterator from being invalidated. */
+ LogRelFlowFuncEnter();
+ mGuestWindows.detachAll(VBoxGuestWinFree, mDisplay);
+ LogRelFlowFuncLeave();
+}
+
+
+/**
+ * Waits for a position or shape-related event from guest windows
+ *
+ * @note Called from the guest event thread.
+ */
+void SeamlessX11::nextConfigurationEvent(void)
+{
+ XEvent event;
+
+ LogRelFlowFuncEnter();
+ /* Start by sending information about the current window setup to the host. We do this
+ here because we want to send all such information from a single thread. */
+ if (mChanged && mEnabled)
+ {
+ updateRects();
+ mHostCallback(mpRects, mcRects);
+ }
+ mChanged = false;
+
+ if (XPending(mDisplay) > 0)
+ {
+ /* We execute this even when seamless is disabled, as it also waits for
+ * enable and disable notification. */
+ XNextEvent(mDisplay, &event);
+ } else
+ {
+ /* This function is called in a loop by upper layer. In order to
+ * prevent CPU spinning, sleep a bit before returning. */
+ RTThreadSleep(300 /* ms */);
+ return;
+ }
+
+ if (!mEnabled)
+ return;
+ switch (event.type)
+ {
+ case ConfigureNotify:
+ {
+ XConfigureEvent *pConf = &event.xconfigure;
+ LogRelFlowFunc(("configure event, window=%lu, x=%i, y=%i, w=%i, h=%i, send_event=%RTbool\n",
+ (unsigned long) pConf->window, (int) pConf->x,
+ (int) pConf->y, (int) pConf->width,
+ (int) pConf->height, pConf->send_event));
+ }
+ doConfigureEvent(event.xconfigure.window);
+ break;
+ case MapNotify:
+ LogRelFlowFunc(("map event, window=%lu, send_event=%RTbool\n",
+ (unsigned long) event.xmap.window,
+ event.xmap.send_event));
+ rebuildWindowTree();
+ break;
+ case PropertyNotify:
+ if ( event.xproperty.atom != XInternAtom(mDisplay, "_NET_CLIENT_LIST", True /* only_if_exists */)
+ || event.xproperty.window != DefaultRootWindow(mDisplay))
+ break;
+ LogRelFlowFunc(("_NET_CLIENT_LIST property event on root window\n"));
+ rebuildWindowTree();
+ break;
+ case VBoxShapeNotify: /* This is defined wrong in my X11 header files! */
+ LogRelFlowFunc(("shape event, window=%lu, send_event=%RTbool\n",
+ (unsigned long) event.xany.window,
+ event.xany.send_event));
+ /* the window member in xany is in the same place as in the shape event */
+ doShapeEvent(event.xany.window);
+ break;
+ case UnmapNotify:
+ LogRelFlowFunc(("unmap event, window=%lu, send_event=%RTbool\n",
+ (unsigned long) event.xunmap.window,
+ event.xunmap.send_event));
+ rebuildWindowTree();
+ break;
+ default:
+ break;
+ }
+ LogRelFlowFunc(("processed event\n"));
+}
+
+/**
+ * Handle a configuration event in the seamless event thread by setting the new position.
+ *
+ * @param hWin the window to be examined
+ */
+void SeamlessX11::doConfigureEvent(Window hWin)
+{
+ VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin);
+ if (pInfo)
+ {
+ XWindowAttributes winAttrib;
+
+ if (!XGetWindowAttributes(mDisplay, hWin, &winAttrib))
+ return;
+ pInfo->mX = winAttrib.x;
+ pInfo->mY = winAttrib.y;
+ pInfo->mWidth = winAttrib.width;
+ pInfo->mHeight = winAttrib.height;
+ mChanged = true;
+ }
+}
+
+/**
+ * Handle a window shape change event in the seamless event thread.
+ *
+ * @param hWin the window to be examined
+ */
+void SeamlessX11::doShapeEvent(Window hWin)
+{
+ LogRelFlowFuncEnter();
+ VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin);
+ if (pInfo)
+ {
+ XRectangle *pRects;
+ int cRects = 0, iOrdering;
+
+ pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects,
+ &iOrdering);
+ if (!pRects)
+ cRects = 0;
+ pInfo->mhasShape = true;
+ if (pInfo->mpRects)
+ XFree(pInfo->mpRects);
+ pInfo->mcRects = cRects;
+ pInfo->mpRects = pRects;
+ mChanged = true;
+ }
+ LogRelFlowFuncLeave();
+}
+
+/**
+ * Gets the list of visible rectangles
+ */
+RTRECT *SeamlessX11::getRects(void)
+{
+ return mpRects;
+}
+
+/**
+ * Gets the number of rectangles in the visible rectangle list
+ */
+size_t SeamlessX11::getRectCount(void)
+{
+ return mcRects;
+}
+
+RTVEC_DECL(RectList, RTRECT)
+
+static DECLCALLBACK(int) getRectsCallback(VBoxGuestWinInfo *pInfo, struct RectList *pRects)
+{
+ if (pInfo->mhasShape)
+ {
+ for (int i = 0; i < pInfo->mcRects; ++i)
+ {
+ RTRECT *pRect;
+
+ pRect = RectListPushBack(pRects);
+ if (!pRect)
+ return VERR_NO_MEMORY;
+ pRect->xLeft = pInfo->mX
+ + pInfo->mpRects[i].x;
+ pRect->yBottom = pInfo->mY
+ + pInfo->mpRects[i].y
+ + pInfo->mpRects[i].height;
+ pRect->xRight = pInfo->mX
+ + pInfo->mpRects[i].x
+ + pInfo->mpRects[i].width;
+ pRect->yTop = pInfo->mY
+ + pInfo->mpRects[i].y;
+ }
+ }
+ else
+ {
+ RTRECT *pRect;
+
+ pRect = RectListPushBack(pRects);
+ if (!pRect)
+ return VERR_NO_MEMORY;
+ pRect->xLeft = pInfo->mX;
+ pRect->yBottom = pInfo->mY
+ + pInfo->mHeight;
+ pRect->xRight = pInfo->mX
+ + pInfo->mWidth;
+ pRect->yTop = pInfo->mY;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Updates the list of seamless rectangles
+ */
+int SeamlessX11::updateRects(void)
+{
+ LogRelFlowFuncEnter();
+ struct RectList rects = RTVEC_INITIALIZER;
+
+ if (mcRects != 0)
+ {
+ int rc = RectListReserve(&rects, mcRects * 2);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ mGuestWindows.doWithAll((PFNVBOXGUESTWINCALLBACK)getRectsCallback, &rects);
+ if (mpRects)
+ RTMemFree(mpRects);
+ mcRects = RectListSize(&rects);
+ mpRects = RectListDetach(&rects);
+ LogRelFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Send a client event to wake up the X11 seamless event loop prior to stopping it.
+ *
+ * @note This function should only be called from the host event thread.
+ */
+bool SeamlessX11::interruptEventWait(void)
+{
+ bool rc = false;
+ Display *pDisplay = XOpenDisplay(NULL);
+
+ LogRelFlowFuncEnter();
+ if (pDisplay == NULL)
+ {
+ VBClLogError("Failed to open X11 display\n");
+ return false;
+ }
+
+ /* Message contents set to zero. */
+ XClientMessageEvent clientMessage =
+ { ClientMessage, 0, 0, 0, 0, XInternAtom(pDisplay, "VBOX_CLIENT_SEAMLESS_HEARTBEAT", false), 8 };
+
+ if (XSendEvent(pDisplay, DefaultRootWindow(mDisplay), false,
+ PropertyChangeMask, (XEvent *)&clientMessage))
+ rc = true;
+ XCloseDisplay(pDisplay);
+ LogRelFlowFunc(("returning %RTbool\n", rc));
+ return rc;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless-x11.h b/src/VBox/Additions/x11/VBoxClient/seamless-x11.h
new file mode 100644
index 00000000..f8ab185d
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.h
@@ -0,0 +1,275 @@
+/* $Id: seamless-x11.h $ */
+/** @file
+ * Seamless mode - X11 guests.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/log.h>
+#include <iprt/avl.h>
+#ifdef RT_NEED_NEW_AND_DELETE
+# include <iprt/mem.h>
+# include <new>
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/shape.h>
+
+#define WM_TYPE_PROP "_NET_WM_WINDOW_TYPE"
+#define WM_TYPE_DESKTOP_PROP "_NET_WM_WINDOW_TYPE_DESKTOP"
+
+/* This is defined wrong in my X11 header files! */
+#define VBoxShapeNotify 64
+
+/**
+ * Callback which provides the interface for notifying the host of changes to
+ * the X11 window configuration, mainly split out from @a VBoxGuestSeamlessHost
+ * to simplify the unit test.
+ */
+typedef void FNSENDREGIONUPDATE(RTRECT *pRects, size_t cRects);
+typedef FNSENDREGIONUPDATE *PFNSENDREGIONUPDATE;
+
+/** Structure containing information about a guest window's position and visible area.
+ Used inside of VBoxGuestWindowList. */
+struct VBoxGuestWinInfo
+{
+public:
+ /** Header structure for insertion into an AVL tree */
+ AVLU32NODECORE Core;
+ /** Is the window currently mapped? */
+ bool mhasShape;
+ /** Co-ordinates in the guest screen. */
+ int mX, mY;
+ /** Window dimensions. */
+ int mWidth, mHeight;
+ /** Number of rectangles used to represent the visible area. */
+ int mcRects;
+ /** Rectangles representing the visible area. These must be allocated
+ * by XMalloc and will be freed automatically if non-null when the class
+ * is destroyed. */
+ XRectangle *mpRects;
+ /** Constructor. */
+ VBoxGuestWinInfo(bool hasShape, int x, int y, int w, int h, int cRects, XRectangle *pRects)
+ : mhasShape(hasShape), mX(x), mY(y), mWidth(w), mHeight(h)
+ , mcRects(cRects), mpRects(pRects)
+ {}
+
+ /** Destructor */
+ ~VBoxGuestWinInfo()
+ {
+ if (mpRects)
+ XFree(mpRects);
+ }
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+
+private:
+ // We don't want a copy constructor or assignment operator
+ VBoxGuestWinInfo(const VBoxGuestWinInfo &);
+ VBoxGuestWinInfo &operator=(const VBoxGuestWinInfo &);
+};
+
+/** Callback type used for "DoWithAll" calls */
+typedef DECLCALLBACKTYPE(int, FNVBOXGUESTWINCALLBACK,(VBoxGuestWinInfo *, void *));
+/** Pointer to VBOXGUESTWINCALLBACK */
+typedef FNVBOXGUESTWINCALLBACK *PFNVBOXGUESTWINCALLBACK;
+
+static inline DECLCALLBACK(int) VBoxGuestWinCleanup(VBoxGuestWinInfo *pInfo, void *)
+{
+ delete pInfo;
+ return VINF_SUCCESS;
+}
+
+/**
+ * This class is just a wrapper around a map of structures containing
+ * information about the windows on the guest system. It has a function for
+ * adding a structure (see addWindow) and one for removing it by window
+ * handle (see removeWindow).
+ */
+class VBoxGuestWindowList
+{
+private:
+ // We don't want a copy constructor or an assignment operator
+ VBoxGuestWindowList(const VBoxGuestWindowList&);
+ VBoxGuestWindowList& operator=(const VBoxGuestWindowList&);
+
+ // Private class members
+ AVLU32TREE mWindows;
+
+public:
+ // Constructor
+ VBoxGuestWindowList(void) : mWindows(NULL) {}
+ // Destructor
+ ~VBoxGuestWindowList()
+ {
+ /** @todo having this inside the container class hard codes that the
+ * elements have to be allocated with the "new" operator, and
+ * I don't see a need to require this. */
+ doWithAll(VBoxGuestWinCleanup, NULL);
+ }
+
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+
+ // Standard operations
+ VBoxGuestWinInfo *find(Window hWin)
+ {
+ return (VBoxGuestWinInfo *)RTAvlU32Get(&mWindows, hWin);
+ }
+
+ void detachAll(PFNVBOXGUESTWINCALLBACK pfnCallback, void *pvParam)
+ {
+ RTAvlU32Destroy(&mWindows, (PAVLU32CALLBACK)pfnCallback, pvParam);
+ }
+
+ int doWithAll(PFNVBOXGUESTWINCALLBACK pfnCallback, void *pvParam)
+ {
+ return RTAvlU32DoWithAll(&mWindows, 1, (PAVLU32CALLBACK)pfnCallback, pvParam);
+ }
+
+ bool addWindow(Window hWin, bool isMapped, int x, int y, int w, int h, int cRects,
+ XRectangle *pRects)
+ {
+ LogRelFlowFunc(("hWin=%lu, isMapped=%RTbool, x=%d, y=%d, w=%d, h=%d, cRects=%d\n",
+ (unsigned long) hWin, isMapped, x, y, w, h, cRects));
+ VBoxGuestWinInfo *pInfo = new VBoxGuestWinInfo(isMapped, x, y, w, h, cRects, pRects);
+ pInfo->Core.Key = hWin;
+ LogRelFlowFuncLeave();
+ return RTAvlU32Insert(&mWindows, &pInfo->Core);
+ }
+
+ VBoxGuestWinInfo *removeWindow(Window hWin)
+ {
+ LogRelFlowFuncEnter();
+ return (VBoxGuestWinInfo *)RTAvlU32Remove(&mWindows, hWin);
+ }
+};
+
+class SeamlessX11
+{
+private:
+ // We don't want a copy constructor or assignment operator
+ SeamlessX11(const SeamlessX11&);
+ SeamlessX11& operator=(const SeamlessX11&);
+
+ // Private member variables
+ /** Pointer to the host callback. */
+ PFNSENDREGIONUPDATE mHostCallback;
+ /** Our connection to the X11 display we are running on. */
+ Display *mDisplay;
+ /** Class to keep track of visible guest windows. */
+ VBoxGuestWindowList mGuestWindows;
+ /** The current set of seamless rectangles. */
+ RTRECT *mpRects;
+ /** The current number of seamless rectangles. */
+ int mcRects;
+ /** Do we support the X shaped window extension? */
+ bool mSupportsShape;
+ /** Is seamless mode currently enabled? */
+ bool mEnabled;
+ /** Have there been changes since the last time we sent a notification? */
+ bool mChanged;
+
+ // Private methods
+
+ // Methods to manage guest window information
+ /**
+ * Store information about a desktop window and register for structure events on it.
+ * If it is mapped, go through the list of it's children and add information about
+ * mapped children to the tree of visible windows, making sure that those windows are
+ * not already in our list of desktop windows.
+ *
+ * @param hWin the window concerned - should be a "desktop" window
+ */
+ void monitorClientList(void);
+ void unmonitorClientList(void);
+ void rebuildWindowTree(void);
+ void addClients(const Window hRoot);
+ bool isVirtualRoot(Window hWin);
+ void addClientWindow(Window hWin);
+ void freeWindowTree(void);
+ void updateHostSeamlessInfo(void);
+ int updateRects(void);
+
+public:
+ /**
+ * Initialise the guest and ensure that it is capable of handling seamless mode
+ * @param pHostCallback Host interface callback to notify of window configuration
+ * changes.
+ *
+ * @returns iprt status code
+ */
+ int init(PFNSENDREGIONUPDATE pHostCallback);
+
+ /**
+ * Shutdown seamless event monitoring.
+ */
+ void uninit(void);
+
+ /**
+ * Initialise seamless event reporting in the guest.
+ *
+ * @returns IPRT status code
+ */
+ int start(void);
+ /** Stop reporting seamless events. */
+ void stop(void);
+ /** Get the current list of visible rectangles. */
+ RTRECT *getRects(void);
+ /** Get the number of visible rectangles in the current list */
+ size_t getRectCount(void);
+
+ /** Process next event in the guest event queue - called by the event thread. */
+ void nextConfigurationEvent(void);
+ /** Wake up the event thread if it is waiting for an event so that it can exit. */
+ bool interruptEventWait(void);
+
+ /* Methods to handle X11 events. These are public so that the unit test
+ * can call them. */
+ void doConfigureEvent(Window hWin);
+ void doShapeEvent(Window hWin);
+
+ SeamlessX11(void)
+ : mHostCallback(NULL), mDisplay(NULL), mpRects(NULL), mcRects(0),
+ mSupportsShape(false), mEnabled(false), mChanged(false) {}
+
+ ~SeamlessX11()
+ {
+ uninit();
+ }
+
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+};
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless.cpp b/src/VBox/Additions/x11/VBoxClient/seamless.cpp
new file mode 100644
index 00000000..365b31f6
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless.cpp
@@ -0,0 +1,360 @@
+/* $Id: seamless.cpp $ */
+/** @file
+ * X11 Guest client - seamless mode: main logic, communication with the host and
+ * wrapper interface for the main code of the VBoxClient deamon. The
+ * X11-specific parts are split out into their own file for ease of testing.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header files *
+*********************************************************************************************************************************/
+#include <new>
+
+#include <X11/Xlib.h>
+
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h>
+
+#include "VBoxClient.h"
+#include "seamless.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Struct for keeping a service instance.
+ */
+struct SEAMLESSSERVICE
+{
+ /** Seamless service object. */
+ SeamlessMain mSeamless;
+};
+
+/** Service instance data. */
+static SEAMLESSSERVICE g_Svc;
+
+
+SeamlessMain::SeamlessMain(void)
+{
+ mX11MonitorThread = NIL_RTTHREAD;
+ mX11MonitorThreadStopping = false;
+
+ mMode = VMMDev_Seamless_Disabled;
+ mfPaused = true;
+}
+
+SeamlessMain::~SeamlessMain()
+{
+ /* Stopping will be done via main.cpp. */
+}
+
+/**
+ * Update the set of visible rectangles in the host.
+ */
+static void sendRegionUpdate(RTRECT *pRects, size_t cRects)
+{
+ if ( cRects
+ && !pRects) /* Assertion */
+ {
+ VBClLogError(("Region update called with NULL pointer\n"));
+ return;
+ }
+ VbglR3SeamlessSendRects(cRects, pRects);
+}
+
+/** @copydoc VBCLSERVICE::pfnInit */
+int SeamlessMain::init(void)
+{
+ int rc;
+ const char *pcszStage;
+
+ do
+ {
+ pcszStage = "Connecting to the X server";
+ rc = mX11Monitor.init(sendRegionUpdate);
+ if (RT_FAILURE(rc))
+ break;
+ pcszStage = "Setting guest IRQ filter mask";
+ rc = VbglR3CtlFilterMask(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, 0);
+ if (RT_FAILURE(rc))
+ break;
+ pcszStage = "Reporting support for seamless capability";
+ rc = VbglR3SeamlessSetCap(true);
+ if (RT_FAILURE(rc))
+ break;
+ rc = startX11MonitorThread();
+ if (RT_FAILURE(rc))
+ break;
+
+ } while(0);
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to start in stage '%s' -- error %Rrc\n", pcszStage, rc);
+
+ return rc;
+}
+
+/** @copydoc VBCLSERVICE::pfnWorker */
+int SeamlessMain::worker(bool volatile *pfShutdown)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Let the main thread know that it can continue spawning services. */
+ RTThreadUserSignal(RTThreadSelf());
+
+ /* This will only exit if something goes wrong. */
+ for (;;)
+ {
+ if (ASMAtomicReadBool(pfShutdown))
+ break;
+
+ rc = nextStateChangeEvent();
+
+ if (rc == VERR_TRY_AGAIN)
+ rc = VINF_SUCCESS;
+
+ if (RT_FAILURE(rc))
+ break;
+
+ if (ASMAtomicReadBool(pfShutdown))
+ break;
+
+ /* If we are not stopping, sleep for a bit to avoid using up too
+ much CPU while retrying. */
+ RTThreadYield();
+ }
+
+ return rc;
+}
+
+/** @copydoc VBCLSERVICE::pfnStop */
+void SeamlessMain::stop(void)
+{
+ VbglR3SeamlessSetCap(false);
+ VbglR3CtlFilterMask(0, VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST);
+ stopX11MonitorThread();
+}
+
+/** @copydoc VBCLSERVICE::pfnTerm */
+int SeamlessMain::term(void)
+{
+ mX11Monitor.uninit();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Waits for a seamless state change events from the host and dispatch it.
+ *
+ * @returns VBox return code, or
+ * VERR_TRY_AGAIN if no new status is available and we have to try it again
+ * at some later point in time.
+ */
+int SeamlessMain::nextStateChangeEvent(void)
+{
+ VMMDevSeamlessMode newMode = VMMDev_Seamless_Disabled;
+
+ int rc = VbglR3SeamlessWaitEvent(&newMode);
+ if (RT_SUCCESS(rc))
+ {
+ mMode = newMode;
+ switch (newMode)
+ {
+ case VMMDev_Seamless_Visible_Region:
+ /* A simplified seamless mode, obtained by making the host VM window
+ * borderless and making the guest desktop transparent. */
+ VBClLogVerbose(2, "\"Visible region\" mode requested\n");
+ break;
+ case VMMDev_Seamless_Disabled:
+ VBClLogVerbose(2, "\"Disabled\" mode requested\n");
+ break;
+ case VMMDev_Seamless_Host_Window:
+ /* One host window represents one guest window. Not yet implemented. */
+ VBClLogVerbose(2, "Unsupported \"host window\" mode requested\n");
+ return VERR_NOT_SUPPORTED;
+ default:
+ VBClLogError("Unsupported mode %d requested\n", newMode);
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_TRY_AGAIN)
+ {
+ if (mMode == VMMDev_Seamless_Visible_Region)
+ mfPaused = false;
+ else
+ mfPaused = true;
+ mX11Monitor.interruptEventWait();
+ }
+ else
+ VBClLogError("VbglR3SeamlessWaitEvent returned %Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * The actual X11 window configuration change monitor thread function.
+ */
+int SeamlessMain::x11MonitorThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+
+ SeamlessMain *pThis = (SeamlessMain *)pvUser;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ RTThreadUserSignal(hThreadSelf);
+
+ VBClLogVerbose(2, "X11 monitor thread started\n");
+
+ while (!pThis->mX11MonitorThreadStopping)
+ {
+ if (!pThis->mfPaused)
+ {
+ rc = pThis->mX11Monitor.start();
+ if (RT_FAILURE(rc))
+ VBClLogFatalError("Failed to change the X11 seamless service state, mfPaused=%RTbool, rc=%Rrc\n",
+ pThis->mfPaused, rc);
+ }
+
+ pThis->mX11Monitor.nextConfigurationEvent();
+
+ if ( pThis->mfPaused
+ || pThis->mX11MonitorThreadStopping)
+ {
+ pThis->mX11Monitor.stop();
+ }
+ }
+
+ VBClLogVerbose(2, "X11 monitor thread ended\n");
+
+ return rc;
+}
+
+/**
+ * Start the X11 window configuration change monitor thread.
+ */
+int SeamlessMain::startX11MonitorThread(void)
+{
+ mX11MonitorThreadStopping = false;
+
+ if (isX11MonitorThreadRunning())
+ return VINF_SUCCESS;
+
+ int rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThread, this, 0,
+ RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE,
+ "seamless x11");
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserWait(mX11MonitorThread, RT_MS_30SEC);
+
+ if (RT_FAILURE(rc))
+ VBClLogError("Failed to start X11 monitor thread, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * Stops the monitor thread.
+ */
+int SeamlessMain::stopX11MonitorThread(void)
+{
+ if (!isX11MonitorThreadRunning())
+ return VINF_SUCCESS;
+
+ mX11MonitorThreadStopping = true;
+ if (!mX11Monitor.interruptEventWait())
+ {
+ VBClLogError("Unable to notify X11 monitor thread\n");
+ return VERR_INVALID_STATE;
+ }
+
+ int rcThread;
+ int rc = RTThreadWait(mX11MonitorThread, RT_MS_30SEC, &rcThread);
+ if (RT_SUCCESS(rc))
+ rc = rcThread;
+
+ if (RT_SUCCESS(rc))
+ {
+ mX11MonitorThread = NIL_RTTHREAD;
+ }
+ else
+ VBClLogError("Waiting for X11 monitor thread to stop failed, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclSeamlessInit(void)
+{
+ return g_Svc.mSeamless.init();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclSeamlessWorker(bool volatile *pfShutdown)
+{
+ return g_Svc.mSeamless.worker(pfShutdown);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclSeamlessStop(void)
+{
+ return g_Svc.mSeamless.stop();
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclSeamlessTerm(void)
+{
+ return g_Svc.mSeamless.term();
+}
+
+VBCLSERVICE g_SvcSeamless =
+{
+ "seamless", /* szName */
+ "Seamless Mode Support", /* pszDescription */
+ ".vboxclient-seamless", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclSeamlessInit, /* pfnInit */
+ vbclSeamlessWorker, /* pfnWorker */
+ vbclSeamlessStop, /* pfnStop*/
+ vbclSeamlessTerm /* pfnTerm */
+};
+
diff --git a/src/VBox/Additions/x11/VBoxClient/seamless.h b/src/VBox/Additions/x11/VBoxClient/seamless.h
new file mode 100644
index 00000000..3e9041a8
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/seamless.h
@@ -0,0 +1,109 @@
+/* $Id: seamless.h $ */
+/** @file
+ * X11 Guest client - seamless mode, missing proper description while using the
+ * potentially confusing word 'host'.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_VBoxClient_seamless_h
+#define GA_INCLUDED_SRC_x11_VBoxClient_seamless_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/thread.h>
+
+#include <VBox/log.h>
+#include <VBox/VBoxGuestLib.h> /* for the R3 guest library functions */
+
+#include "seamless-x11.h"
+
+/**
+ * Interface to the host
+ */
+class SeamlessMain
+{
+private:
+ // We don't want a copy constructor or assignment operator
+ SeamlessMain(const SeamlessMain&);
+ SeamlessMain& operator=(const SeamlessMain&);
+
+ /** X11 event monitor object */
+ SeamlessX11 mX11Monitor;
+
+ /** Thread to start and stop when we enter and leave seamless mode which
+ * monitors X11 windows in the guest. */
+ RTTHREAD mX11MonitorThread;
+ /** Should the X11 monitor thread be stopping? */
+ volatile bool mX11MonitorThreadStopping;
+
+ /** The current seamless mode we are in. */
+ VMMDevSeamlessMode mMode;
+ /** Is the service currently paused? */
+ volatile bool mfPaused;
+
+ /**
+ * Waits for a seamless state change events from the host and dispatch it. This is
+ * meant to be called by the host event monitor thread exclusively.
+ *
+ * @returns IRPT return code.
+ */
+ int nextStateChangeEvent(void);
+
+ /** Thread function to monitor X11 window configuration changes. */
+ static DECLCALLBACK(int) x11MonitorThread(RTTHREAD self, void *pvUser);
+
+ /** Helper to start the X11 monitor thread. */
+ int startX11MonitorThread(void);
+
+ /** Helper to stop the X11 monitor thread again. */
+ int stopX11MonitorThread(void);
+
+ /** Is the service currently actively monitoring X11 windows? */
+ bool isX11MonitorThreadRunning()
+ {
+ return mX11MonitorThread != NIL_RTTHREAD;
+ }
+
+public:
+ SeamlessMain(void);
+ virtual ~SeamlessMain();
+#ifdef RT_NEED_NEW_AND_DELETE
+ RTMEM_IMPLEMENT_NEW_AND_DELETE();
+#endif
+
+ /** @copydoc VBCLSERVICE::pfnInit */
+ int init(void);
+
+ /** @copydoc VBCLSERVICE::pfnWorker */
+ int worker(bool volatile *pfShutdown);
+
+ /** @copydoc VBCLSERVICE::pfnStop */
+ void stop(void);
+
+ /** @copydoc VBCLSERVICE::pfnTerm */
+ int term(void);
+};
+
+#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_seamless_h */
diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup b/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup
diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp
new file mode 100644
index 00000000..0a90684d
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp
@@ -0,0 +1,791 @@
+/* $Id: tstSeamlessX11-auto.cpp $ */
+/** @file
+ * Automated test of the X11 seamless Additions code.
+ * @todo Better separate test data from implementation details!
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <stdlib.h> /* exit() */
+
+#include <X11/Xatom.h>
+#include <X11/Xmu/WinUtil.h>
+
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+#include "../seamless.h"
+
+#undef DefaultRootWindow
+
+/******************************************************
+* Mock X11 functions needed by the seamless X11 class *
+******************************************************/
+
+int XFree(void *data)
+{
+ RTMemFree(data);
+ return 0;
+}
+
+#define TEST_DISPLAY ((Display *)0xffff)
+#define TEST_ROOT ((Window)1)
+
+void VBClLogError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Error: %s", psz);
+
+ RTStrFree(psz);
+}
+
+/** Exit with a fatal error. */
+void VBClLogFatalError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Fatal error: %s", psz);
+
+ RTStrFree(psz);
+
+ exit(1);
+}
+
+extern "C" Display *XOpenDisplay(const char *display_name);
+Display *XOpenDisplay(const char *display_name)
+{
+ RT_NOREF1(display_name);
+ return TEST_DISPLAY;
+}
+
+extern "C" int XCloseDisplay(Display *display);
+int XCloseDisplay(Display *display)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ return 0;
+}
+
+enum
+{
+ ATOM_PROP = 1,
+ ATOM_DESKTOP_PROP
+};
+
+extern "C" Atom XInternAtom(Display *display, const char *atom_name, Bool only_if_exists);
+Atom XInternAtom(Display *display, const char *atom_name, Bool only_if_exists)
+{
+ RT_NOREF2(only_if_exists, display);
+ Assert(display == TEST_DISPLAY);
+ if (!RTStrCmp(atom_name, WM_TYPE_PROP))
+ return (Atom) ATOM_PROP;
+ if (!RTStrCmp(atom_name, WM_TYPE_DESKTOP_PROP))
+ return (Atom) ATOM_DESKTOP_PROP;
+ AssertFailed();
+ return (Atom)0;
+}
+
+/** The window (if any) on which the WM_TYPE_PROP property is set to the
+ * WM_TYPE_DESKTOP_PROP atom. */
+static Window g_hSmlsDesktopWindow = 0;
+
+extern "C" int XGetWindowProperty(Display *display, Window w, Atom property,
+ long long_offset, long long_length,
+ Bool delProp, Atom req_type,
+ Atom *actual_type_return,
+ int *actual_format_return,
+ unsigned long *nitems_return,
+ unsigned long *bytes_after_return,
+ unsigned char **prop_return);
+int XGetWindowProperty(Display *display, Window w, Atom property,
+ long long_offset, long long_length, Bool delProp,
+ Atom req_type, Atom *actual_type_return,
+ int *actual_format_return,
+ unsigned long *nitems_return,
+ unsigned long *bytes_after_return,
+ unsigned char **prop_return)
+{
+ RT_NOREF2(display, long_length);
+ Assert(display == TEST_DISPLAY);
+ Atom atomType = XInternAtom (display, WM_TYPE_PROP, true);
+ Atom atomTypeDesktop = XInternAtom (display, WM_TYPE_DESKTOP_PROP, true);
+ /* We only handle things we expect. */
+ AssertReturn((req_type == XA_ATOM) || (req_type == AnyPropertyType),
+ 0xffff);
+ AssertReturn(property == atomType, 0xffff);
+ *actual_type_return = XA_ATOM;
+ *actual_format_return = sizeof(Atom) * 8;
+ *nitems_return = 0;
+ *bytes_after_return = sizeof(Atom);
+ *prop_return = NULL;
+ if ((w != g_hSmlsDesktopWindow) || (g_hSmlsDesktopWindow == 0))
+ return Success;
+ AssertReturn(long_offset == 0, 0);
+ AssertReturn(delProp == false, 0);
+ unsigned char *pProp;
+ pProp = (unsigned char *)RTMemDup(&atomTypeDesktop,
+ sizeof(atomTypeDesktop));
+ AssertReturn(pProp, 0xffff);
+ *nitems_return = 1;
+ *prop_return = pProp;
+ *bytes_after_return = 0;
+ return 0;
+}
+
+#if 0 /* unused */
+/** Sets the current set of properties for all mock X11 windows */
+static void smlsSetDesktopWindow(Window hWin)
+{
+ g_hSmlsDesktopWindow = hWin;
+}
+#endif
+
+extern "C" Bool XShapeQueryExtension(Display *dpy, int *event_basep, int *error_basep);
+Bool XShapeQueryExtension(Display *dpy, int *event_basep, int *error_basep)
+{
+ RT_NOREF3(dpy, event_basep, error_basep);
+ Assert(dpy == TEST_DISPLAY);
+ return true;
+}
+
+/* We silently ignore this for now. */
+extern "C" int XSelectInput(Display *display, Window w, long event_mask);
+int XSelectInput(Display *display, Window w, long event_mask)
+{
+ RT_NOREF3(display, w, event_mask);
+ Assert(display == TEST_DISPLAY);
+ return 0;
+}
+
+/* We silently ignore this for now. */
+extern "C" void XShapeSelectInput(Display *display, Window w, unsigned long event_mask);
+void XShapeSelectInput(Display *display, Window w, unsigned long event_mask)
+{
+ RT_NOREF3(display, w, event_mask);
+ Assert(display == TEST_DISPLAY);
+}
+
+extern "C" Window XDefaultRootWindow(Display *display);
+Window XDefaultRootWindow(Display *display)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ return TEST_ROOT;
+}
+
+static unsigned g_cSmlsWindows = 0;
+static Window *g_paSmlsWindows = NULL;
+static XWindowAttributes *g_paSmlsWinAttribs = NULL;
+static const char **g_papszSmlsWinNames = NULL;
+
+extern "C" Status XQueryTree(Display *display, Window w, Window *root_return,
+ Window *parent_return, Window **children_return,
+ unsigned int *nchildren_return);
+Status XQueryTree(Display *display, Window w, Window *root_return,
+ Window *parent_return, Window **children_return,
+ unsigned int *nchildren_return)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ AssertReturn(w == TEST_ROOT, False); /* We support nothing else */
+ AssertPtrReturn(children_return, False);
+ AssertReturn(g_paSmlsWindows, False);
+ if (root_return)
+ *root_return = TEST_ROOT;
+ if (parent_return)
+ *parent_return = TEST_ROOT;
+ *children_return = (Window *)RTMemDup(g_paSmlsWindows,
+ g_cSmlsWindows * sizeof(Window));
+ if (nchildren_return)
+ *nchildren_return = g_cSmlsWindows;
+ return (g_cSmlsWindows != 0);
+}
+
+extern "C" Window XmuClientWindow(Display *dpy, Window win);
+Window XmuClientWindow(Display *dpy, Window win)
+{
+ RT_NOREF1(dpy);
+ Assert(dpy == TEST_DISPLAY);
+ return win;
+}
+
+extern "C" Status XGetWindowAttributes(Display *display, Window w,
+ XWindowAttributes *window_attributes_return);
+Status XGetWindowAttributes(Display *display, Window w,
+ XWindowAttributes *window_attributes_return)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ AssertPtrReturn(window_attributes_return, 1);
+ for (unsigned i = 0; i < g_cSmlsWindows; ++i)
+ if (g_paSmlsWindows[i] == w)
+ {
+ *window_attributes_return = g_paSmlsWinAttribs[i];
+ return 1;
+ }
+ return 0;
+}
+
+extern "C" Status XGetWMNormalHints(Display *display, Window w,
+ XSizeHints *hints_return,
+ long *supplied_return);
+
+Status XGetWMNormalHints(Display *display, Window w,
+ XSizeHints *hints_return, long *supplied_return)
+{
+ RT_NOREF4(display, w, hints_return, supplied_return);
+ Assert(display == TEST_DISPLAY);
+ return 1;
+}
+
+static void smlsSetWindowAttributes(XWindowAttributes *pAttribs,
+ Window *pWindows, unsigned cAttribs,
+ const char **paNames)
+{
+ g_paSmlsWinAttribs = pAttribs;
+ g_paSmlsWindows = pWindows;
+ g_cSmlsWindows = cAttribs;
+ g_papszSmlsWinNames = paNames;
+}
+
+static Window g_SmlsShapedWindow = 0;
+static int g_cSmlsShapeRectangles = 0;
+static XRectangle *g_pSmlsShapeRectangles = NULL;
+
+extern "C" XRectangle *XShapeGetRectangles (Display *dpy, Window window,
+ int kind, int *count,
+ int *ordering);
+XRectangle *XShapeGetRectangles (Display *dpy, Window window, int kind,
+ int *count, int *ordering)
+{
+ RT_NOREF2(dpy, kind);
+ Assert(dpy == TEST_DISPLAY);
+ if ((window != g_SmlsShapedWindow) || (window == 0))
+ return NULL; /* Probably not correct, but works for us. */
+ *count = g_cSmlsShapeRectangles;
+ *ordering = 0;
+ return (XRectangle *)RTMemDup(g_pSmlsShapeRectangles,
+ sizeof(XRectangle)
+ * g_cSmlsShapeRectangles);
+}
+
+static void smlsSetShapeRectangles(Window window, int cRects,
+ XRectangle *pRects)
+{
+ g_SmlsShapedWindow = window;
+ g_cSmlsShapeRectangles = cRects;
+ g_pSmlsShapeRectangles = pRects;
+}
+
+static int g_SmlsEventType = 0;
+static Window g_SmlsEventWindow = 0;
+
+/* This should not be needed in the bits of the code we test. */
+extern "C" int XNextEvent(Display *display, XEvent *event_return);
+int XNextEvent(Display *display, XEvent *event_return)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ event_return->xany.type = g_SmlsEventType;
+ event_return->xany.window = g_SmlsEventWindow;
+ event_return->xmap.window = g_SmlsEventWindow;
+ return True;
+}
+
+/* Mock XPending(): this also should not be needed. Just in case, always
+ * return that at least one event is pending to be processed. */
+extern "C" int XPending(Display *display);
+int XPending(Display *display)
+{
+ RT_NOREF1(display);
+ return 1;
+}
+
+static void smlsSetNextEvent(int type, Window window)
+{
+ g_SmlsEventType = type;
+ g_SmlsEventWindow = window;
+}
+
+/* This should not be needed in the bits of the code we test. */
+extern "C" Status XSendEvent(Display *display, Window w, Bool propagate,
+ long event_mask, XEvent *event_send);
+Status XSendEvent(Display *display, Window w, Bool propagate,
+ long event_mask, XEvent *event_send)
+{
+ RT_NOREF5(display, w, propagate, event_mask, event_send);
+ Assert(display == TEST_DISPLAY);
+ AssertFailedReturn(0);
+}
+
+/* This should not be needed in the bits of the code we test. */
+extern "C" int XFlush(Display *display);
+int XFlush(Display *display)
+{
+ RT_NOREF1(display);
+ Assert(display == TEST_DISPLAY);
+ AssertFailedReturn(0);
+}
+
+/** Global "received a notification" flag. */
+static bool g_fNotified = false;
+
+/** Dummy host call-back. */
+static void sendRegionUpdate(RTRECT *pRects, size_t cRects)
+{
+ RT_NOREF2(pRects, cRects);
+ g_fNotified = true;
+}
+
+static bool gotNotification(void)
+{
+ if (!g_fNotified)
+ return false;
+ g_fNotified = false;
+ return true;
+}
+
+/*****************************
+* The actual tests to be run *
+*****************************/
+
+/** The name of the unit test */
+static const char *g_pszTestName = NULL;
+
+/*** Test fixture data and data structures ***/
+
+/** A structure describing a test fixture to be run through. Each fixture
+ * describes the state of the windows visible (and unmapped) on the X server
+ * before and after a particular event is delivered, and the expected
+ * on-screen positions of all interesting visible windows at the end of the
+ * fixture as reported by the code (currently in the order it is likely to
+ * report them in, @todo sort this). We expect that the set of visible
+ * windows will be the same whether we start the code before the event and
+ * handle it or start the code after the event.
+ */
+struct SMLSFIXTURE
+{
+ /** The number of windows visible before the event */
+ unsigned cWindowsBefore;
+ /** An array of Window IDs for the visible and unmapped windows before
+ * the event */
+ Window *pahWindowsBefore;
+ /** The window attributes matching the windows in @a paWindowsBefore */
+ XWindowAttributes *paAttribsBefore;
+ /** The window names matching the windows in @a paWindowsBefore */
+ const char **papszNamesBefore;
+ /** The shaped window before the event - we allow at most one of these.
+ * Zero for none. */
+ Window hShapeWindowBefore;
+ /** The number of rectangles in the shaped window before the event. */
+ int cShapeRectsBefore;
+ /** The rectangles in the shaped window before the event */
+ XRectangle *paShapeRectsBefore;
+ /** The number of windows visible after the event */
+ unsigned cWindowsAfter;
+ /** An array of Window IDs for the visible and unmapped windows after
+ * the event */
+ Window *pahWindowsAfter;
+ /** The window attributes matching the windows in @a paWindowsAfter */
+ XWindowAttributes *paAttribsAfter;
+ /** The window names matching the windows in @a paWindowsAfter */
+ const char **papszNamesAfter;
+ /** The shaped window after the event - we allow at most one of these.
+ * Zero for none. */
+ Window hShapeWindowAfter;
+ /** The number of rectangles in the shaped window after the event. */
+ int cShapeRectsAfter;
+ /** The rectangles in the shaped window after the event */
+ XRectangle *paShapeRectsAfter;
+ /** The event to delivered */
+ int x11EventType;
+ /** The window for which the event in @enmEvent is delivered */
+ Window hEventWindow;
+ /** The number of windows expected to be reported at the end of the
+ * fixture */
+ unsigned cReportedRects;
+ /** The onscreen positions of those windows. */
+ RTRECT *paReportedRects;
+ /** Do we expect notification after the event? */
+ bool fExpectNotification;
+};
+
+/*** Test fixture to test the code against X11 configure (move) events ***/
+
+static Window g_ahWin1[] = { 20 };
+static XWindowAttributes g_aAttrib1Before[] =
+{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
+};
+static XRectangle g_aRectangle1[] =
+{
+ { 0, 0, 50, 50 },
+ { 50, 50, 150, 250 }
+};
+static XWindowAttributes g_aAttrib1After[] =
+{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
+};
+static const char *g_apszNames1[] = { "Test Window" };
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1Before));
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1After));
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_apszNames1));
+
+static RTRECT g_aRects1[] =
+{
+ { 200, 300, 250, 350 },
+ { 250, 350, 400, 600 }
+};
+
+static SMLSFIXTURE g_testMove =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ ConfigureNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** Test fixture to test the code against X11 configure (resize) events ***/
+
+static XWindowAttributes g_aAttrib2Before[] =
+{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
+};
+static XRectangle g_aRectangle2Before[] =
+{
+ { 0, 0, 50, 50 },
+ { 50, 50, 100, 100 }
+};
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib2Before));
+
+static SMLSFIXTURE g_testResize =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib2Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle2Before),
+ g_aRectangle2Before,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ ConfigureNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** Test fixture to test the code against X11 map events ***/
+
+static XWindowAttributes g_aAttrib3Before[] =
+{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped }
+};
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib3Before));
+
+static SMLSFIXTURE g_testMap =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib3Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ MapNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** Test fixtures to test the code against X11 unmap events ***/
+
+static XWindowAttributes g_aAttrib4After[] =
+{ { 100, 200, 300, 400, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped }
+};
+
+AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib4After));
+
+static SMLSFIXTURE g_testUnmap =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib4After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ UnmapNotify,
+ 20,
+ 0,
+ NULL,
+ true
+};
+
+/*** A window we are not monitoring has been unmapped. Nothing should
+ *** happen, especially nothing bad. ***/
+
+static RTRECT g_aRects2[] =
+{
+ { 100, 200, 150, 250 },
+ { 150, 250, 300, 500 }
+};
+
+static SMLSFIXTURE g_testUnmapOther =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1Before,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ UnmapNotify,
+ 21,
+ RT_ELEMENTS(g_aRects2),
+ g_aRects2,
+ false
+};
+
+/*** Test fixture to test the code against X11 shape events ***/
+
+static XRectangle g_aRectangle5Before[] =
+{
+ { 0, 0, 200, 200 }
+};
+
+static SMLSFIXTURE g_testShape =
+{
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle5Before),
+ g_aRectangle5Before,
+ RT_ELEMENTS(g_ahWin1),
+ g_ahWin1,
+ g_aAttrib1After,
+ g_apszNames1,
+ 20,
+ RT_ELEMENTS(g_aRectangle1),
+ g_aRectangle1,
+ VBoxShapeNotify,
+ 20,
+ RT_ELEMENTS(g_aRects1),
+ g_aRects1,
+ true
+};
+
+/*** And the test code proper ***/
+
+/** Compare two RTRECT structures */
+static bool smlsCompRect(RTRECT *pFirst, RTRECT *pSecond)
+{
+ return ( (pFirst->xLeft == pSecond->xLeft)
+ && (pFirst->yTop == pSecond->yTop)
+ && (pFirst->xRight == pSecond->xRight)
+ && (pFirst->yBottom == pSecond->yBottom));
+}
+
+static void smlsPrintDiffRects(RTRECT *pExp, RTRECT *pGot)
+{
+ RTPrintf(" Expected: %d, %d, %d, %d. Got: %d, %d, %d, %d\n",
+ pExp->xLeft, pExp->yTop, pExp->xRight, pExp->yBottom,
+ pGot->xLeft, pGot->yTop, pGot->xRight, pGot->yBottom);
+}
+
+/** Run through a test fixture */
+static unsigned smlsDoFixture(SMLSFIXTURE *pFixture, const char *pszDesc)
+{
+ SeamlessX11 subject;
+ unsigned cErrs = 0;
+
+ subject.init(sendRegionUpdate);
+ smlsSetWindowAttributes(pFixture->paAttribsBefore,
+ pFixture->pahWindowsBefore,
+ pFixture->cWindowsBefore,
+ pFixture->papszNamesBefore);
+ smlsSetShapeRectangles(pFixture->hShapeWindowBefore,
+ pFixture->cShapeRectsBefore,
+ pFixture->paShapeRectsBefore);
+ subject.start();
+ smlsSetWindowAttributes(pFixture->paAttribsAfter,
+ pFixture->pahWindowsAfter,
+ pFixture->cWindowsAfter,
+ pFixture->papszNamesAfter);
+ smlsSetShapeRectangles(pFixture->hShapeWindowAfter,
+ pFixture->cShapeRectsAfter,
+ pFixture->paShapeRectsAfter);
+ smlsSetNextEvent(pFixture->x11EventType, pFixture->hEventWindow);
+ if (gotNotification()) /* Initial window tree rebuild */
+ {
+ RTPrintf("%s: fixture: %s. Notification was set before the first event!!!\n",
+ g_pszTestName, pszDesc);
+ ++cErrs;
+ }
+ subject.nextConfigurationEvent();
+ if (!gotNotification())
+ {
+ RTPrintf("%s: fixture: %s. No notification was sent for the initial window tree rebuild.\n",
+ g_pszTestName, pszDesc);
+ ++cErrs;
+ }
+ smlsSetNextEvent(0, 0);
+ subject.nextConfigurationEvent();
+ if (pFixture->fExpectNotification && !gotNotification())
+ {
+ RTPrintf("%s: fixture: %s. No notification was sent after the event.\n",
+ g_pszTestName, pszDesc);
+ ++cErrs;
+ }
+ RTRECT *pRects = subject.getRects();
+ size_t cRects = subject.getRectCount();
+ if (cRects != pFixture->cReportedRects)
+ {
+ RTPrintf("%s: fixture: %s. Wrong number of rectangles reported after processing event (expected %u, got %u).\n",
+ g_pszTestName, pszDesc, pFixture->cReportedRects,
+ cRects);
+ ++cErrs;
+ }
+ else
+ for (unsigned i = 0; i < cRects; ++i)
+ if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i]))
+ {
+ RTPrintf("%s: fixture: %s. Rectangle %u wrong after processing event.\n",
+ g_pszTestName, pszDesc, i);
+ smlsPrintDiffRects(&pFixture->paReportedRects[i],
+ &pRects[i]);
+ ++cErrs;
+ break;
+ }
+ subject.stop();
+ subject.start();
+ if (cRects != pFixture->cReportedRects)
+ {
+ RTPrintf("%s: fixture: %s. Wrong number of rectangles reported without processing event (expected %u, got %u).\n",
+ g_pszTestName, pszDesc, pFixture->cReportedRects,
+ cRects);
+ ++cErrs;
+ }
+ else
+ for (unsigned i = 0; i < cRects; ++i)
+ if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i]))
+ {
+ RTPrintf("%s: fixture: %s. Rectangle %u wrong without processing event.\n",
+ g_pszTestName, pszDesc, i);
+ smlsPrintDiffRects(&pFixture->paReportedRects[i],
+ &pRects[i]);
+ ++cErrs;
+ break;
+ }
+ return cErrs;
+}
+
+int main(int argc, char **argv)
+{
+ RTR3InitExe(argc, &argv, 0);
+ unsigned cErrs = 0;
+ g_pszTestName = RTPathFilename(argv[0]);
+
+ RTPrintf("%s: TESTING\n", g_pszTestName);
+
+/** @todo r=bird: This testcase is broken and we didn't notice because we
+ * don't run it on the testboxes! @bugref{9842} */
+if (argc == 1)
+{
+ RTPrintf("%s: Note! This testcase is broken, skipping!\n", g_pszTestName);
+ return RTEXITCODE_SUCCESS;
+}
+
+ cErrs += smlsDoFixture(&g_testMove,
+ "ConfigureNotify event (window moved)");
+ // Currently not working
+ cErrs += smlsDoFixture(&g_testResize,
+ "ConfigureNotify event (window resized)");
+ cErrs += smlsDoFixture(&g_testMap, "MapNotify event");
+ cErrs += smlsDoFixture(&g_testUnmap, "UnmapNotify event");
+ cErrs += smlsDoFixture(&g_testUnmapOther,
+ "UnmapNotify event for unmonitored window");
+ cErrs += smlsDoFixture(&g_testShape, "ShapeNotify event");
+ if (cErrs > 0)
+ RTPrintf("%u errors\n", cErrs);
+ return cErrs == 0 ? 0 : 1;
+}
diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp
new file mode 100644
index 00000000..06bd5c8e
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp
@@ -0,0 +1,185 @@
+/* $Id: tstSeamlessX11.cpp $ */
+/** @file
+ * Linux seamless guest additions simulator in host.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <stdlib.h> /* exit() */
+
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include <VBox/VBoxGuestLib.h>
+
+#include "../seamless.h"
+
+static RTSEMEVENT eventSem;
+
+void VBClLogError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Error: %s", psz);
+
+ RTStrFree(psz);
+}
+
+/** Exit with a fatal error. */
+void VBClLogFatalError(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ char *psz = NULL;
+ RTStrAPrintfV(&psz, pszFormat, args);
+ va_end(args);
+
+ AssertPtr(psz);
+ RTPrintf("Fatal error: %s", psz);
+
+ RTStrFree(psz);
+
+ exit(1);
+}
+
+void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...)
+{
+ RT_NOREF(iLevel);
+
+ va_list va;
+ va_start(va, pszFormat);
+ RTPrintf("%s", pszFormat);
+ va_end(va);
+}
+
+int VBClStartVTMonitor()
+{
+ return VINF_SUCCESS;
+}
+
+int VbglR3SeamlessSendRects(uint32_t cRects, PRTRECT pRects)
+{
+ RTPrintf("Received rectangle update (%u rectangles):\n", cRects);
+ for (unsigned i = 0; i < cRects; ++i)
+ {
+ RTPrintf(" xLeft: %d yTop: %d xRight: %d yBottom: %d\n",
+ pRects[i].xLeft, pRects[i].yTop, pRects[i].xRight,
+ pRects[i].yBottom);
+ }
+ return VINF_SUCCESS;
+}
+
+int VbglR3SeamlessSetCap(bool bState)
+{
+ RTPrintf("%s\n", bState ? "Seamless capability set"
+ : "Seamless capability unset");
+ return VINF_SUCCESS;
+}
+
+int VbglR3CtlFilterMask(uint32_t u32OrMask, uint32_t u32NotMask)
+{
+ RTPrintf("IRQ filter mask changed. Or mask: 0x%x. Not mask: 0x%x\n",
+ u32OrMask, u32NotMask);
+ return VINF_SUCCESS;
+}
+
+int VbglR3SeamlessWaitEvent(VMMDevSeamlessMode *pMode)
+{
+ static bool active = false;
+
+ int rc = VINF_SUCCESS;
+ if (!active)
+ {
+ active = true;
+ *pMode = VMMDev_Seamless_Visible_Region;
+ }
+ else
+ rc = RTSemEventWait(eventSem, RT_INDEFINITE_WAIT);
+ return rc;
+}
+
+VBGLR3DECL(int) VbglR3InitUser(void) { return VINF_SUCCESS; }
+VBGLR3DECL(void) VbglR3Term(void) {}
+
+/**
+ * Xlib error handler for certain errors that we can't avoid.
+ */
+int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError)
+{
+ char errorText[1024];
+
+ if (pError->error_code == BadWindow)
+ {
+ /* This can be triggered if a guest application destroys a window before we notice. */
+ RTPrintf("ignoring BadAtom error and returning\n");
+ return 0;
+ }
+ XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText));
+ RTPrintf("An X Window protocol error occurred: %s\n"
+ " Request code: %d\n"
+ " Minor code: %d\n"
+ " Serial number of the failed request: %d\n\n"
+ "exiting.\n",
+ errorText, (int)pError->request_code, (int)pError->minor_code,
+ (int)pError->serial);
+ exit(1);
+}
+
+int main( int argc, char **argv)
+{
+ int rc = VINF_SUCCESS;
+
+ RTR3InitExe(argc, &argv, 0);
+ RTPrintf("VirtualBox guest additions X11 seamless mode testcase\n");
+ if (0 == XInitThreads())
+ {
+ RTPrintf("Failed to initialise X11 threading, exiting.\n");
+ exit(1);
+ }
+ /* Set an X11 error handler, so that we don't die when we get unavoidable errors. */
+ XSetErrorHandler(vboxClientXLibErrorHandler);
+ RTPrintf("\nType Ctrl-C to exit...\n");
+ RTSemEventCreate(&eventSem);
+ /** Our instance of the seamless class. */
+ SeamlessMain seamless;
+ LogRel(("Starting seamless Guest Additions...\n"));
+ rc = seamless.init();
+ if (rc != VINF_SUCCESS)
+ {
+ RTPrintf("Failed to initialise seamless Additions, rc = %Rrc\n", rc);
+ }
+ bool fShutdown = false;
+ rc = seamless.worker(&fShutdown);
+ if (rc != VINF_SUCCESS)
+ {
+ RTPrintf("Failed to run seamless Additions, rc = %Rrc\n", rc);
+ }
+ return rc;
+}
diff --git a/src/VBox/Additions/x11/undefined_xfree86 b/src/VBox/Additions/x11/undefined_xfree86
new file mode 100644
index 00000000..e80b6819
--- /dev/null
+++ b/src/VBox/Additions/x11/undefined_xfree86
@@ -0,0 +1,1188 @@
+# This file contains the complete list, excluding modules of undefined symbols
+# which are allowed in XFree86 4.3 binary driver modules.
+
+AddCallback
+AddEnabledDevice
+AddExtension
+AddExtensionAlias
+AddResource
+AdjustWaitForDelay
+AllocateClientPrivate
+AllocateClientPrivateIndex
+AllocateColormapPrivateIndex
+AllocateGCPrivate
+AllocateGCPrivateIndex
+AllocatePixmap
+AllocatePixmapPrivate
+AllocatePixmapPrivateIndex
+AllocateScreenPrivateIndex
+AllocateWindowPrivate
+AllocateWindowPrivateIndex
+AllocColor
+_alpha_inb
+_alpha_inl
+_alpha_inw
+_alpha_outb
+_alpha_outl
+_alpha_outw
+AltICD2061SetClock
+AssignTypeAndName
+Att409SetClock
+AttendClient
+BadShmSegCode
+BitOrderInvert
+BufFileRead
+BufFileWrite
+_bus_base
+byte_reversed
+ChangeGC
+ChangeResourceValue
+ChangeWindowAttributes
+ChangeWindowProperty
+CheckCursorConfinement
+CheckExtension
+CheckFSFormat
+CheckWindowOptionalNeed
+Chrontel8391SetClock
+CirrusFindClock
+CirrusSetClock
+clients
+ClientSleep
+ClientSleepUntil
+ClientStateCallback
+ClientTimeToServerTime
+ClientWakeup
+CloseFont
+commonCalcClock
+CompareTimeStamps
+ConfiguredMonitor
+CopyGC
+CopyISOLatin1Lowered
+CopySwap32Write
+CreateColormap
+CreateFontRec
+CreateGC
+CreateNewResourceClass
+CreateNewResourceType
+CreateScratchGC
+CreateUnclippedWinSize
+CreateWindow
+currentMaxClients
+CurrentSelections
+currentTime
+dacInTi3026IndReg
+dacOutTi3026IndReg
+debug_inb
+debug_inl
+debug_inw
+debug_outb
+debug_outl
+debug_outw
+DeclareExtensionSecurity
+defaultColorVisualClass
+defaultDPMSEnabled
+DeleteCallback
+DeleteProperty
+DeliverEvents
+deltaSaveUndersViewable
+DestroyFontRec
+DeviceEventCallback
+DGAActive
+DGAAvailable
+DGABlitRect
+DGABlitTransRect
+DGAChangePixmapMode
+DGACloseFramebuffer
+DGACreateColormap
+DGAFillRect
+DGAGetModeInfo
+DGAGetModes
+DGAGetOldDGAMode
+DGAGetViewportStatus
+DGAInit
+DGAInstallCmap
+DGAOpenFramebuffer
+DGASelectInput
+DGASetInputMode
+DGASetMode
+DGASetViewport
+DGASync
+dispatchException
+div
+__div64
+__divdf3
+__divdi3
+__divl
+__divlu
+__divq
+__divqu
+__divsf3
+__divsi3
+dixChangeGC
+DoChangeGC
+DPMSCapableFlag
+DPMSDisabledSwitch
+DPMSEnabled
+DPMSEnabledSwitch
+DPMSGet
+DPMSOffTime
+DPMSPowerLevel
+DPMSSet
+DPMSStandbyTime
+DPMSSupported
+DPMSSuspendTime
+DuplicateModule
+eieio
+erel->rel->r_info
+Error
+ErrorF
+Et4000AltICD2061SetClock
+ET4000gendacSetClock
+ET4000stg1703SetClock
+ET6000SetClock
+EventCallback
+EventSwapVector
+ex
+FakeAllocColor
+FakeClientID
+FakeFreeColor
+FatalError
+FindAllClientResources
+FindClientResourcesByType
+FindWindowWithOptional
+FlushCallback
+FontCacheChangeSettings
+FontCacheCloseCache
+FontCacheGetBitmap
+FontCacheGetEntry
+FontCacheGetSettings
+FontCacheGetStatistics
+FontCacheInsertEntry
+FontCacheOpenCache
+FontCacheSearchEntry
+FontComputeInfoAccelerators
+FontCouldBeTerminal
+FontDefaultFormat
+FontEncDirectory
+FontEncFind
+FontEncFromXLFD
+FontEncMapFind
+FontEncName
+font_encoding_find
+font_encoding_from_xlfd
+font_encoding_name
+font_encoding_recode
+FontEncRecode
+FontFileBitmapSources
+FontFileClose
+FontFileCloseFont
+FontFileCompleteXLFD
+FontFileCountDashes
+FontFileFindNameInDir
+FontFileMatchRenderer
+FontFileOpen
+FontFileOpenBitmap
+FontFilePriorityRegisterRenderer
+FontFileRegisterRenderer
+FontMapFind
+FontMapReverse
+FontMapReverseFree
+FontParseXLFDName
+FontToXError
+FourByteSwap
+fpe_functions
+FreeColors
+FreeCursor
+FreeGC
+FreeResource
+FreeResourceByType
+FreeScratchGC
+FreeScratchPixmapHeader
+func
+ func
+gendacMNToClock
+GetGlyphs
+GetScratchGC
+GetScratchPixmapHeader
+GetTimeInMillis
+GetXIDList
+GetXIDRange
+GiveUp
+globalSerialNumber
+GrabInProgress
+GravityTranslate
+header
+IBMRGBSetClock
+ICS2595SetClock
+ICS5342SetClock
+identifyEncodingFile
+IgnoreClient
+inb
+_inb
+InitButtonClassDeviceStruct
+InitFocusClassDeviceStruct
+InitKeyboardDeviceStruct
+InitKeyClassDeviceStruct
+InitLedFeedbackClassDeviceStruct
+InitPointerDeviceStruct
+InitProximityClassDeviceStruct
+InitPtrFeedbackClassDeviceStruct
+InitValuatorAxisStruct
+InitValuatorClassDeviceStruct
+inl
+_inl
+inputInfo
+inw
+_inw
+ioBase
+isItTimeToYield
+lastDeviceEventTime
+lastResourceType
+ldl_brx
+ldl_u
+ldq_u
+ldw_brx
+ldw_u
+LegalNewID
+LoaderCheckUnresolved
+LoaderDefaultFunc
+LoaderErrorMsg
+LoaderFreeDirList
+LoaderGetOS
+LoaderListDirs
+LoaderRefSymbols
+LoaderRefSymLists
+LoaderReqSymbols
+LoaderReqSymLists
+LoaderSymbol
+LoadExtension
+LoadFont
+LoadGlyphs
+LoadSubModule
+LocalClient
+LookupClient
+LookupDrawable
+LookupIDByClass
+LookupIDByType
+LookupKeyboardDevice
+LookupPointerDevice
+LookupWindow
+MakeAtom
+MakeClientGrabImpervious
+MakeClientGrabPervious
+MakeWindowOptional
+MapWindow
+mem_barrier
+memcpy
+"memcpy",xf86memcpy
+memset
+"memset",xf86memset
+miAllocateGCPrivateIndex
+miChangeBorderWidth
+miChangeClip
+miChangeGC
+miClearDrawable
+miClearToBackground
+miClearVisualTypes
+miClipNotify
+miClipSpans
+miCompositeRects
+miComputeCompositeClip
+miComputeCompositeRegion
+miCopyArea
+miCopyClip
+miCopyGC
+miCopyPlane
+miCreateDefColormap
+miCreateGCOps
+miCreateScreenResources
+miDCInitialize
+miDestroyClip
+miDestroyGC
+miDestroyGCOps
+miEmptyBox
+miEmptyData
+miExpandDirectColors
+miFillArcSetup
+miFillArcSliceSetup
+miFillConvexPoly
+miFillPolygon
+miFindMaxBand
+miGetDefaultVisualMask
+miGetImage
+miGetScreenPixmap
+miGlyphExtents
+miGlyphs
+miHandleExposures
+miHandleValidateExposures
+miHookInitVisuals
+miImageGlyphBlt
+miImageText16
+miImageText8
+miInitializeBackingStore
+miInitializeBanking
+miInitializeColormap
+miInitOverlay
+miInitVisuals
+miInitVisualsProc
+miInstallColormap
+miInstalledMaps
+miIntersect
+miInverse
+miListInstalledColormaps
+miModifyBanking
+miModifyPixmapHeader
+MinorOpcodeOfRequest
+miOverlayCollectUnderlayRegions
+miOverlayComputeCompositeClip
+miOverlayCopyUnderlay
+miOverlayGetPrivateClips
+miOverlaySetRootClip
+miOverlaySetTransFunction
+miPaintWindow
+miPictureInit
+miPointerAbsoluteCursor
+miPointerCurrentScreen
+miPointerGetMotionBufferSize
+miPointerGetMotionEvents
+miPointerInitialize
+miPointerPosition
+miPointerScreenIndex
+miPointerWarpCursor
+miPointInRegion
+miPolyArc
+miPolyBuildEdge
+miPolyBuildPoly
+miPolyFillArc
+miPolyFillRect
+miPolyGlyphBlt
+miPolyPoint
+miPolyRectangle
+miPolySegment
+miPolyText16
+miPolyText8
+miPushPixels
+miPutImage
+miRecolorCursor
+miRectAlloc
+miRectIn
+miRectsToRegion
+miRegionAppend
+miRegionCopy
+miRegionCreate
+miRegionDestroy
+miRegionEmpty
+miRegionExtents
+miRegionInit
+miRegionNotEmpty
+miRegionReset
+miRegionUninit
+miRegionValidate
+miResolveColor
+miRoundCapClip
+miRoundJoinClip
+MiscExtApply
+MiscExtCreateStruct
+MiscExtDestroyStruct
+MiscExtGetFilePaths
+MiscExtGetKbdSettings
+MiscExtGetKbdValue
+MiscExtGetMouseSettings
+MiscExtGetMouseValue
+MiscExtSetGrabKeysState
+MiscExtSetKbdValue
+MiscExtSetMouseDevice
+MiscExtSetMouseValue
+miScreenInit
+miSegregateChildren
+miSendGraphicsExpose
+miSetPixmapDepths
+miSetScreenPixmap
+miSetShape
+miSetVisualTypes
+miSetVisualTypesAndMasks
+miSetZeroLineBias
+miShapedWindowIn
+miSpritePointerFuncs
+miStepDash
+miSubtract
+miTranslateRegion
+miUninstallColormap
+miUnion
+miWideDash
+miWideLine
+miWindowExposures
+miZeroArcSetup
+miZeroClipLine
+miZeroDashLine
+miZeroLine
+miZeroLineScreenIndex
+miZeroPolyArc
+__moddi3
+__modsi3
+monitorResolution
+MoveWindowInStack
+mul
+__mul64
+__muldf3
+__muldi3
+__mulsf3
+__mulsi3
+Must_have_memory
+NameForAtom
+ name, func
+ name, var
+NewCurrentScreen
+NoopDDA
+noPanoramiXExtension
+NotClippedByChildren
+noTestExtensions
+NotImplemented
+noXkbExtension
+NumCurrentSelections
+numSaveUndersViewable
+Ones
+outb
+_outb
+outl
+_outl
+outw
+_outw
+panoramiXdataPtr
+PanoramiXNumScreens
+PciAvoid
+pciBusAddrToHostAddr
+pciFindFirst
+pciFindNext
+pciHostAddrToBusAddr
+pciNumBuses
+pciReadByte
+pciReadLong
+pciReadWord
+pciSetBitsLong
+pciTag
+pciWriteByte
+pciWriteLong
+pciWriteWord
+permitOldBugs
+PictureAddFilter
+PictureGetSubpixelOrder
+PictureInit
+PictureScreenPrivateIndex
+PictureSetFilterAlias
+PictureSetSubpixelOrder
+PictureTransformPoint
+PixmapWidthPaddingInfo
+PointerConfinedToScreen
+ProcBadRequest
+ProcVector
+QueryColors
+QueryGlyphExtents
+QueueWorkProc
+RegisterBlockAndWakeupHandlers
+RegisterResourceName
+rel[i].r_info
+rel->r_info
+rem
+__reml
+__remlu
+RemoveBlockAndWakeupHandlers
+RemoveEnabledDevice
+__remq
+__remqu
+RepadBitmap
+ReplyCallback
+ReplySwapVector
+res8514Exclusive
+res8514Shared
+ResetCurrentRequest
+ResizeChildrenWinSize
+ResourceNames
+_restf14
+_restf17
+_restf18
+_restf19
+_restf20
+_restf22
+_restf23
+_restf24
+_restf25
+_restf26
+_restf27
+_restf28
+_restf29
+resVgaExclusive
+resVgaIoShared
+resVgaMemShared
+resVgaShared
+resVgaSparseExclusive
+resVgaSparseShared
+resVgaUnusedExclusive
+resVgaUnusedShared
+S3AuroraSetClock
+S3gendacSetClock
+s3IBMRGB_Init
+s3IBMRGB_Probe
+s3InIBMRGBIndReg
+s3OutIBMRGBIndReg
+S3Trio64V2SetClock
+S3TrioSetClock
+savedScreenInfo
+_savef14
+_savef17
+_savef18
+_savef19
+_savef20
+_savef22
+_savef23
+_savef24
+_savef25
+_savef26
+_savef27
+_savef28
+_savef29
+SaveScreens
+SC11412SetClock
+screenInfo
+screenIsSaved
+ScreenSaverBlanking
+ScreenSaverTime
+SecurityLookupDrawable
+SecurityLookupIDByClass
+SecurityLookupIDByType
+SecurityLookupWindow
+SendErrorToClient
+SendMappingNotify
+SendVisibilityNotify
+serverClient
+serverGeneration
+ServerGrabCallback
+SetBorderSize
+SetClipRects
+SetCriticalEvent
+SetCriticalOutputPending
+SetDashes
+SetInputCheck
+SetTimeSinceLastInputEvent
+SetWinSize
+ShmCompletionCode
+ShmSegType
+SkippedRequestsCallback
+sparcPromClose
+sparcPromGetBool
+sparcPromGetProperty
+sparcPromInit
+StandardMinorOpcode
+STG1703getIndex
+STG1703magic
+STG1703SetClock
+STG1703setIndex
+stl_brx
+stl_u
+StoreColors
+stq_u
+stw_brx
+stw_u
+Swap32Write
+SwapColorItem
+SwapConnSetupInfo
+SwapConnSetupPrefix
+SwapLongs
+SwapShorts
+sysctlbyname
+TellGainedMap
+TellLostMap
+Ti3025SetClock
+Ti3026SetClock
+Ti3030SetClock
+TimerCancel
+TimerFree
+TimerSet
+TraverseTree
+TryClientEvents
+TwoByteSwap
+TypeMask
+udiv
+__udivdi3
+__udivsi3
+__umoddi3
+__umodsi3
+umul
+UnloadSubModule
+UnmapWindow
+UpdateCurrentTime
+UpdateCurrentTimeIf
+urem
+ValidateGC
+ValidAtom
+ var
+VerifyRectOrder
+VErrorF
+VidModeAddModeline
+VidModeCheckModeForDriver
+VidModeCheckModeForMonitor
+VidModeCopyMode
+VidModeCreateMode
+VidModeDeleteModeline
+VidModeExtensionInit
+VidModeGetClocks
+VidModeGetCurrentModeline
+VidModeGetDotClock
+VidModeGetFirstModeline
+VidModeGetGamma
+VidModeGetGammaRamp
+VidModeGetGammaRampSize
+VidModeGetModeValue
+VidModeGetMonitor
+VidModeGetMonitorValue
+VidModeGetNextModeline
+VidModeGetNumOfClocks
+VidModeGetNumOfModes
+VidModeGetViewPort
+VidModeLockZoom
+VidModeSetCrtcForMode
+VidModeSetGamma
+VidModeSetGammaRamp
+VidModeSetModeValue
+VidModeSetViewPort
+VidModeSwitchMode
+VidModeZoomViewport
+WalkTree
+WindowsRestructured
+WindowTable
+WriteEventsToClient
+write_mem_barrier
+WriteToClient
+x
+Xalloc
+Xcalloc
+XDGAEventBase
+xf86abort
+xf86abs
+xf86access
+xf86acos
+xf86AcquireGART
+xf86AddDeviceToConfigure
+xf86AddDriver
+xf86AddEnabledDevice
+xf86AddEntityToScreen
+xf86AddInputDriver
+xf86AddInputHandler
+xf86AddModuleInfo
+xf86AddNewOption
+xf86AddPixFormat
+xf86AddResToList
+xf86AgpGARTSupported
+xf86AllocateEntityPrivateIndex
+xf86AllocateGARTMemory
+xf86AllocateInput
+xf86AllocateLinearOffscreenArea
+xf86AllocateOffscreenArea
+xf86AllocateOffscreenLinear
+xf86AllocateScreen
+xf86AllocateScrnInfoPrivateIndex
+xf86asin
+xf86atan
+xf86atan2
+xf86atof
+xf86atoi
+xf86atol
+xf86BindGARTMemory
+xf86BlockSIGIO
+xf86Break1
+xf86Break2
+xf86Break3
+xf86bsearch
+xf86BusToMem
+xf86bzero
+xf86calloc
+xf86CaughtSignal
+xf86ceil
+xf86CheckIfOptionUsed
+xf86CheckIfOptionUsedByName
+xf86CheckModeForDriver
+xf86CheckModeForMonitor
+xf86CheckMTRR
+xf86CheckPciGAType
+xf86CheckPciMemBase
+xf86CheckPciSlot
+xf86ChkConflict
+xf86chmod
+xf86chown
+xf86ClaimFbSlot
+xf86ClaimFixedResources
+xf86ClaimIsaSlot
+xf86ClaimNoSlot
+xf86ClaimPciSlot
+xf86clearerr
+xf86ClearPrimInitDone
+xf86close
+xf86closedir
+xf86CloseSerial
+xf86clrdaccommbit
+xf86CollectInputOptions
+xf86CollectOptions
+xf86CommonSpecialKey
+xf86ComparePciBusString
+xf86ConfigActiveIsaEntity
+xf86ConfigActivePciEntity
+xf86ConfigDRI
+xf86ConfigFbEntity
+xf86ConfigIsaEntity
+xf86ConfigIsaEntityInactive
+xf86ConfigPciEntity
+xf86ConfigPciEntityInactive
+xf86cos
+xf86CurrentScreen
+xf86dactocomm
+xf86dactopel
+xf86DeallocateResourcesForEntity
+xf86DeleteDriver
+xf86DeleteInput
+xf86DeleteMode
+xf86DeleteModuleInfo
+xf86DeleteScreen
+xf86DeregisterStateChangeNotificationCallback
+xf86DisableInputHandler
+xf86DisableInterrupts
+xf86DisableIO
+xf86DisableRandR
+xf86DPMSInit
+xf86DrvMsg
+xf86DrvMsgVerb
+xf86DummyVar1
+xf86DummyVar2
+xf86DummyVar3
+xf86DupResList
+xf86EnableAccess
+xf86EnableAGP
+xf86EnableDisableFBAccess
+xf86EnableInputHandler
+xf86EnableInterrupts
+xf86EnableIO
+xf86EnablePciBusMaster
+xf86EnableVTSwitch
+xf86EnterServerState
+xf86eqEnqueue
+xf86errno
+xf86ErrorF
+xf86ErrorFVerb
+xf86execl
+xf86exit
+xf86exp
+xf86fabs
+xf86FBManagerRunning
+xf86fclose
+xf86feof
+xf86ferror
+xf86fflush
+xf86ffs
+xf86fgetc
+xf86fgetpos
+xf86fgets
+xf86FindOption
+xf86FindOptionValue
+xf86FindPciClass
+xf86FindPciDeviceVendor
+xf86FindScreenForEntity
+xf86FindXvOptions
+xf86finite
+xf86FirstLocalDevice
+xf86FixPciResource
+xf86floor
+xf86FlushInput
+xf86fmod
+xf86fopen
+xf86FormatPciBusNumber
+xf86fpossize
+xf86fprintf
+xf86fputc
+xf86fputs
+xf86fread
+xf86free
+xf86FreeOffscreenArea
+xf86FreeOffscreenLinear
+xf86FreeResList
+xf86freopen
+xf86frexp
+xf86fscanf
+xf86fseek
+xf86fsetpos
+xf86fstat
+xf86ftell
+xf86fwrite
+xf86GARTCloseScreen
+xf86GetAGPInfo
+xf86GetAllowMouseOpenFail
+xf86GetBlock
+xf86GetBppFromDepth
+xf86getc
+xf86GetClocks
+xf86getdaccomm
+xf86GetDepth
+xf86GetDevFromEntity
+xf86getegid
+xf86GetEntityForSbusInfo
+xf86GetEntityInfo
+xf86GetEntityPrivate
+xf86getenv
+xf86GetErrno
+xf86geteuid
+xf86GetFlipPixels
+xf86GetGamma
+xf86getjmptype
+xf86GetLastScrnFlag
+xf86GetModInDevAllowNonLocal
+xf86GetModInDevEnabled
+xf86GetModuleVersion
+xf86GetMotionEvents
+xf86GetNearestClock
+xf86GetNumEntityInstances
+xf86GetOptValBool
+xf86GetOptValFreq
+xf86GetOptValInteger
+xf86GetOptValReal
+xf86GetOptValString
+xf86GetOptValULong
+xf86GetOS
+xf86getpagesize
+xf86GetPciConfigInfo
+xf86GetPciDomain
+xf86GetPciEntity
+xf86GetPciInfoForEntity
+xf86GetPciVideoInfo
+xf86getpid
+xf86GetPix24
+xf86GetPixFormat
+xf86GetPointerScreenFuncs
+xf86GetSbusInfoForEntity
+xf86getsecs
+xf86GetSerialModemState
+xf86GetServerName
+xf86GetSparse
+xf86GetVerbosity
+xf86GetVersion
+xf86GetVidModeAllowNonLocal
+xf86GetVidModeEnabled
+xf86GetVisualName
+xf86GetWeight
+xf86HandleColormaps
+xf86HUGE_VAL
+xf86hypot
+xf86InitFBManager
+xf86InitFBManagerArea
+xf86InitFBManagerRegion
+xf86InitialCheckModeForDriver
+xf86InitValuatorAxisStruct
+xf86InitValuatorDefaults
+xf86InstallSIGIOHandler
+xf86inSuspend
+xf86InterceptSignals
+xf86ioctl
+xf86IODelay
+xf86isalnum
+xf86isalpha
+xf86iscntrl
+xf86IsCorePointer
+xf86isdigit
+xf86IsEntityPrimary
+xf86IsEntitySharable
+xf86IsEntityShared
+xf86isgraph
+xf86islower
+xf86IsOptionSet
+xf86IsPc98
+xf86IsPciDevPresent
+xf86IsPrimaryIsa
+xf86IsPrimaryPci
+xf86IsPrimInitDone
+xf86isprint
+xf86ispunct
+xf86IsScreenPrimary
+xf86isspace
+xf86IsUnblank
+xf86isupper
+xf86isxdigit
+xf86JoinResLists
+xf86labs
+xf86ldexp
+xf86LinearVidMem
+xf86LoadDrvSubModule
+xf86LoaderCheckSymbol
+xf86LoaderRefSymbols
+xf86LoaderRefSymLists
+xf86LoaderReqSymbols
+xf86LoaderReqSymLists
+xf86LoadKernelModule
+xf86LoadOneModule
+xf86LoadSubModule
+xf86log
+xf86log10
+"xf86longjmp",longjmp
+xf86LookupMode
+xf86lseek
+xf86malloc
+xf86MapDomainIO
+xf86MapDomainMemory
+xf86MapPciMem
+xf86MapReadSideEffects
+xf86MapSbusMem
+xf86MapVidMem
+xf86MarkOptionUsed
+xf86MarkOptionUsedByName
+xf86MatchDevice
+xf86MatchIsaInstances
+xf86MatchPciInstances
+xf86MatchSbusInstances
+xf86memchr
+xf86memcmp
+xf86memcpy
+xf86memmove
+xf86memset
+xf86MemToBus
+xf86mkdir
+xf86mknod
+xf86mmap
+xf86ModeStatusToString
+xf86modf
+xf86MotionHistoryAllocate
+xf86Msg
+xf86MsgVerb
+xf86munmap
+xf86NameCmp
+xf86NewOption
+xf86NewSerialNumber
+xf86NextOption
+xf86NoSharedResources
+xf86open
+xf86opendir
+xf86OpenSerial
+xf86OptionListCreate
+xf86OptionListFree
+xf86OptionListMerge
+xf86OptionListReport
+xf86OptionName
+xf86OptionValue
+xf86OSKbdPreInit
+xf86OSMouseInit
+xf86p8bit
+xf86ParseIsaBusString
+xf86ParsePciBusString
+xf86perror
+xf86PixmapIndex
+xf86PostButtonEvent
+xf86PostKeyboardEvent
+xf86PostKeyEvent
+xf86PostMotionEvent
+xf86PostProximityEvent
+xf86pow
+xf86PrintChipsets
+xf86PrintDepthBpp
+xf86printf
+xf86PrintModes
+xf86PrintResList
+xf86ProcessCommonOptions
+xf86ProcessOptions
+xf86PruneDriverModes
+xf86PurgeUnlockedOffscreenAreas
+xf86qsort
+xf86QueryLargestOffscreenArea
+xf86QueryLargestOffscreenLinear
+xf86QueueAsyncEvent
+xf86read
+xf86ReadBIOS
+xf86readdir
+xf86ReadDomainMemory
+xf86ReadMmio16
+xf86ReadMmio32
+xf86ReadMmio8
+xf86ReadPciBIOS
+xf86ReadSerial
+xf86realloc
+xf86ReallocatePciResources
+xf86RegisterFreeBoxCallback
+xf86RegisterOffscreenManager
+xf86RegisterResources
+xf86RegisterRootWindowProperty
+xf86RegisterStateChangeNotificationCallback
+xf86ReleaseGART
+xf86remove
+xf86RemoveEnabledDevice
+xf86RemoveEntityFromScreen
+xf86RemoveInputHandler
+xf86RemoveSIGIOHandler
+xf86rename
+xf86ReplaceBoolOption
+xf86ReplaceIntOption
+xf86ReplaceStrOption
+xf86ResizeOffscreenArea
+xf86ResizeOffscreenLinear
+xf86ReturnOptValBool
+xf86rewind
+xf86rewinddir
+xf86SbusHandleColormaps
+xf86SbusHideOsHwCursor
+xf86SbusSetOsHwCursorCmap
+xf86SbusUseBuiltinMode
+xf86ScaleAxis
+xf86scanpci
+xf86ScreenIndex
+xf86Screens
+xf86SerialModemClearBits
+xf86SerialModemSetBits
+xf86SerialSendBreak
+xf86ServerIsExiting
+xf86ServerIsOnlyDetecting
+xf86ServerIsOnlyProbing
+xf86ServerIsResetting
+xf86SetAccessFuncs
+xf86SetBackingStore
+xf86SetBlackWhitePixels
+xf86SetBoolOption
+xf86setbuf
+xf86SetCrtcForModes
+xf86SetCurrentAccess
+xf86setdaccomm
+xf86setdaccommbit
+xf86SetDefaultVisual
+xf86SetDepthBpp
+xf86SetDpi
+xf86SetEntityFuncs
+xf86SetEntityInstanceForScreen
+xf86SetEntitySharable
+xf86SetEntityShared
+xf86SetGamma
+xf86SetIntOption
+xf86setjmp
+xf86setjmp1
+xf86setjmp1_arg2
+"xf86setjmp1",__sigsetjmp
+xf86setjmperror
+"xf86setjmp",setjmp
+xf86SetLastScrnFlag
+xf86SetOperatingState
+xf86SetPciVideo
+xf86SetPrimInitDone
+xf86SetPriority
+xf86SetRealOption
+xf86SetSerial
+xf86SetSerialModemState
+xf86SetSerialSpeed
+xf86SetSilkenMouse
+xf86SetStrOption
+xf86setvbuf
+xf86SetWeight
+xf86shmat
+xf86shmctl
+xf86shmdt
+xf86shmget
+xf86ShowClockRanges
+xf86ShowClocks
+xf86ShowUnusedOptions
+xf86sin
+xf86sleep
+xf86SlowBcopy
+xf86SlowBCopyFromBus
+xf86SlowBCopyToBus
+xf86snprintf
+xf86SoundKbdBell
+xf86sprintf
+xf86SPTimestamp
+xf86sqrt
+xf86sscanf
+xf86stat
+xf86stderr
+xf86stdin
+xf86stdout
+xf86STimestamp
+xf86strcasecmp
+xf86strcat
+xf86strchr
+xf86strcmp
+xf86strcpy
+xf86strcspn
+xf86strdup
+xf86strerror
+xf86StringToToken
+xf86strlen
+xf86strncasecmp
+xf86strncat
+xf86strncmp
+xf86strncpy
+xf86strpbrk
+xf86strrchr
+xf86strspn
+xf86strstr
+xf86strtod
+xf86strtok
+xf86strtol
+xf86strtoul
+xf86tan
+xf86tmpfile
+xf86TokenToOptinfo
+xf86TokenToOptName
+xf86TokenToString
+xf86tolower
+xf86toupper
+xf86UDelay
+xf86UnbindGARTMemory
+xf86UnblockSIGIO
+xf86ungetc
+xf86UnloadSubModule
+xf86UnmapSbusMem
+xf86UnMapVidMem
+xf86usleep
+xf86ValidateModes
+xf86VDrvMsgVerb
+xf86vfprintf
+xf86vsnprintf
+xf86vsprintf
+xf86WaitForInput
+xf86write
+xf86WriteMmio16
+xf86WriteMmio32
+xf86WriteMmio8
+xf86WriteMmioNB16
+xf86WriteMmioNB32
+xf86WriteMmioNB8
+xf86writepci
+xf86WriteSerial
+xf86XInputSetScreen
+xf86XInputSetSendCoreEvents
+xf86XVAllocateVideoAdaptorRec
+xf86XVClipVideoHelper
+xf86XVFillKeyHelper
+xf86XVFreeVideoAdaptorRec
+xf86XVListGenericAdaptors
+xf86XvMCScreenInit
+xf86XVQueryOffscreenImages
+xf86XVRegisterGenericAdaptorDriver
+xf86XVRegisterOffscreenImages
+xf86XVScreenInit
+Xfree
+XineramaDeleteResource
+XineramaGetCursorScreen
+XineramaRegisterConnectionBlockCallback
+XisbBlockDuration
+XisbFree
+XisbNew
+XisbRead
+XisbTrace
+XisbWrite
+XkbInitKeyboardDeviceStruct
+XkbSetRulesDflts
+XNFalloc
+XNFcalloc
+XNFrealloc
+XNFstrdup
+XRC_DRAWABLE
+Xrealloc
+XRT_COLORMAP
+XRT_GC
+XRT_PIXMAP
+XRT_WINDOW
+Xstrdup
+XvGetRTPortProc
+XvGetScreenIndexProc
+XvMCScreenInitProc
+XvScreenInitProc
diff --git a/src/VBox/Additions/x11/undefined_xfree86_modules b/src/VBox/Additions/x11/undefined_xfree86_modules
new file mode 100644
index 00000000..9a800b2c
--- /dev/null
+++ b/src/VBox/Additions/x11/undefined_xfree86_modules
@@ -0,0 +1,16 @@
+# This file contains the list of symbols from XFree86 modules which we use.
+# These symbols must also be explicitly declared in the driver code.
+
+fbPictureInit
+fbScreenInit
+ShadowFBInit2
+vgaHWFreeHWRec
+vgaHWGetHWRec
+vgaHWGetIndex
+vgaHWGetIOBase
+vgaHWRestore
+vgaHWSave
+vgaHWSetStdFuncs
+xf86CreateCursorInfoRec
+xf86DestroyCursorInfoRec
+xf86InitCursor
diff --git a/src/VBox/Additions/x11/undefined_xorg b/src/VBox/Additions/x11/undefined_xorg
new file mode 100644
index 00000000..dcc6c8fc
--- /dev/null
+++ b/src/VBox/Additions/x11/undefined_xorg
@@ -0,0 +1,184 @@
+# This file contains a non-exhaustive list of symbols which are allowed to
+# be undefined in X.Org Server binary driver modules which were not allowed
+# in XFree86 4.3. Some of these may only be allowed on some platforms, but
+# it fixing that would add additional complexity.
+
+__assert_fail
+calloc
+chdir
+chmod
+chown
+close
+closedir
+__ctype_b_loc
+__ctype_mask
+__cxa_finalize
+__deregister_frame_info_bases
+DRI2CloseScreen
+DRI2ScreenInit
+DRICloseScreen
+DRICreateInfoRec
+DRICreatePCIBusID
+DRIDestroyInfoRec
+DRIFinishScreenInit
+DRILock
+DRIQueryVersion
+DRIScreenInit
+DRIUnlock
+drmClose
+drmDropMaster
+drmFreeVersion
+drmGetVersion
+drmIoctl
+drmModeGetResources
+drmModeFreeResources
+drmSetMaster
+___errno
+__errno_location
+fbPictureInit
+fbScreenInit
+fchmod
+fchown
+fcntl
+fflush
+flock
+fprintf
+__fprintf_chk
+fputs
+free
+fstat
+fsync
+ftruncate
+ftruncate64
+futimes
+fwrite
+__fxstat64
+getcwd
+getenv
+geteuid
+GetMotionHistory
+GetMotionHistorySize
+getpwuid_r
+GlxSetVisualConfigs
+__gmon_start__
+iconv
+iconv_close
+iconv_open
+__iob
+ioctl
+__isoc99_sscanf
+isspace
+_ITM_deregisterTMCloneTable
+_ITM_registerTMCloneTable
+_Jv_RegisterClasses
+lchown
+lseek
+lseek64
+lstat
+__lxstat64
+malloc
+memalign
+memchr
+memcmp
+__memcpy_chk
+memmove
+miPointerGetScreen
+mmap64
+mprotect
+munmap
+nanosleep
+nl_langinfo
+open
+open64
+opendir
+pci_device_map_range
+pci_device_unmap_range
+posix_memalign
+pthread_self
+pthread_sigmask
+pthread_yield
+putenv
+read
+readdir
+readdir64
+realloc
+realpath
+__realpath_chk
+__register_frame_info_bases
+rename
+RRCrtcNotify
+RRGetInfo
+RRScreenSizeSet
+RRTellChanged
+setenv
+ShadowFBInit2
+sigdelset
+sigfillset
+snprintf
+__snprintf_chk
+sprintf
+__sprintf_chk
+sscanf
+__stack_chk_fail
+stat
+stderr
+strchr
+strcmp
+strcpy
+strlen
+strncat
+__strncat_chk
+strncmp
+strncpy
+__strncpy_chk
+strpbrk
+strstr
+strtoul
+__strtoul_internal
+SwappedProcVector
+symlink
+tolower
+unlink
+unsetenv
+utimes
+vfprintf
+__vfprintf_chk
+vgaHWFreeHWRec
+vgaHWGetHWRec
+vgaHWGetIndex
+vgaHWGetIOBase
+vgaHWRestore
+vgaHWSave
+vgaHWSetStdFuncs
+vsnprintf
+__vsnprintf_chk
+write
+xf86AddGeneralHandler
+xf86CreateCursorInfoRec
+xf86CrtcConfigInit
+xf86CrtcConfigPrivateIndex
+xf86CrtcCreate
+xf86CrtcScreenInit
+xf86CrtcSetMode
+xf86CrtcSetSizeRange
+xf86DestroyCursorInfoRec
+xf86DPMSSet
+xf86InitCursor
+xf86InitialConfiguration
+xf86InterpretEDID
+xf86ModesAdd
+xf86OutputCreate
+xf86OutputSetEDID
+xf86OutputUseScreenMonitor
+xf86RandR12GetOriginalVirtualSize
+xf86RemoveGeneralHandler
+xf86SaveScreen
+xf86ScreenToScrn
+xf86ScrnToScreen
+xf86SetDesiredModes
+xf86SetModeDefaultName
+xf86SetSingleMode
+xf86UpdateDesktopDimensions
+XNFcallocarray
+__xstat64
+__assert_c99
diff --git a/src/VBox/Additions/x11/vboxmouse/Makefile.kmk b/src/VBox/Additions/x11/vboxmouse/Makefile.kmk
new file mode 100644
index 00000000..bebc3234
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxmouse/Makefile.kmk
@@ -0,0 +1,296 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VBox Additions XFree86 and X.org mouse drivers.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+vboxmouse_xorg_INCS = \
+ $(VBOX_PATH_X11_ROOT)/inputproto-1.9.99.902 \
+ $(VBOX_PATH_X11_ROOT)/libpciaccess-0.10.8 \
+ $(VBOX_PATH_X11_ROOT)/pixman-0.40.0 \
+ $(VBOX_PATH_X11_ROOT)/xextproto-7.1.1 \
+ $(VBOX_PATH_X11_ROOT)/xproto-7.0.31
+
+#
+# vboxmouse_drv
+#
+if1of ($(KBUILD_TARGET), linux)
+ SYSMODS += vboxmouse_drv
+ vboxmouse_drv_TEMPLATE = VBoxGuestR3XFree86Mod
+ vboxmouse_drv_DEFS.linux = linux
+ vboxmouse_drv_DEFS.x86 += __i386__
+ # This one has to be defined when building server code on systems where
+ # unsigned long is 64bits
+ vboxmouse_drv_DEFS.amd64 += _XSERVER64
+ vboxmouse_drv_DEFS += \
+ _POSIX_C_SOURCE=199309L _POSIX_SOURCE _XOPEN_SOURCE _DEFAULT_SOURCE \
+ _BSD_SOURCE _SVID_SOURCE _GNU_SOURCE SHAPE XINPUT XKB LBX XAPPGROUP \
+ XCSECURITY TOGCUP XF86BIGFONT DPMSExtension PIXPRIV PANORAMIX RENDER \
+ GCCUSESGAS AVOID_GLYPHBLT PIXPRIV SINGLEDEPTH XFreeXDGA XvExtension \
+ XFree86LOADER XFree86Server XF86VIDMODE XvMCExtension SMART_SCHEDULE \
+ BUILDDEBUG X_BYTE_ORDER=X_LITTLE_ENDIAN DNDEBUG FUNCPROTO=15 NARROWPROTO \
+ IN_MODULE XFree86Module PNP_MOUSE IN_XF86_MODULE
+ vboxmouse_drv_INCS := \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3 \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11 \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11/extensions \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/Xserver \
+ $(PATH_SUB_CURRENT)
+ vboxmouse_drv_SOURCES = \
+ vboxmouse.c
+ # Any global symbols in the driver object files will be added to XFree86's
+ # symbol table, which can cause problems if we e.g. define a symbol in two
+ # modules.
+ vboxmouse_drv_POST_CMDS = \
+ objcopy --keep-global-symbol vboxmouseModuleData $(out) $(out)-objcopy$$(NLTAB) \
+ $(MV) -f $(out)-objcopy $(out)
+endif
+
+
+#
+# vboxmouse_drv_70
+#
+DLLS += vboxmouse_drv_70
+vboxmouse_drv_70_TEMPLATE = VBoxGuestR3XOrgMod
+vboxmouse_drv_70_DEFS = \
+ XFree86Server IN_MODULE XFree86Module XFree86LOADER XINPUT XORG_7X IN_XF86_MODULE DONT_DEFINE_WRAPPERS NO_ANSIC
+vboxmouse_drv_70_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.0.1 \
+ $(PATH_SUB_CURRENT)
+vboxmouse_drv_70_SOURCES = \
+ vboxmouse.c
+
+
+#
+# vboxmouse_drv_71
+#
+DLLS += vboxmouse_drv_71
+vboxmouse_drv_71_TEMPLATE = VBoxGuestR3XOrgMod
+vboxmouse_drv_71_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+vboxmouse_drv_71_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.1.0 \
+ $(PATH_SUB_CURRENT)
+vboxmouse_drv_71_SOURCES = \
+ vboxmouse.c
+
+
+#
+# vboxmouse_drv_13
+#
+DLLS += vboxmouse_drv_13
+vboxmouse_drv_13_TEMPLATE = VBoxGuestR3XOrgMod
+vboxmouse_drv_13_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+vboxmouse_drv_13_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.3.0.0 \
+ $(PATH_SUB_CURRENT)
+vboxmouse_drv_13_SOURCES = \
+ vboxmouse.c
+
+
+#
+# vboxmouse_drv_14
+#
+DLLS += vboxmouse_drv_14
+vboxmouse_drv_14_TEMPLATE = VBoxGuestR3XOrgMod
+vboxmouse_drv_14_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+vboxmouse_drv_14_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.4.2 \
+ $(PATH_SUB_CURRENT)
+vboxmouse_drv_14_SOURCES = \
+ vboxmouse.c
+
+
+#
+# vboxmouse_drv_15
+#
+DLLS += vboxmouse_drv_15
+vboxmouse_drv_15_TEMPLATE = VBoxGuestR3XOrgMod
+vboxmouse_drv_15_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+vboxmouse_drv_15_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.5.3 \
+ $(PATH_SUB_CURRENT)
+vboxmouse_drv_15_SOURCES = \
+ vboxmouse.c
+
+
+#
+# vboxmouse_drv_16
+#
+DLLS += vboxmouse_drv_16
+vboxmouse_drv_16_TEMPLATE = VBoxGuestR3XOrgMod
+vboxmouse_drv_16_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+vboxmouse_drv_16_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.6.5 \
+ $(PATH_SUB_CURRENT)
+vboxmouse_drv_16_SOURCES = \
+ vboxmouse.c
+
+
+ifneq ($(KBUILD_TARGET),linux)
+
+ #
+ # vboxmouse_drv_17
+ #
+ DLLS += vboxmouse_drv_17
+ vboxmouse_drv_17_TEMPLATE = VBoxGuestR3XOrgMod
+ vboxmouse_drv_17_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+ vboxmouse_drv_17_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.7.7 \
+ $(PATH_SUB_CURRENT)
+ vboxmouse_drv_17_SOURCES = \
+ vboxmouse.c
+
+
+ #
+ # vboxmouse_drv_18
+ #
+ DLLS += vboxmouse_drv_18
+ vboxmouse_drv_18_TEMPLATE = VBoxGuestR3XOrgMod
+ vboxmouse_drv_18_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+ vboxmouse_drv_18_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.8.0 \
+ $(PATH_SUB_CURRENT)
+ vboxmouse_drv_18_SOURCES = \
+ vboxmouse.c
+
+
+ #
+ # vboxmouse_drv_19
+ #
+ DLLS += vboxmouse_drv_19
+ vboxmouse_drv_19_TEMPLATE = VBoxGuestR3XOrgMod
+ vboxmouse_drv_19_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+ vboxmouse_drv_19_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.9.0 \
+ $(PATH_SUB_CURRENT)
+ vboxmouse_drv_19_SOURCES = \
+ vboxmouse.c
+
+
+ #
+ # vboxmouse_drv_110
+ #
+ DLLS += vboxmouse_drv_110
+ vboxmouse_drv_110_TEMPLATE = VBoxGuestR3XOrgMod
+ vboxmouse_drv_110_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+ vboxmouse_drv_110_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.10.0 \
+ $(PATH_SUB_CURRENT)
+ vboxmouse_drv_110_SOURCES = \
+ vboxmouse.c
+
+ DLLS += vboxmouse_drv_111
+ vboxmouse_drv_111_TEMPLATE = VBoxGuestR3XOrgMod
+ vboxmouse_drv_111_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+ vboxmouse_drv_111_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.11.0 \
+ $(PATH_SUB_CURRENT)
+ vboxmouse_drv_111_SOURCES = \
+ vboxmouse.c
+
+ DLLS += vboxmouse_drv_112
+ vboxmouse_drv_112_TEMPLATE = VBoxGuestR3XOrgMod
+ vboxmouse_drv_112_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC
+ vboxmouse_drv_112_INCS := \
+ $(vboxmouse_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.12.0 \
+ $(PATH_SUB_CURRENT)
+ vboxmouse_drv_112_SOURCES = \
+ vboxmouse.c
+
+endif # neq ($(KBUILD_TARGET),linux)
+
+
+ifdef VBOX_USE_SYSTEM_XORG_HEADERS
+ # As vboxmouse_drv is not needed at all for X.Org Server 1.7 and later do not
+ # build it in this case.
+ DLLS := $(filter-out vboxmouse_drv_%,$(DLLS))
+ SYSMODS := $(filter-out vboxmouse_drv%,$(SYSMODS))
+endif
+
+
+# Check the undefined symbols in the X.Org modules against lists of allowed
+# symbols. Not very elegant, but it will catch problems early.
+
+ifdef VBOX_WITH_TESTCASES
+ # ifndef VBOX_ONLY_ADDITIONS
+ ifndef VBOX_USE_SYSTEM_XORG_HEADERS
+ ifeq ($(KBUILD_TARGET),linux)
+ ifeq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH))
+ ifndef VBOX_ONLY_SDK
+ VBOXMOUSE_SRC_PATH := $(PATH_SUB_CURRENT)
+
+ ifeq ($(KBUILD_TARGET),linux)
+ TESTING += $(vboxmouse_drv_0_OUTDIR)/tstvboxmouse68.run
+ OTHERS += $(vboxmouse_drv_0_OUTDIR)/tstvboxmouse68.run
+ $$(vboxmouse_drv_0_OUTDIR)/tstvboxmouse68.run: $$(vboxmouse_drv_1_STAGE_TARGET)
+ $(QUIET)$(call MSG_L1,Checking for unresolved symbols in $<)
+ $(QUIET)/bin/sh $(PATH_ROOT)/src/bldprogs/checkUndefined.sh $(KBUILD_HOST) \
+ "$(vboxmouse_drv_1_STAGE_TARGET)" --static "$(VBOXMOUSE_SRC_PATH)/../undefined_xfree86" "$(VBOXMOUSE_SRC_PATH)/../undefined_xfree86_modules"
+ $(QUIET)$(APPEND) -t "$@" "done"
+ endif
+
+ ##
+ # Using the extra expansion to replace $(ver) before eval, thus everything
+ # else needs escaped dollars.
+ define def_vboxmouse_test
+ TESTING += $$(vboxmouse_drv$(ver)_0_OUTDIR)/tstvboxmouse$(ver).run
+ OTHERS += $$(vboxmouse_drv$(ver)_0_OUTDIR)/tstvboxmouse$(ver).run
+ $$$$(vboxmouse_drv$(ver)_0_OUTDIR)/tstvboxmouse$(ver).run: $$$$(vboxmouse_drv$(ver)_1_STAGE_TARGET)
+ $$(QUIET)$$(call MSG_L1,Checking for unresolved symbols in $$<)
+ $$(QUIET)$$(ASH) $$(PATH_ROOT)/src/bldprogs/checkUndefined.sh $$(KBUILD_HOST) \
+ $$(vboxmouse_drv$(ver)_1_STAGE_TARGET) $$(VBOXMOUSE_SRC_PATH)/../undefined_xfree86 $(VBOXMOUSE_SRC_PATH)/../undefined_xfree86_modules $$(VBOXMOUSE_SRC_PATH)/../undefined_xorg
+ $$(QUIET)$$(APPEND) -t "$$@" "done"
+ endef
+
+ $(foreach ver, _70 _71 _13 _14 _15 _16, $(eval $(def_vboxmouse_test)))
+
+ ifneq ($(KBUILD_TARGET),linux)
+ $(foreach ver, _17 _18 _19 _110 _111 _112 _113, $(eval $(def_vboxmouse_test)))
+
+ endif # neq ($(KBUILD_TARGET),linux)
+
+ endif # ! VBOX_ONLY_SDK
+ endif # eq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH))
+ endif # eq ($(KBUILD_TARGET),linux)
+ endif # ! VBOX_USE_SYSTEM_XORG_HEADERS
+ # endif # ! VBOX_ONLY_ADDITIONS
+endif # VBOX_WITH_TESTCASES
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Additions/x11/vboxmouse/vboxmouse.c b/src/VBox/Additions/x11/vboxmouse/vboxmouse.c
new file mode 100644
index 00000000..dfdc4f31
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxmouse/vboxmouse.c
@@ -0,0 +1,374 @@
+/* $Id: vboxmouse.c $ */
+/** @file
+ * VirtualBox X11 Guest Additions, mouse driver for X.Org server 1.5
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ * --------------------------------------------------------------------
+ *
+ * This code is based on evdev.c from X.Org with the following copyright
+ * and permission notice:
+ *
+ * Copyright © 2004-2008 Red Hat, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission. Red
+ * Hat makes no representations about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+ * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Authors:
+ * Kristian Høgsberg (krh@redhat.com)
+ * Adam Jackson (ajax@redhat.com)
+ */
+
+#include <VBox/VMMDev.h> /* for VMMDEV_MOUSE_XXX */
+#include <VBox/VBoxGuestLib.h>
+#include <iprt/errcore.h>
+#include <xf86.h>
+#include <xf86Xinput.h>
+#include <mipointer.h>
+
+#include <xf86Module.h>
+
+#ifdef VBOX_GUESTR3XF86MOD
+# define _X_EXPORT
+#else
+# include <errno.h>
+# include <fcntl.h>
+# include <unistd.h>
+#endif
+
+#include "product-generated.h"
+
+static void
+VBoxReadInput(InputInfoPtr pInfo)
+{
+ uint32_t cx, cy, fFeatures;
+
+ /* Read a byte from the device to acknowledge the event */
+ char c;
+ int res = read(pInfo->fd, &c, 1);
+ NOREF(res);
+ /* The first test here is a workaround for an apparent bug in Xorg Server 1.5 */
+ if (
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 2
+ miPointerCurrentScreen() != NULL
+#else
+ miPointerGetScreen(pInfo->dev) != NULL
+#endif
+ && RT_SUCCESS(VbglR3GetMouseStatus(&fFeatures, &cx, &cy))
+ && (fFeatures & VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE))
+ {
+#if ABI_XINPUT_VERSION == SET_ABI_VERSION(2, 0)
+ /* Bug in the 1.4 X server series - conversion_proc was no longer
+ * called, but the server didn't yet do the conversion itself. */
+ cx = (cx * screenInfo.screens[0]->width) / 65535;
+ cy = (cy * screenInfo.screens[0]->height) / 65535;
+#endif
+ /* send absolute movement */
+ xf86PostMotionEvent(pInfo->dev, 1, 0, 2, cx, cy);
+ }
+}
+
+static void
+VBoxPtrCtrlProc(DeviceIntPtr device, PtrCtrl *ctrl)
+{
+ /* Nothing to do, dix handles all settings */
+ RT_NOREF(device, ctrl);
+}
+
+static int
+VBoxInit(DeviceIntPtr device)
+{
+ CARD8 map[2] = { 0, 1 };
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
+ Atom axis_labels[2] = { 0, 0 };
+ Atom button_labels[2] = { 0, 0 };
+#endif
+ if (!InitPointerDeviceStruct((DevicePtr)device, map, 2,
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
+ button_labels,
+#endif
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 2
+ miPointerGetMotionEvents, VBoxPtrCtrlProc,
+ miPointerGetMotionBufferSize()
+#elif GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 3
+ GetMotionHistory, VBoxPtrCtrlProc,
+ GetMotionHistorySize(), 2 /* Number of axes */
+
+#elif GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3
+ VBoxPtrCtrlProc, GetMotionHistorySize(),
+ 2 /* Number of axes */
+#else
+# error Unsupported version of X.Org
+#endif
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
+ , axis_labels
+#endif
+ ))
+ return !Success;
+
+ /* Tell the server about the range of axis values we report */
+#if ABI_XINPUT_VERSION <= SET_ABI_VERSION(2, 0)
+ xf86InitValuatorAxisStruct(device, 0, 0, -1, 1, 0, 1);
+ xf86InitValuatorAxisStruct(device, 1, 0, -1, 1, 0, 1);
+#else
+ xf86InitValuatorAxisStruct(device, 0,
+# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
+ axis_labels[0],
+# endif
+ VMMDEV_MOUSE_RANGE_MIN /* min X */, VMMDEV_MOUSE_RANGE_MAX /* max X */,
+ 10000, 0, 10000
+# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 12
+ , Absolute
+# endif
+ );
+
+ xf86InitValuatorAxisStruct(device, 1,
+# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7
+ axis_labels[1],
+# endif
+ VMMDEV_MOUSE_RANGE_MIN /* min Y */, VMMDEV_MOUSE_RANGE_MAX /* max Y */,
+ 10000, 0, 10000
+# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 12
+ , Absolute
+# endif
+ );
+#endif
+ xf86InitValuatorDefaults(device, 0);
+ xf86InitValuatorDefaults(device, 1);
+ xf86MotionHistoryAllocate(device->public.devicePrivate);
+
+ return Success;
+}
+
+static int
+VBoxProc(DeviceIntPtr device, int what)
+{
+ InputInfoPtr pInfo;
+ int rc, xrc;
+ uint32_t fFeatures = 0;
+
+ pInfo = device->public.devicePrivate;
+
+ switch (what)
+ {
+ case DEVICE_INIT:
+ xrc = VBoxInit(device);
+ if (xrc != Success) {
+ VbglR3Term();
+ return xrc;
+ }
+ break;
+
+ case DEVICE_ON:
+ xf86Msg(X_INFO, "%s: On.\n", pInfo->name);
+ if (device->public.on)
+ break;
+ /* Tell the host that we want absolute co-ordinates */
+ rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL);
+ fFeatures &= VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR;
+ if (RT_SUCCESS(rc))
+ rc = VbglR3SetMouseStatus( fFeatures
+ | VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
+ | VMMDEV_MOUSE_NEW_PROTOCOL);
+ if (!RT_SUCCESS(rc)) {
+ xf86Msg(X_ERROR, "%s: Failed to switch guest mouse into absolute mode\n",
+ pInfo->name);
+ return !Success;
+ }
+
+ xf86AddEnabledDevice(pInfo);
+ device->public.on = TRUE;
+ break;
+
+ case DEVICE_OFF:
+ xf86Msg(X_INFO, "%s: Off.\n", pInfo->name);
+ rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL);
+ fFeatures &= VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR;
+ if (RT_SUCCESS(rc))
+ rc = VbglR3SetMouseStatus( fFeatures
+ & ~VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE
+ & ~VMMDEV_MOUSE_NEW_PROTOCOL);
+ xf86RemoveEnabledDevice(pInfo);
+ device->public.on = FALSE;
+ break;
+
+ case DEVICE_CLOSE:
+ VbglR3Term();
+ xf86Msg(X_INFO, "%s: Close\n", pInfo->name);
+ break;
+
+ default:
+ return BadValue;
+ }
+
+ return Success;
+}
+
+static int
+VBoxProbe(InputInfoPtr pInfo)
+{
+ int rc = VbglR3Init();
+ if (!RT_SUCCESS(rc)) {
+ xf86Msg(X_ERROR, "%s: Failed to open the VirtualBox device (error %d)\n",
+ pInfo->name, rc);
+ return BadMatch;
+ }
+
+ return Success;
+}
+
+static Bool
+VBoxConvert(InputInfoPtr pInfo, int first, int num, int v0, int v1, int v2,
+ int v3, int v4, int v5, int *x, int *y)
+{
+ RT_NOREF(pInfo, num, v2, v3, v4, v5);
+
+ if (first == 0) {
+ *x = xf86ScaleAxis(v0, 0, screenInfo.screens[0]->width, 0, 65536);
+ *y = xf86ScaleAxis(v1, 0, screenInfo.screens[0]->height, 0, 65536);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+VBoxPreInitInfo(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
+{
+ const char *device;
+ int rc;
+ RT_NOREF(drv, flags);
+
+ /* Initialise the InputInfoRec. */
+ pInfo->device_control = VBoxProc;
+ pInfo->read_input = VBoxReadInput;
+ /* Unlike evdev, we set this unconditionally, as we don't handle keyboards. */
+ pInfo->type_name = XI_MOUSE;
+ pInfo->flags |= XI86_ALWAYS_CORE;
+
+ device = xf86SetStrOption(pInfo->options, "Device",
+ "/dev/vboxguest");
+
+ xf86Msg(X_CONFIG, "%s: Device: \"%s\"\n", pInfo->name, device);
+ do {
+ pInfo->fd = open(device, O_RDWR, 0);
+ }
+ while (pInfo->fd < 0 && errno == EINTR);
+
+ if (pInfo->fd < 0) {
+ xf86Msg(X_ERROR, "Unable to open VirtualBox device \"%s\".\n", device);
+ return BadMatch;
+ }
+
+ rc = VBoxProbe(pInfo);
+ if (rc != Success)
+ return rc;
+
+ return Success;
+}
+
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 12
+static InputInfoPtr
+VBoxPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
+{
+ InputInfoPtr pInfo = xf86AllocateInput(drv, 0);
+ if (!pInfo)
+ return NULL;
+
+ /* Initialise the InputInfoRec. */
+ pInfo->name = dev->identifier;
+ pInfo->conf_idev = dev;
+ pInfo->conversion_proc = VBoxConvert;
+ pInfo->flags = XI86_POINTER_CAPABLE | XI86_SEND_DRAG_EVENTS;
+
+ xf86CollectInputOptions(pInfo, NULL, NULL);
+ xf86ProcessCommonOptions(pInfo, pInfo->options);
+
+ if (VBoxPreInitInfo(drv, pInfo, flags) != Success) {
+ xf86DeleteInput(pInfo, 0);
+ return NULL;
+ }
+
+ pInfo->flags |= XI86_CONFIGURED;
+ return pInfo;
+}
+#endif
+
+_X_EXPORT InputDriverRec VBOXMOUSE = {
+ 1,
+ "vboxmouse",
+ NULL,
+#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 12
+ VBoxPreInit,
+#else
+ VBoxPreInitInfo,
+#endif
+ NULL,
+ NULL,
+ 0
+};
+
+static pointer
+VBoxPlug(pointer module, pointer options, int *errmaj, int *errmin)
+{
+ RT_NOREF(options, errmaj, errmin);
+ xf86AddInputDriver(&VBOXMOUSE, module, 0);
+ xf86Msg(X_CONFIG, "Load address of symbol \"VBOXMOUSE\" is %p\n",
+ (void *)&VBOXMOUSE);
+ return module;
+}
+
+static XF86ModuleVersionInfo VBoxVersionRec =
+{
+ "vboxmouse",
+ VBOX_VENDOR,
+ MODINFOSTRING1,
+ MODINFOSTRING2,
+ 0, /* Missing from SDK: XORG_VERSION_CURRENT, */
+ 1, 0, 0,
+ ABI_CLASS_XINPUT,
+ ABI_XINPUT_VERSION,
+ MOD_CLASS_XINPUT,
+ {0, 0, 0, 0}
+};
+
+_X_EXPORT XF86ModuleData vboxmouseModuleData =
+{
+ &VBoxVersionRec,
+ VBoxPlug,
+ NULL
+};
diff --git a/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h b/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h
new file mode 100644
index 00000000..7c8212a0
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h
@@ -0,0 +1,63 @@
+/* $Id: HGSMIMemAlloc.h $ */
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/* In builds inside of the VirtualBox source tree we override the default
+ * HGSMIMemAlloc.h using -include, therefore this define must match the one
+ * there. */
+
+#ifndef VBOX_INCLUDED_Graphics_HGSMIMemAlloc_h
+#define VBOX_INCLUDED_Graphics_HGSMIMemAlloc_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "HGSMIDefs.h"
+#include "VBoxVideoIPRT.h"
+
+#define HGSMI_MA_DESC_ORDER_BASE UINT32_C(5)
+
+#define HGSMI_MA_BLOCK_SIZE_MIN (UINT32_C(1) << (HGSMI_MA_DESC_ORDER_BASE + 0))
+
+typedef struct HGSMIMADATA
+{
+ HGSMIAREA area;
+ bool fAllocated;
+} HGSMIMADATA;
+
+RT_C_DECLS_BEGIN
+
+int HGSMIMAInit(HGSMIMADATA *pMA, const HGSMIAREA *pArea,
+ HGSMIOFFSET *paDescriptors, uint32_t cDescriptors,
+ HGSMISIZE cbMaxBlock, const HGSMIENV *pEnv);
+void HGSMIMAUninit(HGSMIMADATA *pMA);
+
+void RT_UNTRUSTED_VOLATILE_GUEST *HGSMIMAAlloc(HGSMIMADATA *pMA, HGSMISIZE cb);
+void HGSMIMAFree(HGSMIMADATA *pMA, void RT_UNTRUSTED_VOLATILE_GUEST *pv);
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_Graphics_HGSMIMemAlloc_h */
diff --git a/src/VBox/Additions/x11/vboxvideo/Makefile.kmk b/src/VBox/Additions/x11/vboxvideo/Makefile.kmk
new file mode 100644
index 00000000..6f98670d
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/Makefile.kmk
@@ -0,0 +1,467 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VBox Linux Additions X.org graphics driver.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+vboxvideo_70_DEFS := \
+ IN_MODULE XORG_7X RENDER=1 IN_RT_STATIC X_BYTE_ORDER=X_LITTLE_ENDIAN
+ifeq ($(KBUILD_TARGET),solaris) # don't use .solaris or anything here.
+ vboxvideo_70_DEFS += __EXTENSIONS__ ## @todo Why this?
+endif
+vboxvideo_13_DEFS := $(vboxvideo_70_DEFS) VBOXVIDEO_13
+vboxvideo_15_DEFS := \
+ $(vboxvideo_13_DEFS) NO_ANSIC PCIACCESS XSERVER_LIBPCIACCESS _XORG_SERVER_H_ _DIX_CONFIG_H_
+vboxvideo_xorg_INCS = \
+ $(VBOX_PATH_X11_ROOT)/fontsproto-2.1.3 \
+ $(VBOX_PATH_X11_ROOT)/inputproto-1.9.99.902 \
+ $(VBOX_PATH_X11_ROOT)/kbproto-1.0.7 \
+ $(VBOX_PATH_X11_ROOT)/libpciaccess-0.10.8 \
+ $(VBOX_PATH_X11_ROOT)/pixman-0.40.0 \
+ $(VBOX_PATH_X11_ROOT)/randrproto-1.5.0 \
+ $(VBOX_PATH_X11_ROOT)/renderproto-0.11.1 \
+ $(VBOX_PATH_X11_ROOT)/xextproto-7.1.1 \
+ $(VBOX_PATH_X11_ROOT)/xproto-7.0.31 \
+ $(VBOX_GRAPHICS_INCS)
+vboxvideo_override_INCLUDES = \
+ -include $(PATH_ROOT)/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h \
+ -include $(PATH_ROOT)/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h
+
+LIBRARIES += vboxvideo_drv_lib
+
+#
+# vboxvideo_drv_lib
+#
+vboxvideo_drv_lib_TEMPLATE = VBoxGuestR3XOrgMod
+# We are relying in the include guard in the two headers below to stop the more
+# generic ones from being included. Not very nice, I know.
+vboxvideo_drv_lib_CFLAGS += $(vboxvideo_override_INCLUDES)
+vboxvideo_drv_lib_CXXFLAGS += $(vboxvideo_override_INCLUDES)
+ifeq ($(KBUILD_TARGET),solaris) # don't use .solaris or anything here. Do we need this? I don't want to find out.
+ vboxvideo_drv_lib_CFLAGS += -D_XPG6 -Wno-shadow # Use XPG6 until we have moved the C++ bits into a library.
+endif
+vboxvideo_drv_lib_SOURCES = \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp \
+ $(PATH_ROOT)/src/VBox/GuestHost/HGSMI/HGSMICommon.cpp \
+ $(PATH_ROOT)/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c
+# $(VBOX_PATH_X11_ROOT)/xorg-server-1.18.0 is for in[blw] and out[blw], xproto
+# for _X_[UN]LIKELY.
+vboxvideo_drv_lib_INCS = \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.18.0 \
+ $(VBOX_PATH_X11_ROOT)/xproto-7.0.18 \
+ $(VBOX_PATH_X11_ROOT)/pixman-0.40.0 \
+ $(PATH_ROOT)/src/VBox/Runtime/include \
+ $(VBOX_GRAPHICS_INCS)
+vboxvideo_drv_lib_INST = $(INST_LIB)
+
+#
+# vboxvideo_drv
+#
+if1of ($(KBUILD_TARGET), linux)
+ SYSMODS += vboxvideo_drv
+endif # target linux
+vboxvideo_drv_TEMPLATE = VBoxGuestR3XFree86Mod
+vboxvideo_drv_CFLAGS += $(vboxvideo_override_INCLUDES)
+vboxvideo_drv_CFLAGS += -Wno-shadow # Avoid MBs of warnings in X11 and OpenGL headers (solaris mostly).
+vboxvideo_drv_CXXFLAGS += $(vboxvideo_override_INCLUDES)
+vboxvideo_drv_DEFS.linux = linux
+vboxvideo_drv_DEFS.x86 = __i386__
+# This one has to be defined when building server code on systems where
+# unsigned long is 64bits
+vboxvideo_drv_DEFS.amd64 += _XSERVER64
+vboxvideo_drv_DEFS = \
+ _POSIX_C_SOURCE=199309L _POSIX_SOURCE _XOPEN_SOURCE _DEFAULT_SOURCE \
+ _BSD_SOURCE _SVID_SOURCE _GNU_SOURCE SHAPE XINPUT XKB LBX XAPPGROUP \
+ XCSECURITY TOGCUP XF86BIGFONT DPMSExtension PIXPRIV PANORAMIX RENDER \
+ GCCUSESGAS AVOID_GLYPHBLT PIXPRIV SINGLEDEPTH XFreeXDGA XvExtension \
+ XFree86LOADER XFree86Server XF86VIDMODE XvMCExtension SMART_SCHEDULE \
+ BUILDDEBUG X_BYTE_ORDER=X_LITTLE_ENDIAN DNDEBUG FUNCPROTO=15 NARROWPROTO \
+ IN_MODULE XFree86Module IN_XF86_MODULE IN_RT_STATIC
+vboxvideo_drv_INCS = \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3 \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11 \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11/extensions \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11/fonts \
+ $(VBOX_PATH_X11_ROOT)/XFree86-4.3/Xserver
+vboxvideo_drv_INCS += \
+ $(PATH_ROOT)/src/VBox/Runtime/include \
+ $(VBOX_GRAPHICS_INCS)
+vboxvideo_drv_SOURCES = \
+ getmode.c \
+ pointer.c \
+ setmode.c \
+ vboxvideo.c \
+ vbva.c \
+ $(vboxvideo_drv_lib_SOURCES)
+# Any global symbols in the driver object files will be added to XFree86's
+# symbol table, which can cause problems if we e.g. define a symbol in two
+# modules.
+vboxvideo_drv_POST_CMDS = \
+ objcopy --keep-global-symbol vboxvideoModuleData $(out) $(out)-objcopy$$(NLTAB) \
+ $(MV) -f $(out)-objcopy $(out)
+
+#
+# vboxvideo_drv_70
+#
+# Remark: The other X.org drivers below are derived from this one. So, to make
+# that as simple as possible we do ifeq/if1of test here and extends the
+# base keywords instead of using .solaris or .linux.
+# Also it is *important* to use := and not = when deriving a property.
+#
+DLLS += vboxvideo_drv_70
+vboxvideo_drv_70_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_70_DEFS = $(vboxvideo_70_DEFS) XORG_VERSION_CURRENT=70000000
+vboxvideo_drv_70_CFLAGS += $(vboxvideo_override_INCLUDES)
+ifeq ($(KBUILD_TARGET),solaris) # don't use .solaris or anything here.
+ vboxvideo_drv_70_CFLAGS += -D_XPG6 -Wno-shadow # Use XPG6 until we have moved the C++ bits into a library.
+endif
+vboxvideo_drv_70_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.0.1
+vboxvideo_drv_70_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_70_SOURCES = $(filter-out $(vboxvideo_drv_lib_SOURCES),$(vboxvideo_drv_SOURCES))
+vboxvideo_drv_70_LIBS = $(PATH_STAGE_LIB)/vboxvideo_drv_lib$(VBOX_SUFF_LIB)
+
+
+#
+# vboxvideo_drv_71
+#
+DLLS += vboxvideo_drv_71
+vboxvideo_drv_71_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_71_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_71_DEFS := $(vboxvideo_70_DEFS) XORG_VERSION_CURRENT=70100000
+vboxvideo_drv_71_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.1.0
+vboxvideo_drv_71_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_71_SOURCES = $(vboxvideo_drv_70_SOURCES)
+vboxvideo_drv_71_LIBS = $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_13
+#
+DLLS += vboxvideo_drv_13
+vboxvideo_drv_13_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_13_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_13_DEFS := $(vboxvideo_13_DEFS) XORG_VERSION_CURRENT=10300000
+vboxvideo_drv_13_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.3.0.0
+vboxvideo_drv_13_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_13_SOURCES = $(vboxvideo_drv_70_SOURCES) edid.c
+vboxvideo_drv_13_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_14
+#
+DLLS += vboxvideo_drv_14
+vboxvideo_drv_14_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_14_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_14_DEFS := $(vboxvideo_13_DEFS) XORG_VERSION_CURRENT=10400000
+vboxvideo_drv_14_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.4.2
+vboxvideo_drv_14_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_14_SOURCES = $(vboxvideo_drv_13_SOURCES)
+vboxvideo_drv_14_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_15
+#
+DLLS += vboxvideo_drv_15
+vboxvideo_drv_15_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_15_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_15_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10503000
+vboxvideo_drv_15_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.5.3
+vboxvideo_drv_15_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_15_SOURCES = $(vboxvideo_drv_13_SOURCES)
+vboxvideo_drv_15_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_16
+#
+DLLS += vboxvideo_drv_16
+vboxvideo_drv_16_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_16_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_16_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10600000
+vboxvideo_drv_16_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.6.5 \
+ vboxvideo_drv_16_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_16_SOURCES := $(vboxvideo_drv_15_SOURCES)
+vboxvideo_drv_16_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_17
+#
+DLLS += vboxvideo_drv_17
+vboxvideo_drv_17_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_17_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_17_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10699000
+vboxvideo_drv_17_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.7.7
+vboxvideo_drv_17_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_17_SOURCES := $(vboxvideo_drv_13_SOURCES)
+vboxvideo_drv_17_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_18
+#
+DLLS += vboxvideo_drv_18
+vboxvideo_drv_18_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_18_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_18_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10800000
+vboxvideo_drv_18_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.8.0
+vboxvideo_drv_18_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_18_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_18_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_19
+#
+DLLS += vboxvideo_drv_19
+vboxvideo_drv_19_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_19_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_19_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10900000
+vboxvideo_drv_19_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.9.0
+vboxvideo_drv_19_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_19_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_19_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_110
+#
+DLLS += vboxvideo_drv_110
+vboxvideo_drv_110_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_110_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_110_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11000000
+vboxvideo_drv_110_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.10.0
+vboxvideo_drv_110_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_110_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_110_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_111
+#
+DLLS += vboxvideo_drv_111
+vboxvideo_drv_111_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_111_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_111_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11100000
+vboxvideo_drv_111_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.11.0
+vboxvideo_drv_111_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_111_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_111_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_112
+#
+DLLS += vboxvideo_drv_112
+vboxvideo_drv_112_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_112_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_112_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11200000
+vboxvideo_drv_112_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.12.0
+vboxvideo_drv_112_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_112_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_112_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_113
+#
+DLLS += vboxvideo_drv_113
+vboxvideo_drv_113_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_113_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_113_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11300000
+vboxvideo_drv_113_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.13.0
+vboxvideo_drv_113_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_113_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_113_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_114
+#
+DLLS += vboxvideo_drv_114
+vboxvideo_drv_114_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_114_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_114_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11400000
+vboxvideo_drv_114_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.14.0
+vboxvideo_drv_114_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_114_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_114_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_115
+#
+DLLS += vboxvideo_drv_115
+vboxvideo_drv_115_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_115_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_115_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11500000
+vboxvideo_drv_115_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.15.0
+vboxvideo_drv_115_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_115_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_115_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_116
+#
+DLLS += vboxvideo_drv_116
+vboxvideo_drv_116_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_116_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_116_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11600000
+vboxvideo_drv_116_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.16.0
+vboxvideo_drv_116_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_116_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_116_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_117
+#
+DLLS += vboxvideo_drv_117
+vboxvideo_drv_117_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_117_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_117_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11700000
+vboxvideo_drv_117_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.17.1
+vboxvideo_drv_117_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_117_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_117_LIBS += $(vboxvideo_drv_70_LIBS)
+
+
+#
+# vboxvideo_drv_118
+#
+DLLS += vboxvideo_drv_118
+vboxvideo_drv_118_TEMPLATE = VBoxGuestR3XOrgMod
+vboxvideo_drv_118_CFLAGS := $(vboxvideo_drv_70_CFLAGS)
+vboxvideo_drv_118_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11800000
+vboxvideo_drv_118_INCS = \
+ $(vboxvideo_xorg_INCS) \
+ $(VBOX_PATH_X11_ROOT)/xorg-server-1.18.0
+vboxvideo_drv_118_INCS += $(PATH_ROOT)/src/VBox/Runtime/include
+vboxvideo_drv_118_SOURCES := $(vboxvideo_drv_17_SOURCES)
+vboxvideo_drv_118_LIBS += $(vboxvideo_drv_70_LIBS)
+
+ifdef VBOX_USE_SYSTEM_XORG_HEADERS
+ # Build using local X.Org headers. We assume X.Org Server 1.7 or later.
+ DLLS := $(filter-out vboxvideo_drv_%,$(DLLS)) vboxvideo_drv_system
+ SYSMODS := $(filter-out vboxvideo_drv%,$(SYSMODS))
+ vboxvideo_drv_system_TEMPLATE = VBoxGuestR3XOrgMod
+ vboxvideo_drv_system_CFLAGS := \
+ $(vboxvideo_drv_70_CFLAGS) -include xorg-server.h
+ vboxvideo_drv_system_DEFS := $(filter-out _XORG_SERVER_H_ _DIX_CONFIG_H_, $(vboxvideo_15_DEFS))
+ vboxvideo_drv_system_INCS += \
+ $(PATH_ROOT)/src/VBox/Runtime/include \
+ $(VBOX_GRAPHICS_INCS) \
+ /usr/include/xorg \
+ /usr/include/pixman-1
+ vboxvideo_drv_system_SOURCES := $(vboxvideo_drv_17_SOURCES)
+endif
+
+
+# Check the undefined symbols in the X.Org modules against lists of allowed
+# symbols. Not very elegant, but it will catch problems early.
+
+ifdef VBOX_WITH_TESTCASES
+ # ifndef VBOX_ONLY_ADDITIONS
+ ifndef VBOX_USE_SYSTEM_XORG_HEADERS
+ if1of ($(KBUILD_TARGET), linux solaris)
+ ifeq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH))
+ ifndef VBOX_ONLY_SDK
+ VBOXVIDEO_SRC_PATH := $(PATH_SUB_CURRENT)
+
+ ifeq ($(KBUILD_TARGET),linux)
+ TESTING += $(vboxvideo_drv_0_OUTDIR)/tstvboxvideo68.run
+ OTHERS += $(vboxvideo_drv_0_OUTDIR)/tstvboxvideo68.run
+ $$(vboxvideo_drv_0_OUTDIR)/tstvboxvideo68.run: $$(vboxvideo_drv_1_STAGE_TARGET)
+ $(QUIET)$(call MSG_L1,Checking for unresolved symbols in $<)
+ $(QUIET)$(ASH) $(PATH_ROOT)/src/bldprogs/checkUndefined.sh $(KBUILD_HOST) \
+ $(vboxvideo_drv_1_STAGE_TARGET) --static $(VBOXVIDEO_SRC_PATH)/../undefined_xfree86 $(VBOXVIDEO_SRC_PATH)/../undefined_xfree86_modules
+ $(QUIET)$(APPEND) -t "$@" "done"
+ endif
+
+ ##
+ # Using the extra expansion to replace $(ver) before eval, thus everything
+ # else needs escaped dollars.
+ define def_vboxvideo_test
+ TESTING += $$(vboxvideo_drv$(ver)_0_OUTDIR)/tstvboxvideo$(ver).run
+ OTHERS += $$(vboxvideo_drv$(ver)_0_OUTDIR)/tstvboxvideo$(ver).run
+ $$$$(vboxvideo_drv$(ver)_0_OUTDIR)/tstvboxvideo$(ver).run: $$$$(vboxvideo_drv$(ver)_1_STAGE_TARGET)
+ $$(QUIET)$$(call MSG_L1,Checking for unresolved symbols in $$<)
+ $$(QUIET)$$(ASH) $$(PATH_ROOT)/src/bldprogs/checkUndefined.sh $$(KBUILD_HOST) \
+ $$(vboxvideo_drv$(ver)_1_STAGE_TARGET) $$(VBOXVIDEO_SRC_PATH)/../undefined_xfree86 $(VBOXVIDEO_SRC_PATH)/../undefined_xfree86_modules $$(VBOXVIDEO_SRC_PATH)/../undefined_xorg
+ $$(QUIET)$$(APPEND) -t "$$@" "done"
+ endef
+
+ $(foreach ver, _70 _71 _13 _14 _15 _16 _17 _18 _19 _110 _111 _112 _113 _114 _115 _116 _117 _118, $(eval $(def_vboxvideo_test)))
+
+ endif # ! VBOX_ONLY_SDK
+ endif # eq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH))
+ endif # 1of ($(KBUILD_TARGET),linux solaris)
+ endif # ! VBOX_USE_SYSTEM_XORG_HEADERS
+ # endif # ! VBOX_ONLY_ADDITIONS
+endif # VBOX_WITH_TESTCASES
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/Additions/x11/vboxvideo/README.testing b/src/VBox/Additions/x11/vboxvideo/README.testing
new file mode 100644
index 00000000..03d6482f
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/README.testing
@@ -0,0 +1,33 @@
+This file contains some notes about things to try out to give the X.Org video
+driver a reasonably thorough test. We will add cases of things which have been
+known to fail in the past to this file as we discover them. Tests should be
+carried out with Additions installed, and both with and without 3D enabled in
+the machine settings.
+
+ * Test XFree86 guests (CentOS 3), early X.Org (CentOS 5) and recent
+ (CentOS 6 and 7, current Ubuntu/Fedora). Test Solaris guests (10 and 11?).
+ * Dynamic resizing should work, on CentOS 6 and later Linux guests it should
+ work without VBoxClient running.
+ * Disabling and enabling virtual screens (VBoxManage in 4.3).
+ * Dynamic resizing with one of more virtual screens disabled.
+ * Test switching to virtual terminals and back from windowed, full screen and
+ seamless modes (seamless currently only works properly with VBoxClient
+ running).
+ * Test switching directly between normal, full-screen, seamless and scaled
+ modes.
+ * Test re-ordering the virtual screen using the native guest operating system
+ tools and make sure that mouse integration still works as expected.
+ * Test disabling and re-enabling guest screens with the native system tools.
+ * Try disabling and re-enabling mouse integration and check that capturing
+ works with multiple guest screens.
+ * Shutting down and re-starting a virtual machine should restore the last size
+ for all monitors (note: currently only after log-in). Full shut-down, not
+ a reboot.
+ * Test power management by disabling guest screens ("xrandr --output VGA-n
+ --off") and re-enabling them ("xrandr --output VGA-n --preferred --pos XxY")
+ where X and Y are the position of the screen before disabling it.
+ * Test sending video mode hints with screen position information via
+ VBoxManage. The screen position is a hint only. The approximate position
+ should be preserved after a shut down and re-start of the guest.
+ * Test re-starting the X server after resizing all guest windows. The server
+ should not crash.
diff --git a/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h b/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h
new file mode 100644
index 00000000..7b7ecf6e
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h
@@ -0,0 +1,243 @@
+/* $Id: VBoxVideoIPRT.h $ */
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* In builds inside of the VirtualBox source tree we override the default
+ * VBoxVideoIPRT.h using -include, therefore this define must match the one
+ * there. */
+
+#ifndef VBOX_INCLUDED_Graphics_VBoxVideoIPRT_h
+#define VBOX_INCLUDED_Graphics_VBoxVideoIPRT_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+# include "VBoxVideoErr.h"
+
+#ifndef __cplusplus
+typedef enum
+{
+ false = 0,
+ true
+} bool;
+# define RT_C_DECLS_BEGIN
+# define RT_C_DECLS_END
+#else
+# define RT_C_DECLS_BEGIN extern "C" {
+# define RT_C_DECLS_END }
+#endif
+
+#if defined(IN_XF86_MODULE) && !defined(NO_ANSIC)
+# ifdef __cplusplus
+/* xf86Module.h redefines this. */
+# define NULL 0
+# endif
+RT_C_DECLS_BEGIN
+# include "xf86_ansic.h"
+RT_C_DECLS_END
+#endif /* defined(IN_XF86_MODULE) && !defined(NO_ANSIC) */
+#define __STDC_LIMIT_MACROS /* define *INT*_MAX on C++ too. */
+#include "compiler.h" /* Can pull in <sdtint.h>. Must come after xf86_ansic.h on XFree86. */
+#include <X11/Xfuncproto.h>
+#include <stdint.h>
+#if defined(IN_XF86_MODULE) && !defined(NO_ANSIC)
+# ifndef offsetof
+# define offsetof(type, member) ( (int)(uintptr_t)&( ((type *)(void *)0)->member) )
+# endif
+#else /* !(defined(IN_XF86_MODULE) && !defined(NO_ANSIC)) */
+# include <stdarg.h>
+# include <stddef.h>
+# include <string.h>
+#endif /* !(defined(IN_XF86_MODULE) && !defined(NO_ANSIC)) */
+
+/* XFree86 (and newer Xfuncproto.h) do not have these. Not that I care much for micro-optimisations
+ * in most cases anyway. */
+#ifndef _X_LIKELY
+# define _X_LIKELY(x) (x)
+#endif
+#ifndef _X_UNLIKELY
+# define _X_UNLIKELY(x) (x)
+#endif
+
+RT_C_DECLS_BEGIN
+extern int RTASSERTVAR[1];
+RT_C_DECLS_END
+
+#define AssertCompile(expr) \
+ extern int RTASSERTVAR[1] __attribute__((__unused__)), \
+ RTASSERTVAR[(expr) ? 1 : 0] __attribute__((__unused__))
+#define AssertCompileSize(type, size) \
+ AssertCompile(sizeof(type) == (size))
+#define AssertPtrNullReturnVoid(a) do { } while(0)
+
+#if !defined(IN_XF86_MODULE) && defined(DEBUG)
+# include <assert.h>
+# define Assert assert
+# define AssertFailed() assert(0)
+# define AssertMsg(expr, msg) \
+ do { \
+ if (!(expr)) xf86ErrorF msg; \
+ assert((expr)); \
+ } while (0)
+# define AssertPtr assert
+# define AssertPtrReturn(pv, rcRet) do { assert(pv); if (pv) {} else return(rcRet); } while(0)
+# define AssertRC(expr) assert (!expr)
+#else
+# define Assert(expr) do { } while(0)
+# define AssertFailed() do { } while(0)
+# define AssertMsg(expr, msg) do { } while(0)
+# define AssertPtr(ptr) do { } while(0)
+# define AssertPtrReturn(pv, rcRet) do { if (pv) {} else return(rcRet); } while(0)
+# define AssertRC(expr) do { } while(0)
+#endif
+
+#define DECLCALLBACK(a_RetType) a_RetType
+#define DECLCALLBACKTYPE(a_RetType, a_Name, a_Args) a_RetType a_Name a_Args
+#define DECLCALLBACKMEMBER(a_RetType, a_Name, a_Args) a_RetType (*a_Name) a_Args
+#if __GNUC__ >= 4
+# define DECLHIDDEN(type) __attribute__((visibility("hidden"))) type
+#else
+# define DECLHIDDEN(type) type
+#endif
+#define DECLINLINE(type) static __inline__ type
+
+#define _1K 1024
+#define ASMCompilerBarrier mem_barrier
+#define RT_BIT(bit) ( 1U << (bit) )
+#define RT_BOOL(Value) ( !!(Value) )
+#define RT_BZERO(pv, cb) do { memset((pv), 0, cb); } while (0)
+#define RT_CLAMP(Value, Min, Max) ( (Value) > (Max) ? (Max) : (Value) < (Min) ? (Min) : (Value) )
+#define RT_ELEMENTS(aArray) ( sizeof(aArray) / sizeof((aArray)[0]) )
+#define RTIOPORT unsigned short
+#define RT_NOREF(...) (void)(__VA_ARGS__)
+#define RT_OFFSETOF(type, member) offsetof(type, member)
+#define RT_UOFFSETOF(type, member) offsetof(type, member)
+#define RT_ZERO(Obj) RT_BZERO(&(Obj), sizeof(Obj))
+#define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) + 0x1000U >= 0x2000U )
+#ifndef INT16_C
+# define INT16_C(Value) (Value)
+#endif
+#ifndef UINT16_C
+# define UINT16_C(Value) (Value)
+#endif
+#ifndef INT32_C
+# define INT32_C(Value) (Value ## U)
+#endif
+#ifndef UINT32_C
+# define UINT32_C(Value) (Value ## U)
+#endif
+#define RT_UNTRUSTED_GUEST
+#define RT_UNTRUSTED_VOLATILE_GUEST volatile
+#define RT_UNTRUSTED_HOST
+#define RT_UNTRUSTED_VOLATILE_HOST volatile
+#define RT_UNTRUSTED_HSTGST
+#define RT_UNTRUSTED_VOLATILE_HSTGST volatile
+
+#define likely _X_LIKELY
+#define unlikely _X_UNLIKELY
+
+/**
+ * A point in a two dimentional coordinate system.
+ */
+typedef struct RTPOINT
+{
+ /** X coordinate. */
+ int32_t x;
+ /** Y coordinate. */
+ int32_t y;
+} RTPOINT;
+
+/**
+ * Rectangle data type, double point.
+ */
+typedef struct RTRECT
+{
+ /** left X coordinate. */
+ int32_t xLeft;
+ /** top Y coordinate. */
+ int32_t yTop;
+ /** right X coordinate. (exclusive) */
+ int32_t xRight;
+ /** bottom Y coordinate. (exclusive) */
+ int32_t yBottom;
+} RTRECT;
+
+/**
+ * Rectangle data type, point + size.
+ */
+typedef struct RTRECT2
+{
+ /** X coordinate.
+ * Unless stated otherwise, this is the top left corner. */
+ int32_t x;
+ /** Y coordinate.
+ * Unless stated otherwise, this is the top left corner. */
+ int32_t y;
+ /** The width.
+ * Unless stated otherwise, this is to the right of (x,y) and will not
+ * be a negative number. */
+ int32_t cx;
+ /** The height.
+ * Unless stated otherwise, this is down from (x,y) and will not be a
+ * negative number. */
+ int32_t cy;
+} RTRECT2;
+
+/**
+ * The size of a rectangle.
+ */
+typedef struct RTRECTSIZE
+{
+ /** The width (along the x-axis). */
+ uint32_t cx;
+ /** The height (along the y-axis). */
+ uint32_t cy;
+} RTRECTSIZE;
+
+/** @name Port I/O helpers
+ * @{ */
+
+/** Write an 8-bit value to an I/O port. */
+#define VBVO_PORT_WRITE_U8(Port, Value) \
+ outb(Port, Value)
+/** Write a 16-bit value to an I/O port. */
+#define VBVO_PORT_WRITE_U16(Port, Value) \
+ outw(Port, Value)
+/** Write a 32-bit value to an I/O port. */
+#define VBVO_PORT_WRITE_U32(Port, Value) \
+ outl(Port, Value)
+/** Read an 8-bit value from an I/O port. */
+#define VBVO_PORT_READ_U8(Port) \
+ inb(Port)
+/** Read a 16-bit value from an I/O port. */
+#define VBVO_PORT_READ_U16(Port) \
+ inw(Port)
+/** Read a 32-bit value from an I/O port. */
+#define VBVO_PORT_READ_U32(Port) \
+ inl(Port)
+
+/** @} */
+
+#endif /* !VBOX_INCLUDED_Graphics_VBoxVideoIPRT_h */
diff --git a/src/VBox/Additions/x11/vboxvideo/edid.c b/src/VBox/Additions/x11/vboxvideo/edid.c
new file mode 100644
index 00000000..96432848
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/edid.c
@@ -0,0 +1,165 @@
+/* $Id: edid.c $ */
+/** @file
+ *
+ * Linux Additions X11 graphics driver, EDID construction
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ * This file is based on drmmode_display.c from the X.Org xf86-video-intel
+ * driver with the following copyright notice:
+ *
+ * Copyright © 2007 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 (including the next
+ * paragraph) 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.
+ *
+ * Authors:
+ * Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com>
+ */
+
+#include "misc.h"
+#include "xf86DDC.h"
+#include "xf86Crtc.h"
+#include "vboxvideo.h"
+
+enum { EDID_SIZE = 128 };
+
+const unsigned char g_acszEDIDBase[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). Should we ask the host for this? */
+ 0xEE, /* features (standby, suspend, off, RGB, standard colour space,
+ * preferred timing mode) */
+ 0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
+ /* chromaticity for standard colour space - should we ask the host? */
+ 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* descriptor block 1 goes here */
+ 0x00, 0x00, 0x00, 0xFD, 0x00, /* descriptor block 2, monitor ranges */
+ 0x00, 0xC8, 0x00, 0xC8, 0x64, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20, /* 0-200Hz vertical, 0-200KHz horizontal, 1000MHz pixel clock */
+ 0x00, 0x00, 0x00, 0xFC, 0x00, /* descriptor block 3, monitor name */
+ 'V', 'B', 'O', 'X', ' ', 'm', 'o', 'n', 'i', 't', 'o', 'r', '\n',
+ 0x00, 0x00, 0x00, 0x10, 0x00, /* descriptor block 4: dummy data */
+ 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+ 0x20,
+ 0x00, /* number of extensions */
+ 0x00 /* checksum goes here */
+};
+
+static void fillDescBlockTimings(unsigned char *pchDescBlock,
+ DisplayModePtr mode)
+{
+ struct detailed_timings timing;
+
+ timing.clock = mode->Clock * 1000;
+ timing.h_active = mode->HDisplay;
+ timing.h_blanking = mode->HTotal - mode->HDisplay;
+ timing.v_active = mode->VDisplay;
+ timing.v_blanking = mode->VTotal - mode->VDisplay;
+ timing.h_sync_off = mode->HSyncStart - mode->HDisplay;
+ timing.h_sync_width = mode->HSyncEnd - mode->HSyncStart;
+ timing.v_sync_off = mode->VSyncStart - mode->VDisplay;
+ timing.v_sync_width = mode->VSyncEnd - mode->VSyncStart;
+ pchDescBlock[0] = (timing.clock / 10000) & 0xff;
+ pchDescBlock[1] = (timing.clock / 10000) >> 8;
+ pchDescBlock[2] = timing.h_active & 0xff;
+ pchDescBlock[3] = timing.h_blanking & 0xff;
+ pchDescBlock[4] = (timing.h_active >> 4) & 0xf0;
+ pchDescBlock[4] |= (timing.h_blanking >> 8) & 0xf;
+ pchDescBlock[5] = timing.v_active & 0xff;
+ pchDescBlock[6] = timing.v_blanking & 0xff;
+ pchDescBlock[7] = (timing.v_active >> 4) & 0xf0;
+ pchDescBlock[7] |= (timing.v_blanking >> 8) & 0xf;
+ pchDescBlock[8] = timing.h_sync_off & 0xff;
+ pchDescBlock[9] = timing.h_sync_width & 0xff;
+ pchDescBlock[10] = (timing.v_sync_off << 4) & 0xf0;
+ pchDescBlock[10] |= timing.v_sync_width & 0xf;
+ pchDescBlock[11] = (timing.h_sync_off >> 2) & 0xC0;
+ pchDescBlock[11] |= (timing.h_sync_width >> 4) & 0x30;
+ pchDescBlock[11] |= (timing.v_sync_off >> 2) & 0xC;
+ pchDescBlock[11] |= (timing.v_sync_width >> 4) & 0x3;
+ pchDescBlock[12] = pchDescBlock[13] = pchDescBlock[14]
+ = pchDescBlock[15] = pchDescBlock[16]
+ = pchDescBlock[17] = 0;
+}
+
+
+static void setEDIDChecksum(unsigned char *pch)
+{
+ unsigned i, sum = 0;
+ for (i = 0; i < EDID_SIZE - 1; ++i)
+ sum += pch[i];
+ pch[EDID_SIZE - 1] = (0x100 - (sum & 0xFF)) & 0xFF;
+}
+
+
+/**
+ * Construct an EDID for an output given a preferred mode. The main reason for
+ * doing this is to confound gnome-settings-deamon which tries to reset the
+ * last mode configuration if the same monitors are plugged in again, which is
+ * a reasonable thing to do but not what we want in a VM. We evily store
+ * the (empty) raw EDID data at the end of the structure so that it gets
+ * freed automatically along with the structure.
+ */
+Bool VBOXEDIDSet(xf86OutputPtr output, DisplayModePtr pmode)
+{
+ unsigned char *pch, *pchEDID;
+ xf86MonPtr pEDIDMon;
+
+ pch = calloc(1, sizeof(xf86Monitor) + EDID_SIZE);
+ if (!pch)
+ {
+ xf86DrvMsg(output->scrn->scrnIndex, X_ERROR,
+ "Can't allocate memory for EDID structure.\n");
+ return FALSE;
+ }
+ pchEDID = pch + sizeof(xf86Monitor);
+ memcpy(pchEDID, g_acszEDIDBase, EDID_SIZE);
+ pchEDID[12] = pmode->HDisplay & 0xff;
+ pchEDID[13] = pmode->HDisplay >> 8;
+ pchEDID[14] = pmode->VDisplay & 0xff;
+ pchEDID[15] = pmode->VDisplay >> 8;
+ fillDescBlockTimings(pchEDID + 54, pmode);
+ setEDIDChecksum(pchEDID);
+ pEDIDMon = xf86InterpretEDID(output->scrn->scrnIndex, pchEDID);
+ if (!pEDIDMon)
+ {
+ free(pch);
+ return FALSE;
+ }
+ memcpy(pch, pEDIDMon, sizeof(xf86Monitor));
+ free(pEDIDMon);
+ pEDIDMon = (xf86MonPtr)pch;
+ xf86OutputSetEDID(output, pEDIDMon);
+ return TRUE;
+}
diff --git a/src/VBox/Additions/x11/vboxvideo/getmode.c b/src/VBox/Additions/x11/vboxvideo/getmode.c
new file mode 100644
index 00000000..8f182b93
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/getmode.c
@@ -0,0 +1,325 @@
+/* $Id: getmode.c $ */
+/** @file
+ * VirtualBox X11 Additions graphics driver dynamic video mode functions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "vboxvideo.h"
+
+#define NEED_XF86_TYPES
+#include "xf86.h"
+
+#ifdef XORG_7X
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+#endif
+
+#ifdef VBOXVIDEO_13
+# ifdef RT_OS_LINUX
+# include <linux/input.h>
+# ifndef EVIOCGRAB
+# define EVIOCGRAB _IOW('E', 0x90, int)
+# endif
+# ifndef KEY_SWITCHVIDEOMODE
+# define KEY_SWITCHVIDEOMODE 227
+# endif
+# include <dirent.h>
+# include <errno.h>
+# include <fcntl.h>
+# include <unistd.h>
+# endif /* RT_OS_LINUX */
+#endif /* VBOXVIDEO_13 */
+
+/**************************************************************************
+* Main functions *
+**************************************************************************/
+
+/**
+ * Fills a display mode M with a built-in mode of name pszName and dimensions
+ * cx and cy.
+ */
+static void vboxFillDisplayMode(ScrnInfoPtr pScrn, DisplayModePtr m,
+ const char *pszName, unsigned cx, unsigned cy)
+{
+ VBOXPtr pVBox = pScrn->driverPrivate;
+ char szName[256];
+ DisplayModePtr pPrev = m->prev;
+ DisplayModePtr pNext = m->next;
+
+ if (!pszName)
+ {
+ sprintf(szName, "%ux%u", cx, cy);
+ pszName = szName;
+ }
+ TRACE_LOG("pszName=%s, cx=%u, cy=%u\n", pszName, cx, cy);
+ if (m->name)
+ free((void*)m->name);
+ memset(m, '\0', sizeof(*m));
+ m->prev = pPrev;
+ m->next = pNext;
+ m->status = MODE_OK;
+ m->type = M_T_BUILTIN;
+ /* Older versions of VBox only support screen widths which are a multiple
+ * of 8 */
+ if (pVBox->fAnyX)
+ m->HDisplay = cx;
+ else
+ m->HDisplay = cx & ~7;
+ m->HSyncStart = m->HDisplay + 2;
+ m->HSyncEnd = m->HDisplay + 4;
+ m->HTotal = m->HDisplay + 6;
+ m->VDisplay = cy;
+ m->VSyncStart = m->VDisplay + 2;
+ m->VSyncEnd = m->VDisplay + 4;
+ m->VTotal = m->VDisplay + 6;
+ m->Clock = m->HTotal * m->VTotal * 60 / 1000; /* kHz */
+ m->name = xnfstrdup(pszName);
+}
+
+/**
+ * Allocates an empty display mode and links it into the doubly linked list of
+ * modes pointed to by pScrn->modes. Returns a pointer to the newly allocated
+ * memory.
+ */
+static DisplayModePtr vboxAddEmptyScreenMode(ScrnInfoPtr pScrn)
+{
+ DisplayModePtr pMode = xnfcalloc(sizeof(DisplayModeRec), 1);
+
+ TRACE_ENTRY();
+ if (!pScrn->modes)
+ {
+ pScrn->modes = pMode;
+ pMode->next = pMode;
+ pMode->prev = pMode;
+ }
+ else
+ {
+ pMode->next = pScrn->modes;
+ pMode->prev = pScrn->modes->prev;
+ pMode->next->prev = pMode;
+ pMode->prev->next = pMode;
+ }
+ return pMode;
+}
+
+/**
+ * Create display mode entries in the screen information structure for each
+ * of the graphics modes that we wish to support, that is:
+ * - A dynamic mode in first place which will be updated by the RandR code.
+ * - Any modes that the user requested in xorg.conf/XFree86Config.
+ */
+void vboxAddModes(ScrnInfoPtr pScrn)
+{
+ unsigned cx = 0;
+ unsigned cy = 0;
+ unsigned i;
+ DisplayModePtr pMode;
+
+ /* Add two dynamic mode entries. When we receive a new size hint we will
+ * update whichever of these is not current. */
+ pMode = vboxAddEmptyScreenMode(pScrn);
+ vboxFillDisplayMode(pScrn, pMode, NULL, 800, 600);
+ pMode = vboxAddEmptyScreenMode(pScrn);
+ vboxFillDisplayMode(pScrn, pMode, NULL, 800, 600);
+ /* Add any modes specified by the user. We assume here that the mode names
+ * reflect the mode sizes. */
+ for (i = 0; pScrn->display->modes && pScrn->display->modes[i]; i++)
+ {
+ if (sscanf(pScrn->display->modes[i], "%ux%u", &cx, &cy) == 2)
+ {
+ pMode = vboxAddEmptyScreenMode(pScrn);
+ vboxFillDisplayMode(pScrn, pMode, pScrn->display->modes[i], cx, cy);
+ }
+ }
+}
+
+/** Set the initial values for the guest screen size hints to standard values
+ * in case nothing else is available. */
+void VBoxInitialiseSizeHints(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ unsigned i;
+
+ for (i = 0; i < pVBox->cScreens; ++i)
+ {
+ pVBox->pScreens[i].aPreferredSize.cx = 800;
+ pVBox->pScreens[i].aPreferredSize.cy = 600;
+ pVBox->pScreens[i].afConnected = true;
+ }
+ /* Set up the first mode correctly to match the requested initial mode. */
+ pScrn->modes->HDisplay = pVBox->pScreens[0].aPreferredSize.cx;
+ pScrn->modes->VDisplay = pVBox->pScreens[0].aPreferredSize.cy;
+}
+
+static Bool useHardwareCursor(uint32_t fCursorCapabilities)
+{
+ if (fCursorCapabilities & VBOX_VBVA_CURSOR_CAPABILITY_HARDWARE)
+ return true;
+ return false;
+}
+
+static void compareAndMaybeSetUseHardwareCursor(VBOXPtr pVBox, uint32_t fCursorCapabilities, Bool *pfChanged, Bool fSet)
+{
+ if (pVBox->fUseHardwareCursor != useHardwareCursor(fCursorCapabilities))
+ *pfChanged = true;
+ if (fSet)
+ pVBox->fUseHardwareCursor = useHardwareCursor(fCursorCapabilities);
+}
+
+#define COMPARE_AND_MAYBE_SET(pDest, src, pfChanged, fSet) \
+do { \
+ if (*(pDest) != (src)) \
+ { \
+ if (fSet) \
+ *(pDest) = (src); \
+ *(pfChanged) = true; \
+ } \
+} while(0)
+
+/** Read in information about the most recent size hints and cursor
+ * capabilities requested for the guest screens from HGSMI. */
+void vbvxReadSizesAndCursorIntegrationFromHGSMI(ScrnInfoPtr pScrn, Bool *pfNeedUpdate)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ int rc;
+ unsigned i;
+ Bool fChanged = false;
+ uint32_t fCursorCapabilities;
+
+ if (!pVBox->fHaveHGSMIModeHints)
+ return;
+ rc = VBoxHGSMIGetModeHints(&pVBox->guestCtx, pVBox->cScreens, pVBox->paVBVAModeHints);
+ AssertMsg(rc == VINF_SUCCESS, ("VBoxHGSMIGetModeHints failed, rc=%d.\n", rc));
+ for (i = 0; i < pVBox->cScreens; ++i)
+ if (pVBox->paVBVAModeHints[i].magic == VBVAMODEHINT_MAGIC)
+ {
+ COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredSize.cx, pVBox->paVBVAModeHints[i].cx & 0x8fff, &fChanged, true);
+ COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredSize.cy, pVBox->paVBVAModeHints[i].cy & 0x8fff, &fChanged, true);
+ COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afConnected, RT_BOOL(pVBox->paVBVAModeHints[i].fEnabled), &fChanged, true);
+ COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredLocation.x, (int32_t)pVBox->paVBVAModeHints[i].dx & 0x8fff, &fChanged,
+ true);
+ COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredLocation.y, (int32_t)pVBox->paVBVAModeHints[i].dy & 0x8fff, &fChanged,
+ true);
+ if (pVBox->paVBVAModeHints[i].dx != ~(uint32_t)0 && pVBox->paVBVAModeHints[i].dy != ~(uint32_t)0)
+ COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afHaveLocation, true, &fChanged, true);
+ else
+ COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afHaveLocation, false, &fChanged, true);
+ }
+ rc = VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_CURSOR_CAPABILITIES, &fCursorCapabilities);
+ AssertMsg(rc == VINF_SUCCESS, ("Getting VBOX_VBVA_CONF32_CURSOR_CAPABILITIES failed, rc=%d.\n", rc));
+ compareAndMaybeSetUseHardwareCursor(pVBox, fCursorCapabilities, &fChanged, true);
+ if (pfNeedUpdate != NULL && fChanged)
+ *pfNeedUpdate = true;
+}
+
+#undef COMPARE_AND_MAYBE_SET
+
+#ifdef VBOXVIDEO_13
+# ifdef RT_OS_LINUX
+/** We have this for two purposes: one is to ensure that the X server is woken
+ * up when we get a video ACPI event. Two is to grab ACPI video events to
+ * prevent gnome-settings-daemon from seeing them, as older versions ignored
+ * the time stamp and handled them at the wrong time. */
+static void acpiEventHandler(int fd, void *pvData)
+{
+ struct input_event event;
+ ssize_t rc;
+ RT_NOREF(pvData);
+
+ do
+ rc = read(fd, &event, sizeof(event));
+ while (rc > 0 || (rc == -1 && errno == EINTR));
+ /* Why do they return EAGAIN instead of zero bytes read like everyone else does? */
+ AssertMsg(rc != -1 || errno == EAGAIN, ("Reading ACPI input event failed.\n"));
+}
+
+void vbvxSetUpLinuxACPI(ScreenPtr pScreen)
+{
+ static const char s_szDevInput[] = "/dev/input/";
+ struct dirent *pDirent;
+ char szFile[sizeof(s_szDevInput) + sizeof(pDirent->d_name) + 16];
+ VBOXPtr pVBox = VBOXGetRec(xf86Screens[pScreen->myNum]);
+ DIR *pDir;
+ int fd = -1;
+
+ if (pVBox->fdACPIDevices != -1 || pVBox->hACPIEventHandler != NULL)
+ FatalError("ACPI input file descriptor not initialised correctly.\n");
+ pDir = opendir("/dev/input");
+ if (pDir == NULL)
+ return;
+ memcpy(szFile, s_szDevInput, sizeof(s_szDevInput));
+ for (pDirent = readdir(pDir); pDirent != NULL; pDirent = readdir(pDir))
+ {
+ if (strncmp(pDirent->d_name, "event", sizeof("event") - 1) == 0)
+ {
+#define BITS_PER_BLOCK (sizeof(unsigned long) * 8)
+ char szDevice[64] = "";
+ unsigned long afKeys[KEY_MAX / BITS_PER_BLOCK];
+ size_t const cchName = strlen(pDirent->d_name);
+ if (cchName + sizeof(s_szDevInput) > sizeof(szFile))
+ continue;
+ memcpy(&szFile[sizeof(s_szDevInput) - 1], pDirent->d_name, cchName + 1);
+ if (fd != -1)
+ close(fd);
+ fd = open(szFile, O_RDONLY | O_NONBLOCK);
+ if ( fd == -1
+ || ioctl(fd, EVIOCGNAME(sizeof(szDevice)), szDevice) == -1
+ || strcmp(szDevice, "Video Bus") != 0)
+ continue;
+ if ( ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(afKeys)), afKeys) == -1
+ || (( afKeys[KEY_SWITCHVIDEOMODE / BITS_PER_BLOCK]
+ >> KEY_SWITCHVIDEOMODE % BITS_PER_BLOCK) & 1) == 0)
+ break;
+ if (ioctl(fd, EVIOCGRAB, (void *)1) != 0)
+ break;
+ pVBox->hACPIEventHandler
+ = xf86AddGeneralHandler(fd, acpiEventHandler, pScreen);
+ if (pVBox->hACPIEventHandler == NULL)
+ break;
+ pVBox->fdACPIDevices = fd;
+ fd = -1;
+ break;
+#undef BITS_PER_BLOCK
+ }
+ }
+ if (fd != -1)
+ close(fd);
+ closedir(pDir);
+}
+
+void vbvxCleanUpLinuxACPI(ScreenPtr pScreen)
+{
+ VBOXPtr pVBox = VBOXGetRec(xf86Screens[pScreen->myNum]);
+ if (pVBox->fdACPIDevices != -1)
+ close(pVBox->fdACPIDevices);
+ pVBox->fdACPIDevices = -1;
+ xf86RemoveGeneralHandler(pVBox->hACPIEventHandler);
+ pVBox->hACPIEventHandler = NULL;
+}
+# endif /* RT_OS_LINUX */
+#endif /* VBOXVIDEO_13 */
diff --git a/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c b/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c
new file mode 100644
index 00000000..3fb35831
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c
@@ -0,0 +1,104 @@
+/* $Id: hgsmimemalloc.c $ */
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Memory allocator
+ * ----------------
+ *
+ * Implementation
+ * --------------
+ *
+ * Since the X.Org driver is single threaded and works using an allocate,
+ * submit and free pattern, we replace the generic allocator with a simple
+ * Boolean. Need more be said?
+ *
+ * bird> Yes, it's buggy. You never set fAllocated. See HGSMIMAAlloc().
+ */
+
+#include <VBoxVideoIPRT.h>
+#include <HGSMIMemAlloc.h>
+#include <HGSMI.h>
+
+int HGSMIMAInit(HGSMIMADATA *pMA, const HGSMIAREA *pArea,
+ HGSMIOFFSET *paDescriptors, uint32_t cDescriptors, HGSMISIZE cbMaxBlock,
+ const HGSMIENV *pEnv)
+{
+ (void)paDescriptors;
+ (void)cDescriptors;
+ (void)cbMaxBlock;
+ (void)pEnv;
+ if (!(pArea->cbArea < UINT32_C(0x80000000)))
+ return VERR_INVALID_PARAMETER;
+ if (!(pArea->cbArea >= HGSMI_MA_BLOCK_SIZE_MIN))
+ return VERR_INVALID_PARAMETER;
+
+ pMA->area = *pArea;
+ pMA->fAllocated = false;
+ return VINF_SUCCESS;
+}
+
+void HGSMIMAUninit(HGSMIMADATA *pMA)
+{
+ (void)pMA;
+}
+
+static HGSMIOFFSET HGSMIMAPointerToOffset(const HGSMIMADATA *pMA, const void RT_UNTRUSTED_VOLATILE_GUEST *pv)
+{
+ if (HGSMIAreaContainsPointer(&pMA->area, pv))
+ return HGSMIPointerToOffset(&pMA->area, pv);
+
+ AssertFailed();
+ return HGSMIOFFSET_VOID;
+}
+
+static void RT_UNTRUSTED_VOLATILE_GUEST *HGSMIMAOffsetToPointer(const HGSMIMADATA *pMA, HGSMIOFFSET off)
+{
+ if (HGSMIAreaContainsOffset(&pMA->area, off))
+ return HGSMIOffsetToPointer(&pMA->area, off);
+
+ AssertFailed();
+ return NULL;
+}
+
+void RT_UNTRUSTED_VOLATILE_GUEST *HGSMIMAAlloc(HGSMIMADATA *pMA, HGSMISIZE cb)
+{
+ (void)cb;
+ if (pMA->fAllocated)
+ return NULL;
+ HGSMIOFFSET off = pMA->area.offBase;
+ return HGSMIMAOffsetToPointer(pMA, off);
+ pMA->fAllocated = true; /** @todo r=bird: Errr. what's this doing *after* the return statement? */
+}
+
+void HGSMIMAFree(HGSMIMADATA *pMA, void RT_UNTRUSTED_VOLATILE_GUEST *pv)
+{
+ HGSMIOFFSET off = HGSMIMAPointerToOffset(pMA, pv);
+ if (off != HGSMIOFFSET_VOID)
+ pMA->fAllocated = false;
+ else
+ AssertFailed();
+}
+
diff --git a/src/VBox/Additions/x11/vboxvideo/pointer.c b/src/VBox/Additions/x11/vboxvideo/pointer.c
new file mode 100644
index 00000000..9a6ade80
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/pointer.c
@@ -0,0 +1,496 @@
+/* $Id: pointer.c $ */
+/** @file
+ * VirtualBox X11 Additions graphics driver utility functions
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PCIACCESS
+# include "xf86Pci.h"
+# include <Pci.h>
+#endif
+
+#include "xf86.h"
+#define NEED_XF86_TYPES
+#include "compiler.h"
+#include "cursorstr.h"
+#include "servermd.h"
+
+#include "vboxvideo.h"
+
+#ifdef XORG_7X
+# include <stdlib.h>
+# include <string.h>
+#endif
+
+#define VBOX_MAX_CURSOR_WIDTH 64
+#define VBOX_MAX_CURSOR_HEIGHT 64
+
+/**************************************************************************
+* Debugging functions and macros *
+**************************************************************************/
+
+/* #define DEBUG_POINTER */
+
+#ifdef DEBUG
+# define PUT_PIXEL(c) ErrorF ("%c", c)
+#else /* DEBUG_VIDEO not defined */
+# define PUT_PIXEL(c) do { } while(0)
+#endif /* DEBUG_VIDEO not defined */
+
+/** Macro to printf an error message and return from a function */
+#define RETERROR(scrnIndex, RetVal, ...) \
+ do \
+ { \
+ xf86DrvMsg(scrnIndex, X_ERROR, __VA_ARGS__); \
+ return RetVal; \
+ } \
+ while (0)
+
+/** Structure to pass cursor image data between realise_cursor() and
+ * load_cursor_image(). The members match the parameters to
+ * @a VBoxHGSMIUpdatePointerShape(). */
+struct vboxCursorImage
+{
+ uint32_t fFlags;
+ uint32_t cHotX;
+ uint32_t cHotY;
+ uint32_t cWidth;
+ uint32_t cHeight;
+ uint8_t *pPixels;
+ uint32_t cbLength;
+};
+
+#ifdef DEBUG_POINTER
+static void
+vbox_show_shape(unsigned short w, unsigned short h, CARD32 bg, unsigned char *image)
+{
+ size_t x, y;
+ unsigned short pitch;
+ CARD32 *color;
+ unsigned char *mask;
+ size_t sizeMask;
+
+ image += sizeof(struct vboxCursorImage);
+ mask = image;
+ pitch = (w + 7) / 8;
+ sizeMask = (pitch * h + 3) & ~3;
+ color = (CARD32 *)(image + sizeMask);
+
+ TRACE_ENTRY();
+ for (y = 0; y < h; ++y, mask += pitch, color += w)
+ {
+ for (x = 0; x < w; ++x)
+ {
+ if (mask[x / 8] & (1 << (7 - (x % 8))))
+ ErrorF (" ");
+ else
+ {
+ CARD32 c = color[x];
+ if (c == bg)
+ ErrorF("Y");
+ else
+ ErrorF("X");
+ }
+ }
+ ErrorF("\n");
+ }
+}
+#endif
+
+/**************************************************************************
+* Main functions *
+**************************************************************************/
+
+void vbvxCursorTerm(VBOXPtr pVBox)
+{
+ TRACE_ENTRY();
+
+ xf86DestroyCursorInfoRec(pVBox->pCurs);
+ pVBox->pCurs = NULL;
+ TRACE_EXIT();
+}
+
+static void
+vbox_vmm_hide_cursor(ScrnInfoPtr pScrn, VBOXPtr pVBox)
+{
+ int rc;
+ RT_NOREF(pScrn);
+
+ rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, 0, 0, 0, 0, 0, NULL, 0);
+ AssertMsg(rc == VINF_SUCCESS, ("Could not hide the virtual mouse pointer, VBox error %d.\n", rc));
+}
+
+static void
+vbox_vmm_show_cursor(ScrnInfoPtr pScrn, VBOXPtr pVBox)
+{
+ int rc;
+ RT_NOREF(pScrn);
+
+ if (!pVBox->fUseHardwareCursor)
+ return;
+ rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, VBOX_MOUSE_POINTER_VISIBLE,
+ 0, 0, 0, 0, NULL, 0);
+ AssertMsg(rc == VINF_SUCCESS, ("Could not unhide the virtual mouse pointer.\n"));
+}
+
+static void
+vbox_vmm_load_cursor_image(ScrnInfoPtr pScrn, VBOXPtr pVBox,
+ unsigned char *pvImage)
+{
+ int rc;
+ struct vboxCursorImage *pImage;
+ pImage = (struct vboxCursorImage *)pvImage;
+ RT_NOREF(pScrn);
+
+#ifdef DEBUG_POINTER
+ vbox_show_shape(pImage->cWidth, pImage->cHeight, 0, pvImage);
+#endif
+
+ rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, pImage->fFlags,
+ pImage->cHotX, pImage->cHotY, pImage->cWidth, pImage->cHeight,
+ pImage->pPixels, pImage->cbLength);
+ AssertMsg(rc == VINF_SUCCESS, ("Unable to set the virtual mouse pointer image.\n"));
+}
+
+static void
+vbox_set_cursor_colors(ScrnInfoPtr pScrn, int bg, int fg)
+{
+ RT_NOREF(pScrn);
+ RT_NOREF(bg);
+ RT_NOREF(fg);
+ /* ErrorF("vbox_set_cursor_colors NOT IMPLEMENTED\n"); */
+}
+
+
+static void
+vbox_set_cursor_position(ScrnInfoPtr pScrn, int x, int y)
+{
+ VBOXPtr pVBox = pScrn->driverPrivate;
+
+ /* This currently does nothing. */
+ VBoxHGSMICursorPosition(&pVBox->guestCtx, true, x, y, NULL, NULL);
+}
+
+static void
+vbox_hide_cursor(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = pScrn->driverPrivate;
+
+ vbox_vmm_hide_cursor(pScrn, pVBox);
+}
+
+static void
+vbox_show_cursor(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = pScrn->driverPrivate;
+
+ vbox_vmm_show_cursor(pScrn, pVBox);
+}
+
+static void
+vbox_load_cursor_image(ScrnInfoPtr pScrn, unsigned char *image)
+{
+ VBOXPtr pVBox = pScrn->driverPrivate;
+
+ vbox_vmm_load_cursor_image(pScrn, pVBox, image);
+}
+
+static Bool
+vbox_use_hw_cursor(ScreenPtr pScreen, CursorPtr pCurs)
+{
+ ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
+ VBOXPtr pVBox = pScrn->driverPrivate;
+ RT_NOREF(pCurs);
+ return pVBox->fUseHardwareCursor;
+}
+
+static unsigned char
+color_to_byte(unsigned c)
+{
+ return (c >> 8) & 0xff;
+}
+
+static unsigned char *
+vbox_realize_cursor(xf86CursorInfoPtr infoPtr, CursorPtr pCurs)
+{
+ VBOXPtr pVBox;
+ CursorBitsPtr bitsp;
+ unsigned short w, h, x, y;
+ unsigned char *c, *p, *pm, *ps, *m;
+ size_t sizeRequest, sizeRgba, sizeMask, srcPitch, dstPitch;
+ CARD32 fc, bc, *cp;
+ int scrnIndex = infoPtr->pScrn->scrnIndex;
+ struct vboxCursorImage *pImage;
+
+ pVBox = infoPtr->pScrn->driverPrivate;
+ bitsp = pCurs->bits;
+ w = bitsp->width;
+ h = bitsp->height;
+
+ if (!w || !h || w > VBOX_MAX_CURSOR_WIDTH || h > VBOX_MAX_CURSOR_HEIGHT)
+ RETERROR(scrnIndex, NULL,
+ "Error invalid cursor dimensions %dx%d\n", w, h);
+
+ if ((bitsp->xhot > w) || (bitsp->yhot > h))
+ RETERROR(scrnIndex, NULL,
+ "Error invalid cursor hotspot location %dx%d (max %dx%d)\n",
+ bitsp->xhot, bitsp->yhot, w, h);
+
+ srcPitch = PixmapBytePad (bitsp->width, 1);
+ dstPitch = (w + 7) / 8;
+ sizeMask = ((dstPitch * h) + 3) & (size_t) ~3;
+ sizeRgba = w * h * 4;
+ sizeRequest = sizeMask + sizeRgba + sizeof(*pImage);
+
+ p = c = calloc (1, sizeRequest);
+ if (!c)
+ RETERROR(scrnIndex, NULL,
+ "Error failed to alloc %lu bytes for cursor\n",
+ (unsigned long) sizeRequest);
+
+ pImage = (struct vboxCursorImage *)p;
+ pImage->pPixels = m = p + sizeof(*pImage);
+ cp = (CARD32 *)(m + sizeMask);
+
+ TRACE_LOG ("w=%d h=%d sm=%d sr=%d p=%d\n",
+ w, h, (int) sizeMask, (int) sizeRgba, (int) dstPitch);
+ TRACE_LOG ("m=%p c=%p cp=%p\n", m, c, (void *)cp);
+
+ fc = color_to_byte (pCurs->foreBlue)
+ | (color_to_byte (pCurs->foreGreen) << 8)
+ | (color_to_byte (pCurs->foreRed) << 16);
+
+ bc = color_to_byte (pCurs->backBlue)
+ | (color_to_byte (pCurs->backGreen) << 8)
+ | (color_to_byte (pCurs->backRed) << 16);
+
+ /*
+ * Convert the Xorg source/mask bits to the and/xor bits VBox needs.
+ * Xorg:
+ * The mask is a bitmap indicating which parts of the cursor are
+ * transparent and which parts are drawn. The source is a bitmap
+ * indicating which parts of the non-transparent portion of the
+ * the cursor should be painted in the foreground color and which
+ * should be painted in the background color. By default, set bits
+ * indicate the opaque part of the mask bitmap and clear bits
+ * indicate the transparent part.
+ * VBox:
+ * The color data is the XOR mask. The AND mask bits determine
+ * which pixels of the color data (XOR mask) will replace (overwrite)
+ * the screen pixels (AND mask bit = 0) and which ones will be XORed
+ * with existing screen pixels (AND mask bit = 1).
+ * For example when you have the AND mask all 0, then you see the
+ * correct mouse pointer image surrounded by black square.
+ */
+ for (pm = bitsp->mask, ps = bitsp->source, y = 0;
+ y < h;
+ ++y, pm += srcPitch, ps += srcPitch, m += dstPitch)
+ {
+ for (x = 0; x < w; ++x)
+ {
+ if (pm[x / 8] & (1 << (x % 8)))
+ {
+ /* opaque, leave AND mask bit at 0 */
+ if (ps[x / 8] & (1 << (x % 8)))
+ {
+ *cp++ = fc;
+ PUT_PIXEL('X');
+ }
+ else
+ {
+ *cp++ = bc;
+ PUT_PIXEL('*');
+ }
+ }
+ else
+ {
+ /* transparent, set AND mask bit */
+ m[x / 8] |= 1 << (7 - (x % 8));
+ /* don't change the screen pixel */
+ *cp++ = 0;
+ PUT_PIXEL(' ');
+ }
+ }
+ PUT_PIXEL('\n');
+ }
+
+ pImage->cWidth = w;
+ pImage->cHeight = h;
+ pImage->cHotX = bitsp->xhot;
+ pImage->cHotY = bitsp->yhot;
+ pImage->fFlags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE;
+ pImage->cbLength = sizeRequest - sizeof(*pImage);
+
+#ifdef DEBUG_POINTER
+ ErrorF("shape = %p\n", p);
+ vbox_show_shape(w, h, bc, c);
+#endif
+
+ return p;
+}
+
+#ifdef ARGB_CURSOR
+static Bool
+vbox_use_hw_cursor_argb(ScreenPtr pScreen, CursorPtr pCurs)
+{
+ ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
+ VBOXPtr pVBox = pScrn->driverPrivate;
+
+ if (!pVBox->fUseHardwareCursor)
+ return FALSE;
+ if ( (pCurs->bits->height > VBOX_MAX_CURSOR_HEIGHT)
+ || (pCurs->bits->width > VBOX_MAX_CURSOR_WIDTH)
+ || (pScrn->bitsPerPixel <= 8))
+ return FALSE;
+ return TRUE;
+}
+
+
+static void
+vbox_load_cursor_argb(ScrnInfoPtr pScrn, CursorPtr pCurs)
+{
+ VBOXPtr pVBox;
+ CursorBitsPtr bitsp;
+ unsigned short w, h;
+ unsigned short cx, cy;
+ unsigned char *pm;
+ CARD32 *pc;
+ size_t sizeData, sizeMask;
+ CARD8 *p;
+ int scrnIndex;
+ uint32_t fFlags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE
+ | VBOX_MOUSE_POINTER_ALPHA;
+
+ pVBox = pScrn->driverPrivate;
+ bitsp = pCurs->bits;
+ w = bitsp->width;
+ h = bitsp->height;
+ scrnIndex = pScrn->scrnIndex;
+
+ /* Mask must be generated for alpha cursors, that is required by VBox. */
+ /* note: (michael) the next struct must be 32bit aligned. */
+ sizeMask = ((w + 7) / 8 * h + 3) & ~3;
+
+ if (!w || !h || w > VBOX_MAX_CURSOR_WIDTH || h > VBOX_MAX_CURSOR_HEIGHT)
+ RETERROR(scrnIndex, ,
+ "Error invalid cursor dimensions %dx%d\n", w, h);
+
+ if ((bitsp->xhot > w) || (bitsp->yhot > h))
+ RETERROR(scrnIndex, ,
+ "Error invalid cursor hotspot location %dx%d (max %dx%d)\n",
+ bitsp->xhot, bitsp->yhot, w, h);
+
+ sizeData = w * h * 4 + sizeMask;
+ p = calloc(1, sizeData);
+ if (!p)
+ RETERROR(scrnIndex, ,
+ "Error failed to alloc %lu bytes for cursor\n",
+ (unsigned long)sizeData);
+
+ memcpy(p + sizeMask, bitsp->argb, w * h * 4);
+
+ /* Emulate the AND mask. */
+ pm = p;
+ pc = bitsp->argb;
+
+ /* Init AND mask to 1 */
+ memset(pm, 0xFF, sizeMask);
+
+ /*
+ * The additions driver must provide the AND mask for alpha cursors. The host frontend
+ * which can handle alpha channel, will ignore the AND mask and draw an alpha cursor.
+ * But if the host does not support ARGB, then it simply uses the AND mask and the color
+ * data to draw a normal color cursor.
+ */
+ for (cy = 0; cy < h; cy++)
+ {
+ unsigned char bitmask = 0x80;
+
+ for (cx = 0; cx < w; cx++, bitmask >>= 1)
+ {
+ if (bitmask == 0)
+ bitmask = 0x80;
+
+ if (pc[cx] >= 0xF0000000)
+ pm[cx / 8] &= ~bitmask;
+ }
+
+ /* Point to next source and dest scans */
+ pc += w;
+ pm += (w + 7) / 8;
+ }
+
+ VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, fFlags, bitsp->xhot,
+ bitsp->yhot, w, h, p, sizeData);
+ free(p);
+}
+#endif
+
+Bool vbvxCursorInit(ScreenPtr pScreen)
+{
+ ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum];
+ VBOXPtr pVBox = pScrn->driverPrivate;
+ xf86CursorInfoPtr pCurs = NULL;
+ Bool rc = TRUE;
+
+ TRACE_ENTRY();
+ pVBox->pCurs = pCurs = xf86CreateCursorInfoRec();
+ if (!pCurs) {
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
+ "Failed to create X Window cursor information structures for virtual mouse.\n");
+ rc = FALSE;
+ }
+ if (rc) {
+ pCurs->MaxWidth = VBOX_MAX_CURSOR_WIDTH;
+ pCurs->MaxHeight = VBOX_MAX_CURSOR_HEIGHT;
+ pCurs->Flags = HARDWARE_CURSOR_TRUECOLOR_AT_8BPP
+ | HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_1
+ | HARDWARE_CURSOR_BIT_ORDER_MSBFIRST
+ | HARDWARE_CURSOR_UPDATE_UNHIDDEN;
+
+ pCurs->SetCursorColors = vbox_set_cursor_colors;
+ pCurs->SetCursorPosition = vbox_set_cursor_position;
+ pCurs->LoadCursorImage = vbox_load_cursor_image;
+ pCurs->HideCursor = vbox_hide_cursor;
+ pCurs->ShowCursor = vbox_show_cursor;
+ pCurs->UseHWCursor = vbox_use_hw_cursor;
+ pCurs->RealizeCursor = vbox_realize_cursor;
+
+#ifdef ARGB_CURSOR
+ pCurs->UseHWCursorARGB = vbox_use_hw_cursor_argb;
+ pCurs->LoadCursorARGB = vbox_load_cursor_argb;
+#endif
+
+ rc = xf86InitCursor(pScreen, pCurs);
+ }
+ if (!rc)
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
+ "Failed to enable mouse pointer integration.\n");
+ if (!rc && (pCurs != NULL))
+ xf86DestroyCursorInfoRec(pCurs);
+ return rc;
+}
diff --git a/src/VBox/Additions/x11/vboxvideo/setmode.c b/src/VBox/Additions/x11/vboxvideo/setmode.c
new file mode 100644
index 00000000..876c043c
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/setmode.c
@@ -0,0 +1,129 @@
+/* $Id: setmode.c $ */
+/** @file
+ * Linux Additions X11 graphics driver, mode setting
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ * This file is based on X11 VESA driver (hardly any traces left here):
+ *
+ * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com)
+ *
+ * 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 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.
+ *
+ * Except as contained in this notice, the name of Conectiva Linux shall
+ * not be used in advertising or otherwise to promote the sale, use or other
+ * dealings in this Software without prior written authorization from
+ * Conectiva Linux.
+ *
+ * Authors: Paulo César Pereira de Andrade <pcpa@conectiva.com.br>
+ * Michael Thayer <michael.thayer@oracle.com>
+ */
+
+#ifdef XORG_7X
+/* We include <unistd.h> for Solaris below, and the ANSI C emulation layer
+ * interferes with that. */
+# define _XF86_ANSIC_H
+# define XF86_LIBC_H
+# include <string.h>
+#endif
+#include "vboxvideo.h"
+#include "xf86.h"
+
+/* VGA hardware functions for setting and restoring text mode */
+#include "vgaHW.h"
+
+#ifdef RT_OS_SOLARIS
+# include <sys/vuid_event.h>
+# include <sys/msio.h>
+# include <errno.h>
+# include <fcntl.h>
+# include <unistd.h>
+#endif
+
+/** Clear the virtual framebuffer in VRAM. Optionally also clear up to the
+ * size of a new framebuffer. Framebuffer sizes larger than available VRAM
+ * be treated as zero and passed over. */
+void vbvxClearVRAM(ScrnInfoPtr pScrn, size_t cbOldSize, size_t cbNewSize)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+
+ /* Assume 32BPP - this is just a sanity test. */
+ AssertMsg( cbOldSize / 4 <= VBOX_VIDEO_MAX_VIRTUAL * VBOX_VIDEO_MAX_VIRTUAL
+ && cbNewSize / 4 <= VBOX_VIDEO_MAX_VIRTUAL * VBOX_VIDEO_MAX_VIRTUAL,
+ ("cbOldSize=%llu cbNewSize=%llu, max=%u.\n", (unsigned long long)cbOldSize, (unsigned long long)cbNewSize,
+ VBOX_VIDEO_MAX_VIRTUAL * VBOX_VIDEO_MAX_VIRTUAL));
+ if (cbOldSize > (size_t)pVBox->cbFBMax)
+ cbOldSize = pVBox->cbFBMax;
+ if (cbNewSize > (size_t)pVBox->cbFBMax)
+ cbNewSize = pVBox->cbFBMax;
+ memset(pVBox->base, 0, max(cbOldSize, cbNewSize));
+}
+
+/** Set a graphics mode. Poke any required values into registers, do an HGSMI
+ * mode set and tell the host we support advanced graphics functions.
+ */
+void vbvxSetMode(ScrnInfoPtr pScrn, unsigned cDisplay, unsigned cWidth, unsigned cHeight, int x, int y, Bool fEnabled,
+ Bool fConnected, struct vbvxFrameBuffer *pFrameBuffer)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ uint32_t offStart;
+ uint16_t fFlags;
+ int rc;
+ Bool fEnabledAndVisible = fEnabled && x + cWidth <= pFrameBuffer->cWidth && y + cHeight <= pFrameBuffer->cHeight;
+ /* Recent host code has a flag to blank the screen; older code needs BPP set to zero. */
+ uint32_t cBPP = fEnabledAndVisible || pVBox->fHostHasScreenBlankingFlag ? pFrameBuffer->cBPP : 0;
+
+ TRACE_LOG("cDisplay=%u, cWidth=%u, cHeight=%u, x=%d, y=%d, fEnabled=%d, fConnected=%d, pFrameBuffer: { x0=%d, y0=%d, cWidth=%u, cHeight=%u, cBPP=%u }\n",
+ cDisplay, cWidth, cHeight, x, y, fEnabled, fConnected, pFrameBuffer->x0, pFrameBuffer->y0, pFrameBuffer->cWidth,
+ pFrameBuffer->cHeight, pFrameBuffer->cBPP);
+ AssertMsg(cWidth != 0 && cHeight != 0, ("cWidth = 0 or cHeight = 0\n"));
+ offStart = (y * pFrameBuffer->cWidth + x) * pFrameBuffer->cBPP / 8;
+ if (cDisplay == 0 && fEnabled)
+ VBoxVideoSetModeRegisters(cWidth, cHeight, pFrameBuffer->cWidth, pFrameBuffer->cBPP, 0, x, y);
+ fFlags = VBVA_SCREEN_F_ACTIVE;
+ fFlags |= (fConnected ? 0 : VBVA_SCREEN_F_DISABLED);
+ fFlags |= (!fEnabledAndVisible && pVBox->fHostHasScreenBlankingFlag ? VBVA_SCREEN_F_BLANK : 0);
+ VBoxHGSMIProcessDisplayInfo(&pVBox->guestCtx, cDisplay, x - pFrameBuffer->x0, y - pFrameBuffer->y0, offStart,
+ pFrameBuffer->cWidth * pFrameBuffer->cBPP / 8, cWidth, cHeight, cBPP, fFlags);
+ rc = VBoxHGSMIUpdateInputMapping(&pVBox->guestCtx, 0 - pFrameBuffer->x0, 0 - pFrameBuffer->y0, pFrameBuffer->cWidth,
+ pFrameBuffer->cHeight);
+ if (RT_FAILURE(rc))
+ FatalError("Failed to update the input mapping.\n");
+}
+
+/** Tell the virtual mouse device about the new virtual desktop size. */
+void vbvxSetSolarisMouseRange(int width, int height)
+{
+#ifdef RT_OS_SOLARIS
+ int rc;
+ int hMouse = open("/dev/mouse", O_RDWR);
+
+ if (hMouse >= 0)
+ {
+ do {
+ Ms_screen_resolution Res = { height, width };
+ rc = ioctl(hMouse, MSIOSRESOLUTION, &Res);
+ } while ((rc != 0) && (errno == EINTR));
+ close(hMouse);
+ }
+#else
+ (void)width; (void)height;
+#endif
+}
diff --git a/src/VBox/Additions/x11/vboxvideo/vboxvideo.c b/src/VBox/Additions/x11/vboxvideo/vboxvideo.c
new file mode 100644
index 00000000..99a8b1fa
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/vboxvideo.c
@@ -0,0 +1,1491 @@
+/* $Id: vboxvideo.c $ */
+/** @file
+ * Linux Additions X11 graphics driver
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ * This file is based on the X.Org VESA driver:
+ *
+ * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com)
+ * Copyright 2008 Red Hat, Inc.
+ * 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, 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 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.
+ *
+ * Except as contained in this notice, the name of Conectiva Linux shall
+ * not be used in advertising or otherwise to promote the sale, use or other
+ * dealings in this Software without prior written authorization from
+ * Conectiva Linux.
+ *
+ * Authors: Paulo César Pereira de Andrade <pcpa@conectiva.com.br>
+ * David Dawes <dawes@xfree86.org>
+ * Adam Jackson <ajax@redhat.com>
+ * Dave Airlie <airlied@redhat.com>
+ * Michael Thayer <michael.thayer@oracle.com>
+ */
+
+#include "vboxvideo.h"
+#include <VBoxVideoVBE.h>
+
+/* Basic definitions and functions needed by all drivers. */
+#include "xf86.h"
+/* For video memory mapping. */
+#include "xf86_OSproc.h"
+#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 6
+/* PCI resources. */
+# include "xf86Resources.h"
+#endif
+/* Generic server linear frame-buffer APIs. */
+#include "fb.h"
+/* Colormap and visual handling. */
+#include "micmap.h"
+#include "xf86cmap.h"
+/* ShadowFB support */
+#include "shadowfb.h"
+/* VGA hardware functions for setting and restoring text mode */
+#include "vgaHW.h"
+#ifdef VBOXVIDEO_13
+/* X.org 1.3+ mode setting */
+# define _HAVE_STRING_ARCH_strsep /* bits/string2.h, __strsep_1c. */
+# include "xf86Crtc.h"
+# include "xf86Modes.h"
+/* For xf86RandR12GetOriginalVirtualSize(). */
+# include "xf86RandR12.h"
+#endif
+/* For setting the root window property. */
+#include "property.h"
+#include <X11/Xatom.h>
+
+#ifdef XORG_7X
+# include <stdlib.h>
+# include <string.h>
+# include <fcntl.h>
+# include <unistd.h>
+#endif
+
+/* Mandatory functions */
+
+static const OptionInfoRec * VBOXAvailableOptions(int chipid, int busid);
+static void VBOXIdentify(int flags);
+#ifndef PCIACCESS
+static Bool VBOXProbe(DriverPtr drv, int flags);
+#else
+static Bool VBOXPciProbe(DriverPtr drv, int entity_num,
+ struct pci_device *dev, intptr_t match_data);
+#endif
+static Bool VBOXPreInit(ScrnInfoPtr pScrn, int flags);
+static Bool VBOXScreenInit(ScreenPtr pScreen, int argc, char **argv);
+static Bool VBOXEnterVT(ScrnInfoPtr pScrn);
+static void VBOXLeaveVT(ScrnInfoPtr pScrn);
+static Bool VBOXCloseScreen(ScreenPtr pScreen);
+#ifndef VBOXVIDEO_13
+static Bool VBOXSaveScreen(ScreenPtr pScreen, int mode);
+#endif
+static Bool VBOXSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr pMode);
+static void VBOXAdjustFrame(ScrnInfoPtr pScrn, int x, int y);
+static void VBOXFreeScreen(ScrnInfoPtr pScrn);
+#ifndef VBOXVIDEO_13
+static void VBOXDisplayPowerManagementSet(ScrnInfoPtr pScrn, int mode, int flags);
+#endif
+
+/* locally used functions */
+static Bool VBOXMapVidMem(ScrnInfoPtr pScrn);
+static void VBOXUnmapVidMem(ScrnInfoPtr pScrn);
+static void VBOXSaveMode(ScrnInfoPtr pScrn);
+static void VBOXRestoreMode(ScrnInfoPtr pScrn);
+static void setSizesAndCursorIntegration(ScrnInfoPtr pScrn, Bool fScreenInitTime);
+
+#ifndef XF86_SCRN_INTERFACE
+# define xf86ScreenToScrn(pScreen) xf86Screens[(pScreen)->myNum]
+# define xf86ScrnToScreen(pScrn) screenInfo.screens[(pScrn)->scrnIndex]
+#endif
+
+static inline void VBOXSetRec(ScrnInfoPtr pScrn)
+{
+ if (!pScrn->driverPrivate)
+ {
+ VBOXPtr pVBox = (VBOXPtr)xnfcalloc(sizeof(VBOXRec), 1);
+ pScrn->driverPrivate = pVBox;
+#if defined(VBOXVIDEO_13) && defined(RT_OS_LINUX)
+ pVBox->fdACPIDevices = -1;
+#endif
+ }
+}
+
+enum GenericTypes
+{
+ CHIP_VBOX_GENERIC
+};
+
+#ifdef PCIACCESS
+static const struct pci_id_match vbox_device_match[] = {
+ {
+ VBOX_VENDORID, VBOX_DEVICEID, PCI_MATCH_ANY, PCI_MATCH_ANY,
+ 0, 0, 0
+ },
+
+ { 0, 0, 0 },
+};
+#endif
+
+/* Supported chipsets */
+static SymTabRec VBOXChipsets[] =
+{
+ {VBOX_DEVICEID, "vbox"},
+ {-1, NULL}
+};
+
+static PciChipsets VBOXPCIchipsets[] = {
+ { VBOX_DEVICEID, VBOX_DEVICEID, RES_SHARED_VGA },
+ { -1, -1, RES_UNDEFINED },
+};
+
+/*
+ * This contains the functions needed by the server after loading the
+ * driver module. It must be supplied, and gets added the driver list by
+ * the Module Setup function in the dynamic case. In the static case a
+ * reference to this is compiled in, and this requires that the name of
+ * this DriverRec be an upper-case version of the driver name.
+ */
+
+#ifdef XORG_7X
+_X_EXPORT
+#endif
+DriverRec VBOXVIDEO = {
+ VBOX_VERSION,
+ VBOX_DRIVER_NAME,
+ VBOXIdentify,
+#ifdef PCIACCESS
+ NULL,
+#else
+ VBOXProbe,
+#endif
+ VBOXAvailableOptions,
+ NULL,
+ 0,
+#ifdef XORG_7X
+ NULL,
+#endif
+#ifdef PCIACCESS
+ vbox_device_match,
+ VBOXPciProbe
+#endif
+};
+
+/* No options for now */
+static const OptionInfoRec VBOXOptions[] = {
+ { -1, NULL, OPTV_NONE, {0}, FALSE }
+};
+
+#ifndef XORG_7X
+/*
+ * List of symbols from other modules that this module references. This
+ * list is used to tell the loader that it is OK for symbols here to be
+ * unresolved providing that it hasn't been told that they haven't been
+ * told that they are essential via a call to xf86LoaderReqSymbols() or
+ * xf86LoaderReqSymLists(). The purpose is this is to avoid warnings about
+ * unresolved symbols that are not required.
+ */
+static const char *fbSymbols[] = {
+ "fbPictureInit",
+ "fbScreenInit",
+ NULL
+};
+
+static const char *shadowfbSymbols[] = {
+ "ShadowFBInit2",
+ NULL
+};
+
+static const char *ramdacSymbols[] = {
+ "xf86DestroyCursorInfoRec",
+ "xf86InitCursor",
+ "xf86CreateCursorInfoRec",
+ NULL
+};
+
+static const char *vgahwSymbols[] = {
+ "vgaHWFreeHWRec",
+ "vgaHWGetHWRec",
+ "vgaHWGetIOBase",
+ "vgaHWGetIndex",
+ "vgaHWRestore",
+ "vgaHWSave",
+ "vgaHWSetStdFuncs",
+ NULL
+};
+#endif /* !XORG_7X */
+
+/** Resize the virtual framebuffer. */
+static Bool adjustScreenPixmap(ScrnInfoPtr pScrn, int width, int height)
+{
+ ScreenPtr pScreen = xf86ScrnToScreen(pScrn);
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ int adjustedWidth = pScrn->bitsPerPixel == 16 ? (width + 1) & ~1 : width;
+ int cbLine = adjustedWidth * pScrn->bitsPerPixel / 8;
+ PixmapPtr pPixmap;
+
+ TRACE_LOG("width=%d, height=%d\n", width, height);
+ AssertMsg(width >= 0 && height >= 0, ("Invalid negative width (%d) or height (%d)\n", width, height));
+ if (pScreen == NULL) /* Not yet initialised. */
+ return TRUE;
+ pPixmap = pScreen->GetScreenPixmap(pScreen);
+ AssertMsg(pPixmap != NULL, ("Failed to get the screen pixmap.\n"));
+ TRACE_LOG("pPixmap=%p adjustedWidth=%d height=%d pScrn->depth=%d pScrn->bitsPerPixel=%d cbLine=%d pVBox->base=%p pPixmap->drawable.width=%d pPixmap->drawable.height=%d\n",
+ (void *)pPixmap, adjustedWidth, height, pScrn->depth,
+ pScrn->bitsPerPixel, cbLine, pVBox->base,
+ pPixmap->drawable.width, pPixmap->drawable.height);
+ if ( adjustedWidth != pPixmap->drawable.width
+ || height != pPixmap->drawable.height)
+ {
+ if ( adjustedWidth > VBOX_VIDEO_MAX_VIRTUAL || height > VBOX_VIDEO_MAX_VIRTUAL
+ || (unsigned)cbLine * (unsigned)height >= pVBox->cbFBMax)
+ {
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
+ "Virtual framebuffer %dx%d too large. For information, video memory: %u Kb.\n",
+ adjustedWidth, height, (unsigned) pVBox->cbFBMax / 1024);
+ return FALSE;
+ }
+ if (pScrn->vtSema)
+ vbvxClearVRAM(pScrn, ((size_t)pScrn->virtualX) * pScrn->virtualY * (pScrn->bitsPerPixel / 8),
+ ((size_t)adjustedWidth) * height * (pScrn->bitsPerPixel / 8));
+ pScreen->ModifyPixmapHeader(pPixmap, adjustedWidth, height, pScrn->depth, pScrn->bitsPerPixel, cbLine, pVBox->base);
+ }
+ pScrn->displayWidth = pScrn->virtualX = adjustedWidth;
+ pScrn->virtualY = height;
+ return TRUE;
+}
+
+#ifndef VBOXVIDEO_13
+/** Set a video mode to the hardware, RandR 1.1 version.
+ *
+ * Since we no longer do virtual frame buffers, adjust the screen pixmap
+ * dimensions to match. The "override" parameters are for when we received a
+ * mode hint while switched to a virtual terminal. In this case VBoxClient will
+ * have told us about the mode, but not yet been able to do a mode switch using
+ * RandR. We solve this by setting the requested mode to the host but keeping
+ * the virtual frame-
+ * buffer matching what the X server expects. */
+static void setModeRandR11(ScrnInfoPtr pScrn, DisplayModePtr pMode, Bool fScreenInitTime, Bool fEnterVTTime,
+ int cXOverRide, int cYOverRide)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ struct vbvxFrameBuffer frameBuffer = { 0, 0, pMode->HDisplay, pMode->VDisplay, pScrn->bitsPerPixel};
+ int cXPhysical = cXOverRide > 0 ? min(cXOverRide, pMode->HDisplay) : pMode->HDisplay;
+ int cYPhysical = cYOverRide > 0 ? min(cYOverRide, pMode->VDisplay) : pMode->VDisplay;
+
+ pVBox->pScreens[0].aScreenLocation.cx = pMode->HDisplay;
+ pVBox->pScreens[0].aScreenLocation.cy = pMode->VDisplay;
+ if (fScreenInitTime)
+ {
+ /* The screen structure is not fully set up yet, so do not touch it. */
+ pScrn->displayWidth = pScrn->virtualX = pMode->HDisplay;
+ pScrn->virtualY = pMode->VDisplay;
+ }
+ else
+ {
+ xf86ScrnToScreen(pScrn)->width = pMode->HDisplay;
+ xf86ScrnToScreen(pScrn)->height = pMode->VDisplay;
+ /* This prevents a crash in CentOS 3. I was unable to debug it to
+ * satisfaction, partly due to the lack of symbols. My guess is that
+ * pScrn->ModifyPixmapHeader() expects certain things to be set up when
+ * it sees pScrn->vtSema set to true which are not quite done at this
+ * point of the VT switch. */
+ if (fEnterVTTime)
+ pScrn->vtSema = FALSE;
+ adjustScreenPixmap(pScrn, pMode->HDisplay, pMode->VDisplay);
+ if (fEnterVTTime)
+ pScrn->vtSema = TRUE;
+ }
+ if (pMode->HDisplay != 0 && pMode->VDisplay != 0 && pScrn->vtSema)
+ vbvxSetMode(pScrn, 0, cXPhysical, cYPhysical, 0, 0, true, true, &frameBuffer);
+ pScrn->currentMode = pMode;
+}
+#endif
+
+#ifdef VBOXVIDEO_13
+/* X.org 1.3+ mode-setting support ******************************************/
+
+/** Set a video mode to the hardware, RandR 1.2 version. If this is the first
+ * screen, re-set the current mode for all others (the offset for the first
+ * screen is always treated as zero by the hardware, so all other screens need
+ * to be changed to compensate for any changes!). The mode to set is taken
+ * from the X.Org Crtc structure. */
+static void setModeRandR12(ScrnInfoPtr pScrn, unsigned cScreen)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ unsigned i;
+ struct vbvxFrameBuffer frameBuffer = { pVBox->pScreens[0].paCrtcs->x, pVBox->pScreens[0].paCrtcs->y, pScrn->virtualX,
+ pScrn->virtualY, pScrn->bitsPerPixel };
+ unsigned cFirst = cScreen;
+ unsigned cLast = cScreen != 0 ? cScreen + 1 : pVBox->cScreens;
+ int originalX, originalY;
+
+ /* Check that this code cannot trigger the resizing bug in X.Org Server 1.3.
+ * See the work-around in ScreenInit. */
+ xf86RandR12GetOriginalVirtualSize(pScrn, &originalX, &originalY);
+ AssertMsg(originalX == VBOX_VIDEO_MAX_VIRTUAL && originalY == VBOX_VIDEO_MAX_VIRTUAL, ("OriginalSize=%dx%d",
+ originalX, originalY));
+ for (i = cFirst; i < cLast; ++i)
+ if (pVBox->pScreens[i].paCrtcs->mode.HDisplay != 0 && pVBox->pScreens[i].paCrtcs->mode.VDisplay != 0 && pScrn->vtSema)
+ vbvxSetMode(pScrn, i, pVBox->pScreens[i].paCrtcs->mode.HDisplay, pVBox->pScreens[i].paCrtcs->mode.VDisplay,
+ pVBox->pScreens[i].paCrtcs->x, pVBox->pScreens[i].paCrtcs->y, pVBox->pScreens[i].fPowerOn,
+ pVBox->pScreens[i].paOutputs->status == XF86OutputStatusConnected, &frameBuffer);
+}
+
+/** Wrapper around setModeRandR12() to avoid exposing non-obvious semantics.
+ */
+static void setAllModesRandR12(ScrnInfoPtr pScrn)
+{
+ setModeRandR12(pScrn, 0);
+}
+
+/* For descriptions of these functions and structures, see
+ hw/xfree86/modes/xf86Crtc.h and hw/xfree86/modes/xf86Modes.h in the
+ X.Org source tree. */
+
+static Bool vbox_config_resize(ScrnInfoPtr pScrn, int cw, int ch)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ Bool rc;
+ unsigned i;
+
+ TRACE_LOG("width=%d, height=%d\n", cw, ch);
+ rc = adjustScreenPixmap(pScrn, cw, ch);
+ /* Power-on all screens (the server expects this) and set the new pitch to them. */
+ for (i = 0; i < pVBox->cScreens; ++i)
+ pVBox->pScreens[i].fPowerOn = true;
+ setAllModesRandR12(pScrn);
+ vbvxSetSolarisMouseRange(cw, ch);
+ return rc;
+}
+
+static const xf86CrtcConfigFuncsRec VBOXCrtcConfigFuncs = {
+ vbox_config_resize
+};
+
+static void
+vbox_crtc_dpms(xf86CrtcPtr crtc, int mode)
+{
+ ScrnInfoPtr pScrn = crtc->scrn;
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ unsigned cDisplay = (uintptr_t)crtc->driver_private;
+
+ TRACE_LOG("mode=%d\n", mode);
+ pVBox->pScreens[cDisplay].fPowerOn = (mode != DPMSModeOff);
+ setModeRandR12(pScrn, cDisplay);
+}
+
+static Bool
+vbox_crtc_lock (xf86CrtcPtr crtc)
+{ RT_NOREF(crtc); return FALSE; }
+
+
+/* We use this function to check whether the X server owns the active virtual
+ * terminal before attempting a mode switch, since the RandR extension isn't
+ * very dilligent here, which can mean crashes if we are unlucky. This is
+ * not the way it the function is intended - it is meant for reporting modes
+ * which the hardware can't handle. I hope that this won't confuse any clients
+ * connecting to us. */
+static Bool
+vbox_crtc_mode_fixup (xf86CrtcPtr crtc, DisplayModePtr mode,
+ DisplayModePtr adjusted_mode)
+{ RT_NOREF(crtc, mode, adjusted_mode); return TRUE; }
+
+static void
+vbox_crtc_stub (xf86CrtcPtr crtc)
+{ RT_NOREF(crtc); }
+
+static void
+vbox_crtc_mode_set (xf86CrtcPtr crtc, DisplayModePtr mode,
+ DisplayModePtr adjusted_mode, int x, int y)
+{
+ RT_NOREF(mode);
+ VBOXPtr pVBox = VBOXGetRec(crtc->scrn);
+ unsigned cDisplay = (uintptr_t)crtc->driver_private;
+
+ TRACE_LOG("name=%s, HDisplay=%d, VDisplay=%d, x=%d, y=%d\n", adjusted_mode->name,
+ adjusted_mode->HDisplay, adjusted_mode->VDisplay, x, y);
+ pVBox->pScreens[cDisplay].fPowerOn = true;
+ pVBox->pScreens[cDisplay].aScreenLocation.cx = adjusted_mode->HDisplay;
+ pVBox->pScreens[cDisplay].aScreenLocation.cy = adjusted_mode->VDisplay;
+ pVBox->pScreens[cDisplay].aScreenLocation.x = x;
+ pVBox->pScreens[cDisplay].aScreenLocation.y = y;
+ setModeRandR12(crtc->scrn, cDisplay);
+}
+
+static void
+vbox_crtc_gamma_set (xf86CrtcPtr crtc, CARD16 *red,
+ CARD16 *green, CARD16 *blue, int size)
+{ RT_NOREF(crtc, red, green, blue, size); }
+
+static void *
+vbox_crtc_shadow_allocate (xf86CrtcPtr crtc, int width, int height)
+{ RT_NOREF(crtc, width, height); return NULL; }
+
+static const xf86CrtcFuncsRec VBOXCrtcFuncs = {
+ .dpms = vbox_crtc_dpms,
+ .save = NULL, /* These two are never called by the server. */
+ .restore = NULL,
+ .lock = vbox_crtc_lock,
+ .unlock = NULL, /* This will not be invoked if lock returns FALSE. */
+ .mode_fixup = vbox_crtc_mode_fixup,
+ .prepare = vbox_crtc_stub,
+ .mode_set = vbox_crtc_mode_set,
+ .commit = vbox_crtc_stub,
+ .gamma_set = vbox_crtc_gamma_set,
+ .shadow_allocate = vbox_crtc_shadow_allocate,
+ .shadow_create = NULL, /* These two should not be invoked if allocate
+ returns NULL. */
+ .shadow_destroy = NULL,
+ .set_cursor_colors = NULL, /* We are still using the old cursor API. */
+ .set_cursor_position = NULL,
+ .show_cursor = NULL,
+ .hide_cursor = NULL,
+ .load_cursor_argb = NULL,
+ .destroy = vbox_crtc_stub
+};
+
+static void
+vbox_output_stub (xf86OutputPtr output)
+{ RT_NOREF(output); }
+
+static void
+vbox_output_dpms (xf86OutputPtr output, int mode)
+{
+ RT_NOREF(output, mode);
+}
+
+static int
+vbox_output_mode_valid (xf86OutputPtr output, DisplayModePtr mode)
+{
+ return MODE_OK;
+}
+
+static Bool
+vbox_output_mode_fixup (xf86OutputPtr output, DisplayModePtr mode,
+ DisplayModePtr adjusted_mode)
+{ RT_NOREF(output, mode, adjusted_mode); return TRUE; }
+
+static void
+vbox_output_mode_set (xf86OutputPtr output, DisplayModePtr mode,
+ DisplayModePtr adjusted_mode)
+{ RT_NOREF(output, mode, adjusted_mode); }
+
+static xf86OutputStatus
+vbox_output_detect (xf86OutputPtr output)
+{
+ ScrnInfoPtr pScrn = output->scrn;
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ uint32_t iScreen = (uintptr_t)output->driver_private;
+ return pVBox->pScreens[iScreen].afConnected
+ ? XF86OutputStatusConnected : XF86OutputStatusDisconnected;
+}
+
+static DisplayModePtr vbox_output_add_mode(VBOXPtr pVBox, DisplayModePtr *pModes, const char *pszName, int x, int y,
+ Bool isPreferred, Bool isUserDef)
+{
+ TRACE_LOG("pszName=%s, x=%d, y=%d\n", pszName ? pszName : "(null)", x, y);
+ DisplayModePtr pMode = xnfcalloc(1, sizeof(DisplayModeRec));
+ int cRefresh = 60;
+
+ pMode->status = MODE_OK;
+ /* We don't ask the host whether it likes user defined modes,
+ * as we assume that the user really wanted that mode. */
+ pMode->type = isUserDef ? M_T_USERDEF : M_T_BUILTIN;
+ if (isPreferred)
+ pMode->type |= M_T_PREFERRED;
+ /* Older versions of VBox only support screen widths which are a multiple
+ * of 8 */
+ if (pVBox->fAnyX)
+ pMode->HDisplay = x;
+ else
+ pMode->HDisplay = x & ~7;
+ pMode->HSyncStart = pMode->HDisplay + 2;
+ pMode->HSyncEnd = pMode->HDisplay + 4;
+ pMode->HTotal = pMode->HDisplay + 6;
+ pMode->VDisplay = y;
+ pMode->VSyncStart = pMode->VDisplay + 2;
+ pMode->VSyncEnd = pMode->VDisplay + 4;
+ pMode->VTotal = pMode->VDisplay + 6;
+ pMode->Clock = pMode->HTotal * pMode->VTotal * cRefresh / 1000; /* kHz */
+ if (NULL == pszName) {
+ xf86SetModeDefaultName(pMode);
+ } else {
+ pMode->name = xnfstrdup(pszName);
+ }
+ *pModes = xf86ModesAdd(*pModes, pMode);
+ return pMode;
+}
+
+static DisplayModePtr
+vbox_output_get_modes (xf86OutputPtr output)
+{
+ DisplayModePtr pModes = NULL;
+ DisplayModePtr pPreferred = NULL;
+ ScrnInfoPtr pScrn = output->scrn;
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+
+ TRACE_ENTRY();
+ uint32_t iScreen = (uintptr_t)output->driver_private;
+ pPreferred = vbox_output_add_mode(pVBox, &pModes, NULL,
+ RT_CLAMP(pVBox->pScreens[iScreen].aPreferredSize.cx, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL),
+ RT_CLAMP(pVBox->pScreens[iScreen].aPreferredSize.cy, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL),
+ TRUE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 2560, 1600, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 2560, 1440, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 2048, 1536, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 1920, 1600, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 1920, 1080, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 1680, 1050, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 1600, 1200, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 1400, 1050, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 1280, 1024, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 1024, 768, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 800, 600, FALSE, FALSE);
+ vbox_output_add_mode(pVBox, &pModes, NULL, 640, 480, FALSE, FALSE);
+ VBOXEDIDSet(output, pPreferred);
+ TRACE_EXIT();
+ return pModes;
+}
+
+static const xf86OutputFuncsRec VBOXOutputFuncs = {
+ .create_resources = vbox_output_stub,
+ .dpms = vbox_output_dpms,
+ .save = NULL, /* These two are never called by the server. */
+ .restore = NULL,
+ .mode_valid = vbox_output_mode_valid,
+ .mode_fixup = vbox_output_mode_fixup,
+ .prepare = vbox_output_stub,
+ .commit = vbox_output_stub,
+ .mode_set = vbox_output_mode_set,
+ .detect = vbox_output_detect,
+ .get_modes = vbox_output_get_modes,
+#ifdef RANDR_12_INTERFACE
+ .set_property = NULL,
+#endif
+ .destroy = vbox_output_stub
+};
+#endif /* VBOXVIDEO_13 */
+
+/* Module loader interface */
+static MODULESETUPPROTO(vboxSetup);
+
+static XF86ModuleVersionInfo vboxVersionRec =
+{
+ VBOX_DRIVER_NAME,
+ "Oracle Corporation",
+ MODINFOSTRING1,
+ MODINFOSTRING2,
+#ifdef XORG_7X
+ XORG_VERSION_CURRENT,
+#else
+ XF86_VERSION_CURRENT,
+#endif
+ 1, /* Module major version. Xorg-specific */
+ 0, /* Module minor version. Xorg-specific */
+ 1, /* Module patchlevel. Xorg-specific */
+ ABI_CLASS_VIDEODRV, /* This is a video driver */
+ ABI_VIDEODRV_VERSION,
+ MOD_CLASS_VIDEODRV,
+ {0, 0, 0, 0}
+};
+
+/*
+ * This data is accessed by the loader. The name must be the module name
+ * followed by "ModuleData".
+ */
+#ifdef XORG_7X
+_X_EXPORT
+#endif
+XF86ModuleData vboxvideoModuleData = { &vboxVersionRec, vboxSetup, NULL };
+
+static pointer
+vboxSetup(pointer Module, pointer Options, int *ErrorMajor, int *ErrorMinor)
+{
+ static Bool Initialised = FALSE;
+ RT_NOREF(Options, ErrorMinor);
+
+ if (!Initialised)
+ {
+ Initialised = TRUE;
+#ifdef PCIACCESS
+ xf86AddDriver(&VBOXVIDEO, Module, HaveDriverFuncs);
+#else
+ xf86AddDriver(&VBOXVIDEO, Module, 0);
+#endif
+#ifndef XORG_7X
+ LoaderRefSymLists(fbSymbols,
+ shadowfbSymbols,
+ ramdacSymbols,
+ vgahwSymbols,
+ NULL);
+#endif
+ xf86Msg(X_CONFIG, "Load address of symbol \"VBOXVIDEO\" is %p\n",
+ (void *)&VBOXVIDEO);
+ return (pointer)TRUE;
+ }
+
+ if (ErrorMajor)
+ *ErrorMajor = LDR_ONCEONLY;
+ return (NULL);
+}
+
+
+static const OptionInfoRec *
+VBOXAvailableOptions(int chipid, int busid)
+{
+ RT_NOREF(chipid, busid);
+ return (VBOXOptions);
+}
+
+static void
+VBOXIdentify(int flags)
+{
+ RT_NOREF(flags);
+ xf86PrintChipsets(VBOX_NAME, "guest driver for VirtualBox", VBOXChipsets);
+}
+
+#ifndef XF86_SCRN_INTERFACE
+# define SCRNINDEXAPI(pfn) pfn ## Index
+static Bool VBOXScreenInitIndex(int scrnIndex, ScreenPtr pScreen, int argc, char **argv)
+{
+ RT_NOREF(scrnIndex);
+ return VBOXScreenInit(pScreen, argc, argv);
+}
+
+static Bool VBOXEnterVTIndex(int scrnIndex, int flags)
+{ RT_NOREF(flags); return VBOXEnterVT(xf86Screens[scrnIndex]); }
+
+static void VBOXLeaveVTIndex(int scrnIndex, int flags)
+{ RT_NOREF(flags); VBOXLeaveVT(xf86Screens[scrnIndex]); }
+
+static Bool VBOXCloseScreenIndex(int scrnIndex, ScreenPtr pScreen)
+{ RT_NOREF(scrnIndex); return VBOXCloseScreen(pScreen); }
+
+static Bool VBOXSwitchModeIndex(int scrnIndex, DisplayModePtr pMode, int flags)
+{ RT_NOREF(flags); return VBOXSwitchMode(xf86Screens[scrnIndex], pMode); }
+
+static void VBOXAdjustFrameIndex(int scrnIndex, int x, int y, int flags)
+{ RT_NOREF(flags); VBOXAdjustFrame(xf86Screens[scrnIndex], x, y); }
+
+static void VBOXFreeScreenIndex(int scrnIndex, int flags)
+{ RT_NOREF(flags); VBOXFreeScreen(xf86Screens[scrnIndex]); }
+# else
+# define SCRNINDEXAPI(pfn) pfn
+#endif /* XF86_SCRN_INTERFACE */
+
+static void setScreenFunctions(ScrnInfoPtr pScrn, xf86ProbeProc pfnProbe)
+{
+ pScrn->driverVersion = VBOX_VERSION;
+ pScrn->driverName = VBOX_DRIVER_NAME;
+ pScrn->name = VBOX_NAME;
+ pScrn->Probe = pfnProbe;
+ pScrn->PreInit = VBOXPreInit;
+ pScrn->ScreenInit = SCRNINDEXAPI(VBOXScreenInit);
+ pScrn->SwitchMode = SCRNINDEXAPI(VBOXSwitchMode);
+ pScrn->AdjustFrame = SCRNINDEXAPI(VBOXAdjustFrame);
+ pScrn->EnterVT = SCRNINDEXAPI(VBOXEnterVT);
+ pScrn->LeaveVT = SCRNINDEXAPI(VBOXLeaveVT);
+ pScrn->FreeScreen = SCRNINDEXAPI(VBOXFreeScreen);
+}
+
+/*
+ * One of these functions is called once, at the start of the first server
+ * generation to do a minimal probe for supported hardware.
+ */
+
+#ifdef PCIACCESS
+static Bool
+VBOXPciProbe(DriverPtr drv, int entity_num, struct pci_device *dev,
+ intptr_t match_data)
+{
+ ScrnInfoPtr pScrn;
+ int drmFd;
+
+ TRACE_ENTRY();
+
+ drmFd = open("/dev/dri/card0", O_RDWR, 0);
+ if (drmFd >= 0)
+ {
+ xf86Msg(X_INFO, "vboxvideo: kernel driver found, not loading.\n");
+ close(drmFd);
+ return FALSE;
+ }
+ /* It is safe to call this, as the X server enables I/O access before
+ * calling the probe call-backs. */
+ if (!xf86EnableIO())
+ {
+ xf86Msg(X_INFO, "vboxvideo: this driver requires direct hardware access. You may wish to use the kernel driver instead.\n");
+ return FALSE;
+ }
+ pScrn = xf86ConfigPciEntity(NULL, 0, entity_num, VBOXPCIchipsets,
+ NULL, NULL, NULL, NULL, NULL);
+ if (pScrn != NULL) {
+ VBOXPtr pVBox;
+
+ VBOXSetRec(pScrn);
+ pVBox = VBOXGetRec(pScrn);
+ if (!pVBox)
+ return FALSE;
+ setScreenFunctions(pScrn, NULL);
+ pVBox->pciInfo = dev;
+ }
+
+ TRACE_LOG("returning %s\n", pScrn == NULL ? "false" : "true");
+ return (pScrn != NULL);
+}
+#endif
+
+#ifndef PCIACCESS
+static Bool
+VBOXProbe(DriverPtr drv, int flags)
+{
+ Bool foundScreen = FALSE;
+ int numDevSections;
+ GDevPtr *devSections;
+
+ /*
+ * Find the config file Device sections that match this
+ * driver, and return if there are none.
+ */
+ if ((numDevSections = xf86MatchDevice(VBOX_NAME,
+ &devSections)) <= 0)
+ return (FALSE);
+
+ /* PCI BUS */
+ if (xf86GetPciVideoInfo())
+ {
+ int numUsed;
+ int *usedChips;
+ int i;
+ numUsed = xf86MatchPciInstances(VBOX_NAME, VBOX_VENDORID,
+ VBOXChipsets, VBOXPCIchipsets,
+ devSections, numDevSections,
+ drv, &usedChips);
+ if (numUsed > 0)
+ {
+ if (flags & PROBE_DETECT)
+ foundScreen = TRUE;
+ else
+ for (i = 0; i < numUsed; i++)
+ {
+ ScrnInfoPtr pScrn = NULL;
+ /* Allocate a ScrnInfoRec */
+ if ((pScrn = xf86ConfigPciEntity(pScrn,0,usedChips[i],
+ VBOXPCIchipsets,NULL,
+ NULL,NULL,NULL,NULL)))
+ {
+ setScreenFunctions(pScrn, VBOXProbe);
+ foundScreen = TRUE;
+ }
+ }
+ free(usedChips);
+ }
+ }
+ free(devSections);
+ return (foundScreen);
+}
+#endif
+
+
+/*
+ * QUOTE from the XFree86 DESIGN document:
+ *
+ * The purpose of this function is to find out all the information
+ * required to determine if the configuration is usable, and to initialise
+ * those parts of the ScrnInfoRec that can be set once at the beginning of
+ * the first server generation.
+ *
+ * (...)
+ *
+ * This includes probing for video memory, clocks, ramdac, and all other
+ * HW info that is needed. It includes determining the depth/bpp/visual
+ * and related info. It includes validating and determining the set of
+ * video modes that will be used (and anything that is required to
+ * determine that).
+ *
+ * This information should be determined in the least intrusive way
+ * possible. The state of the HW must remain unchanged by this function.
+ * Although video memory (including MMIO) may be mapped within this
+ * function, it must be unmapped before returning.
+ *
+ * END QUOTE
+ */
+
+static Bool
+VBOXPreInit(ScrnInfoPtr pScrn, int flags)
+{
+ VBOXPtr pVBox;
+ Gamma gzeros = {0.0, 0.0, 0.0};
+ rgb rzeros = {0, 0, 0};
+
+ TRACE_ENTRY();
+ /* Are we really starting the server, or is this just a dummy run? */
+ if (flags & PROBE_DETECT)
+ return (FALSE);
+
+ xf86DrvMsg(pScrn->scrnIndex, X_INFO,
+ "VirtualBox guest additions video driver version %d.%d\n",
+ VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR);
+
+ /* The ramdac module is needed for the hardware cursor. */
+ if (!xf86LoadSubModule(pScrn, "ramdac"))
+ return FALSE;
+
+ /* The framebuffer module. */
+ if (!xf86LoadSubModule(pScrn, "fb"))
+ return (FALSE);
+
+ if (!xf86LoadSubModule(pScrn, "shadowfb"))
+ return FALSE;
+
+ if (!xf86LoadSubModule(pScrn, "vgahw"))
+ return FALSE;
+
+ /* Get our private data from the ScrnInfoRec structure. */
+ VBOXSetRec(pScrn);
+ pVBox = VBOXGetRec(pScrn);
+ if (!pVBox)
+ return FALSE;
+
+ /* Entity information seems to mean bus information. */
+ pVBox->pEnt = xf86GetEntityInfo(pScrn->entityList[0]);
+
+#ifndef PCIACCESS
+ if (pVBox->pEnt->location.type != BUS_PCI)
+ return FALSE;
+
+ pVBox->pciInfo = xf86GetPciInfoForEntity(pVBox->pEnt->index);
+ pVBox->pciTag = pciTag(pVBox->pciInfo->bus,
+ pVBox->pciInfo->device,
+ pVBox->pciInfo->func);
+#endif
+
+ /* Set up our ScrnInfoRec structure to describe our virtual
+ capabilities to X. */
+
+ pScrn->chipset = "vbox";
+ /** @note needed during colourmap initialisation */
+ pScrn->rgbBits = 8;
+
+ /* Let's create a nice, capable virtual monitor. */
+ pScrn->monitor = pScrn->confScreen->monitor;
+ pScrn->monitor->DDC = NULL;
+ pScrn->monitor->nHsync = 1;
+ pScrn->monitor->hsync[0].lo = 1;
+ pScrn->monitor->hsync[0].hi = 10000;
+ pScrn->monitor->nVrefresh = 1;
+ pScrn->monitor->vrefresh[0].lo = 1;
+ pScrn->monitor->vrefresh[0].hi = 100;
+
+ pScrn->progClock = TRUE;
+
+ /* Using the PCI information caused problems with non-powers-of-two
+ sized video RAM configurations */
+ pVBox->cbFBMax = VBoxVideoGetVRAMSize();
+ pScrn->videoRam = pVBox->cbFBMax / 1024;
+
+ /* Check if the chip restricts horizontal resolution or not. */
+ pVBox->fAnyX = VBoxVideoAnyWidthAllowed();
+
+ /* Set up clock information that will support all modes we need. */
+ pScrn->clockRanges = xnfcalloc(sizeof(ClockRange), 1);
+ pScrn->clockRanges->minClock = 1000;
+ pScrn->clockRanges->maxClock = 1000000000;
+ pScrn->clockRanges->clockIndex = -1;
+ pScrn->clockRanges->ClockMulFactor = 1;
+ pScrn->clockRanges->ClockDivFactor = 1;
+
+ if (!xf86SetDepthBpp(pScrn, 24, 0, 0, Support32bppFb))
+ return FALSE;
+ /* We only support 16 and 24 bits depth (i.e. 16 and 32bpp) */
+ if (pScrn->bitsPerPixel != 32 && pScrn->bitsPerPixel != 16)
+ {
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
+ "The VBox additions only support 16 and 32bpp graphics modes\n");
+ return FALSE;
+ }
+ xf86PrintDepthBpp(pScrn);
+ vboxAddModes(pScrn);
+
+#ifdef VBOXVIDEO_13
+ pScrn->virtualX = VBOX_VIDEO_MAX_VIRTUAL;
+ pScrn->virtualY = VBOX_VIDEO_MAX_VIRTUAL;
+#else
+ /* We don't validate with xf86ValidateModes and xf86PruneModes as we
+ * already know what we like and what we don't. */
+
+ pScrn->currentMode = pScrn->modes;
+
+ /* Set the right virtual resolution. */
+ pScrn->virtualX = pScrn->bitsPerPixel == 16 ? (pScrn->currentMode->HDisplay + 1) & ~1 : pScrn->currentMode->HDisplay;
+ pScrn->virtualY = pScrn->currentMode->VDisplay;
+
+#endif /* !VBOXVIDEO_13 */
+
+ pScrn->displayWidth = pScrn->virtualX;
+
+ xf86PrintModes(pScrn);
+
+ /* VGA hardware initialisation */
+ if (!vgaHWGetHWRec(pScrn))
+ return FALSE;
+ /* Must be called before any VGA registers are saved or restored */
+ vgaHWSetStdFuncs(VGAHWPTR(pScrn));
+ vgaHWGetIOBase(VGAHWPTR(pScrn));
+
+ /* Colour weight - we always call this, since we are always in
+ truecolour. */
+ if (!xf86SetWeight(pScrn, rzeros, rzeros))
+ return (FALSE);
+
+ /* visual init */
+ if (!xf86SetDefaultVisual(pScrn, -1))
+ return (FALSE);
+
+ xf86SetGamma(pScrn, gzeros);
+
+ /* Set the DPI. Perhaps we should read this from the host? */
+ xf86SetDpi(pScrn, 96, 96);
+
+ if (pScrn->memPhysBase == 0) {
+#ifdef PCIACCESS
+ pScrn->memPhysBase = pVBox->pciInfo->regions[0].base_addr;
+#else
+ pScrn->memPhysBase = pVBox->pciInfo->memBase[0];
+#endif
+ pScrn->fbOffset = 0;
+ }
+
+ TRACE_EXIT();
+ return (TRUE);
+}
+
+/**
+ * Dummy function for setting the colour palette, which we actually never
+ * touch. However, the server still requires us to provide this.
+ */
+static void
+vboxLoadPalette(ScrnInfoPtr pScrn, int numColors, int *indices,
+ LOCO *colors, VisualPtr pVisual)
+{
+ RT_NOREF(pScrn, numColors, indices, colors, pVisual);
+}
+
+/** Set the graphics and guest cursor support capabilities to the host if
+ * the user-space helper is running. */
+static void updateGraphicsCapability(ScrnInfoPtr pScrn, Bool hasVT)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+
+ if (!pVBox->fHaveHGSMIModeHints)
+ return;
+ VBoxHGSMISendCapsInfo(&pVBox->guestCtx, hasVT
+ ? VBVACAPS_VIDEO_MODE_HINTS | VBVACAPS_DISABLE_CURSOR_INTEGRATION
+ : VBVACAPS_DISABLE_CURSOR_INTEGRATION);
+}
+
+#ifndef VBOXVIDEO_13
+
+#define PREFERRED_MODE_ATOM_NAME "VBOXVIDEO_PREFERRED_MODE"
+
+static void setSizesRandR11(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ DisplayModePtr pNewMode;
+ int32_t propertyValue;
+
+ pNewMode = pScrn->modes != pScrn->currentMode ? pScrn->modes : pScrn->modes->next;
+ pNewMode->HDisplay = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cx, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL);
+ pNewMode->VDisplay = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cy, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL);
+ propertyValue = (pNewMode->HDisplay << 16) + pNewMode->VDisplay;
+ ChangeWindowProperty(ROOT_WINDOW(pScrn), MakeAtom(PREFERRED_MODE_ATOM_NAME,
+ sizeof(PREFERRED_MODE_ATOM_NAME) - 1, TRUE), XA_INTEGER, 32,
+ PropModeReplace, 1, &propertyValue, TRUE);
+}
+
+#endif
+
+static void reprobeCursor(ScrnInfoPtr pScrn)
+{
+ if (ROOT_WINDOW(pScrn) == NULL)
+ return;
+#ifdef XF86_SCRN_INTERFACE
+ pScrn->EnableDisableFBAccess(pScrn, FALSE);
+ pScrn->EnableDisableFBAccess(pScrn, TRUE);
+#else
+ pScrn->EnableDisableFBAccess(pScrn->scrnIndex, FALSE);
+ pScrn->EnableDisableFBAccess(pScrn->scrnIndex, TRUE);
+#endif
+}
+
+static void setSizesAndCursorIntegration(ScrnInfoPtr pScrn, Bool fScreenInitTime)
+{
+ RT_NOREF(fScreenInitTime);
+ TRACE_LOG("fScreenInitTime=%d\n", (int)fScreenInitTime);
+#ifdef VBOXVIDEO_13
+# if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) >= 5
+ RRGetInfo(xf86ScrnToScreen(pScrn), TRUE);
+# else
+ RRGetInfo(xf86ScrnToScreen(pScrn));
+# endif
+#else
+ setSizesRandR11(pScrn);
+#endif
+ /* This calls EnableDisableFBAccess(), so only use when switched in. */
+ if (pScrn->vtSema)
+ reprobeCursor(pScrn);
+}
+
+/* We update the size hints from the X11 property set by VBoxClient every time
+ * that the X server goes to sleep (to catch the property change request).
+ * Although this is far more often than necessary it should not have real-life
+ * performance consequences and allows us to simplify the code quite a bit. */
+static void vboxBlockHandler(pointer pData,
+#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 23
+ OSTimePtr pTimeout,
+ pointer pReadmask
+#else
+ void *pTimeout
+#endif
+ )
+{
+ ScrnInfoPtr pScrn = (ScrnInfoPtr)pData;
+ Bool fNeedUpdate = false;
+
+ RT_NOREF(pTimeout);
+#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 23
+ RT_NOREF(pReadmask);
+#endif
+ if (pScrn->vtSema)
+ vbvxReadSizesAndCursorIntegrationFromHGSMI(pScrn, &fNeedUpdate);
+ if (fNeedUpdate)
+ setSizesAndCursorIntegration(pScrn, false);
+}
+
+/*
+ * QUOTE from the XFree86 DESIGN document:
+ *
+ * This is called at the start of each server generation.
+ *
+ * (...)
+ *
+ * Decide which operations need to be placed under resource access
+ * control. (...) Map any video memory or other memory regions. (...)
+ * Save the video card state. (...) Initialise the initial video
+ * mode.
+ *
+ * End QUOTE.
+ */
+static Bool VBOXScreenInit(ScreenPtr pScreen, int argc, char **argv)
+{
+ ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ VisualPtr visual;
+ RT_NOREF(argc, argv);
+
+ TRACE_ENTRY();
+
+ if (!VBOXMapVidMem(pScrn))
+ return (FALSE);
+
+ /* save current video state */
+ VBOXSaveMode(pScrn);
+
+ /* mi layer - reset the visual list (?)*/
+ miClearVisualTypes();
+ if (!miSetVisualTypes(pScrn->depth, TrueColorMask,
+ pScrn->rgbBits, TrueColor))
+ return (FALSE);
+ if (!miSetPixmapDepths())
+ return (FALSE);
+
+ if (!fbScreenInit(pScreen, pVBox->base,
+ pScrn->virtualX, pScrn->virtualY,
+ pScrn->xDpi, pScrn->yDpi,
+ pScrn->displayWidth, pScrn->bitsPerPixel))
+ return (FALSE);
+
+ /* Fixup RGB ordering */
+ /** @note the X server uses this even in true colour. */
+ visual = pScreen->visuals + pScreen->numVisuals;
+ while (--visual >= pScreen->visuals) {
+ if ((visual->class | DynamicClass) == DirectColor) {
+ visual->offsetRed = pScrn->offset.red;
+ visual->offsetGreen = pScrn->offset.green;
+ visual->offsetBlue = pScrn->offset.blue;
+ visual->redMask = pScrn->mask.red;
+ visual->greenMask = pScrn->mask.green;
+ visual->blueMask = pScrn->mask.blue;
+ }
+ }
+
+ /* must be after RGB ordering fixed */
+ fbPictureInit(pScreen, 0, 0);
+
+ xf86SetBlackWhitePixels(pScreen);
+ pScrn->vtSema = TRUE;
+
+#if defined(VBOXVIDEO_13) && defined(RT_OS_LINUX)
+ vbvxSetUpLinuxACPI(pScreen);
+#endif
+
+ if (!VBoxHGSMIIsSupported())
+ {
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Graphics device too old to support.\n");
+ return FALSE;
+ }
+ vbvxSetUpHGSMIHeapInGuest(pVBox, pScrn->videoRam * 1024);
+ pVBox->cScreens = VBoxHGSMIGetMonitorCount(&pVBox->guestCtx);
+ pVBox->pScreens = xnfcalloc(pVBox->cScreens, sizeof(*pVBox->pScreens));
+ pVBox->paVBVAModeHints = xnfcalloc(pVBox->cScreens, sizeof(*pVBox->paVBVAModeHints));
+ xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Requested monitor count: %u\n", pVBox->cScreens);
+ vboxEnableVbva(pScrn);
+ /* Set up the dirty rectangle handler. It will be added into a function
+ * chain and gets removed when the screen is cleaned up. */
+ if (ShadowFBInit2(pScreen, NULL, vbvxHandleDirtyRect) != TRUE)
+ return FALSE;
+ VBoxInitialiseSizeHints(pScrn);
+
+#ifdef VBOXVIDEO_13
+ /* Initialise CRTC and output configuration for use with randr1.2. */
+ xf86CrtcConfigInit(pScrn, &VBOXCrtcConfigFuncs);
+
+ {
+ uint32_t i;
+
+ for (i = 0; i < pVBox->cScreens; ++i)
+ {
+ char szOutput[256];
+
+ /* Setup our virtual CRTCs. */
+ pVBox->pScreens[i].paCrtcs = xf86CrtcCreate(pScrn, &VBOXCrtcFuncs);
+ pVBox->pScreens[i].paCrtcs->driver_private = (void *)(uintptr_t)i;
+
+ /* Set up our virtual outputs. */
+ snprintf(szOutput, sizeof(szOutput), "VGA-%u", i);
+ pVBox->pScreens[i].paOutputs
+ = xf86OutputCreate(pScrn, &VBOXOutputFuncs, szOutput);
+
+ /* We are not interested in the monitor section in the
+ * configuration file. */
+ xf86OutputUseScreenMonitor(pVBox->pScreens[i].paOutputs, FALSE);
+ pVBox->pScreens[i].paOutputs->possible_crtcs = 1 << i;
+ pVBox->pScreens[i].paOutputs->possible_clones = 0;
+ pVBox->pScreens[i].paOutputs->driver_private = (void *)(uintptr_t)i;
+ TRACE_LOG("Created crtc (%p) and output %s (%p)\n",
+ (void *)pVBox->pScreens[i].paCrtcs, szOutput,
+ (void *)pVBox->pScreens[i].paOutputs);
+ }
+ }
+
+ /* Set a sane minimum and maximum mode size to match what the hardware
+ * supports. */
+ xf86CrtcSetSizeRange(pScrn, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL, VBOX_VIDEO_MAX_VIRTUAL);
+
+ /* Now create our initial CRTC/output configuration. */
+ if (!xf86InitialConfiguration(pScrn, TRUE)) {
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Initial CRTC configuration failed!\n");
+ return (FALSE);
+ }
+
+ /* Work around a bug in the original X server modesetting code, which took
+ * the first valid values set to these two as maxima over the server
+ * lifetime. This bug was introduced on Feb 15 2007 and was fixed in commit
+ * fa877d7f three months later, so it was present in X.Org Server 1.3. */
+ pScrn->virtualX = VBOX_VIDEO_MAX_VIRTUAL;
+ pScrn->virtualY = VBOX_VIDEO_MAX_VIRTUAL;
+
+ /* Initialise randr 1.2 mode-setting functions. */
+ if (!xf86CrtcScreenInit(pScreen)) {
+ return FALSE;
+ }
+
+ /* set first video mode */
+ if (!xf86SetDesiredModes(pScrn)) {
+ return FALSE;
+ }
+#else /* !VBOXVIDEO_13 */
+ /* set first video mode */
+ setModeRandR11(pScrn, pScrn->currentMode, true, false, 0, 0);
+#endif /* !VBOXVIDEO_13 */
+
+ /* Say that we support graphics. */
+ updateGraphicsCapability(pScrn, TRUE);
+
+#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) >= 23
+# define WakeupHandlerProcPtr ServerWakeupHandlerProcPtr
+#endif
+
+ /* Register block and wake-up handlers for getting new screen size hints. */
+ RegisterBlockAndWakeupHandlers(vboxBlockHandler, (WakeupHandlerProcPtr)NoopDDA, (pointer)pScrn);
+
+ /* software cursor */
+ miDCInitialize(pScreen, xf86GetPointerScreenFuncs());
+
+ /* colourmap code */
+ if (!miCreateDefColormap(pScreen))
+ return (FALSE);
+
+ if(!xf86HandleColormaps(pScreen, 256, 8, vboxLoadPalette, NULL, 0))
+ return (FALSE);
+
+ pVBox->CloseScreen = pScreen->CloseScreen;
+ pScreen->CloseScreen = SCRNINDEXAPI(VBOXCloseScreen);
+#ifdef VBOXVIDEO_13
+ pScreen->SaveScreen = xf86SaveScreen;
+#else
+ pScreen->SaveScreen = VBOXSaveScreen;
+#endif
+
+#ifdef VBOXVIDEO_13
+ xf86DPMSInit(pScreen, xf86DPMSSet, 0);
+#else
+ /* We probably do want to support power management - even if we just use
+ a dummy function. */
+ xf86DPMSInit(pScreen, VBOXDisplayPowerManagementSet, 0);
+#endif
+
+ /* Report any unused options (only for the first generation) */
+ if (serverGeneration == 1)
+ xf86ShowUnusedOptions(pScrn->scrnIndex, pScrn->options);
+
+ if (vbvxCursorInit(pScreen) != TRUE)
+ xf86DrvMsg(pScrn->scrnIndex, X_ERROR,
+ "Unable to start the VirtualBox mouse pointer integration with the host system.\n");
+
+ return (TRUE);
+}
+
+#define NO_VT_ATOM_NAME "VBOXVIDEO_NO_VT"
+
+static Bool VBOXEnterVT(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+#ifndef VBOXVIDEO_13
+ /* If we got a mode request while we were switched out, temporarily override
+ * the physical mode set to the device while keeping things consistent from
+ * the server's point of view. */
+ int cXOverRide = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cx, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL);
+ int cYOverRide = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cy, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL);
+#endif
+
+ TRACE_ENTRY();
+ vbvxSetUpHGSMIHeapInGuest(pVBox, pScrn->videoRam * 1024);
+ vboxEnableVbva(pScrn);
+ /* Re-set video mode */
+#ifdef VBOXVIDEO_13
+ if (!xf86SetDesiredModes(pScrn)) {
+ return FALSE;
+ }
+#else
+ setModeRandR11(pScrn, pScrn->currentMode, false, true, cXOverRide, cYOverRide);
+ DeleteProperty(ROOT_WINDOW(pScrn), MakeAtom(NO_VT_ATOM_NAME, sizeof(NO_VT_ATOM_NAME) - 1, TRUE));
+#endif
+ updateGraphicsCapability(pScrn, TRUE);
+ return TRUE;
+}
+
+static void VBOXLeaveVT(ScrnInfoPtr pScrn)
+{
+#ifdef VBOXVIDEO_13
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ unsigned i;
+#else
+ int32_t propertyValue = 0;
+#endif
+
+ TRACE_ENTRY();
+#ifdef VBOXVIDEO_13
+ for (i = 0; i < pVBox->cScreens; ++i)
+ vbox_crtc_dpms(pVBox->pScreens[i].paCrtcs, DPMSModeOff);
+#else
+ ChangeWindowProperty(ROOT_WINDOW(pScrn), MakeAtom(NO_VT_ATOM_NAME, sizeof(NO_VT_ATOM_NAME) - 1, FALSE), XA_INTEGER, 32,
+ PropModeReplace, 1, &propertyValue, TRUE);
+#endif
+ updateGraphicsCapability(pScrn, FALSE);
+ vboxDisableVbva(pScrn);
+ vbvxClearVRAM(pScrn, ((size_t)pScrn->virtualX) * pScrn->virtualY * (pScrn->bitsPerPixel / 8), 0);
+ VBOXRestoreMode(pScrn);
+ TRACE_EXIT();
+}
+
+static Bool VBOXCloseScreen(ScreenPtr pScreen)
+{
+ ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen);
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ BOOL ret;
+
+ if (pScrn->vtSema)
+ {
+#ifdef VBOXVIDEO_13
+ unsigned i;
+
+ for (i = 0; i < pVBox->cScreens; ++i)
+ vbox_crtc_dpms(pVBox->pScreens[i].paCrtcs, DPMSModeOff);
+#endif
+ vboxDisableVbva(pScrn);
+ vbvxClearVRAM(pScrn, ((size_t)pScrn->virtualX) * pScrn->virtualY * (pScrn->bitsPerPixel / 8), 0);
+ }
+ if (pScrn->vtSema)
+ VBOXRestoreMode(pScrn);
+ if (pScrn->vtSema)
+ VBOXUnmapVidMem(pScrn);
+ pScrn->vtSema = FALSE;
+
+ vbvxCursorTerm(pVBox);
+
+ pScreen->CloseScreen = pVBox->CloseScreen;
+#if defined(VBOXVIDEO_13) && defined(RT_OS_LINUX)
+ vbvxCleanUpLinuxACPI(pScreen);
+#endif
+#ifndef XF86_SCRN_INTERFACE
+ ret = pScreen->CloseScreen(pScreen->myNum, pScreen);
+#else
+ ret = pScreen->CloseScreen(pScreen);
+#endif
+ return ret;
+}
+
+static Bool VBOXSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr pMode)
+{
+ Bool rc = TRUE;
+
+ TRACE_LOG("HDisplay=%d, VDisplay=%d\n", pMode->HDisplay, pMode->VDisplay);
+#ifdef VBOXVIDEO_13
+ rc = xf86SetSingleMode(pScrn, pMode, RR_Rotate_0);
+#else
+ setModeRandR11(pScrn, pMode, false, false, 0, 0);
+#endif
+ TRACE_LOG("returning %s\n", rc ? "TRUE" : "FALSE");
+ return rc;
+}
+
+static void VBOXAdjustFrame(ScrnInfoPtr pScrn, int x, int y)
+{ RT_NOREF(pScrn, x, y); }
+
+static void VBOXFreeScreen(ScrnInfoPtr pScrn)
+{
+ /* Destroy the VGA hardware record */
+ vgaHWFreeHWRec(pScrn);
+ /* And our private record */
+ free(pScrn->driverPrivate);
+ pScrn->driverPrivate = NULL;
+}
+
+static Bool
+VBOXMapVidMem(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ Bool rc = TRUE;
+
+ TRACE_ENTRY();
+ if (!pVBox->base)
+ {
+#ifdef PCIACCESS
+ (void) pci_device_map_range(pVBox->pciInfo,
+ pScrn->memPhysBase,
+ pScrn->videoRam * 1024,
+ PCI_DEV_MAP_FLAG_WRITABLE,
+ & pVBox->base);
+#else
+ pVBox->base = xf86MapPciMem(pScrn->scrnIndex,
+ VIDMEM_FRAMEBUFFER,
+ pVBox->pciTag, pScrn->memPhysBase,
+ (unsigned) pScrn->videoRam * 1024);
+#endif
+ if (!pVBox->base)
+ rc = FALSE;
+ }
+ TRACE_LOG("returning %s\n", rc ? "TRUE" : "FALSE");
+ return rc;
+}
+
+static void
+VBOXUnmapVidMem(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+
+ TRACE_ENTRY();
+ if (pVBox->base == NULL)
+ return;
+
+#ifdef PCIACCESS
+ (void) pci_device_unmap_range(pVBox->pciInfo,
+ pVBox->base,
+ pScrn->videoRam * 1024);
+#else
+ xf86UnMapVidMem(pScrn->scrnIndex, pVBox->base,
+ (unsigned) pScrn->videoRam * 1024);
+#endif
+ pVBox->base = NULL;
+ TRACE_EXIT();
+}
+
+#ifndef VBOXVIDEO_13
+static Bool
+VBOXSaveScreen(ScreenPtr pScreen, int mode)
+{
+ RT_NOREF(pScreen, mode);
+ return TRUE;
+}
+#endif
+
+void
+VBOXSaveMode(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ vgaRegPtr vgaReg;
+
+ TRACE_ENTRY();
+ vgaReg = &VGAHWPTR(pScrn)->SavedReg;
+ vgaHWSave(pScrn, vgaReg, VGA_SR_ALL);
+ pVBox->fSavedVBEMode = VBoxVideoGetModeRegisters(&pVBox->cSavedWidth,
+ &pVBox->cSavedHeight,
+ &pVBox->cSavedPitch,
+ &pVBox->cSavedBPP,
+ &pVBox->fSavedFlags);
+}
+
+void
+VBOXRestoreMode(ScrnInfoPtr pScrn)
+{
+ VBOXPtr pVBox = VBOXGetRec(pScrn);
+ vgaRegPtr vgaReg;
+
+ TRACE_ENTRY();
+ vgaReg = &VGAHWPTR(pScrn)->SavedReg;
+ vgaHWRestore(pScrn, vgaReg, VGA_SR_ALL);
+ if (pVBox->fSavedVBEMode)
+ VBoxVideoSetModeRegisters(pVBox->cSavedWidth, pVBox->cSavedHeight,
+ pVBox->cSavedPitch, pVBox->cSavedBPP,
+ pVBox->fSavedFlags, 0, 0);
+ else
+ VBoxVideoDisableVBE();
+}
+
+#ifndef VBOXVIDEO_13
+static void
+VBOXDisplayPowerManagementSet(ScrnInfoPtr pScrn, int mode, int flags)
+{
+ RT_NOREF(pScrn, mode, flags);
+}
+#endif
diff --git a/src/VBox/Additions/x11/vboxvideo/vboxvideo.h b/src/VBox/Additions/x11/vboxvideo/vboxvideo.h
new file mode 100644
index 00000000..8c47c61c
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/vboxvideo.h
@@ -0,0 +1,240 @@
+/* $Id: vboxvideo.h $ */
+/** @file
+ * VirtualBox X11 Additions graphics driver
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ * This file is based on the X11 VESA driver:
+ *
+ * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com)
+ *
+ * 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 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.
+ *
+ * Except as contained in this notice, the name of Conectiva Linux shall
+ * not be used in advertising or otherwise to promote the sale, use or other
+ * dealings in this Software without prior written authorization from
+ * Conectiva Linux.
+ *
+ * Authors: Paulo César Pereira de Andrade <pcpa@conectiva.com.br>
+ * Michael Thayer <michael.thayer@oracle.com>
+ */
+
+#ifndef GA_INCLUDED_SRC_x11_vboxvideo_vboxvideo_h
+#define GA_INCLUDED_SRC_x11_vboxvideo_vboxvideo_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBoxVideoGuest.h>
+#include <VBoxVideo.h>
+#include "version-generated.h"
+
+#define VBOX_VENDORID 0x80EE
+#define VBOX_DEVICEID 0xBEEF
+
+#ifndef VBVA_SCREEN_F_BLANK
+# define VBVA_SCREEN_F_BLANK 0x0004
+#endif
+
+#include <VBoxVideoVBE.h>
+
+#include "xf86.h"
+#include "xf86str.h"
+#include "xf86Cursor.h"
+
+#ifdef DEBUG
+
+#define TRACE_ENTRY() do { xf86ErrorF("%s: entering\n", __func__); } while(0)
+#define TRACE_EXIT() do { xf86ErrorF("%s: leaving\n", __func__); } while(0)
+#define TRACE_LINE() \
+ do { xf86ErrorF("%s: line\n", __func__, __LINE__); } while(0)
+#define TRACE_LOG(...) \
+do { \
+ xf86ErrorF("%s: ", __func__); \
+ xf86ErrorF(__VA_ARGS__); \
+} while(0)
+
+#else /* !DEBUG */
+
+#define TRACE_ENTRY() do { } while (0)
+#define TRACE_EXIT() do { } while (0)
+#define TRACE_LOG(...) do { } while (0)
+
+#endif /* !DEBUG */
+
+#define VBOX_VERSION VBOX_VERSION_MAJOR * 10000 \
+ + VBOX_VERSION_MINOR * 100
+#define VBOX_NAME "VBoxVideo"
+#define VBOX_DRIVER_NAME "vboxvideo"
+
+#define VBOX_VIDEO_MAJOR VBOX_VERSION_MAJOR
+#define VBOX_VIDEO_MINOR VBOX_VERSION_MINOR
+
+#define VBOX_VIDEO_MIN_SIZE 64
+#define VBOX_VIDEO_MAX_VIRTUAL (INT16_MAX - 1)
+
+#define VBOXPTR(p) ((VBOXPtr)((p)->driverPrivate))
+
+/** Helper to work round different ways of getting the root window in different
+ * server versions. */
+#if defined(XORG_VERSION_CURRENT) && XORG_VERSION_CURRENT < 70000000 \
+ && XORG_VERSION_CURRENT >= 10900000
+# define ROOT_WINDOW(pScrn) screenInfo.screens[(pScrn)->scrnIndex]->root
+#else
+# define ROOT_WINDOW(pScrn) WindowTable[(pScrn)->scrnIndex]
+#endif
+
+/** ChangeWindowProperty for X.Org Server 1.19 and later */
+#if defined(XORG_VERSION_CURRENT) && XORG_VERSION_CURRENT < 70000000 \
+ && XORG_VERSION_CURRENT >= 11900000
+# define ChangeWindowProperty(pWin, property, type, format, mode, \
+ len, value, sendevent) \
+ dixChangeWindowProperty(serverClient, pWin, property, type, format, \
+ mode, len, value, sendevent)
+#endif
+
+/** Structure containing all virtual monitor-specific information. */
+struct VBoxScreen
+{
+ /** Position information for each virtual screen for the purposes of
+ * sending dirty rectangle information to the right one. */
+ RTRECT2 aScreenLocation;
+ /** Is this CRTC enabled or in DPMS off state? */
+ Bool fPowerOn;
+#ifdef VBOXVIDEO_13
+ /** The virtual crtcs. */
+ struct _xf86Crtc *paCrtcs;
+ /** The virtual outputs, logically not distinct from crtcs. */
+ struct _xf86Output *paOutputs;
+#endif
+ /** Offsets of VBVA buffers in video RAM */
+ uint32_t aoffVBVABuffer;
+ /** Context information about the VBVA buffers for each screen */
+ struct VBVABUFFERCONTEXT aVbvaCtx;
+ /** The current preferred resolution for the screen */
+ RTRECTSIZE aPreferredSize;
+ /** The current preferred location for the screen. */
+ RTPOINT aPreferredLocation;
+ /** Has this screen been enabled by the host? */
+ Bool afConnected;
+ /** Does this screen have a preferred location? */
+ Bool afHaveLocation;
+};
+
+typedef struct VBOXRec
+{
+ EntityInfoPtr pEnt;
+#ifdef PCIACCESS
+ struct pci_device *pciInfo;
+#else
+ pciVideoPtr pciInfo;
+ PCITAG pciTag;
+#endif
+ void *base;
+ /** The amount of VRAM available for use as a framebuffer */
+ unsigned long cbFBMax;
+ /** The size of the framebuffer and the VBVA buffers at the end of it. */
+ unsigned long cbView;
+ /** Whether the pre-X-server mode was a VBE mode */
+ Bool fSavedVBEMode;
+ /** Paramters of the saved pre-X-server VBE mode, invalid if there is none
+ */
+ uint16_t cSavedWidth, cSavedHeight, cSavedPitch, cSavedBPP, fSavedFlags;
+ CloseScreenProcPtr CloseScreen;
+ /** Default X server procedure for enabling and disabling framebuffer access */
+ xf86EnableDisableFBAccessProc *EnableDisableFBAccess;
+ OptionInfoPtr Options;
+ /** @todo we never actually free this */
+ xf86CursorInfoPtr pCurs;
+ /** Do we currently want to use the host cursor? */
+ Bool fUseHardwareCursor;
+ /** Number of screens attached */
+ uint32_t cScreens;
+ /** Information about each virtual screen. */
+ struct VBoxScreen *pScreens;
+ /** Can we get mode hint and cursor integration information from HGSMI? */
+ Bool fHaveHGSMIModeHints;
+ /** Does the host support the screen blanking flag? */
+ Bool fHostHasScreenBlankingFlag;
+ /** Array of structures for receiving mode hints. */
+ VBVAMODEHINT *paVBVAModeHints;
+#ifdef VBOXVIDEO_13
+# ifdef RT_OS_LINUX
+ /** Input device file descriptor for getting ACPI hot-plug events. */
+ int fdACPIDevices;
+ /** Input handler handle for ACPI hot-plug listener. */
+ void *hACPIEventHandler;
+# endif
+#endif
+ /** HGSMI guest heap context */
+ HGSMIGUESTCOMMANDCONTEXT guestCtx;
+ /** Unrestricted horizontal resolution flag. */
+ Bool fAnyX;
+} VBOXRec, *VBOXPtr;
+
+#define VBOXGetRec(pScrn) ((VBOXPtr)(pScrn)->driverPrivate)
+
+/* setmode.c */
+
+/** Structure describing the virtual frame buffer. It starts at the beginning
+ * of the video RAM. */
+struct vbvxFrameBuffer {
+ /** X offset of first screen in frame buffer. */
+ int x0;
+ /** Y offset of first screen in frame buffer. */
+ int y0;
+ /** Frame buffer virtual width. */
+ unsigned cWidth;
+ /** Frame buffer virtual height. */
+ unsigned cHeight;
+ /** Bits per pixel. */
+ unsigned cBPP;
+};
+
+extern void vbvxClearVRAM(ScrnInfoPtr pScrn, size_t cbOldSize, size_t cbNewSize);
+extern void vbvxSetMode(ScrnInfoPtr pScrn, unsigned cDisplay, unsigned cWidth, unsigned cHeight, int x, int y, Bool fEnabled,
+ Bool fConnected, struct vbvxFrameBuffer *pFrameBuffer);
+extern void vbvxSetSolarisMouseRange(int width, int height);
+
+/* pointer.h */
+extern Bool vbvxCursorInit(ScreenPtr pScreen);
+extern void vbvxCursorTerm(VBOXPtr pVBox);
+
+/* vbva.c */
+extern void vbvxHandleDirtyRect(ScrnInfoPtr pScrn, int iRects, BoxPtr aRects);
+extern void vbvxSetUpHGSMIHeapInGuest(VBOXPtr pVBox, uint32_t cbVRAM);
+extern Bool vboxEnableVbva(ScrnInfoPtr pScrn);
+extern void vboxDisableVbva(ScrnInfoPtr pScrn);
+
+/* getmode.c */
+extern void vboxAddModes(ScrnInfoPtr pScrn);
+extern void VBoxInitialiseSizeHints(ScrnInfoPtr pScrn);
+extern void vbvxReadSizesAndCursorIntegrationFromProperties(ScrnInfoPtr pScrn, Bool *pfNeedUpdate);
+extern void vbvxReadSizesAndCursorIntegrationFromHGSMI(ScrnInfoPtr pScrn, Bool *pfNeedUpdate);
+extern void vbvxSetUpLinuxACPI(ScreenPtr pScreen);
+extern void vbvxCleanUpLinuxACPI(ScreenPtr pScreen);
+
+/* EDID generation */
+#ifdef VBOXVIDEO_13
+extern Bool VBOXEDIDSet(struct _xf86Output *output, DisplayModePtr pmode);
+#endif
+
+#endif /* !GA_INCLUDED_SRC_x11_vboxvideo_vboxvideo_h */
+
diff --git a/src/VBox/Additions/x11/vboxvideo/vbva.c b/src/VBox/Additions/x11/vboxvideo/vbva.c
new file mode 100644
index 00000000..e885d082
--- /dev/null
+++ b/src/VBox/Additions/x11/vboxvideo/vbva.c
@@ -0,0 +1,254 @@
+/* $Id: vbva.c $ */
+/** @file
+ * VirtualBox X11 Additions graphics driver 2D acceleration functions
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#if defined(IN_XF86_MODULE) && !defined(NO_ANSIC)
+# include "xf86_ansic.h"
+#endif
+#include "compiler.h"
+
+#include "vboxvideo.h"
+
+#ifdef XORG_7X
+# include <stdlib.h>
+# include <string.h>
+#endif
+
+/**************************************************************************
+* Main functions *
+**************************************************************************/
+
+/**
+ * Callback function called by the X server to tell us about dirty
+ * rectangles in the video buffer.
+ *
+ * @param pScrn pointer to the information structure for the current
+ * screen
+ * @param iRects Number of dirty rectangles to update
+ * @param aRects Array of structures containing the coordinates of the
+ * rectangles
+ */
+void vbvxHandleDirtyRect(ScrnInfoPtr pScrn, int iRects, BoxPtr aRects)
+{
+ VBVACMDHDR cmdHdr;
+ VBOXPtr pVBox;
+ int i;
+ unsigned j;
+
+ pVBox = pScrn->driverPrivate;
+ if (!pScrn->vtSema)
+ return;
+
+ for (j = 0; j < pVBox->cScreens; ++j)
+ {
+ /* Just continue quietly if VBVA is not currently active. */
+ struct VBVABUFFER *pVBVA = pVBox->pScreens[j].aVbvaCtx.pVBVA;
+ if ( !pVBVA
+ || !(pVBVA->hostFlags.u32HostEvents & VBVA_F_MODE_ENABLED))
+ continue;
+ for (i = 0; i < iRects; ++i)
+ {
+ if ( aRects[i].x1 > pVBox->pScreens[j].aScreenLocation.x
+ + pVBox->pScreens[j].aScreenLocation.cx
+ || aRects[i].y1 > pVBox->pScreens[j].aScreenLocation.y
+ + pVBox->pScreens[j].aScreenLocation.cy
+ || aRects[i].x2 < pVBox->pScreens[j].aScreenLocation.x
+ || aRects[i].y2 < pVBox->pScreens[j].aScreenLocation.y)
+ continue;
+ cmdHdr.x = (int16_t)aRects[i].x1 - pVBox->pScreens[0].aScreenLocation.x;
+ cmdHdr.y = (int16_t)aRects[i].y1 - pVBox->pScreens[0].aScreenLocation.y;
+ cmdHdr.w = (uint16_t)(aRects[i].x2 - aRects[i].x1);
+ cmdHdr.h = (uint16_t)(aRects[i].y2 - aRects[i].y1);
+
+#if 0
+ TRACE_LOG("display=%u, x=%d, y=%d, w=%d, h=%d\n",
+ j, cmdHdr.x, cmdHdr.y, cmdHdr.w, cmdHdr.h);
+#endif
+
+ if (VBoxVBVABufferBeginUpdate(&pVBox->pScreens[j].aVbvaCtx,
+ &pVBox->guestCtx))
+ {
+ VBoxVBVAWrite(&pVBox->pScreens[j].aVbvaCtx, &pVBox->guestCtx, &cmdHdr,
+ sizeof(cmdHdr));
+ VBoxVBVABufferEndUpdate(&pVBox->pScreens[j].aVbvaCtx);
+ }
+ }
+ }
+}
+
+static DECLCALLBACK(void *) hgsmiEnvAlloc(void *pvEnv, HGSMISIZE cb)
+{
+ RT_NOREF(pvEnv);
+ return calloc(1, cb);
+}
+
+static DECLCALLBACK(void) hgsmiEnvFree(void *pvEnv, void *pv)
+{
+ RT_NOREF(pvEnv);
+ free(pv);
+}
+
+static HGSMIENV g_hgsmiEnv =
+{
+ NULL,
+ hgsmiEnvAlloc,
+ hgsmiEnvFree
+};
+
+/**
+ * Calculate the location in video RAM of and initialise the heap for guest to
+ * host messages.
+ */
+void vbvxSetUpHGSMIHeapInGuest(VBOXPtr pVBox, uint32_t cbVRAM)
+{
+ int rc;
+ uint32_t offVRAMBaseMapping, offGuestHeapMemory, cbGuestHeapMemory;
+ void *pvGuestHeapMemory;
+
+ VBoxHGSMIGetBaseMappingInfo(cbVRAM, &offVRAMBaseMapping, NULL, &offGuestHeapMemory, &cbGuestHeapMemory, NULL);
+ pvGuestHeapMemory = ((uint8_t *)pVBox->base) + offVRAMBaseMapping + offGuestHeapMemory;
+ rc = VBoxHGSMISetupGuestContext(&pVBox->guestCtx, pvGuestHeapMemory, cbGuestHeapMemory,
+ offVRAMBaseMapping + offGuestHeapMemory, &g_hgsmiEnv);
+ AssertMsg(RT_SUCCESS(rc), ("Failed to set up the guest-to-host message buffer heap, rc=%d\n", rc));
+ pVBox->cbView = offVRAMBaseMapping;
+}
+
+/** Callback to fill in the view structures */
+static DECLCALLBACK(int) vboxFillViewInfo(void *pvVBox, struct VBVAINFOVIEW *pViews, uint32_t cViews)
+{
+ VBOXPtr pVBox = (VBOXPtr)pvVBox;
+ unsigned i;
+ for (i = 0; i < cViews; ++i)
+ {
+ pViews[i].u32ViewIndex = i;
+ pViews[i].u32ViewOffset = 0;
+ pViews[i].u32ViewSize = pVBox->cbView;
+ pViews[i].u32MaxScreenSize = pVBox->cbFBMax;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initialise VirtualBox's accelerated video extensions.
+ *
+ * @returns TRUE on success, FALSE on failure
+ */
+static Bool vboxSetupVRAMVbva(VBOXPtr pVBox)
+{
+ int rc = VINF_SUCCESS;
+ unsigned i;
+
+ pVBox->cbFBMax = pVBox->cbView;
+ for (i = 0; i < pVBox->cScreens; ++i)
+ {
+ pVBox->cbFBMax -= VBVA_MIN_BUFFER_SIZE;
+ pVBox->pScreens[i].aoffVBVABuffer = pVBox->cbFBMax;
+ TRACE_LOG("VBVA buffer offset for screen %u: 0x%lx\n", i,
+ (unsigned long) pVBox->cbFBMax);
+ VBoxVBVASetupBufferContext(&pVBox->pScreens[i].aVbvaCtx,
+ pVBox->pScreens[i].aoffVBVABuffer,
+ VBVA_MIN_BUFFER_SIZE);
+ }
+ TRACE_LOG("Maximum framebuffer size: %lu (0x%lx)\n",
+ (unsigned long) pVBox->cbFBMax,
+ (unsigned long) pVBox->cbFBMax);
+ rc = VBoxHGSMISendViewInfo(&pVBox->guestCtx, pVBox->cScreens,
+ vboxFillViewInfo, (void *)pVBox);
+ AssertMsg(RT_SUCCESS(rc), ("Failed to send the view information to the host, rc=%d\n", rc));
+ return TRUE;
+}
+
+static Bool haveHGSMIModeHintAndCursorReportingInterface(VBOXPtr pVBox)
+{
+ uint32_t fModeHintReporting, fCursorReporting;
+
+ return RT_SUCCESS(VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_MODE_HINT_REPORTING, &fModeHintReporting))
+ && RT_SUCCESS(VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_GUEST_CURSOR_REPORTING, &fCursorReporting))
+ && fModeHintReporting == VINF_SUCCESS
+ && fCursorReporting == VINF_SUCCESS;
+}
+
+static Bool hostHasScreenBlankingFlag(VBOXPtr pVBox)
+{
+ uint32_t fScreenFlags;
+
+ return RT_SUCCESS(VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_SCREEN_FLAGS, &fScreenFlags))
+ && fScreenFlags & VBVA_SCREEN_F_BLANK;
+}
+
+/**
+ * Inform VBox that we will supply it with dirty rectangle information
+ * and install the dirty rectangle handler.
+ *
+ * @returns TRUE for success, FALSE for failure
+ * @param pScrn Pointer to a structure describing the X screen in use
+ */
+Bool
+vboxEnableVbva(ScrnInfoPtr pScrn)
+{
+ Bool rc = TRUE;
+ unsigned i;
+ VBOXPtr pVBox = pScrn->driverPrivate;
+
+ TRACE_ENTRY();
+ if (!vboxSetupVRAMVbva(pVBox))
+ return FALSE;
+ for (i = 0; i < pVBox->cScreens; ++i)
+ {
+ struct VBVABUFFER *pVBVA;
+
+ pVBVA = (struct VBVABUFFER *) ( ((uint8_t *)pVBox->base)
+ + pVBox->pScreens[i].aoffVBVABuffer);
+ if (!VBoxVBVAEnable(&pVBox->pScreens[i].aVbvaCtx, &pVBox->guestCtx,
+ pVBVA, i))
+ rc = FALSE;
+ }
+ AssertMsg(rc, ("Failed to enable screen update reporting for at least one virtual monitor.\n"));
+ pVBox->fHaveHGSMIModeHints = haveHGSMIModeHintAndCursorReportingInterface(pVBox);
+ pVBox->fHostHasScreenBlankingFlag = hostHasScreenBlankingFlag(pVBox);
+ return rc;
+}
+
+/**
+ * Inform VBox that we will stop supplying it with dirty rectangle
+ * information. This function is intended to be called when an X
+ * virtual terminal is disabled, or the X server is terminated.
+ *
+ * @param pScrn Pointer to a structure describing the X screen in use
+ */
+void
+vboxDisableVbva(ScrnInfoPtr pScrn)
+{
+ unsigned i;
+ VBOXPtr pVBox = pScrn->driverPrivate;
+
+ TRACE_ENTRY();
+ for (i = 0; i < pVBox->cScreens; ++i)
+ VBoxVBVADisable(&pVBox->pScreens[i].aVbvaCtx, &pVBox->guestCtx, i);
+}