summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-server/linux
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-server/linux')
-rw-r--r--src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp476
-rw-r--r--src/VBox/Main/src-server/linux/HostHardwareLinux.cpp1331
-rw-r--r--src/VBox/Main/src-server/linux/HostPowerLinux.cpp198
-rw-r--r--src/VBox/Main/src-server/linux/Makefile.kup0
-rw-r--r--src/VBox/Main/src-server/linux/NetIf-linux.cpp329
-rw-r--r--src/VBox/Main/src-server/linux/PerformanceLinux.cpp609
-rw-r--r--src/VBox/Main/src-server/linux/USBGetDevices.cpp1795
-rw-r--r--src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp399
-rw-r--r--src/VBox/Main/src-server/linux/vbox-libhal.cpp105
9 files changed, 5242 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp b/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp
new file mode 100644
index 00000000..ed80412e
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/HostDnsServiceLinux.cpp
@@ -0,0 +1,476 @@
+/* $Id: HostDnsServiceLinux.cpp $ */
+/** @file
+ * Linux specific DNS information fetching.
+ */
+
+/*
+ * 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_MAIN_HOST
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/file.h>
+#include <VBox/log.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+#include <errno.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+
+#include <linux/limits.h>
+
+/* Workaround for <sys/cdef.h> defining __flexarr to [] which beats us in
+ * struct inotify_event (char name __flexarr). */
+#include <sys/cdefs.h>
+#undef __flexarr
+#define __flexarr [0]
+#include <sys/inotify.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include "../HostDnsService.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char g_szEtcFolder[] = "/etc";
+static const char g_szResolvConfPath[] = "/etc/resolv.conf";
+static const char g_szResolvConfFilename[] = "resolv.conf";
+
+
+HostDnsServiceLinux::~HostDnsServiceLinux()
+{
+ if (m_fdShutdown >= 0)
+ {
+ close(m_fdShutdown);
+ m_fdShutdown = -1;
+ }
+}
+
+HRESULT HostDnsServiceLinux::init(HostDnsMonitorProxy *pProxy)
+{
+ return HostDnsServiceResolvConf::init(pProxy, "/etc/resolv.conf");
+}
+
+int HostDnsServiceLinux::monitorThreadShutdown(RTMSINTERVAL uTimeoutMs)
+{
+ RT_NOREF(uTimeoutMs);
+
+ if (m_fdShutdown >= 0)
+ send(m_fdShutdown, "", 1, MSG_NOSIGNAL);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Format the notifcation event mask into a buffer for logging purposes.
+ */
+static const char *InotifyMaskToStr(char *psz, size_t cb, uint32_t fMask)
+{
+ static struct { const char *pszName; uint32_t cchName, fFlag; } const s_aFlags[] =
+ {
+# define ENTRY(fFlag) { #fFlag, sizeof(#fFlag) - 1, fFlag }
+ ENTRY(IN_ACCESS),
+ ENTRY(IN_MODIFY),
+ ENTRY(IN_ATTRIB),
+ ENTRY(IN_CLOSE_WRITE),
+ ENTRY(IN_CLOSE_NOWRITE),
+ ENTRY(IN_OPEN),
+ ENTRY(IN_MOVED_FROM),
+ ENTRY(IN_MOVED_TO),
+ ENTRY(IN_CREATE),
+ ENTRY(IN_DELETE),
+ ENTRY(IN_DELETE_SELF),
+ ENTRY(IN_MOVE_SELF),
+ ENTRY(IN_Q_OVERFLOW),
+ ENTRY(IN_IGNORED),
+ ENTRY(IN_UNMOUNT),
+ ENTRY(IN_ISDIR),
+ };
+ size_t offDst = 0;
+ for (size_t i = 0; i < RT_ELEMENTS(s_aFlags); i++)
+ if (fMask & s_aFlags[i].fFlag)
+ {
+ if (offDst && offDst < cb)
+ psz[offDst++] = ' ';
+ if (offDst < cb)
+ {
+ size_t cbToCopy = RT_MIN(s_aFlags[i].cchName, cb - offDst);
+ memcpy(&psz[offDst], s_aFlags[i].pszName, cbToCopy);
+ offDst += cbToCopy;
+ }
+
+ fMask &= ~s_aFlags[i].fFlag;
+ if (!fMask)
+ break;
+ }
+ if (fMask && offDst < cb)
+ RTStrPrintf(&psz[offDst], cb - offDst, offDst ? " %#x" : "%#x", fMask);
+ else
+ psz[RT_MIN(offDst, cb - 1)] = '\0';
+ return psz;
+}
+
+/**
+ * Helper for HostDnsServiceLinux::monitorThreadProc.
+ */
+static int monitorSymlinkedDir(int iInotifyFd, char szRealResolvConf[PATH_MAX], size_t *poffFilename)
+{
+ RT_BZERO(szRealResolvConf, PATH_MAX);
+
+ /* Check that it's a symlink first. */
+ struct stat st;
+ if ( lstat(g_szResolvConfPath, &st) >= 0
+ && S_ISLNK(st.st_mode))
+ {
+ /* If realpath fails, the file must've been deleted while we were busy: */
+ if ( realpath(g_szResolvConfPath, szRealResolvConf)
+ && strchr(szRealResolvConf, '/'))
+ {
+ /* Cut of the filename part. We only need that for deletion checks and such. */
+ size_t const offFilename = strrchr(szRealResolvConf, '/') - &szRealResolvConf[0];
+ *poffFilename = offFilename + 1;
+ szRealResolvConf[offFilename] = '\0';
+
+ /* Try set up directory monitoring. (File monitoring is done via the symlink.) */
+ return inotify_add_watch(iInotifyFd, szRealResolvConf, IN_MOVE | IN_CREATE | IN_DELETE);
+ }
+ }
+
+ *poffFilename = 0;
+ szRealResolvConf[0] = '\0';
+ return -1;
+}
+
+/** @todo If this code is needed elsewhere, we should abstract it into an IPRT
+ * thingy that monitors a file (path) for changes. This code is a little
+ * bit too complex to be duplicated. */
+int HostDnsServiceLinux::monitorThreadProc(void)
+{
+ /*
+ * Create a socket pair for signalling shutdown (see monitorThreadShutdown).
+ * ASSUME Linux 2.6.27 or later and that we can use SOCK_CLOEXEC.
+ */
+ int aiStopPair[2];
+ int iRc = socketpair(AF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0, aiStopPair);
+ int iErr = errno;
+ AssertLogRelMsgReturn(iRc == 0, ("socketpair: failed (%d: %s)\n", iErr, strerror(iErr)), RTErrConvertFromErrno(iErr));
+
+ m_fdShutdown = aiStopPair[0];
+
+ onMonitorThreadInitDone();
+
+ /*
+ * inotify initialization (using inotify_init1 w/ IN_CLOEXEC introduced
+ * in 2.6.27 shouldn't be a problem any more).
+ *
+ * Note! Ignoring failures here is safe, because poll will ignore entires
+ * with negative fd values.
+ */
+ int const iNotifyFd = inotify_init1(IN_CLOEXEC);
+ if (iNotifyFd < 0)
+ LogRel(("HostDnsServiceLinux::monitorThreadProc: Warning! inotify_init failed (errno=%d)\n", errno));
+
+ /* Monitor the /etc directory so we can detect moves, creating and unlinking
+ involving /etc/resolv.conf: */
+ int const iWdDir = inotify_add_watch(iNotifyFd, g_szEtcFolder, IN_MOVE | IN_CREATE | IN_DELETE);
+
+ /* In case g_szResolvConfPath is a symbolic link, monitor the target directory
+ too for changes to what it links to (kept up to date via iWdDir). */
+ char szRealResolvConf[PATH_MAX];
+ size_t offRealResolvConfName = 0;
+ int iWdSymDir = ::monitorSymlinkedDir(iNotifyFd, szRealResolvConf, &offRealResolvConfName);
+
+ /* Monitor the resolv.conf itself if it exists, following all symlinks. */
+ int iWdFile = inotify_add_watch(iNotifyFd, g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF);
+
+ LogRel5(("HostDnsServiceLinux::monitorThreadProc: inotify: %d - iWdDir=%d iWdSymDir=%d iWdFile=%d\n",
+ iNotifyFd, iWdDir, iWdSymDir, iWdFile));
+
+ /*
+ * poll initialization:
+ */
+ pollfd aFdPolls[2];
+ RT_ZERO(aFdPolls);
+
+ aFdPolls[0].fd = iNotifyFd;
+ aFdPolls[0].events = POLLIN;
+
+ aFdPolls[1].fd = aiStopPair[1];
+ aFdPolls[1].events = POLLIN;
+
+ /*
+ * The monitoring loop.
+ */
+ int vrcRet = VINF_SUCCESS;
+ for (;;)
+ {
+ /*
+ * Wait for something to happen.
+ */
+ iRc = poll(aFdPolls, RT_ELEMENTS(aFdPolls), -1 /*infinite timeout*/);
+ if (iRc == -1)
+ {
+ if (errno != EINTR)
+ {
+ LogRelMax(32, ("HostDnsServiceLinux::monitorThreadProc: poll failed %d: errno=%d\n", iRc, errno));
+ RTThreadSleep(1);
+ }
+ continue;
+ }
+ Log5Func(("poll returns %d: [0]=%#x [1]=%#x\n", iRc, aFdPolls[1].revents, aFdPolls[0].revents));
+
+ AssertMsgBreakStmt( (aFdPolls[0].revents & (POLLERR | POLLNVAL)) == 0 /* (ok for fd=-1 too, revents=0 then) */
+ && (aFdPolls[1].revents & (POLLERR | POLLNVAL)) == 0,
+ ("Debug Me: [0]=%d,%#x [1]=%d, %#x\n",
+ aFdPolls[0].fd, aFdPolls[0].revents, aFdPolls[0].fd, aFdPolls[1].revents),
+ vrcRet = VERR_INTERNAL_ERROR);
+
+ /*
+ * Check for shutdown first.
+ */
+ if (aFdPolls[1].revents & POLLIN)
+ break; /** @todo should probably drain aiStopPair[1] here if we're really paranoid.
+ * we'll be closing our end of the socket/pipe, so any stuck write
+ * should return too (ECONNRESET, ENOTCONN or EPIPE). */
+
+ if (aFdPolls[0].revents & POLLIN)
+ {
+ /*
+ * Read the notification event.
+ */
+#define INOTIFY_EVENT_SIZE (RT_UOFFSETOF(struct inotify_event, name))
+ union
+ {
+ uint8_t abBuf[(INOTIFY_EVENT_SIZE * 2 - 1 + NAME_MAX) / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE * 4];
+ uint64_t uAlignTrick[2];
+ } uEvtBuf;
+
+ ssize_t cbEvents = read(iNotifyFd, &uEvtBuf, sizeof(uEvtBuf));
+ Log5Func(("read(inotify) -> %zd\n", cbEvents));
+ if (cbEvents > 0)
+ Log5(("%.*Rhxd\n", cbEvents, &uEvtBuf));
+
+ /*
+ * Process the events.
+ *
+ * We'll keep the old watch descriptor number till after we're done
+ * parsing this block of events. Even so, the removal of watches
+ * isn't race free, as they'll get automatically removed when what
+ * is being watched is unliked.
+ */
+ int iWdFileNew = iWdFile;
+ int iWdSymDirNew = iWdSymDir;
+ bool fTryReRead = false;
+ struct inotify_event const *pCurEvt = (struct inotify_event const *)&uEvtBuf;
+ while (cbEvents >= (ssize_t)INOTIFY_EVENT_SIZE)
+ {
+ char szTmp[64];
+ if (pCurEvt->len == 0)
+ LogRel5(("HostDnsServiceLinux::monitorThreadProc: event: wd=%#x mask=%#x (%s) cookie=%#x\n",
+ pCurEvt->wd, pCurEvt->mask, InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask), pCurEvt->cookie));
+ else
+ LogRel5(("HostDnsServiceLinux::monitorThreadProc: event: wd=%#x mask=%#x (%s) cookie=%#x len=%#x '%s'\n",
+ pCurEvt->wd, pCurEvt->mask, InotifyMaskToStr(szTmp, sizeof(szTmp), pCurEvt->mask),
+ pCurEvt->cookie, pCurEvt->len, pCurEvt->name));
+
+ /*
+ * The file itself (symlinks followed, remember):
+ */
+ if (pCurEvt->wd == iWdFile)
+ {
+ if (pCurEvt->mask & IN_CLOSE_WRITE)
+ {
+ Log5Func(("file: close-after-write => trigger re-read\n"));
+ fTryReRead = true;
+ }
+ else if (pCurEvt->mask & IN_DELETE_SELF)
+ {
+ Log5Func(("file: deleted self\n"));
+ if (iWdFileNew != -1)
+ {
+ iRc = inotify_rm_watch(iNotifyFd, iWdFileNew);
+ AssertMsg(iRc >= 0, ("%d/%d\n", iRc, errno));
+ iWdFileNew = -1;
+ }
+ }
+ else if (pCurEvt->mask & IN_IGNORED)
+ iWdFileNew = -1; /* file deleted */
+ else
+ AssertMsgFailed(("file: mask=%#x\n", pCurEvt->mask));
+ }
+ /*
+ * The /etc directory
+ *
+ * We only care about events relating to the creation, deletion and
+ * renaming of 'resolv.conf'. We'll restablish both the direct file
+ * watching and the watching of any symlinked directory on all of
+ * these events, although for the former we'll delay the re-starting
+ * of the watching till all events have been processed.
+ */
+ else if (pCurEvt->wd == iWdDir)
+ {
+ if ( pCurEvt->len > 0
+ && strcmp(g_szResolvConfFilename, pCurEvt->name) == 0)
+ {
+ if (pCurEvt->mask & (IN_MOVE | IN_CREATE | IN_DELETE))
+ {
+ if (iWdFileNew >= 0)
+ {
+ iRc = inotify_rm_watch(iNotifyFd, iWdFileNew);
+ Log5Func(("dir: moved / created / deleted: dropped file watch (%d - iRc=%d/err=%d)\n",
+ iWdFileNew, iRc, errno));
+ iWdFileNew = -1;
+ }
+ if (iWdSymDirNew >= 0)
+ {
+ iRc = inotify_rm_watch(iNotifyFd, iWdSymDirNew);
+ Log5Func(("dir: moved / created / deleted: dropped symlinked dir watch (%d - %s/%s - iRc=%d/err=%d)\n",
+ iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName], iRc, errno));
+ iWdSymDirNew = -1;
+ offRealResolvConfName = 0;
+ }
+ if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE))
+ {
+ Log5Func(("dir: moved_to / created: trigger re-read\n"));
+ fTryReRead = true;
+
+ iWdSymDirNew = ::monitorSymlinkedDir(iNotifyFd, szRealResolvConf, &offRealResolvConfName);
+ if (iWdSymDirNew < 0)
+ Log5Func(("dir: moved_to / created: re-stablished symlinked-directory monitoring: iWdSymDir=%d (%s/%s)\n",
+ iWdSymDirNew, szRealResolvConf, &szRealResolvConf[offRealResolvConfName]));
+ }
+ }
+ else
+ AssertMsgFailed(("dir: %#x\n", pCurEvt->mask));
+ }
+ }
+ /*
+ * The directory of a symlinked resolv.conf.
+ *
+ * Where we only care when the symlink target is created, moved_to,
+ * deleted or moved_from - i.e. a minimal version of the /etc event
+ * processing above.
+ *
+ * Note! Since we re-statablish monitoring above, szRealResolvConf
+ * might not match the event we're processing. Fortunately,
+ * this shouldn't be important except for debug logging.
+ */
+ else if (pCurEvt->wd == iWdSymDir)
+ {
+ if ( pCurEvt->len > 0
+ && offRealResolvConfName > 0
+ && strcmp(&szRealResolvConf[offRealResolvConfName], pCurEvt->name) == 0)
+ {
+ if (iWdFileNew >= 0)
+ {
+ iRc = inotify_rm_watch(iNotifyFd, iWdFileNew);
+ Log5Func(("symdir: moved / created / deleted: drop file watch (%d - iRc=%d/err=%d)\n",
+ iWdFileNew, iRc, errno));
+ iWdFileNew = -1;
+ }
+ if (pCurEvt->mask & (IN_MOVED_TO | IN_CREATE))
+ {
+ Log5Func(("symdir: moved_to / created: trigger re-read\n"));
+ fTryReRead = true;
+ }
+ }
+ }
+ /* We can get here it seems if our inotify_rm_watch calls above takes
+ place after new events relating to the two descriptors happens. */
+ else
+ Log5Func(("Unknown (obsoleted) wd value: %d (mask=%#x cookie=%#x len=%#x)\n",
+ pCurEvt->wd, pCurEvt->mask, pCurEvt->cookie, pCurEvt->len));
+
+ /* advance to the next event */
+ Assert(pCurEvt->len / INOTIFY_EVENT_SIZE * INOTIFY_EVENT_SIZE == pCurEvt->len);
+ size_t const cbCurEvt = INOTIFY_EVENT_SIZE + pCurEvt->len;
+ pCurEvt = (struct inotify_event const *)((uintptr_t)pCurEvt + cbCurEvt);
+ cbEvents -= cbCurEvt;
+ }
+
+ /*
+ * Commit the new watch descriptor numbers now that we're
+ * done processing event using the old ones.
+ */
+ iWdFile = iWdFileNew;
+ iWdSymDir = iWdSymDirNew;
+
+ /*
+ * If the resolv.conf watch descriptor is -1, try restablish it here.
+ */
+ if (iWdFile == -1)
+ {
+ iWdFile = inotify_add_watch(iNotifyFd, g_szResolvConfPath, IN_CLOSE_WRITE | IN_DELETE_SELF);
+ if (iWdFile >= 0)
+ {
+ Log5Func(("Re-established file watcher: iWdFile=%d\n", iWdFile));
+ fTryReRead = true;
+ }
+ }
+
+ /*
+ * If any of the events indicate that we should re-read the file, we
+ * do so now. Should reduce number of unnecessary re-reads.
+ */
+ if (fTryReRead)
+ {
+ Log5Func(("Calling readResolvConf()...\n"));
+ try
+ {
+ readResolvConf();
+ }
+ catch (...)
+ {
+ LogRel(("HostDnsServiceLinux::monitorThreadProc: readResolvConf threw exception!\n"));
+ }
+ }
+ }
+ }
+
+ /*
+ * Close file descriptors.
+ */
+ if (aiStopPair[0] == m_fdShutdown) /* paranoia */
+ {
+ m_fdShutdown = -1;
+ close(aiStopPair[0]);
+ }
+ close(aiStopPair[1]);
+ close(iNotifyFd);
+ LogRel5(("HostDnsServiceLinux::monitorThreadProc: returns %Rrc\n", vrcRet));
+ return vrcRet;
+}
+
diff --git a/src/VBox/Main/src-server/linux/HostHardwareLinux.cpp b/src/VBox/Main/src-server/linux/HostHardwareLinux.cpp
new file mode 100644
index 00000000..39893deb
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/HostHardwareLinux.cpp
@@ -0,0 +1,1331 @@
+/* $Id: HostHardwareLinux.cpp $ */
+/** @file
+ * VirtualBox Main - Code for handling hardware detection under Linux, VBoxSVC.
+ */
+
+/*
+ * 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 LOG_GROUP LOG_GROUP_MAIN
+#include "HostHardwareLinux.h"
+
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/asm.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include <linux/cdrom.h>
+#include <linux/fd.h>
+#include <linux/major.h>
+
+#include <linux/version.h>
+#include <scsi/scsi.h>
+
+#include <iprt/linux/sysfs.h>
+
+#ifdef VBOX_USB_WITH_SYSFS
+# ifdef VBOX_USB_WITH_INOTIFY
+# include <fcntl.h> /* O_CLOEXEC */
+# include <poll.h>
+# include <signal.h>
+# include <unistd.h>
+# include <sys/inotify.h>
+# endif
+#endif
+
+//#include <vector>
+
+#include <errno.h>
+#include <dirent.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+
+/*
+ * Define NVME constant here to allow building
+ * on several kernel versions even if the
+ * building host doesn't contain certain NVME
+ * includes
+ */
+#define NVME_IOCTL_ID _IO('N', 0x40)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef TESTCASE
+static bool testing() { return true; }
+static bool fNoProbe = false;
+static bool noProbe() { return fNoProbe; }
+static void setNoProbe(bool val) { fNoProbe = val; }
+#else
+static bool testing() { return false; }
+static bool noProbe() { return false; }
+static void setNoProbe(bool val) { (void)val; }
+#endif
+
+
+/*********************************************************************************************************************************
+* Typedefs and Defines *
+*********************************************************************************************************************************/
+typedef enum SysfsWantDevice_T
+{
+ DVD,
+ Floppy,
+ FixedDisk
+} SysfsWantDevice_T;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList, bool isDVD, bool *pfSuccess) RT_NOTHROW_PROTO;
+static int getDriveInfoFromSysfs(DriveInfoList *pList, SysfsWantDevice_T wantDevice, bool *pfSuccess) RT_NOTHROW_PROTO;
+
+
+/**
+ * Find the length of a string, ignoring trailing non-ascii or control
+ * characters
+ *
+ * @note Code duplicated in HostHardwareFreeBSD.cpp
+ */
+static size_t strLenStripped(const char *pcsz) RT_NOTHROW_DEF
+{
+ size_t cch = 0;
+ for (size_t i = 0; pcsz[i] != '\0'; ++i)
+ if (pcsz[i] > 32 /*space*/ && pcsz[i] < 127 /*delete*/)
+ cch = i;
+ return cch + 1;
+}
+
+
+/**
+ * Get the name of a floppy drive according to the Linux floppy driver.
+ *
+ * @returns true on success, false if the name was not available (i.e. the
+ * device was not readable, or the file name wasn't a PC floppy
+ * device)
+ * @param pcszNode the path to the device node for the device
+ * @param Number the Linux floppy driver number for the drive. Required.
+ * @param pszName where to store the name retrieved
+ */
+static bool floppyGetName(const char *pcszNode, unsigned Number, floppy_drive_name pszName) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszNode, false);
+ AssertPtrReturn(pszName, false);
+ AssertReturn(Number <= 7, false);
+ RTFILE File;
+ int vrc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
+ if (RT_SUCCESS(vrc))
+ {
+ int iRcIoCtl;
+ vrc = RTFileIoCtl(File, FDGETDRVTYP, pszName, 0, &iRcIoCtl);
+ RTFileClose(File);
+ if (RT_SUCCESS(vrc) && iRcIoCtl >= 0)
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Create a UDI and a description for a floppy drive based on a number and the
+ * driver's name for it.
+ *
+ * We deliberately return an ugly sequence of characters as the description
+ * rather than an English language string to avoid translation issues.
+ *
+ * @param pcszName the floppy driver name for the device (optional)
+ * @param Number the number of the floppy (0 to 3 on FDC 0, 4 to 7 on
+ * FDC 1)
+ * @param pszDesc where to store the device description (optional)
+ * @param cbDesc the size of the buffer in @a pszDesc
+ * @param pszUdi where to store the device UDI (optional)
+ * @param cbUdi the size of the buffer in @a pszUdi
+ */
+static void floppyCreateDeviceStrings(const floppy_drive_name pcszName, unsigned Number,
+ char *pszDesc, size_t cbDesc, char *pszUdi, size_t cbUdi) RT_NOTHROW_DEF
+{
+ AssertPtrNullReturnVoid(pcszName);
+ AssertPtrNullReturnVoid(pszDesc);
+ AssertReturnVoid(!pszDesc || cbDesc > 0);
+ AssertPtrNullReturnVoid(pszUdi);
+ AssertReturnVoid(!pszUdi || cbUdi > 0);
+ AssertReturnVoid(Number <= 7);
+ if (pcszName)
+ {
+ const char *pcszSize;
+ switch(pcszName[0])
+ {
+ case 'd': case 'q': case 'h':
+ pcszSize = "5.25\"";
+ break;
+ case 'D': case 'H': case 'E': case 'u':
+ pcszSize = "3.5\"";
+ break;
+ default:
+ pcszSize = "(unknown)";
+ }
+ if (pszDesc)
+ RTStrPrintf(pszDesc, cbDesc, "%s %s K%s", pcszSize, &pcszName[1],
+ Number > 3 ? ", FDC 2" : "");
+ }
+ else
+ {
+ if (pszDesc)
+ RTStrPrintf(pszDesc, cbDesc, "FDD %d%s", (Number & 4) + 1,
+ Number > 3 ? ", FDC 2" : "");
+ }
+ if (pszUdi)
+ RTStrPrintf(pszUdi, cbUdi,
+ "/org/freedesktop/Hal/devices/platform_floppy_%u_storage",
+ Number);
+}
+
+
+/**
+ * Check whether a device number might correspond to a CD-ROM device according
+ * to Documentation/devices.txt in the Linux kernel source.
+ *
+ * @returns true if it might, false otherwise
+ * @param Number the device number (major and minor combination)
+ */
+static bool isCdromDevNum(dev_t Number) RT_NOTHROW_DEF
+{
+ int major = major(Number);
+ int minor = minor(Number);
+ if (major == IDE0_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == SCSI_CDROM_MAJOR)
+ return true;
+ if (major == CDU31A_CDROM_MAJOR)
+ return true;
+ if (major == GOLDSTAR_CDROM_MAJOR)
+ return true;
+ if (major == OPTICS_CDROM_MAJOR)
+ return true;
+ if (major == SANYO_CDROM_MAJOR)
+ return true;
+ if (major == MITSUMI_X_CDROM_MAJOR)
+ return true;
+ if (major == IDE1_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == MITSUMI_CDROM_MAJOR)
+ return true;
+ if (major == CDU535_CDROM_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM2_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM3_MAJOR)
+ return true;
+ if (major == MATSUSHITA_CDROM4_MAJOR)
+ return true;
+ if (major == AZTECH_CDROM_MAJOR)
+ return true;
+ if (major == 30 /* CM205_CDROM_MAJOR */) /* no #define for some reason */
+ return true;
+ if (major == CM206_CDROM_MAJOR)
+ return true;
+ if (major == IDE3_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == 46 /* Parallel port ATAPI CD-ROM */) /* no #define */
+ return true;
+ if (major == IDE4_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE5_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE6_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE7_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE8_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == IDE9_MAJOR && !(minor & 0x3f))
+ return true;
+ if (major == 113 /* VIOCD_MAJOR */)
+ return true;
+ return false;
+}
+
+
+/**
+ * Send an SCSI INQUIRY command to a device and return selected information.
+ *
+ * @returns iprt status code
+ * @retval VERR_TRY_AGAIN if the query failed but might succeed next time
+ * @param pcszNode the full path to the device node
+ * @param pbType where to store the SCSI device type on success (optional)
+ * @param pszVendor where to store the vendor id string on success (optional)
+ * @param cbVendor the size of the @a pszVendor buffer
+ * @param pszModel where to store the product id string on success (optional)
+ * @param cbModel the size of the @a pszModel buffer
+ * @note check documentation on the SCSI INQUIRY command and the Linux kernel
+ * SCSI headers included above if you want to understand what is going
+ * on in this method.
+ */
+static int cdromDoInquiry(const char *pcszNode, uint8_t *pbType, char *pszVendor, size_t cbVendor,
+ char *pszModel, size_t cbModel) RT_NOTHROW_DEF
+{
+ LogRelFlowFunc(("pcszNode=%s, pbType=%p, pszVendor=%p, cbVendor=%zu, pszModel=%p, cbModel=%zu\n",
+ pcszNode, pbType, pszVendor, cbVendor, pszModel, cbModel));
+ AssertPtrReturn(pcszNode, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pbType, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszVendor, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszModel, VERR_INVALID_POINTER);
+
+ RTFILE hFile = NIL_RTFILE;
+ int vrc = RTFileOpen(&hFile, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
+ if (RT_SUCCESS(vrc))
+ {
+ int iRcIoCtl = 0;
+ unsigned char auchResponse[96] = { 0 };
+ struct cdrom_generic_command CdromCommandReq;
+ RT_ZERO(CdromCommandReq);
+ CdromCommandReq.cmd[0] = INQUIRY;
+ CdromCommandReq.cmd[4] = sizeof(auchResponse);
+ CdromCommandReq.buffer = auchResponse;
+ CdromCommandReq.buflen = sizeof(auchResponse);
+ CdromCommandReq.data_direction = CGC_DATA_READ;
+ CdromCommandReq.timeout = 5000; /* ms */
+ vrc = RTFileIoCtl(hFile, CDROM_SEND_PACKET, &CdromCommandReq, 0, &iRcIoCtl);
+ if (RT_SUCCESS(vrc) && iRcIoCtl < 0)
+ vrc = RTErrConvertFromErrno(-CdromCommandReq.stat);
+ RTFileClose(hFile);
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pbType)
+ *pbType = auchResponse[0] & 0x1f;
+ if (pszVendor)
+ {
+ RTStrPrintf(pszVendor, cbVendor, "%.8s", &auchResponse[8] /* vendor id string */);
+ RTStrPurgeEncoding(pszVendor);
+ }
+ if (pszModel)
+ {
+ RTStrPrintf(pszModel, cbModel, "%.16s", &auchResponse[16] /* product id string */);
+ RTStrPurgeEncoding(pszModel);
+ }
+ LogRelFlowFunc(("returning success: type=%u, vendor=%.8s, product=%.16s\n",
+ auchResponse[0] & 0x1f, &auchResponse[8], &auchResponse[16]));
+ return VINF_SUCCESS;
+ }
+ }
+ LogRelFlowFunc(("returning %Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Initialise the device strings (description and UDI) for a DVD drive based on
+ * vendor and model name strings.
+ *
+ * @param pcszVendor the vendor ID string
+ * @param pcszModel the product ID string
+ * @param pszDesc where to store the description string (optional)
+ * @param cbDesc the size of the buffer in @a pszDesc
+ * @param pszUdi where to store the UDI string (optional)
+ * @param cbUdi the size of the buffer in @a pszUdi
+ *
+ * @note Used for more than DVDs these days.
+ */
+static void dvdCreateDeviceStrings(const char *pcszVendor, const char *pcszModel,
+ char *pszDesc, size_t cbDesc, char *pszUdi, size_t cbUdi) RT_NOEXCEPT
+{
+ AssertPtrReturnVoid(pcszVendor);
+ AssertPtrReturnVoid(pcszModel);
+ AssertPtrNullReturnVoid(pszDesc);
+ AssertReturnVoid(!pszDesc || cbDesc > 0);
+ AssertPtrNullReturnVoid(pszUdi);
+ AssertReturnVoid(!pszUdi || cbUdi > 0);
+
+ size_t cchModel = strLenStripped(pcszModel);
+ /*
+ * Vendor and Model strings can contain trailing spaces.
+ * Create trimmed copy of them because we should not modify
+ * original strings.
+ */
+ char* pszStartTrimmed = RTStrStripL(pcszVendor);
+ char* pszVendor = RTStrDup(pszStartTrimmed);
+ RTStrStripR(pszVendor);
+ pszStartTrimmed = RTStrStripL(pcszModel);
+ char* pszModel = RTStrDup(pszStartTrimmed);
+ RTStrStripR(pszModel);
+
+ size_t cbVendor = strlen(pszVendor);
+
+ /* Create a cleaned version of the model string for the UDI string. */
+ char szCleaned[128];
+ for (unsigned i = 0; i < sizeof(szCleaned) && pcszModel[i] != '\0'; ++i)
+ if ( (pcszModel[i] >= '0' && pcszModel[i] <= '9')
+ || (pcszModel[i] >= 'A' && pcszModel[i] <= 'z'))
+ szCleaned[i] = pcszModel[i];
+ else
+ szCleaned[i] = '_';
+ szCleaned[RT_MIN(cchModel, sizeof(szCleaned) - 1)] = '\0';
+
+ /* Construct the description string as "Vendor Product" */
+ if (pszDesc)
+ {
+ if (cbVendor > 0)
+ {
+ RTStrPrintf(pszDesc, cbDesc, "%.*s %s", cbVendor, pszVendor, strlen(pszModel) > 0 ? pszModel : "(unknown drive model)");
+ RTStrPurgeEncoding(pszDesc);
+ }
+ else
+ RTStrCopy(pszDesc, cbDesc, pszModel);
+ }
+ /* Construct the UDI string */
+ if (pszUdi)
+ {
+ if (cchModel > 0)
+ RTStrPrintf(pszUdi, cbUdi, "/org/freedesktop/Hal/devices/storage_model_%s", szCleaned);
+ else
+ pszUdi[0] = '\0';
+ }
+}
+
+
+/**
+ * Check whether the device is the NVME device.
+ * @returns true on success, false if the name was not available (i.e. the
+ * device was not readable, or the file name wasn't a NVME device)
+ * @param pcszNode the path to the device node for the device
+ */
+static bool probeNVME(const char *pcszNode) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszNode, false);
+ RTFILE File;
+ int vrc = RTFileOpen(&File, pcszNode, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK);
+ if (RT_SUCCESS(vrc))
+ {
+ int iRcIoCtl;
+ vrc = RTFileIoCtl(File, NVME_IOCTL_ID, NULL, 0, &iRcIoCtl);
+ RTFileClose(File);
+ if (RT_SUCCESS(vrc) && iRcIoCtl >= 0)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Check whether a device node points to a valid device and create a UDI and
+ * a description for it, and store the device number, if it does.
+ *
+ * @returns true if the device is valid, false otherwise
+ * @param pcszNode the path to the device node
+ * @param isDVD are we looking for a DVD device (or a floppy device)?
+ * @param pDevice where to store the device node (optional)
+ * @param pszDesc where to store the device description (optional)
+ * @param cbDesc the size of the buffer in @a pszDesc
+ * @param pszUdi where to store the device UDI (optional)
+ * @param cbUdi the size of the buffer in @a pszUdi
+ */
+static bool devValidateDevice(const char *pcszNode, bool isDVD, dev_t *pDevice,
+ char *pszDesc, size_t cbDesc, char *pszUdi, size_t cbUdi) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszNode, false);
+ AssertPtrNullReturn(pDevice, false);
+ AssertPtrNullReturn(pszDesc, false);
+ AssertReturn(!pszDesc || cbDesc > 0, false);
+ AssertPtrNullReturn(pszUdi, false);
+ AssertReturn(!pszUdi || cbUdi > 0, false);
+
+ RTFSOBJINFO ObjInfo;
+ if (RT_FAILURE(RTPathQueryInfo(pcszNode, &ObjInfo, RTFSOBJATTRADD_UNIX)))
+ return false;
+ if (!RTFS_IS_DEV_BLOCK(ObjInfo.Attr.fMode))
+ return false;
+ if (pDevice)
+ *pDevice = ObjInfo.Attr.u.Unix.Device;
+
+ if (isDVD)
+ {
+ char szVendor[128], szModel[128];
+ uint8_t u8Type;
+ if (!isCdromDevNum(ObjInfo.Attr.u.Unix.Device))
+ return false;
+ if (RT_FAILURE(cdromDoInquiry(pcszNode, &u8Type,
+ szVendor, sizeof(szVendor),
+ szModel, sizeof(szModel))))
+ return false;
+ if (u8Type != TYPE_ROM)
+ return false;
+ dvdCreateDeviceStrings(szVendor, szModel, pszDesc, cbDesc, pszUdi, cbUdi);
+ }
+ else
+ {
+ /* Floppies on Linux are legacy devices with hardcoded majors and minors */
+ if (major(ObjInfo.Attr.u.Unix.Device) != FLOPPY_MAJOR)
+ return false;
+
+ unsigned Number;
+ switch (minor(ObjInfo.Attr.u.Unix.Device))
+ {
+ case 0: case 1: case 2: case 3:
+ Number = minor(ObjInfo.Attr.u.Unix.Device);
+ break;
+ case 128: case 129: case 130: case 131:
+ Number = minor(ObjInfo.Attr.u.Unix.Device) - 128 + 4;
+ break;
+ default:
+ return false;
+ }
+
+ floppy_drive_name szName;
+ if (!floppyGetName(pcszNode, Number, szName))
+ return false;
+ floppyCreateDeviceStrings(szName, Number, pszDesc, cbDesc, pszUdi, cbUdi);
+ }
+ return true;
+}
+
+
+int VBoxMainDriveInfo::updateDVDs() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int vrc;
+ try
+ {
+ mDVDList.clear();
+ /* Always allow the user to override our auto-detection using an
+ * environment variable. */
+ bool fSuccess = false; /* Have we succeeded in finding anything yet? */
+ vrc = getDriveInfoFromEnv("VBOX_CDROM", &mDVDList, true /* isDVD */, &fSuccess);
+ setNoProbe(false);
+ if (RT_SUCCESS(vrc) && (!fSuccess || testing()))
+ vrc = getDriveInfoFromSysfs(&mDVDList, DVD, &fSuccess);
+ if (RT_SUCCESS(vrc) && testing())
+ {
+ setNoProbe(true);
+ vrc = getDriveInfoFromSysfs(&mDVDList, DVD, &fSuccess);
+ }
+ }
+ catch (std::bad_alloc &e)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+int VBoxMainDriveInfo::updateFloppies() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int vrc;
+ try
+ {
+ mFloppyList.clear();
+ bool fSuccess = false; /* Have we succeeded in finding anything yet? */
+ vrc = getDriveInfoFromEnv("VBOX_FLOPPY", &mFloppyList, false /* isDVD */, &fSuccess);
+ setNoProbe(false);
+ if (RT_SUCCESS(vrc) && (!fSuccess || testing()))
+ vrc = getDriveInfoFromSysfs(&mFloppyList, Floppy, &fSuccess);
+ if (RT_SUCCESS(vrc) && testing())
+ {
+ setNoProbe(true);
+ vrc = getDriveInfoFromSysfs(&mFloppyList, Floppy, &fSuccess);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+int VBoxMainDriveInfo::updateFixedDrives() RT_NOEXCEPT
+{
+ LogFlowThisFunc(("entered\n"));
+ int vrc;
+ try
+ {
+ mFixedDriveList.clear();
+ setNoProbe(false);
+ bool fSuccess = false; /* Have we succeeded in finding anything yet? */
+ vrc = getDriveInfoFromSysfs(&mFixedDriveList, FixedDisk, &fSuccess);
+ if (RT_SUCCESS(vrc) && testing())
+ {
+ setNoProbe(true);
+ vrc = getDriveInfoFromSysfs(&mFixedDriveList, FixedDisk, &fSuccess);
+ }
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ LogFlowThisFunc(("vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Extract the names of drives from an environment variable and add them to a
+ * list if they are valid.
+ *
+ * @returns iprt status code
+ * @param pcszVar the name of the environment variable. The variable
+ * value should be a list of device node names, separated
+ * by ':' characters.
+ * @param pList the list to append the drives found to
+ * @param isDVD are we looking for DVD drives or for floppies?
+ * @param pfSuccess this will be set to true if we found at least one drive
+ * and to false otherwise. Optional.
+ *
+ * @note This is duplicated in HostHardwareFreeBSD.cpp.
+ */
+static int getDriveInfoFromEnv(const char *pcszVar, DriveInfoList *pList, bool isDVD, bool *pfSuccess) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pcszVar, VERR_INVALID_POINTER);
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER);
+ LogFlowFunc(("pcszVar=%s, pList=%p, isDVD=%d, pfSuccess=%p\n", pcszVar, pList, isDVD, pfSuccess));
+ int vrc = VINF_SUCCESS;
+ bool success = false;
+ char *pszFreeMe = RTEnvDupEx(RTENV_DEFAULT, pcszVar);
+
+ try
+ {
+ char *pszCurrent = pszFreeMe;
+ while (pszCurrent && *pszCurrent != '\0')
+ {
+ char *pszNext = strchr(pszCurrent, ':');
+ if (pszNext)
+ *pszNext++ = '\0';
+
+ char szReal[RTPATH_MAX];
+ char szDesc[256], szUdi[256];
+ if ( RT_SUCCESS(RTPathReal(pszCurrent, szReal, sizeof(szReal)))
+ && devValidateDevice(szReal, isDVD, NULL, szDesc, sizeof(szDesc), szUdi, sizeof(szUdi)))
+ {
+ pList->push_back(DriveInfo(szReal, szUdi, szDesc));
+ success = true;
+ }
+ pszCurrent = pszNext;
+ }
+ if (pfSuccess != NULL)
+ *pfSuccess = success;
+ }
+ catch (std::bad_alloc &)
+ {
+ vrc = VERR_NO_MEMORY;
+ }
+ RTStrFree(pszFreeMe);
+ LogFlowFunc(("vrc=%Rrc, success=%d\n", vrc, success));
+ return vrc;
+}
+
+
+class SysfsBlockDev
+{
+public:
+ SysfsBlockDev(const char *pcszName, SysfsWantDevice_T wantDevice) RT_NOEXCEPT
+ : mpcszName(pcszName), mWantDevice(wantDevice), misConsistent(true), misValid(false)
+ {
+ if (findDeviceNode())
+ {
+ switch (mWantDevice)
+ {
+ case DVD: validateAndInitForDVD(); break;
+ case Floppy: validateAndInitForFloppy(); break;
+ default: validateAndInitForFixedDisk(); break;
+ }
+ }
+ }
+private:
+ /** The name of the subdirectory of /sys/block for this device */
+ const char *mpcszName;
+ /** Are we looking for a floppy, a DVD or a fixed disk device? */
+ SysfsWantDevice_T mWantDevice;
+ /** The device node for the device */
+ char mszNode[RTPATH_MAX];
+ /** Does the sysfs entry look like we expect it too? This is a canary
+ * for future sysfs ABI changes. */
+ bool misConsistent;
+ /** Is this entry a valid specimen of what we are looking for? */
+ bool misValid;
+ /** Human readable drive description string */
+ char mszDesc[256];
+ /** Unique identifier for the drive. Should be identical to hal's UDI for
+ * the device. May not be unique for two identical drives. */
+ char mszUdi[256];
+private:
+ /* Private methods */
+
+ /**
+ * Fill in the device node member based on the /sys/block subdirectory.
+ * @returns boolean success value
+ */
+ bool findDeviceNode() RT_NOEXCEPT
+ {
+ dev_t dev = 0;
+ int vrc = RTLinuxSysFsReadDevNumFile(&dev, "block/%s/dev", mpcszName);
+ if (RT_FAILURE(vrc) || dev == 0)
+ {
+ misConsistent = false;
+ return false;
+ }
+ vrc = RTLinuxCheckDevicePath(dev, RTFS_TYPE_DEV_BLOCK, mszNode, sizeof(mszNode), "%s", mpcszName);
+ return RT_SUCCESS(vrc);
+ }
+
+ /** Check whether the sysfs block entry is valid for a DVD device and
+ * initialise the string data members for the object. We try to get all
+ * the information we need from sysfs if possible, to avoid unnecessarily
+ * poking the device, and if that fails we fall back to an SCSI INQUIRY
+ * command. */
+ void validateAndInitForDVD() RT_NOEXCEPT
+ {
+ int64_t type = 0;
+ int vrc = RTLinuxSysFsReadIntFile(10, &type, "block/%s/device/type", mpcszName);
+ if (RT_SUCCESS(vrc) && type != TYPE_ROM)
+ return;
+ if (type == TYPE_ROM)
+ {
+ char szVendor[128];
+ vrc = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor), NULL, "block/%s/device/vendor", mpcszName);
+ if (RT_SUCCESS(vrc))
+ {
+ char szModel[128];
+ vrc = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel), NULL, "block/%s/device/model", mpcszName);
+ if (RT_SUCCESS(vrc))
+ {
+ misValid = true;
+ dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ return;
+ }
+ }
+ }
+ if (!noProbe())
+ probeAndInitForDVD();
+ }
+
+ /** Try to find out whether a device is a DVD drive by sending it an
+ * SCSI INQUIRY command. If it is, initialise the string and validity
+ * data members for the object based on the returned data.
+ */
+ void probeAndInitForDVD() RT_NOEXCEPT
+ {
+ AssertReturnVoid(mszNode[0] != '\0');
+ uint8_t bType = 0;
+ char szVendor[128] = "";
+ char szModel[128] = "";
+ int vrc = cdromDoInquiry(mszNode, &bType, szVendor, sizeof(szVendor), szModel, sizeof(szModel));
+ if (RT_SUCCESS(vrc) && bType == TYPE_ROM)
+ {
+ misValid = true;
+ dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ }
+ }
+
+ /** Check whether the sysfs block entry is valid for a floppy device and
+ * initialise the string data members for the object. Since we only
+ * support floppies using the basic "floppy" driver, we check the driver
+ * using the entry name and a driver-specific ioctl. */
+ void validateAndInitForFloppy() RT_NOEXCEPT
+ {
+ floppy_drive_name szName;
+ char szDriver[8];
+ if ( mpcszName[0] != 'f'
+ || mpcszName[1] != 'd'
+ || mpcszName[2] < '0'
+ || mpcszName[2] > '7'
+ || mpcszName[3] != '\0')
+ return;
+ bool fHaveName = false;
+ if (!noProbe())
+ fHaveName = floppyGetName(mszNode, mpcszName[2] - '0', szName);
+ int vrc = RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), NULL, "block/%s/%s", mpcszName, "device/driver");
+ if (RT_SUCCESS(vrc))
+ {
+ if (RTStrCmp(szDriver, "floppy"))
+ return;
+ }
+ else if (!fHaveName)
+ return;
+ floppyCreateDeviceStrings(fHaveName ? szName : NULL,
+ mpcszName[2] - '0', mszDesc,
+ sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ misValid = true;
+ }
+
+ void validateAndInitForFixedDisk() RT_NOEXCEPT
+ {
+ /*
+ * For current task only device path is needed. Therefore, device probing
+ * is skipped and other fields are empty if there aren't files in the
+ * device entry.
+ */
+ int64_t type = 0;
+ int vrc = RTLinuxSysFsReadIntFile(10, &type, "block/%s/device/type", mpcszName);
+ if (!RT_SUCCESS(vrc) || type != TYPE_DISK)
+ {
+ if (noProbe() || !probeNVME(mszNode))
+ {
+ char szDriver[16];
+ vrc = RTLinuxSysFsGetLinkDest(szDriver, sizeof(szDriver), NULL, "block/%s/%s", mpcszName, "device/device/driver");
+ if (RT_FAILURE(vrc) || RTStrCmp(szDriver, "nvme"))
+ return;
+ }
+ }
+ char szVendor[128];
+ char szModel[128];
+ size_t cbRead = 0;
+ vrc = RTLinuxSysFsReadStrFile(szVendor, sizeof(szVendor), &cbRead, "block/%s/device/vendor", mpcszName);
+ szVendor[cbRead] = '\0';
+ /* Assume the model is always present. Vendor is not present for NVME disks */
+ cbRead = 0;
+ vrc = RTLinuxSysFsReadStrFile(szModel, sizeof(szModel), &cbRead, "block/%s/device/model", mpcszName);
+ szModel[cbRead] = '\0';
+ if (RT_SUCCESS(vrc))
+ {
+ misValid = true;
+ dvdCreateDeviceStrings(szVendor, szModel, mszDesc, sizeof(mszDesc), mszUdi, sizeof(mszUdi));
+ }
+ }
+
+public:
+ bool isConsistent() const RT_NOEXCEPT
+ {
+ return misConsistent;
+ }
+ bool isValid() const RT_NOEXCEPT
+ {
+ return misValid;
+ }
+ const char *getDesc() const RT_NOEXCEPT
+ {
+ return mszDesc;
+ }
+ const char *getUdi() const RT_NOEXCEPT
+ {
+ return mszUdi;
+ }
+ const char *getNode() const RT_NOEXCEPT
+ {
+ return mszNode;
+ }
+};
+
+
+/**
+ * Helper function to query the sysfs subsystem for information about DVD
+ * drives attached to the system.
+ * @returns iprt status code
+ * @param pList where to add information about the drives detected
+ * @param wantDevice The kind of devices we're looking for.
+ * @param pfSuccess Did we find anything?
+ *
+ * @returns IPRT status code
+ * @throws Nothing.
+ */
+static int getDriveInfoFromSysfs(DriveInfoList *pList, SysfsWantDevice_T wantDevice, bool *pfSuccess) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pList, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfSuccess, VERR_INVALID_POINTER); /* Valid or Null */
+ LogFlowFunc (("pList=%p, wantDevice=%u, pfSuccess=%p\n",
+ pList, (unsigned)wantDevice, pfSuccess));
+ if (!RTPathExists("/sys"))
+ return VINF_SUCCESS;
+
+ bool fSuccess = true;
+ unsigned cFound = 0;
+ RTDIR hDir = NIL_RTDIR;
+ int vrc = RTDirOpen(&hDir, "/sys/block");
+ /* This might mean that sysfs semantics have changed */
+ AssertReturn(vrc != VERR_FILE_NOT_FOUND, VINF_SUCCESS);
+ if (RT_SUCCESS(vrc))
+ {
+ for (;;)
+ {
+ RTDIRENTRY entry;
+ vrc = RTDirRead(hDir, &entry, NULL);
+ Assert(vrc != VERR_BUFFER_OVERFLOW); /* Should never happen... */
+ if (RT_FAILURE(vrc)) /* Including overflow and no more files */
+ break;
+ if (entry.szName[0] == '.')
+ continue;
+ SysfsBlockDev dev(entry.szName, wantDevice);
+ /* This might mean that sysfs semantics have changed */
+ AssertBreakStmt(dev.isConsistent(), fSuccess = false);
+ if (!dev.isValid())
+ continue;
+ try
+ {
+ pList->push_back(DriveInfo(dev.getNode(), dev.getUdi(), dev.getDesc()));
+ }
+ catch (std::bad_alloc &e)
+ {
+ vrc = VERR_NO_MEMORY;
+ break;
+ }
+ ++cFound;
+ }
+ RTDirClose(hDir);
+ }
+ if (vrc == VERR_NO_MORE_FILES)
+ vrc = VINF_SUCCESS;
+ else if (RT_FAILURE(vrc))
+ /* Clean up again */
+ while (cFound-- > 0)
+ pList->pop_back();
+ if (pfSuccess)
+ *pfSuccess = fSuccess;
+ LogFlow (("vrc=%Rrc, fSuccess=%u\n", vrc, (unsigned)fSuccess));
+ return vrc;
+}
+
+
+/** Helper for readFilePathsFromDir(). Adds a path to the vector if it is not
+ * NULL and not a dotfile (".", "..", ".*"). */
+static int maybeAddPathToVector(const char *pcszPath, const char *pcszEntry, VECTOR_PTR(char *) *pvecpchDevs) RT_NOTHROW_DEF
+{
+ if (!pcszPath)
+ return 0;
+ if (pcszEntry[0] == '.')
+ return 0;
+ char *pszPath = RTStrDup(pcszPath);
+ if (pszPath)
+ {
+ int vrc = VEC_PUSH_BACK_PTR(pvecpchDevs, char *, pszPath);
+ if (RT_SUCCESS(vrc))
+ return 0;
+ }
+ return ENOMEM;
+}
+
+/**
+ * Helper for readFilePaths().
+ *
+ * Adds the entries from the open directory @a pDir to the vector @a pvecpchDevs
+ * using either the full path or the realpath() and skipping hidden files
+ * and files on which realpath() fails.
+ */
+static int readFilePathsFromDir(const char *pcszPath, DIR *pDir, VECTOR_PTR(char *) *pvecpchDevs, int withRealPath) RT_NOTHROW_DEF
+{
+ struct dirent entry, *pResult;
+ int err;
+
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ for (err = readdir_r(pDir, &entry, &pResult);
+ pResult != NULL && err == 0;
+ err = readdir_r(pDir, &entry, &pResult))
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic pop
+#endif
+ {
+ /* We (implicitly) require that PATH_MAX be defined */
+ char szPath[PATH_MAX + 1], szRealPath[PATH_MAX + 1], *pszPath;
+ if (snprintf(szPath, sizeof(szPath), "%s/%s", pcszPath,
+ entry.d_name) < 0)
+ return errno;
+ if (withRealPath)
+ pszPath = realpath(szPath, szRealPath);
+ else
+ pszPath = szPath;
+ if ((err = maybeAddPathToVector(pszPath, entry.d_name, pvecpchDevs)))
+ return err;
+ }
+ return err;
+}
+
+
+/**
+ * Helper for walkDirectory to dump the names of a directory's entries into a
+ * vector of char pointers.
+ *
+ * @returns zero on success or (positive) posix error value.
+ * @param pcszPath the path to dump.
+ * @param pvecpchDevs an empty vector of char pointers - must be cleaned up
+ * by the caller even on failure.
+ * @param withRealPath whether to canonicalise the filename with realpath
+ */
+static int readFilePaths(const char *pcszPath, VECTOR_PTR(char *) *pvecpchDevs, int withRealPath) RT_NOTHROW_DEF
+{
+ AssertPtrReturn(pvecpchDevs, EINVAL);
+ AssertReturn(VEC_SIZE_PTR(pvecpchDevs) == 0, EINVAL);
+ AssertPtrReturn(pcszPath, EINVAL);
+
+ DIR *pDir = opendir(pcszPath);
+ if (!pDir)
+ return RTErrConvertFromErrno(errno);
+ int err = readFilePathsFromDir(pcszPath, pDir, pvecpchDevs, withRealPath);
+ if (closedir(pDir) < 0 && !err)
+ err = errno;
+ return RTErrConvertFromErrno(err);
+}
+
+
+class hotplugNullImpl : public VBoxMainHotplugWaiterImpl
+{
+public:
+ hotplugNullImpl(const char *) {}
+ virtual ~hotplugNullImpl (void) {}
+ /** @copydoc VBoxMainHotplugWaiter::Wait */
+ virtual int Wait (RTMSINTERVAL cMillies)
+ {
+ NOREF(cMillies);
+ return VERR_NOT_SUPPORTED;
+ }
+ /** @copydoc VBoxMainHotplugWaiter::Interrupt */
+ virtual void Interrupt (void) {}
+ virtual int getStatus(void)
+ {
+ return VERR_NOT_SUPPORTED;
+ }
+
+};
+
+#ifdef VBOX_USB_WITH_SYSFS
+# ifdef VBOX_USB_WITH_INOTIFY
+/** Class wrapper around an inotify watch (or a group of them to be precise).
+ */
+typedef struct inotifyWatch
+{
+ /** The native handle of the inotify fd. */
+ int mhInotify;
+} inotifyWatch;
+
+/** The flags we pass to inotify - modify, create, delete, change permissions
+ */
+#define MY_IN_FLAGS (IN_CREATE | IN_DELETE | IN_MODIFY | IN_ATTRIB)
+AssertCompile(MY_IN_FLAGS == 0x306);
+
+static int iwAddWatch(inotifyWatch *pSelf, const char *pcszPath)
+{
+ errno = 0;
+ if ( inotify_add_watch(pSelf->mhInotify, pcszPath, MY_IN_FLAGS) >= 0
+ || errno == EACCES)
+ return VINF_SUCCESS;
+ /* Other errors listed in the manpage can be treated as fatal */
+ return RTErrConvertFromErrno(errno);
+}
+
+/** Object initialisation */
+static int iwInit(inotifyWatch *pSelf)
+{
+ AssertPtr(pSelf);
+ pSelf->mhInotify = -1;
+ int fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
+ if (fd >= 0)
+ {
+ pSelf->mhInotify = fd;
+ return VINF_SUCCESS;
+ }
+ Assert(errno > 0);
+ return RTErrConvertFromErrno(errno);
+}
+
+static void iwTerm(inotifyWatch *pSelf)
+{
+ AssertPtrReturnVoid(pSelf);
+ if (pSelf->mhInotify != -1)
+ {
+ close(pSelf->mhInotify);
+ pSelf->mhInotify = -1;
+ }
+}
+
+static int iwGetFD(inotifyWatch *pSelf)
+{
+ AssertPtrReturn(pSelf, -1);
+ return pSelf->mhInotify;
+}
+
+# define SYSFS_WAKEUP_STRING "Wake up!"
+
+class hotplugInotifyImpl : public VBoxMainHotplugWaiterImpl
+{
+ /** Pipe used to interrupt wait(), the read end. */
+ int mhWakeupPipeR;
+ /** Pipe used to interrupt wait(), the write end. */
+ int mhWakeupPipeW;
+ /** The inotify watch set */
+ inotifyWatch mWatches;
+ /** Flag to mark that the Wait() method is currently being called, and to
+ * ensure that it isn't called multiple times in parallel. */
+ volatile uint32_t mfWaiting;
+ /** The root of the USB devices tree. */
+ const char *mpcszDevicesRoot;
+ /** iprt result code from object initialisation. Should be AssertReturn-ed
+ * on at the start of all methods. I went this way because I didn't want
+ * to deal with exceptions. */
+ int mStatus;
+ /** ID values associates with the wakeup pipe and the FAM socket for polling
+ */
+ enum
+ {
+ RPIPE_ID = 0,
+ INOTIFY_ID,
+ MAX_POLLID
+ };
+
+ /** Clean up any resources in use, gracefully skipping over any which have
+ * not yet been allocated or already cleaned up. Intended to be called
+ * from the destructor or after a failed initialisation. */
+ void term(void);
+
+ int drainInotify();
+
+ /** Read the wakeup string from the wakeup pipe */
+ int drainWakeupPipe(void);
+public:
+ hotplugInotifyImpl(const char *pcszDevicesRoot);
+ virtual ~hotplugInotifyImpl(void)
+ {
+ term();
+#ifdef DEBUG
+ /** The first call to term should mark all resources as freed, so this
+ * should be a semantic no-op. */
+ term();
+#endif
+ }
+ /** Is inotify available and working on this system? If so we expect that
+ * this implementation will be usable. */
+ static bool Available(void)
+ {
+ int const fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
+ if (fd >= 0)
+ close(fd);
+ return fd >= 0;
+ }
+
+ virtual int getStatus(void)
+ {
+ return mStatus;
+ }
+
+ /** @copydoc VBoxMainHotplugWaiter::Wait */
+ virtual int Wait(RTMSINTERVAL);
+ /** @copydoc VBoxMainHotplugWaiter::Interrupt */
+ virtual void Interrupt(void);
+};
+
+/** Simplified version of RTPipeCreate */
+static int pipeCreateSimple(int *phPipeRead, int *phPipeWrite)
+{
+ AssertPtrReturn(phPipeRead, VERR_INVALID_POINTER);
+ AssertPtrReturn(phPipeWrite, VERR_INVALID_POINTER);
+
+ /*
+ * Create the pipe and set the close-on-exec flag.
+ * ASSUMES we're building and running on Linux 2.6.27 or later (pipe2).
+ */
+ int aFds[2] = {-1, -1};
+ if (pipe2(aFds, O_CLOEXEC))
+ return RTErrConvertFromErrno(errno);
+
+ *phPipeRead = aFds[0];
+ *phPipeWrite = aFds[1];
+
+ /*
+ * Before we leave, make sure to shut up SIGPIPE.
+ */
+ signal(SIGPIPE, SIG_IGN);
+ return VINF_SUCCESS;
+}
+
+hotplugInotifyImpl::hotplugInotifyImpl(const char *pcszDevicesRoot)
+ : mhWakeupPipeR(-1), mhWakeupPipeW(-1), mfWaiting(0)
+ , mpcszDevicesRoot(pcszDevicesRoot), mStatus(VERR_WRONG_ORDER)
+{
+# ifdef DEBUG
+ /* Excercise the code path (term() on a not-fully-initialised object) as
+ * well as we can. On an uninitialised object this method is a semantic
+ * no-op. */
+ mWatches.mhInotify = -1; /* term will access this variable */
+ term();
+ /* For now this probing method should only be used if nothing else is
+ * available */
+# endif
+
+ int vrc = iwInit(&mWatches);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = iwAddWatch(&mWatches, mpcszDevicesRoot);
+ if (RT_SUCCESS(vrc))
+ vrc = pipeCreateSimple(&mhWakeupPipeR, &mhWakeupPipeW);
+ }
+ mStatus = vrc;
+ if (RT_FAILURE(vrc))
+ term();
+}
+
+void hotplugInotifyImpl::term(void)
+{
+ /** This would probably be a pending segfault, so die cleanly */
+ AssertRelease(!mfWaiting);
+ if (mhWakeupPipeR != -1)
+ {
+ close(mhWakeupPipeR);
+ mhWakeupPipeR = -1;
+ }
+ if (mhWakeupPipeW != -1)
+ {
+ close(mhWakeupPipeW);
+ mhWakeupPipeW = -1;
+ }
+ iwTerm(&mWatches);
+}
+
+int hotplugInotifyImpl::drainInotify()
+{
+ char chBuf[RTPATH_MAX + 256]; /* Should always be big enough */
+ ssize_t cchRead;
+
+ AssertRCReturn(mStatus, VERR_WRONG_ORDER);
+ errno = 0;
+ do
+ cchRead = read(iwGetFD(&mWatches), chBuf, sizeof(chBuf));
+ while (cchRead > 0);
+ if (cchRead == 0)
+ return VINF_SUCCESS;
+ if ( cchRead < 0
+ && ( errno == EAGAIN
+#if EAGAIN != EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ ))
+ return VINF_SUCCESS;
+ Assert(errno > 0);
+ return RTErrConvertFromErrno(errno);
+}
+
+int hotplugInotifyImpl::drainWakeupPipe(void)
+{
+ char szBuf[sizeof(SYSFS_WAKEUP_STRING)];
+ ssize_t cbRead;
+
+ AssertRCReturn(mStatus, VERR_WRONG_ORDER);
+ cbRead = read(mhWakeupPipeR, szBuf, sizeof(szBuf));
+ Assert(cbRead > 0);
+ NOREF(cbRead);
+ return VINF_SUCCESS;
+}
+
+int hotplugInotifyImpl::Wait(RTMSINTERVAL aMillies)
+{
+ AssertRCReturn(mStatus, VERR_WRONG_ORDER);
+ bool fEntered = ASMAtomicCmpXchgU32(&mfWaiting, 1, 0);
+ AssertReturn(fEntered, VERR_WRONG_ORDER);
+
+ VECTOR_PTR(char *) vecpchDevs;
+ VEC_INIT_PTR(&vecpchDevs, char *, RTStrFree);
+ int vrc = readFilePaths(mpcszDevicesRoot, &vecpchDevs, false);
+ if (RT_SUCCESS(vrc))
+ {
+ char **ppszEntry;
+ VEC_FOR_EACH(&vecpchDevs, char *, ppszEntry)
+ if (RT_FAILURE(vrc = iwAddWatch(&mWatches, *ppszEntry)))
+ break;
+
+ if (RT_SUCCESS(vrc))
+ {
+ struct pollfd pollFD[MAX_POLLID];
+ pollFD[RPIPE_ID].fd = mhWakeupPipeR;
+ pollFD[RPIPE_ID].events = POLLIN;
+ pollFD[INOTIFY_ID].fd = iwGetFD(&mWatches);
+ pollFD[INOTIFY_ID].events = POLLIN | POLLERR | POLLHUP;
+ errno = 0;
+ int cPolled = poll(pollFD, RT_ELEMENTS(pollFD), aMillies);
+ if (cPolled < 0)
+ {
+ Assert(errno > 0);
+ vrc = RTErrConvertFromErrno(errno);
+ }
+ else if (pollFD[RPIPE_ID].revents)
+ {
+ vrc = drainWakeupPipe();
+ if (RT_SUCCESS(vrc))
+ vrc = VERR_INTERRUPTED;
+ }
+ else if ((pollFD[INOTIFY_ID].revents))
+ {
+ if (cPolled == 1)
+ vrc = drainInotify();
+ else
+ AssertFailedStmt(vrc = VERR_INTERNAL_ERROR);
+ }
+ else
+ {
+ if (errno == 0 && cPolled == 0)
+ vrc = VERR_TIMEOUT;
+ else
+ AssertFailedStmt(vrc = VERR_INTERNAL_ERROR);
+ }
+ }
+ }
+
+ mfWaiting = 0;
+ VEC_CLEANUP_PTR(&vecpchDevs);
+ return vrc;
+}
+
+void hotplugInotifyImpl::Interrupt(void)
+{
+ AssertRCReturnVoid(mStatus);
+ ssize_t cbWritten = write(mhWakeupPipeW, SYSFS_WAKEUP_STRING,
+ sizeof(SYSFS_WAKEUP_STRING));
+ if (cbWritten > 0)
+ fsync(mhWakeupPipeW);
+}
+
+# endif /* VBOX_USB_WITH_INOTIFY */
+#endif /* VBOX_USB_WTH_SYSFS */
+
+VBoxMainHotplugWaiter::VBoxMainHotplugWaiter(const char *pcszDevicesRoot)
+{
+ try
+ {
+#ifdef VBOX_USB_WITH_SYSFS
+# ifdef VBOX_USB_WITH_INOTIFY
+ if (hotplugInotifyImpl::Available())
+ {
+ mImpl = new hotplugInotifyImpl(pcszDevicesRoot);
+ return;
+ }
+# endif /* VBOX_USB_WITH_INOTIFY */
+#endif /* VBOX_USB_WITH_SYSFS */
+ mImpl = new hotplugNullImpl(pcszDevicesRoot);
+ }
+ catch (std::bad_alloc &e)
+ { }
+}
diff --git a/src/VBox/Main/src-server/linux/HostPowerLinux.cpp b/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
new file mode 100644
index 00000000..c27b149a
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/HostPowerLinux.cpp
@@ -0,0 +1,198 @@
+/* $Id: HostPowerLinux.cpp $ */
+/** @file
+ * VirtualBox interface to host's power notification service
+ */
+
+/*
+ * 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>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_HOST
+#include "HostPower.h"
+#include "LoggingNew.h"
+
+#include <iprt/asm.h>
+#include <iprt/power.h>
+#include <iprt/time.h>
+
+static bool checkDBusError(DBusError *pError, DBusConnection **pConnection)
+{
+ if (dbus_error_is_set(pError))
+ {
+ LogRel(("HostPowerServiceLinux: DBus connection Error (%s)\n", pError->message));
+ dbus_error_free(pError);
+ if (*pConnection)
+ {
+ /* Close the socket or whatever underlying the connection. */
+ dbus_connection_close(*pConnection);
+ /* Free in-process resources used for the now-closed connection. */
+ dbus_connection_unref(*pConnection);
+ *pConnection = NULL;
+ }
+ return true;
+ }
+ return false;
+}
+
+HostPowerServiceLinux::HostPowerServiceLinux(VirtualBox *aVirtualBox)
+ : HostPowerService(aVirtualBox)
+ , mThread(NIL_RTTHREAD)
+ , mpConnection(NULL)
+{
+ DBusError error;
+
+ int vrc = RTDBusLoadLib();
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("HostPowerServiceLinux: DBus library not found. Service not available.\n"));
+ return;
+ }
+ dbus_error_init(&error);
+ /* Connect to the DBus. The connection will be not shared with any other
+ * in-process callers of dbus_bus_get(). This is considered wasteful (see
+ * API documentation) but simplifies our code, specifically shutting down.
+ * The session bus allows up to 100000 connections per user as it "is just
+ * running as the user anyway" (see session.conf.in in the DBus sources). */
+ mpConnection = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
+ if (checkDBusError(&error, &mpConnection))
+ return;
+ /* We do not want to exit(1) if the connection is broken. */
+ dbus_connection_set_exit_on_disconnect(mpConnection, FALSE);
+ /* Tell the bus to wait for the sleep signal(s). */
+ /* The current systemd-logind interface. */
+ dbus_bus_add_match(mpConnection, "type='signal',interface='org.freedesktop.login1.Manager'", &error);
+ /* The previous UPower interfaces (2010 - ca 2013). */
+ dbus_bus_add_match(mpConnection, "type='signal',interface='org.freedesktop.UPower'", &error);
+ dbus_connection_flush(mpConnection);
+ if (checkDBusError(&error, &mpConnection))
+ return;
+
+ /* Grab another reference so that both the destruct and thread each has one: */
+ DBusConnection *pForAssert = dbus_connection_ref(mpConnection);
+ Assert(pForAssert == mpConnection); RT_NOREF(pForAssert);
+
+ /* Create the new worker thread. */
+ vrc = RTThreadCreate(&mThread, HostPowerServiceLinux::powerChangeNotificationThread, this, 0 /* cbStack */,
+ RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "MainPower");
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("HostPowerServiceLinux: RTThreadCreate failed with %Rrc\n", vrc));
+ dbus_connection_unref(mpConnection);
+ }
+}
+
+
+HostPowerServiceLinux::~HostPowerServiceLinux()
+{
+ /* Closing the connection should cause the event loop to exit. */
+ LogFunc((": Stopping thread\n"));
+ if (mpConnection)
+ {
+ dbus_connection_close(mpConnection);
+ dbus_connection_unref(mpConnection);
+ mpConnection = NULL;
+ }
+
+ if (mThread != NIL_RTTHREAD)
+ {
+ /* HACK ALERT! This poke call should _not_ be necessary as dbus_connection_close()
+ should close the socket and force the poll/dbus_connection_read_write
+ call to return with POLLHUP/FALSE. It does so when stepping it in the
+ debugger, but not in real life (asan build; dbus-1.12.20-1.fc32; linux 5.8).
+
+ Poking the thread is a crude crude way to wake it up from whatever
+ stuff it's actually blocked on and realize that the connection has
+ been dropped. */
+
+ uint64_t msElapsed = RTTimeMilliTS();
+ int vrc = RTThreadWait(mThread, 10 /*ms*/, NULL);
+ if (RT_FAILURE(vrc))
+ {
+ RTThreadPoke(mThread);
+ vrc = RTThreadWait(mThread, RT_MS_5SEC, NULL);
+ }
+ msElapsed = RTTimeMilliTS() - msElapsed;
+ if (vrc != VINF_SUCCESS)
+ LogRelThisFunc(("RTThreadWait() failed after %llu ms: %Rrc\n", msElapsed, vrc));
+ mThread = NIL_RTTHREAD;
+ }
+}
+
+
+DECLCALLBACK(int) HostPowerServiceLinux::powerChangeNotificationThread(RTTHREAD hThreadSelf, void *pInstance)
+{
+ NOREF(hThreadSelf);
+ HostPowerServiceLinux *pPowerObj = static_cast<HostPowerServiceLinux *>(pInstance);
+ DBusConnection *pConnection = pPowerObj->mpConnection;
+
+ Log(("HostPowerServiceLinux: Thread started\n"));
+ while (dbus_connection_read_write(pConnection, -1))
+ {
+ DBusMessage *pMessage = NULL;
+
+ for (;;)
+ {
+ pMessage = dbus_connection_pop_message(pConnection);
+ if (pMessage == NULL)
+ break;
+
+ /* The systemd-logind interface notification. */
+ DBusMessageIter args;
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.login1.Manager", "PrepareForSleep")
+ && dbus_message_iter_init(pMessage, &args)
+ && dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_BOOLEAN)
+ {
+ dbus_bool_t fSuspend;
+ dbus_message_iter_get_basic(&args, &fSuspend);
+
+ /* Trinary operator does not work here as Reason_... is an
+ * anonymous enum. */
+ if (fSuspend)
+ pPowerObj->notify(Reason_HostSuspend);
+ else
+ pPowerObj->notify(Reason_HostResume);
+ }
+
+ /* The UPowerd interface notifications. Sleeping is the older one,
+ * NotifySleep the newer. This gives us one second grace before the
+ * suspend triggers. */
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "Sleeping")
+ || dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "NotifySleep"))
+ pPowerObj->notify(Reason_HostSuspend);
+ if ( dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "Resuming")
+ || dbus_message_is_signal(pMessage, "org.freedesktop.UPower", "NotifyResume"))
+ pPowerObj->notify(Reason_HostResume);
+
+ /* Free local resources held for the message. */
+ dbus_message_unref(pMessage);
+ }
+ }
+
+ /* Close the socket or whatever underlying the connection. */
+ dbus_connection_close(pConnection);
+
+ /* Free in-process resources used for the now-closed connection. */
+ dbus_connection_unref(pConnection);
+
+ Log(("HostPowerServiceLinux: Exiting thread\n"));
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Main/src-server/linux/Makefile.kup b/src/VBox/Main/src-server/linux/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/Makefile.kup
diff --git a/src/VBox/Main/src-server/linux/NetIf-linux.cpp b/src/VBox/Main/src-server/linux/NetIf-linux.cpp
new file mode 100644
index 00000000..f4a99eeb
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/NetIf-linux.cpp
@@ -0,0 +1,329 @@
+/* $Id: NetIf-linux.cpp $ */
+/** @file
+ * Main - NetIfList, Linux implementation.
+ */
+
+/*
+ * 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 LOG_GROUP LOG_GROUP_MAIN_HOST
+
+#include <iprt/errcore.h>
+#include <list>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <linux/wireless.h>
+#include <net/if_arp.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <iprt/asm.h>
+
+#include "HostNetworkInterfaceImpl.h"
+#include "netif.h"
+#include "LoggingNew.h"
+
+/**
+ * Obtain the name of the interface used for default routing.
+ *
+ * NOTE: There is a copy in Devices/Network/testcase/tstIntNet-1.cpp.
+ *
+ * @returns VBox status code.
+ *
+ * @param pszName The buffer where to put the name.
+ * @param cbName Size of of the destination buffer.
+ */
+static int getDefaultIfaceName(char *pszName, size_t cbName)
+{
+ FILE *fp = fopen("/proc/net/route", "r");
+ char szBuf[1024];
+ char szIfName[17];
+ uint32_t uAddr;
+ uint32_t uGateway;
+ uint32_t uMask;
+ int iTmp;
+ unsigned uFlags;
+
+ if (fp)
+ {
+ while (fgets(szBuf, sizeof(szBuf)-1, fp))
+ {
+ int n = sscanf(szBuf, "%16s %x %x %x %d %d %d %x %d %d %d\n",
+ szIfName, &uAddr, &uGateway, &uFlags, &iTmp, &iTmp, &iTmp,
+ &uMask, &iTmp, &iTmp, &iTmp);
+ if (n < 10 || !(uFlags & RTF_UP))
+ continue;
+
+ if (uAddr == 0 && uMask == 0)
+ {
+ fclose(fp);
+ szIfName[sizeof(szIfName) - 1] = '\0';
+ return RTStrCopy(pszName, cbName, szIfName);
+ }
+ }
+ fclose(fp);
+ }
+ return VERR_INTERNAL_ERROR;
+}
+
+static uint32_t getInterfaceSpeed(const char *pszName)
+{
+ /*
+ * I wish I could do simple ioctl here, but older kernels require root
+ * privileges for any ethtool commands.
+ */
+ char szBuf[256];
+ uint32_t uSpeed = 0;
+ /* First, we try to retrieve the speed via sysfs. */
+ RTStrPrintf(szBuf, sizeof(szBuf), "/sys/class/net/%s/speed", pszName);
+ FILE *fp = fopen(szBuf, "r");
+ if (fp)
+ {
+ if (fscanf(fp, "%u", &uSpeed) != 1)
+ uSpeed = 0;
+ fclose(fp);
+ }
+ if (uSpeed == 10)
+ {
+ /* Check the cable is plugged in at all */
+ unsigned uCarrier = 0;
+ RTStrPrintf(szBuf, sizeof(szBuf), "/sys/class/net/%s/carrier", pszName);
+ fp = fopen(szBuf, "r");
+ if (fp)
+ {
+ if (fscanf(fp, "%u", &uCarrier) != 1 || uCarrier == 0)
+ uSpeed = 0;
+ fclose(fp);
+ }
+ }
+
+ if (uSpeed == 0)
+ {
+ /* Failed to get speed via sysfs, go to plan B. */
+ int vrc = NetIfAdpCtlOut(pszName, "speed", szBuf, sizeof(szBuf));
+ if (RT_SUCCESS(vrc))
+ uSpeed = RTStrToUInt32(szBuf);
+ }
+ return uSpeed;
+}
+
+static int getInterfaceInfo(int iSocket, const char *pszName, PNETIFINFO pInfo)
+{
+ // Zeroing out pInfo is a bad idea as it should contain both short and long names at
+ // this point. So make sure the structure is cleared by the caller if necessary!
+ // memset(pInfo, 0, sizeof(*pInfo));
+ struct ifreq Req;
+ RT_ZERO(Req);
+ RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pszName);
+ if (ioctl(iSocket, SIOCGIFHWADDR, &Req) >= 0)
+ {
+ switch (Req.ifr_hwaddr.sa_family)
+ {
+ case ARPHRD_ETHER:
+ pInfo->enmMediumType = NETIF_T_ETHERNET;
+ break;
+ default:
+ pInfo->enmMediumType = NETIF_T_UNKNOWN;
+ break;
+ }
+ /* Generate UUID from name and MAC address. */
+ RTUUID uuid;
+ RTUuidClear(&uuid);
+ memcpy(&uuid, Req.ifr_name, RT_MIN(sizeof(Req.ifr_name), sizeof(uuid)));
+ uuid.Gen.u8ClockSeqHiAndReserved = (uint8_t)((uuid.Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80);
+ uuid.Gen.u16TimeHiAndVersion = (uint16_t)((uuid.Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000);
+ memcpy(uuid.Gen.au8Node, &Req.ifr_hwaddr.sa_data, sizeof(uuid.Gen.au8Node));
+ pInfo->Uuid = uuid;
+
+ memcpy(&pInfo->MACAddress, Req.ifr_hwaddr.sa_data, sizeof(pInfo->MACAddress));
+
+ if (ioctl(iSocket, SIOCGIFADDR, &Req) >= 0)
+ memcpy(pInfo->IPAddress.au8,
+ &((struct sockaddr_in *)&Req.ifr_addr)->sin_addr.s_addr,
+ sizeof(pInfo->IPAddress.au8));
+
+ if (ioctl(iSocket, SIOCGIFNETMASK, &Req) >= 0)
+ memcpy(pInfo->IPNetMask.au8,
+ &((struct sockaddr_in *)&Req.ifr_addr)->sin_addr.s_addr,
+ sizeof(pInfo->IPNetMask.au8));
+
+ if (ioctl(iSocket, SIOCGIFFLAGS, &Req) >= 0)
+ pInfo->enmStatus = Req.ifr_flags & IFF_UP ? NETIF_S_UP : NETIF_S_DOWN;
+
+ struct iwreq WRq;
+ RT_ZERO(WRq);
+ RTStrCopy(WRq.ifr_name, sizeof(WRq.ifr_name), pszName);
+ pInfo->fWireless = ioctl(iSocket, SIOCGIWNAME, &WRq) >= 0;
+
+ FILE *fp = fopen("/proc/net/if_inet6", "r");
+ if (fp)
+ {
+ RTNETADDRIPV6 IPv6Address;
+ unsigned uIndex, uLength, uScope, uTmp;
+ char szName[30];
+ for (;;)
+ {
+ RT_ZERO(szName);
+ int n = fscanf(fp,
+ "%08x%08x%08x%08x"
+ " %02x %02x %02x %02x %20s\n",
+ &IPv6Address.au32[0], &IPv6Address.au32[1],
+ &IPv6Address.au32[2], &IPv6Address.au32[3],
+ &uIndex, &uLength, &uScope, &uTmp, szName);
+ if (n == EOF)
+ break;
+ if (n != 9 || uLength > 128)
+ {
+ Log(("getInterfaceInfo: Error while reading /proc/net/if_inet6, n=%d uLength=%u\n",
+ n, uLength));
+ break;
+ }
+ if (!strcmp(Req.ifr_name, szName))
+ {
+ pInfo->IPv6Address.au32[0] = htonl(IPv6Address.au32[0]);
+ pInfo->IPv6Address.au32[1] = htonl(IPv6Address.au32[1]);
+ pInfo->IPv6Address.au32[2] = htonl(IPv6Address.au32[2]);
+ pInfo->IPv6Address.au32[3] = htonl(IPv6Address.au32[3]);
+ RTNetPrefixToMaskIPv6(uLength, &pInfo->IPv6NetMask);
+ }
+ }
+ fclose(fp);
+ }
+ /*
+ * Don't even try to get speed for non-Ethernet interfaces, it only
+ * produces errors.
+ */
+ if (pInfo->enmMediumType == NETIF_T_ETHERNET)
+ pInfo->uSpeedMbits = getInterfaceSpeed(pszName);
+ else
+ pInfo->uSpeedMbits = 0;
+ }
+ return VINF_SUCCESS;
+}
+
+int NetIfList(std::list <ComObjPtr<HostNetworkInterface> > &list)
+{
+ char szDefaultIface[256];
+ int vrc = getDefaultIfaceName(szDefaultIface, sizeof(szDefaultIface));
+ if (RT_FAILURE(vrc))
+ {
+ Log(("NetIfList: Failed to find default interface.\n"));
+ szDefaultIface[0] = '\0';
+ }
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock >= 0)
+ {
+ FILE *fp = fopen("/proc/net/dev", "r");
+ if (fp)
+ {
+ char buf[256];
+ while (fgets(buf, sizeof(buf), fp))
+ {
+ char *pszEndOfName = strchr(buf, ':');
+ if (!pszEndOfName)
+ continue;
+ *pszEndOfName = 0;
+ size_t iFirstNonWS = strspn(buf, " ");
+ char *pszName = buf + iFirstNonWS;
+ NETIFINFO Info;
+ RT_ZERO(Info);
+ vrc = getInterfaceInfo(sock, pszName, &Info);
+ if (RT_FAILURE(vrc))
+ break;
+ if (Info.enmMediumType == NETIF_T_ETHERNET)
+ {
+ ComObjPtr<HostNetworkInterface> IfObj;
+ IfObj.createObject();
+
+ HostNetworkInterfaceType_T enmType;
+ if (strncmp(pszName, RT_STR_TUPLE("vboxnet")))
+ enmType = HostNetworkInterfaceType_Bridged;
+ else
+ enmType = HostNetworkInterfaceType_HostOnly;
+
+ if (SUCCEEDED(IfObj->init(pszName, enmType, &Info)))
+ {
+ if (strcmp(pszName, szDefaultIface) == 0)
+ list.push_front(IfObj);
+ else
+ list.push_back(IfObj);
+ }
+ }
+
+ }
+ fclose(fp);
+ }
+ close(sock);
+ }
+ else
+ vrc = VERR_INTERNAL_ERROR;
+
+ return vrc;
+}
+
+int NetIfGetConfigByName(PNETIFINFO pInfo)
+{
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return VERR_NOT_IMPLEMENTED;
+ int vrc = getInterfaceInfo(sock, pInfo->szShortName, pInfo);
+ close(sock);
+ return vrc;
+}
+
+/**
+ * Retrieve the physical link speed in megabits per second. If the interface is
+ * not up or otherwise unavailable the zero speed is returned.
+ *
+ * @returns VBox status code.
+ *
+ * @param pcszIfName Interface name.
+ * @param puMbits Where to store the link speed.
+ */
+int NetIfGetLinkSpeed(const char *pcszIfName, uint32_t *puMbits)
+{
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return VERR_OUT_OF_RESOURCES;
+ struct ifreq Req;
+ RT_ZERO(Req);
+ RTStrCopy(Req.ifr_name, sizeof(Req.ifr_name), pcszIfName);
+ if (ioctl(sock, SIOCGIFHWADDR, &Req) >= 0)
+ {
+ if (ioctl(sock, SIOCGIFFLAGS, &Req) >= 0)
+ if (Req.ifr_flags & IFF_UP)
+ {
+ close(sock);
+ *puMbits = getInterfaceSpeed(pcszIfName);
+ return VINF_SUCCESS;
+ }
+ }
+ close(sock);
+ *puMbits = 0;
+ return VWRN_NOT_FOUND;
+}
diff --git a/src/VBox/Main/src-server/linux/PerformanceLinux.cpp b/src/VBox/Main/src-server/linux/PerformanceLinux.cpp
new file mode 100644
index 00000000..0d6441cb
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/PerformanceLinux.cpp
@@ -0,0 +1,609 @@
+/* $Id: PerformanceLinux.cpp $ */
+/** @file
+ * VBox Linux-specific Performance Classes implementation.
+ */
+
+/*
+ * 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
+ */
+
+#define LOG_GROUP LOG_GROUP_MAIN_PERFORMANCECOLLECTOR
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/statvfs.h>
+#include <errno.h>
+#include <mntent.h>
+#include <iprt/alloc.h>
+#include <iprt/cdefs.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/mp.h>
+#include <iprt/linux/sysfs.h>
+
+#include <map>
+#include <vector>
+
+#include "LoggingNew.h"
+#include "Performance.h"
+
+#define VBOXVOLINFO_NAME "VBoxVolInfo"
+
+namespace pm {
+
+class CollectorLinux : public CollectorHAL
+{
+public:
+ CollectorLinux();
+ virtual int preCollect(const CollectorHints& hints, uint64_t /* iTick */);
+ virtual int getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available);
+ virtual int getHostFilesystemUsage(const char *name, ULONG *total, ULONG *used, ULONG *available);
+ virtual int getHostDiskSize(const char *name, uint64_t *size);
+ virtual int getProcessMemoryUsage(RTPROCESS process, ULONG *used);
+
+ virtual int getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle);
+ virtual int getRawHostNetworkLoad(const char *name, uint64_t *rx, uint64_t *tx);
+ virtual int getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms);
+ virtual int getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total);
+
+ virtual int getDiskListByFs(const char *name, DiskList& listUsage, DiskList& listLoad);
+private:
+ virtual int _getRawHostCpuLoad();
+ int getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed);
+ void getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits);
+ void addVolumeDependencies(const char *pcszVolume, DiskList& listDisks);
+ void addRaidDisks(const char *pcszDevice, DiskList& listDisks);
+ char *trimTrailingDigits(char *pszName);
+ char *trimNewline(char *pszName);
+
+ struct VMProcessStats
+ {
+ uint64_t cpuUser;
+ uint64_t cpuKernel;
+ ULONG pagesUsed;
+ };
+
+ typedef std::map<RTPROCESS, VMProcessStats> VMProcessMap;
+
+ VMProcessMap mProcessStats;
+ uint64_t mUser, mKernel, mIdle;
+ uint64_t mSingleUser, mSingleKernel, mSingleIdle;
+ uint32_t mHZ;
+ ULONG mTotalRAM;
+};
+
+CollectorHAL *createHAL()
+{
+ return new CollectorLinux();
+}
+
+// Collector HAL for Linux
+
+CollectorLinux::CollectorLinux()
+{
+ long hz = sysconf(_SC_CLK_TCK);
+ if (hz == -1)
+ {
+ LogRel(("CollectorLinux failed to obtain HZ from kernel, assuming 100.\n"));
+ mHZ = 100;
+ }
+ else
+ mHZ = (uint32_t)hz;
+ LogFlowThisFunc(("mHZ=%u\n", mHZ));
+
+ uint64_t cb;
+ int vrc = RTSystemQueryTotalRam(&cb);
+ if (RT_FAILURE(vrc))
+ mTotalRAM = 0;
+ else
+ mTotalRAM = (ULONG)(cb / 1024);
+}
+
+int CollectorLinux::preCollect(const CollectorHints& hints, uint64_t /* iTick */)
+{
+ std::vector<RTPROCESS> processes;
+ hints.getProcesses(processes);
+
+ std::vector<RTPROCESS>::iterator it;
+ for (it = processes.begin(); it != processes.end(); ++it)
+ {
+ VMProcessStats vmStats;
+ int vrc = getRawProcessStats(*it, &vmStats.cpuUser, &vmStats.cpuKernel, &vmStats.pagesUsed);
+ /* On failure, do NOT stop. Just skip the entry. Having the stats for
+ * one (probably broken) process frozen/zero is a minor issue compared
+ * to not updating many process stats and the host cpu stats. */
+ if (RT_SUCCESS(vrc))
+ mProcessStats[*it] = vmStats;
+ }
+ if (hints.isHostCpuLoadCollected() || !mProcessStats.empty())
+ {
+ _getRawHostCpuLoad();
+ }
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::_getRawHostCpuLoad()
+{
+ int vrc = VINF_SUCCESS;
+ long long unsigned uUser, uNice, uKernel, uIdle, uIowait, uIrq, uSoftirq;
+ FILE *f = fopen("/proc/stat", "r");
+
+ if (f)
+ {
+ char szBuf[128];
+ if (fgets(szBuf, sizeof(szBuf), f))
+ {
+ if (sscanf(szBuf, "cpu %llu %llu %llu %llu %llu %llu %llu",
+ &uUser, &uNice, &uKernel, &uIdle, &uIowait,
+ &uIrq, &uSoftirq) == 7)
+ {
+ mUser = uUser + uNice;
+ mKernel = uKernel + uIrq + uSoftirq;
+ mIdle = uIdle + uIowait;
+ }
+ /* Try to get single CPU stats. */
+ if (fgets(szBuf, sizeof(szBuf), f))
+ {
+ if (sscanf(szBuf, "cpu0 %llu %llu %llu %llu %llu %llu %llu",
+ &uUser, &uNice, &uKernel, &uIdle, &uIowait,
+ &uIrq, &uSoftirq) == 7)
+ {
+ mSingleUser = uUser + uNice;
+ mSingleKernel = uKernel + uIrq + uSoftirq;
+ mSingleIdle = uIdle + uIowait;
+ }
+ else
+ {
+ /* Assume that this is not an SMP system. */
+ Assert(RTMpGetCount() == 1);
+ mSingleUser = mUser;
+ mSingleKernel = mKernel;
+ mSingleIdle = mIdle;
+ }
+ }
+ else
+ vrc = VERR_FILE_IO_ERROR;
+ }
+ else
+ vrc = VERR_FILE_IO_ERROR;
+ fclose(f);
+ }
+ else
+ vrc = VERR_ACCESS_DENIED;
+
+ return vrc;
+}
+
+int CollectorLinux::getRawHostCpuLoad(uint64_t *user, uint64_t *kernel, uint64_t *idle)
+{
+ *user = mUser;
+ *kernel = mKernel;
+ *idle = mIdle;
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getRawProcessCpuLoad(RTPROCESS process, uint64_t *user, uint64_t *kernel, uint64_t *total)
+{
+ VMProcessMap::const_iterator it = mProcessStats.find(process);
+
+ if (it == mProcessStats.end())
+ {
+ Log (("No stats pre-collected for process %x\n", process));
+ return VERR_INTERNAL_ERROR;
+ }
+ *user = it->second.cpuUser;
+ *kernel = it->second.cpuKernel;
+ *total = mUser + mKernel + mIdle;
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getHostMemoryUsage(ULONG *total, ULONG *used, ULONG *available)
+{
+ AssertReturn(mTotalRAM, VERR_INTERNAL_ERROR);
+ uint64_t cb;
+ int vrc = RTSystemQueryAvailableRam(&cb);
+ if (RT_SUCCESS(vrc))
+ {
+ *total = mTotalRAM;
+ *available = (ULONG)(cb / 1024);
+ *used = *total - *available;
+ }
+ return vrc;
+}
+
+int CollectorLinux::getHostFilesystemUsage(const char *path, ULONG *total, ULONG *used, ULONG *available)
+{
+ struct statvfs stats;
+
+ if (statvfs(path, &stats) == -1)
+ {
+ LogRel(("Failed to collect %s filesystem usage: errno=%d.\n", path, errno));
+ return VERR_ACCESS_DENIED;
+ }
+ uint64_t cbBlock = stats.f_frsize ? stats.f_frsize : stats.f_bsize;
+ *total = (ULONG)(cbBlock * stats.f_blocks / _1M);
+ *used = (ULONG)(cbBlock * (stats.f_blocks - stats.f_bfree) / _1M);
+ *available = (ULONG)(cbBlock * stats.f_bavail / _1M);
+
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getHostDiskSize(const char *pszFile, uint64_t *size)
+{
+ char *pszPath = NULL;
+
+ RTStrAPrintf(&pszPath, "/sys/block/%s/size", pszFile);
+ Assert(pszPath);
+
+ int vrc = VINF_SUCCESS;
+ if (!RTLinuxSysFsExists(pszPath))
+ vrc = VERR_FILE_NOT_FOUND;
+ else
+ {
+ int64_t cSize = 0;
+ vrc = RTLinuxSysFsReadIntFile(0, &cSize, pszPath);
+ if (RT_SUCCESS(vrc))
+ *size = cSize * 512;
+ }
+ RTStrFree(pszPath);
+ return vrc;
+}
+
+int CollectorLinux::getProcessMemoryUsage(RTPROCESS process, ULONG *used)
+{
+ VMProcessMap::const_iterator it = mProcessStats.find(process);
+
+ if (it == mProcessStats.end())
+ {
+ Log (("No stats pre-collected for process %x\n", process));
+ return VERR_INTERNAL_ERROR;
+ }
+ *used = it->second.pagesUsed * (PAGE_SIZE / 1024);
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getRawProcessStats(RTPROCESS process, uint64_t *cpuUser, uint64_t *cpuKernel, ULONG *memPagesUsed)
+{
+ int vrc = VINF_SUCCESS;
+ char *pszName;
+ pid_t pid2;
+ char c;
+ int iTmp;
+ long long unsigned int u64Tmp;
+ unsigned uTmp;
+ unsigned long ulTmp;
+ signed long ilTmp;
+ ULONG u32user, u32kernel;
+ char buf[80]; /** @todo this should be tied to max allowed proc name. */
+
+ RTStrAPrintf(&pszName, "/proc/%d/stat", process);
+ FILE *f = fopen(pszName, "r");
+ RTStrFree(pszName);
+
+ if (f)
+ {
+ if (fscanf(f, "%d %79s %c %d %d %d %d %d %u %lu %lu %lu %lu %u %u "
+ "%ld %ld %ld %ld %ld %ld %llu %lu %u",
+ &pid2, buf, &c, &iTmp, &iTmp, &iTmp, &iTmp, &iTmp, &uTmp,
+ &ulTmp, &ulTmp, &ulTmp, &ulTmp, &u32user, &u32kernel,
+ &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &ilTmp, &u64Tmp,
+ &ulTmp, memPagesUsed) == 24)
+ {
+ Assert((pid_t)process == pid2);
+ *cpuUser = u32user;
+ *cpuKernel = u32kernel;
+ }
+ else
+ vrc = VERR_FILE_IO_ERROR;
+ fclose(f);
+ }
+ else
+ vrc = VERR_ACCESS_DENIED;
+
+ return vrc;
+}
+
+int CollectorLinux::getRawHostNetworkLoad(const char *pszFile, uint64_t *rx, uint64_t *tx)
+{
+ char szIfName[/*IFNAMSIZ*/ 16 + 36];
+
+ RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/rx_bytes", pszFile);
+ if (!RTLinuxSysFsExists(szIfName))
+ return VERR_FILE_NOT_FOUND;
+
+ int64_t cSize = 0;
+ int vrc = RTLinuxSysFsReadIntFile(0, &cSize, szIfName);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ *rx = cSize;
+
+ RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/net/%s/statistics/tx_bytes", pszFile);
+ if (!RTLinuxSysFsExists(szIfName))
+ return VERR_FILE_NOT_FOUND;
+
+ vrc = RTLinuxSysFsReadIntFile(0, &cSize, szIfName);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ *tx = cSize;
+ return VINF_SUCCESS;
+}
+
+int CollectorLinux::getRawHostDiskLoad(const char *name, uint64_t *disk_ms, uint64_t *total_ms)
+{
+#if 0
+ int vrc = VINF_SUCCESS;
+ char szIfName[/*IFNAMSIZ*/ 16 + 36];
+ long long unsigned int u64Busy, tmp;
+
+ RTStrPrintf(szIfName, sizeof(szIfName), "/sys/class/block/%s/stat", name);
+ FILE *f = fopen(szIfName, "r");
+ if (f)
+ {
+ if (fscanf(f, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
+ &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
+ {
+ *disk_ms = u64Busy;
+ *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
+ }
+ else
+ vrc = VERR_FILE_IO_ERROR;
+ fclose(f);
+ }
+ else
+ vrc = VERR_ACCESS_DENIED;
+#else
+ int vrc = VERR_MISSING;
+ FILE *f = fopen("/proc/diskstats", "r");
+ if (f)
+ {
+ char szBuf[128];
+ while (fgets(szBuf, sizeof(szBuf), f))
+ {
+ char *pszBufName = szBuf;
+ while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
+ while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip major */
+ while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
+ while (RT_C_IS_DIGIT(*pszBufName)) ++pszBufName; /* Skip minor */
+ while (*pszBufName == ' ') ++pszBufName; /* Skip spaces */
+
+ char *pszBufData = strchr(pszBufName, ' ');
+ if (!pszBufData)
+ {
+ LogRel(("CollectorLinux::getRawHostDiskLoad() failed to parse disk stats: %s\n", szBuf));
+ continue;
+ }
+ *pszBufData++ = '\0';
+ if (!strcmp(name, pszBufName))
+ {
+ long long unsigned int u64Busy, tmp;
+
+ if (sscanf(pszBufData, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
+ &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &u64Busy, &tmp) == 11)
+ {
+ *disk_ms = u64Busy;
+ *total_ms = (uint64_t)(mSingleUser + mSingleKernel + mSingleIdle) * 1000 / mHZ;
+ vrc = VINF_SUCCESS;
+ }
+ else
+ vrc = VERR_FILE_IO_ERROR;
+ break;
+ }
+ }
+ fclose(f);
+ }
+#endif
+
+ return vrc;
+}
+
+char *CollectorLinux::trimNewline(char *pszName)
+{
+ size_t cbName = strlen(pszName);
+ if (cbName == 0)
+ return pszName;
+
+ char *pszEnd = pszName + cbName - 1;
+ while (pszEnd > pszName && *pszEnd == '\n')
+ pszEnd--;
+ pszEnd[1] = '\0';
+
+ return pszName;
+}
+
+char *CollectorLinux::trimTrailingDigits(char *pszName)
+{
+ size_t cbName = strlen(pszName);
+ if (cbName == 0)
+ return pszName;
+
+ char *pszEnd = pszName + cbName - 1;
+ while (pszEnd > pszName && (RT_C_IS_DIGIT(*pszEnd) || *pszEnd == '\n'))
+ pszEnd--;
+ pszEnd[1] = '\0';
+
+ return pszName;
+}
+
+/**
+ * Use the partition name to get the name of the disk. Any path component is stripped.
+ * if fTrimDigits is true, trailing digits are stripped as well, for example '/dev/sda5'
+ * is converted to 'sda'.
+ *
+ * @param pszDiskName Where to store the name of the disk.
+ * @param cbDiskName The size of the buffer pszDiskName points to.
+ * @param pszDevName The device name used to get the disk name.
+ * @param fTrimDigits Trim trailing digits (e.g. /dev/sda5)
+ */
+void CollectorLinux::getDiskName(char *pszDiskName, size_t cbDiskName, const char *pszDevName, bool fTrimDigits)
+{
+ unsigned cbName = 0;
+ size_t cbDevName = strlen(pszDevName);
+ const char *pszEnd = pszDevName + cbDevName - 1;
+ if (fTrimDigits)
+ while (pszEnd > pszDevName && RT_C_IS_DIGIT(*pszEnd))
+ pszEnd--;
+ while (pszEnd > pszDevName && *pszEnd != '/')
+ {
+ cbName++;
+ pszEnd--;
+ }
+ RTStrCopy(pszDiskName, RT_MIN(cbName + 1, cbDiskName), pszEnd + 1);
+}
+
+void CollectorLinux::addRaidDisks(const char *pcszDevice, DiskList& listDisks)
+{
+ FILE *f = fopen("/proc/mdstat", "r");
+ if (f)
+ {
+ char szBuf[128];
+ while (fgets(szBuf, sizeof(szBuf), f))
+ {
+ char *pszBufName = szBuf;
+
+ char *pszBufData = strchr(pszBufName, ' ');
+ if (!pszBufData)
+ {
+ LogRel(("CollectorLinux::addRaidDisks() failed to parse disk stats: %s\n", szBuf));
+ continue;
+ }
+ *pszBufData++ = '\0';
+ if (!strcmp(pcszDevice, pszBufName))
+ {
+ while (*pszBufData == ':') ++pszBufData; /* Skip delimiter */
+ while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
+ while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip status */
+ while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
+ while (RT_C_IS_ALNUM(*pszBufData)) ++pszBufData; /* Skip type */
+
+ while (*pszBufData != '\0')
+ {
+ while (*pszBufData == ' ') ++pszBufData; /* Skip spaces */
+ char *pszDisk = pszBufData;
+ while (RT_C_IS_ALPHA(*pszBufData))
+ ++pszBufData;
+ if (*pszBufData)
+ {
+ *pszBufData++ = '\0';
+ listDisks.push_back(RTCString(pszDisk));
+ while (*pszBufData != '\0' && *pszBufData != ' ')
+ ++pszBufData;
+ }
+ else
+ listDisks.push_back(RTCString(pszDisk));
+ }
+ break;
+ }
+ }
+ fclose(f);
+ }
+}
+
+void CollectorLinux::addVolumeDependencies(const char *pcszVolume, DiskList& listDisks)
+{
+ char szVolInfo[RTPATH_MAX];
+ int vrc = RTPathAppPrivateArch(szVolInfo, sizeof(szVolInfo) - sizeof("/" VBOXVOLINFO_NAME " ") - strlen(pcszVolume));
+ if (RT_FAILURE(vrc))
+ {
+ LogRel(("VolInfo: Failed to get program path, vrc=%Rrc\n", vrc));
+ return;
+ }
+ strcat(szVolInfo, "/" VBOXVOLINFO_NAME " ");
+ strcat(szVolInfo, pcszVolume);
+
+ FILE *fp = popen(szVolInfo, "r");
+ if (fp)
+ {
+ char szBuf[128];
+
+ while (fgets(szBuf, sizeof(szBuf), fp))
+ if (strncmp(szBuf, RT_STR_TUPLE("dm-")))
+ listDisks.push_back(RTCString(trimTrailingDigits(szBuf)));
+ else
+ listDisks.push_back(RTCString(trimNewline(szBuf)));
+
+ pclose(fp);
+ }
+ else
+ listDisks.push_back(RTCString(pcszVolume));
+}
+
+int CollectorLinux::getDiskListByFs(const char *pszPath, DiskList& listUsage, DiskList& listLoad)
+{
+ FILE *mtab = setmntent("/etc/mtab", "r");
+ if (mtab)
+ {
+ struct mntent *mntent;
+ while ((mntent = getmntent(mtab)))
+ {
+ /* Skip rootfs entry, there must be another root mount. */
+ if (strcmp(mntent->mnt_fsname, "rootfs") == 0)
+ continue;
+ if (strcmp(pszPath, mntent->mnt_dir) == 0)
+ {
+ char szDevName[128];
+ char szFsName[1024];
+ /* Try to resolve symbolic link if necessary. Yes, we access the file system here! */
+ int vrc = RTPathReal(mntent->mnt_fsname, szFsName, sizeof(szFsName));
+ if (RT_FAILURE(vrc))
+ continue; /* something got wrong, just ignore this path */
+ /* check against the actual mtab entry, NOT the real path as /dev/mapper/xyz is
+ * often a symlink to something else */
+ if (!strncmp(mntent->mnt_fsname, RT_STR_TUPLE("/dev/mapper")))
+ {
+ /* LVM */
+ getDiskName(szDevName, sizeof(szDevName), mntent->mnt_fsname, false /*=fTrimDigits*/);
+ addVolumeDependencies(szDevName, listUsage);
+ listLoad = listUsage;
+ }
+ else if (!strncmp(szFsName, RT_STR_TUPLE("/dev/md")))
+ {
+ /* Software RAID */
+ getDiskName(szDevName, sizeof(szDevName), szFsName, false /*=fTrimDigits*/);
+ listUsage.push_back(RTCString(szDevName));
+ addRaidDisks(szDevName, listLoad);
+ }
+ else
+ {
+ /* Plain disk partition. Trim the trailing digits to get the drive name */
+ getDiskName(szDevName, sizeof(szDevName), szFsName, true /*=fTrimDigits*/);
+ listUsage.push_back(RTCString(szDevName));
+ listLoad.push_back(RTCString(szDevName));
+ }
+ if (listUsage.empty() || listLoad.empty())
+ {
+ LogRel(("Failed to retrive disk info: getDiskName(%s) --> %s\n",
+ mntent->mnt_fsname, szDevName));
+ }
+ break;
+ }
+ }
+ endmntent(mtab);
+ }
+ return VINF_SUCCESS;
+}
+
+}
+
diff --git a/src/VBox/Main/src-server/linux/USBGetDevices.cpp b/src/VBox/Main/src-server/linux/USBGetDevices.cpp
new file mode 100644
index 00000000..4e0c1bb7
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/USBGetDevices.cpp
@@ -0,0 +1,1795 @@
+/* $Id: USBGetDevices.cpp $ */
+/** @file
+ * VirtualBox Linux host USB device enumeration.
+ */
+
+/*
+ * 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 *
+*********************************************************************************************************************************/
+#define VBOX_USB_WITH_USBFS
+#include "USBGetDevices.h"
+
+#include <VBox/err.h>
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+
+#include <iprt/linux/sysfs.h>
+#include <iprt/cdefs.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/fs.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include "vector.h"
+
+#ifdef VBOX_WITH_LINUX_COMPILER_H
+# include <linux/compiler.h>
+#endif
+#include <linux/usbdevice_fs.h>
+
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Structure describing a host USB device */
+typedef struct USBDeviceInfo
+{
+ /** The device node of the device. */
+ char *mDevice;
+ /** The system identifier of the device. Specific to the probing
+ * method. */
+ char *mSysfsPath;
+ /** List of interfaces as sysfs paths */
+ VECTOR_PTR(char *) mvecpszInterfaces;
+} USBDeviceInfo;
+
+
+/**
+ * Does some extra checks to improve the detected device state.
+ *
+ * We cannot distinguish between USED_BY_HOST_CAPTURABLE and
+ * USED_BY_GUEST, HELD_BY_PROXY all that well and it shouldn't be
+ * necessary either.
+ *
+ * We will however, distinguish between the device we have permissions
+ * to open and those we don't. This is necessary for two reasons.
+ *
+ * Firstly, because it's futile to even attempt opening a device which we
+ * don't have access to, it only serves to confuse the user. (That said,
+ * it might also be a bit confusing for the user to see that a USB device
+ * is grayed out with no further explanation, and no way of generating an
+ * error hinting at why this is the case.)
+ *
+ * Secondly and more importantly, we're racing against udevd with respect
+ * to permissions and group settings on newly plugged devices. When we
+ * detect a new device that we cannot access we will poll on it for a few
+ * seconds to give udevd time to fix it. The polling is actually triggered
+ * in the 'new device' case in the compare loop.
+ *
+ * The USBDEVICESTATE_USED_BY_HOST state is only used for this no-access
+ * case, while USBDEVICESTATE_UNSUPPORTED is only used in the 'hub' case.
+ * When it's neither of these, we set USBDEVICESTATE_UNUSED or
+ * USBDEVICESTATE_USED_BY_HOST_CAPTURABLE depending on whether there is
+ * a driver associated with any of the interfaces.
+ *
+ * All except the access check and a special idVendor == 0 precaution
+ * is handled at parse time.
+ *
+ * @returns The adjusted state.
+ * @param pDevice The device.
+ */
+static USBDEVICESTATE usbDeterminState(PCUSBDEVICE pDevice)
+{
+ /*
+ * If it's already flagged as unsupported, there is nothing to do.
+ */
+ USBDEVICESTATE enmState = pDevice->enmState;
+ if (enmState == USBDEVICESTATE_UNSUPPORTED)
+ return USBDEVICESTATE_UNSUPPORTED;
+
+ /*
+ * Root hubs and similar doesn't have any vendor id, just
+ * refuse these device.
+ */
+ if (!pDevice->idVendor)
+ return USBDEVICESTATE_UNSUPPORTED;
+
+ /*
+ * Check if we've got access to the device, if we haven't flag
+ * it as used-by-host.
+ */
+#ifndef VBOX_USB_WITH_SYSFS
+ const char *pszAddress = pDevice->pszAddress;
+#else
+ if (pDevice->pszAddress == NULL)
+ /* We can't do much with the device without an address. */
+ return USBDEVICESTATE_UNSUPPORTED;
+ const char *pszAddress = strstr(pDevice->pszAddress, "//device:");
+ pszAddress = pszAddress != NULL
+ ? pszAddress + sizeof("//device:") - 1
+ : pDevice->pszAddress;
+#endif
+ if ( access(pszAddress, R_OK | W_OK) != 0
+ && errno == EACCES)
+ return USBDEVICESTATE_USED_BY_HOST;
+
+#ifdef VBOX_USB_WITH_SYSFS
+ /**
+ * @todo Check that any other essential fields are present and mark as
+ * invalid if not. Particularly to catch the case where the device was
+ * unplugged while we were reading in its properties.
+ */
+#endif
+
+ return enmState;
+}
+
+
+/**
+ * Dumps a USBDEVICE structure to the log using LogLevel 3.
+ * @param pDev The structure to log.
+ * @todo This is really common code.
+ */
+static void usbLogDevice(PUSBDEVICE pDev)
+{
+ NOREF(pDev);
+ if (LogIs3Enabled())
+ {
+ Log3(("USB device:\n"));
+ Log3(("Product: %s (%x)\n", pDev->pszProduct, pDev->idProduct));
+ Log3(("Manufacturer: %s (Vendor ID %x)\n", pDev->pszManufacturer, pDev->idVendor));
+ Log3(("Serial number: %s (%llx)\n", pDev->pszSerialNumber, pDev->u64SerialHash));
+ Log3(("Device revision: %d\n", pDev->bcdDevice));
+ Log3(("Device class: %x\n", pDev->bDeviceClass));
+ Log3(("Device subclass: %x\n", pDev->bDeviceSubClass));
+ Log3(("Device protocol: %x\n", pDev->bDeviceProtocol));
+ Log3(("USB version number: %d\n", pDev->bcdUSB));
+ Log3(("Device speed: %s\n",
+ pDev->enmSpeed == USBDEVICESPEED_UNKNOWN ? "unknown"
+ : pDev->enmSpeed == USBDEVICESPEED_LOW ? "1.5 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_FULL ? "12 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_HIGH ? "480 MBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_SUPER ? "5.0 GBit/s"
+ : pDev->enmSpeed == USBDEVICESPEED_VARIABLE ? "variable"
+ : "invalid"));
+ Log3(("Number of configurations: %d\n", pDev->bNumConfigurations));
+ Log3(("Bus number: %d\n", pDev->bBus));
+ Log3(("Port number: %d\n", pDev->bPort));
+ Log3(("Device number: %d\n", pDev->bDevNum));
+ Log3(("Device state: %s\n",
+ pDev->enmState == USBDEVICESTATE_UNSUPPORTED ? "unsupported"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_HOST ? "in use by host"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE ? "in use by host, possibly capturable"
+ : pDev->enmState == USBDEVICESTATE_UNUSED ? "not in use"
+ : pDev->enmState == USBDEVICESTATE_HELD_BY_PROXY ? "held by proxy"
+ : pDev->enmState == USBDEVICESTATE_USED_BY_GUEST ? "used by guest"
+ : "invalid"));
+ Log3(("OS device address: %s\n", pDev->pszAddress));
+ }
+}
+
+
+#ifdef VBOX_USB_WITH_USBFS
+
+/**
+ * "reads" the number suffix.
+ *
+ * It's more like validating it and skipping the necessary number of chars.
+ */
+static int usbfsReadSkipSuffix(char **ppszNext)
+{
+ char *pszNext = *ppszNext;
+ if (!RT_C_IS_SPACE(*pszNext) && *pszNext)
+ {
+ /* skip unit */
+ if (pszNext[0] == 'm' && pszNext[1] == 's')
+ pszNext += 2;
+ else if (pszNext[0] == 'm' && pszNext[1] == 'A')
+ pszNext += 2;
+
+ /* skip parenthesis */
+ if (*pszNext == '(')
+ {
+ pszNext = strchr(pszNext, ')');
+ if (!pszNext++)
+ {
+ AssertMsgFailed(("*ppszNext=%s\n", *ppszNext));
+ return VERR_PARSE_ERROR;
+ }
+ }
+
+ /* blank or end of the line. */
+ if (!RT_C_IS_SPACE(*pszNext) && *pszNext)
+ {
+ AssertMsgFailed(("pszNext=%s\n", pszNext));
+ return VERR_PARSE_ERROR;
+ }
+
+ /* it's ok. */
+ *ppszNext = pszNext;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads a USB number returning the number and the position of the next character to parse.
+ */
+static int usbfsReadNum(const char *pszValue, unsigned uBase, uint32_t u32Mask, void *pvNum, char **ppszNext)
+{
+ /*
+ * Initialize return value to zero and strip leading spaces.
+ */
+ switch (u32Mask)
+ {
+ case 0xff: *(uint8_t *)pvNum = 0; break;
+ case 0xffff: *(uint16_t *)pvNum = 0; break;
+ case 0xffffffff: *(uint32_t *)pvNum = 0; break;
+ }
+ pszValue = RTStrStripL(pszValue);
+ if (*pszValue)
+ {
+ /*
+ * Try convert the number.
+ */
+ char *pszNext;
+ uint32_t u32 = 0;
+ RTStrToUInt32Ex(pszValue, &pszNext, uBase, &u32);
+ if (pszNext == pszValue)
+ {
+ AssertMsgFailed(("pszValue=%d\n", pszValue));
+ return VERR_NO_DATA;
+ }
+
+ /*
+ * Check the range.
+ */
+ if (u32 & ~u32Mask)
+ {
+ AssertMsgFailed(("pszValue=%d u32=%#x lMask=%#x\n", pszValue, u32, u32Mask));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ int vrc = usbfsReadSkipSuffix(&pszNext);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ *ppszNext = pszNext;
+
+ /*
+ * Set the value.
+ */
+ switch (u32Mask)
+ {
+ case 0xff: *(uint8_t *)pvNum = (uint8_t)u32; break;
+ case 0xffff: *(uint16_t *)pvNum = (uint16_t)u32; break;
+ case 0xffffffff: *(uint32_t *)pvNum = (uint32_t)u32; break;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int usbfsRead8(const char *pszValue, unsigned uBase, uint8_t *pu8, char **ppszNext)
+{
+ return usbfsReadNum(pszValue, uBase, 0xff, pu8, ppszNext);
+}
+
+
+static int usbfsRead16(const char *pszValue, unsigned uBase, uint16_t *pu16, char **ppszNext)
+{
+ return usbfsReadNum(pszValue, uBase, 0xffff, pu16, ppszNext);
+}
+
+
+/**
+ * Reads a USB BCD number returning the number and the position of the next character to parse.
+ * The returned number contains the integer part in the high byte and the decimal part in the low byte.
+ */
+static int usbfsReadBCD(const char *pszValue, unsigned uBase, uint16_t *pu16, char **ppszNext)
+{
+ /*
+ * Initialize return value to zero and strip leading spaces.
+ */
+ *pu16 = 0;
+ pszValue = RTStrStripL(pszValue);
+ if (*pszValue)
+ {
+ /*
+ * Try convert the number.
+ */
+ /* integer part */
+ char *pszNext;
+ uint32_t u32Int = 0;
+ RTStrToUInt32Ex(pszValue, &pszNext, uBase, &u32Int);
+ if (pszNext == pszValue)
+ {
+ AssertMsgFailed(("pszValue=%s\n", pszValue));
+ return VERR_NO_DATA;
+ }
+ if (u32Int & ~0xff)
+ {
+ AssertMsgFailed(("pszValue=%s u32Int=%#x (int)\n", pszValue, u32Int));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /* skip dot and read decimal part */
+ if (*pszNext != '.')
+ {
+ AssertMsgFailed(("pszValue=%s pszNext=%s (int)\n", pszValue, pszNext));
+ return VERR_PARSE_ERROR;
+ }
+ char *pszValue2 = RTStrStripL(pszNext + 1);
+ uint32_t u32Dec = 0;
+ RTStrToUInt32Ex(pszValue2, &pszNext, uBase, &u32Dec);
+ if (pszNext == pszValue)
+ {
+ AssertMsgFailed(("pszValue=%s\n", pszValue));
+ return VERR_NO_DATA;
+ }
+ if (u32Dec & ~0xff)
+ {
+ AssertMsgFailed(("pszValue=%s u32Dec=%#x\n", pszValue, u32Dec));
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /*
+ * Validate and skip stuff following the number.
+ */
+ int vrc = usbfsReadSkipSuffix(&pszNext);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ *ppszNext = pszNext;
+
+ /*
+ * Set the value.
+ */
+ *pu16 = (uint16_t)((u32Int << 8) | (uint16_t)u32Dec);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads a string, i.e. allocates memory and copies it.
+ *
+ * We assume that a string is Utf8 and if that's not the case
+ * (pre-2.6.32-kernels used Latin-1, but so few devices return non-ASCII that
+ * this usually goes unnoticed) then we mercilessly force it to be so.
+ */
+static int usbfsReadStr(const char *pszValue, const char **ppsz)
+{
+ char *psz;
+
+ if (*ppsz)
+ RTStrFree((char *)*ppsz);
+ psz = RTStrDup(pszValue);
+ if (psz)
+ {
+ USBLibPurgeEncoding(psz);
+ *ppsz = psz;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Skips the current property.
+ */
+static char *usbfsReadSkip(char *pszValue)
+{
+ char *psz = strchr(pszValue, '=');
+ if (psz)
+ psz = strchr(psz + 1, '=');
+ if (!psz)
+ return strchr(pszValue, '\0');
+ while (psz > pszValue && !RT_C_IS_SPACE(psz[-1]))
+ psz--;
+ Assert(psz > pszValue);
+ return psz;
+}
+
+
+/**
+ * Determine the USB speed.
+ */
+static int usbfsReadSpeed(const char *pszValue, USBDEVICESPEED *pSpd, char **ppszNext)
+{
+ pszValue = RTStrStripL(pszValue);
+ /* verified with Linux 2.4.0 ... Linux 2.6.25 */
+ if (!strncmp(pszValue, RT_STR_TUPLE("1.5")))
+ *pSpd = USBDEVICESPEED_LOW;
+ else if (!strncmp(pszValue, RT_STR_TUPLE("12 ")))
+ *pSpd = USBDEVICESPEED_FULL;
+ else if (!strncmp(pszValue, RT_STR_TUPLE("480")))
+ *pSpd = USBDEVICESPEED_HIGH;
+ else if (!strncmp(pszValue, RT_STR_TUPLE("5000")))
+ *pSpd = USBDEVICESPEED_SUPER;
+ else
+ *pSpd = USBDEVICESPEED_UNKNOWN;
+ while (pszValue[0] != '\0' && !RT_C_IS_SPACE(pszValue[0]))
+ pszValue++;
+ *ppszNext = (char *)pszValue;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Compare a prefix and returns pointer to the char following it if it matches.
+ */
+static char *usbfsPrefix(char *psz, const char *pszPref, size_t cchPref)
+{
+ if (strncmp(psz, pszPref, cchPref))
+ return NULL;
+ return psz + cchPref;
+}
+
+
+/** Just a worker for USBProxyServiceLinux::getDevices that avoids some code duplication. */
+static int usbfsAddDeviceToChain(PUSBDEVICE pDev, PUSBDEVICE *ppFirst, PUSBDEVICE **pppNext, const char *pszUsbfsRoot,
+ bool fUnsupportedDevicesToo, int vrc)
+{
+ /* usbDeterminState requires the address. */
+ PUSBDEVICE pDevNew = (PUSBDEVICE)RTMemDup(pDev, sizeof(*pDev));
+ if (pDevNew)
+ {
+ RTStrAPrintf((char **)&pDevNew->pszAddress, "%s/%03d/%03d", pszUsbfsRoot, pDevNew->bBus, pDevNew->bDevNum);
+ if (pDevNew->pszAddress)
+ {
+ pDevNew->enmState = usbDeterminState(pDevNew);
+ if (pDevNew->enmState != USBDEVICESTATE_UNSUPPORTED || fUnsupportedDevicesToo)
+ {
+ if (*pppNext)
+ **pppNext = pDevNew;
+ else
+ *ppFirst = pDevNew;
+ *pppNext = &pDevNew->pNext;
+ }
+ else
+ deviceFree(pDevNew);
+ }
+ else
+ {
+ deviceFree(pDevNew);
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ deviceFreeMembers(pDev);
+ }
+
+ return vrc;
+}
+
+
+static int usbfsOpenDevicesFile(const char *pszUsbfsRoot, FILE **ppFile)
+{
+ char *pszPath;
+ FILE *pFile;
+ RTStrAPrintf(&pszPath, "%s/devices", pszUsbfsRoot);
+ if (!pszPath)
+ return VERR_NO_MEMORY;
+ pFile = fopen(pszPath, "r");
+ RTStrFree(pszPath);
+ if (!pFile)
+ return RTErrConvertFromErrno(errno);
+ *ppFile = pFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * USBProxyService::getDevices() implementation for usbfs.
+ *
+ * The @a fUnsupportedDevicesToo flag tells the function to return information
+ * about unsupported devices as well. This is used as a sanity test to check
+ * that a devices file is really what we expect.
+ */
+static PUSBDEVICE usbfsGetDevices(const char *pszUsbfsRoot, bool fUnsupportedDevicesToo)
+{
+ PUSBDEVICE pFirst = NULL;
+ FILE *pFile = NULL;
+ int vrc = usbfsOpenDevicesFile(pszUsbfsRoot, &pFile);
+ if (RT_SUCCESS(vrc))
+ {
+ PUSBDEVICE *ppNext = NULL;
+ int cHits = 0;
+ char szLine[1024];
+ USBDEVICE Dev;
+ RT_ZERO(Dev);
+ Dev.enmState = USBDEVICESTATE_UNUSED;
+
+ /* Set close on exit and hope no one is racing us. */
+ vrc = fcntl(fileno(pFile), F_SETFD, FD_CLOEXEC) >= 0
+ ? VINF_SUCCESS
+ : RTErrConvertFromErrno(errno);
+ while ( RT_SUCCESS(vrc)
+ && fgets(szLine, sizeof(szLine), pFile))
+ {
+ char *psz;
+ char *pszValue;
+
+ /* validate and remove the trailing newline. */
+ psz = strchr(szLine, '\0');
+ if (psz[-1] != '\n' && !feof(pFile))
+ {
+ AssertMsgFailed(("Line too long. (cch=%d)\n", strlen(szLine)));
+ continue;
+ }
+
+ /* strip */
+ psz = RTStrStrip(szLine);
+ if (!*psz)
+ continue;
+
+ /*
+ * Interpret the line.
+ * (Ordered by normal occurrence.)
+ */
+ char ch = psz[0];
+ if (psz[1] != ':')
+ continue;
+ psz = RTStrStripL(psz + 3);
+#define PREFIX(str) ( (pszValue = usbfsPrefix(psz, str, sizeof(str) - 1)) != NULL )
+ switch (ch)
+ {
+ /*
+ * T: Bus=dd Lev=dd Prnt=dd Port=dd Cnt=dd Dev#=ddd Spd=ddd MxCh=dd
+ * | | | | | | | | |__MaxChildren
+ * | | | | | | | |__Device Speed in Mbps
+ * | | | | | | |__DeviceNumber
+ * | | | | | |__Count of devices at this level
+ * | | | | |__Connector/Port on Parent for this device
+ * | | | |__Parent DeviceNumber
+ * | | |__Level in topology for this bus
+ * | |__Bus number
+ * |__Topology info tag
+ */
+ case 'T':
+ /* add */
+ AssertMsg(cHits >= 3 || cHits == 0, ("cHits=%d\n", cHits));
+ if (cHits >= 3)
+ vrc = usbfsAddDeviceToChain(&Dev, &pFirst, &ppNext, pszUsbfsRoot, fUnsupportedDevicesToo, vrc);
+ else
+ deviceFreeMembers(&Dev);
+
+ /* Reset device state */
+ RT_ZERO(Dev);
+ Dev.enmState = USBDEVICESTATE_UNUSED;
+ cHits = 1;
+
+ /* parse the line. */
+ while (*psz && RT_SUCCESS(vrc))
+ {
+ if (PREFIX("Bus="))
+ vrc = usbfsRead8(pszValue, 10, &Dev.bBus, &psz);
+ else if (PREFIX("Port="))
+ vrc = usbfsRead8(pszValue, 10, &Dev.bPort, &psz);
+ else if (PREFIX("Spd="))
+ vrc = usbfsReadSpeed(pszValue, &Dev.enmSpeed, &psz);
+ else if (PREFIX("Dev#="))
+ vrc = usbfsRead8(pszValue, 10, &Dev.bDevNum, &psz);
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ break;
+
+ /*
+ * Bandwidth info:
+ * B: Alloc=ddd/ddd us (xx%), #Int=ddd, #Iso=ddd
+ * | | | |__Number of isochronous requests
+ * | | |__Number of interrupt requests
+ * | |__Total Bandwidth allocated to this bus
+ * |__Bandwidth info tag
+ */
+ case 'B':
+ break;
+
+ /*
+ * D: Ver=x.xx Cls=xx(sssss) Sub=xx Prot=xx MxPS=dd #Cfgs=dd
+ * | | | | | | |__NumberConfigurations
+ * | | | | | |__MaxPacketSize of Default Endpoint
+ * | | | | |__DeviceProtocol
+ * | | | |__DeviceSubClass
+ * | | |__DeviceClass
+ * | |__Device USB version
+ * |__Device info tag #1
+ */
+ case 'D':
+ while (*psz && RT_SUCCESS(vrc))
+ {
+ if (PREFIX("Ver="))
+ vrc = usbfsReadBCD(pszValue, 16, &Dev.bcdUSB, &psz);
+ else if (PREFIX("Cls="))
+ {
+ vrc = usbfsRead8(pszValue, 16, &Dev.bDeviceClass, &psz);
+ if (RT_SUCCESS(vrc) && Dev.bDeviceClass == 9 /* HUB */)
+ Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
+ }
+ else if (PREFIX("Sub="))
+ vrc = usbfsRead8(pszValue, 16, &Dev.bDeviceSubClass, &psz);
+ else if (PREFIX("Prot="))
+ vrc = usbfsRead8(pszValue, 16, &Dev.bDeviceProtocol, &psz);
+ //else if (PREFIX("MxPS="))
+ // vrc = usbRead16(pszValue, 10, &Dev.wMaxPacketSize, &psz);
+ else if (PREFIX("#Cfgs="))
+ vrc = usbfsRead8(pszValue, 10, &Dev.bNumConfigurations, &psz);
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ cHits++;
+ break;
+
+ /*
+ * P: Vendor=xxxx ProdID=xxxx Rev=xx.xx
+ * | | | |__Product revision number
+ * | | |__Product ID code
+ * | |__Vendor ID code
+ * |__Device info tag #2
+ */
+ case 'P':
+ while (*psz && RT_SUCCESS(vrc))
+ {
+ if (PREFIX("Vendor="))
+ vrc = usbfsRead16(pszValue, 16, &Dev.idVendor, &psz);
+ else if (PREFIX("ProdID="))
+ vrc = usbfsRead16(pszValue, 16, &Dev.idProduct, &psz);
+ else if (PREFIX("Rev="))
+ vrc = usbfsReadBCD(pszValue, 16, &Dev.bcdDevice, &psz);
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ cHits++;
+ break;
+
+ /*
+ * String.
+ */
+ case 'S':
+ if (PREFIX("Manufacturer="))
+ vrc = usbfsReadStr(pszValue, &Dev.pszManufacturer);
+ else if (PREFIX("Product="))
+ vrc = usbfsReadStr(pszValue, &Dev.pszProduct);
+ else if (PREFIX("SerialNumber="))
+ {
+ vrc = usbfsReadStr(pszValue, &Dev.pszSerialNumber);
+ if (RT_SUCCESS(vrc))
+ Dev.u64SerialHash = USBLibHashSerial(pszValue);
+ }
+ break;
+
+ /*
+ * C:* #Ifs=dd Cfg#=dd Atr=xx MPwr=dddmA
+ * | | | | | |__MaxPower in mA
+ * | | | | |__Attributes
+ * | | | |__ConfiguratioNumber
+ * | | |__NumberOfInterfaces
+ * | |__ "*" indicates the active configuration (others are " ")
+ * |__Config info tag
+ */
+ case 'C':
+ break;
+
+ /*
+ * I: If#=dd Alt=dd #EPs=dd Cls=xx(sssss) Sub=xx Prot=xx Driver=ssss
+ * | | | | | | | |__Driver name
+ * | | | | | | | or "(none)"
+ * | | | | | | |__InterfaceProtocol
+ * | | | | | |__InterfaceSubClass
+ * | | | | |__InterfaceClass
+ * | | | |__NumberOfEndpoints
+ * | | |__AlternateSettingNumber
+ * | |__InterfaceNumber
+ * |__Interface info tag
+ */
+ case 'I':
+ {
+ /* Check for thing we don't support. */
+ while (*psz && RT_SUCCESS(vrc))
+ {
+ if (PREFIX("Driver="))
+ {
+ const char *pszDriver = NULL;
+ vrc = usbfsReadStr(pszValue, &pszDriver);
+ if ( !pszDriver
+ || !*pszDriver
+ || !strcmp(pszDriver, "(none)")
+ || !strcmp(pszDriver, "(no driver)"))
+ /* no driver */;
+ else if (!strcmp(pszDriver, "hub"))
+ Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
+ else if (Dev.enmState == USBDEVICESTATE_UNUSED)
+ Dev.enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ RTStrFree((char *)pszDriver);
+ break; /* last attrib */
+ }
+ else if (PREFIX("Cls="))
+ {
+ uint8_t bInterfaceClass;
+ vrc = usbfsRead8(pszValue, 16, &bInterfaceClass, &psz);
+ if (RT_SUCCESS(vrc) && bInterfaceClass == 9 /* HUB */)
+ Dev.enmState = USBDEVICESTATE_UNSUPPORTED;
+ }
+ else
+ psz = usbfsReadSkip(psz);
+ psz = RTStrStripL(psz);
+ }
+ break;
+ }
+
+
+ /*
+ * E: Ad=xx(s) Atr=xx(ssss) MxPS=dddd Ivl=dddms
+ * | | | | |__Interval (max) between transfers
+ * | | | |__EndpointMaxPacketSize
+ * | | |__Attributes(EndpointType)
+ * | |__EndpointAddress(I=In,O=Out)
+ * |__Endpoint info tag
+ */
+ case 'E':
+ break;
+
+ }
+#undef PREFIX
+ } /* parse loop */
+ fclose(pFile);
+
+ /*
+ * Add the current entry.
+ */
+ AssertMsg(cHits >= 3 || cHits == 0, ("cHits=%d\n", cHits));
+ if (cHits >= 3)
+ vrc = usbfsAddDeviceToChain(&Dev, &pFirst, &ppNext, pszUsbfsRoot, fUnsupportedDevicesToo, vrc);
+
+ /*
+ * Success?
+ */
+ if (RT_FAILURE(vrc))
+ {
+ while (pFirst)
+ {
+ PUSBDEVICE pFree = pFirst;
+ pFirst = pFirst->pNext;
+ deviceFree(pFree);
+ }
+ }
+ }
+ if (RT_FAILURE(vrc))
+ LogFlow(("USBProxyServiceLinux::getDevices: vrc=%Rrc\n", vrc));
+ return pFirst;
+}
+
+#endif /* VBOX_USB_WITH_USBFS */
+#ifdef VBOX_USB_WITH_SYSFS
+
+static void usbsysfsCleanupDevInfo(USBDeviceInfo *pSelf)
+{
+ RTStrFree(pSelf->mDevice);
+ RTStrFree(pSelf->mSysfsPath);
+ pSelf->mDevice = pSelf->mSysfsPath = NULL;
+ VEC_CLEANUP_PTR(&pSelf->mvecpszInterfaces);
+}
+
+
+static int usbsysfsInitDevInfo(USBDeviceInfo *pSelf, const char *aDevice, const char *aSystemID)
+{
+ pSelf->mDevice = aDevice ? RTStrDup(aDevice) : NULL;
+ pSelf->mSysfsPath = aSystemID ? RTStrDup(aSystemID) : NULL;
+ VEC_INIT_PTR(&pSelf->mvecpszInterfaces, char *, RTStrFree);
+ if ((aDevice && !pSelf->mDevice) || (aSystemID && ! pSelf->mSysfsPath))
+ {
+ usbsysfsCleanupDevInfo(pSelf);
+ return 0;
+ }
+ return 1;
+}
+
+# define USBDEVICE_MAJOR 189
+
+/**
+ * Calculate the bus (a.k.a root hub) number of a USB device from it's sysfs
+ * path.
+ *
+ * sysfs nodes representing root hubs have file names of the form
+ * usb<n>, where n is the bus number; other devices start with that number.
+ * See [http://www.linux-usb.org/FAQ.html#i6] and
+ * [http://www.kernel.org/doc/Documentation/usb/proc_usb_info.txt] for
+ * equivalent information about usbfs.
+ *
+ * @returns a bus number greater than 0 on success or 0 on failure.
+ */
+static unsigned usbsysfsGetBusFromPath(const char *pszPath)
+{
+ const char *pszFile = strrchr(pszPath, '/');
+ if (!pszFile)
+ return 0;
+ unsigned bus = RTStrToUInt32(pszFile + 1);
+ if ( !bus
+ && pszFile[1] == 'u' && pszFile[2] == 's' && pszFile[3] == 'b')
+ bus = RTStrToUInt32(pszFile + 4);
+ return bus;
+}
+
+
+/**
+ * Calculate the device number of a USB device.
+ *
+ * See drivers/usb/core/hub.c:usb_new_device as of Linux 2.6.20.
+ */
+static dev_t usbsysfsMakeDevNum(unsigned bus, unsigned device)
+{
+ AssertReturn(bus > 0, 0);
+ AssertReturn(((device - 1) & ~127) == 0, 0);
+ AssertReturn(device > 0, 0);
+ return makedev(USBDEVICE_MAJOR, ((bus - 1) << 7) + device - 1);
+}
+
+
+/**
+ * If a file @a pszNode from /sys/bus/usb/devices is a device rather than an
+ * interface add an element for the device to @a pvecDevInfo.
+ */
+static int usbsysfsAddIfDevice(const char *pszDevicesRoot, const char *pszNode, VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo)
+{
+ const char *pszFile = strrchr(pszNode, '/');
+ if (!pszFile)
+ return VERR_INVALID_PARAMETER;
+ if (strchr(pszFile, ':'))
+ return VINF_SUCCESS;
+
+ unsigned bus = usbsysfsGetBusFromPath(pszNode);
+ if (!bus)
+ return VINF_SUCCESS;
+
+ int64_t device;
+ int vrc = RTLinuxSysFsReadIntFile(10, &device, "%s/devnum", pszNode);
+ if (RT_FAILURE(vrc))
+ return VINF_SUCCESS;
+
+ dev_t devnum = usbsysfsMakeDevNum(bus, (int)device);
+ if (!devnum)
+ return VINF_SUCCESS;
+
+ char szDevPath[RTPATH_MAX];
+ vrc = RTLinuxCheckDevicePath(devnum, RTFS_TYPE_DEV_CHAR, szDevPath, sizeof(szDevPath),
+ "%s/%.3d/%.3d", pszDevicesRoot, bus, device);
+ if (RT_FAILURE(vrc))
+ return VINF_SUCCESS;
+
+ USBDeviceInfo info;
+ if (usbsysfsInitDevInfo(&info, szDevPath, pszNode))
+ {
+ vrc = VEC_PUSH_BACK_OBJ(pvecDevInfo, USBDeviceInfo, &info);
+ if (RT_SUCCESS(vrc))
+ return VINF_SUCCESS;
+ }
+ usbsysfsCleanupDevInfo(&info);
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * The logic for testing whether a sysfs address corresponds to an interface of
+ * a device.
+ *
+ * Both must be referenced by their canonical sysfs paths. This is not tested,
+ * as the test requires file-system interaction.
+ */
+static bool usbsysfsMuiIsAnInterfaceOf(const char *pszIface, const char *pszDev)
+{
+ size_t cchDev = strlen(pszDev);
+
+ AssertPtr(pszIface);
+ AssertPtr(pszDev);
+ Assert(pszIface[0] == '/');
+ Assert(pszDev[0] == '/');
+ Assert(pszDev[cchDev - 1] != '/');
+
+ /* If this passes, pszIface is at least cchDev long */
+ if (strncmp(pszIface, pszDev, cchDev))
+ return false;
+
+ /* If this passes, pszIface is longer than cchDev */
+ if (pszIface[cchDev] != '/')
+ return false;
+
+ /* In sysfs an interface is an immediate subdirectory of the device */
+ if (strchr(pszIface + cchDev + 1, '/'))
+ return false;
+
+ /* And it always has a colon in its name */
+ if (!strchr(pszIface + cchDev + 1, ':'))
+ return false;
+
+ /* And hopefully we have now elimitated everything else */
+ return true;
+}
+
+
+# ifdef DEBUG
+# ifdef __cplusplus
+/** Unit test the logic in muiIsAnInterfaceOf in debug builds. */
+class testIsAnInterfaceOf
+{
+public:
+ testIsAnInterfaceOf()
+ {
+ Assert(usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0",
+ "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
+ Assert(!usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1",
+ "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
+ Assert(!usbsysfsMuiIsAnInterfaceOf("/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-0:1.0/driver",
+ "/sys/devices/pci0000:00/0000:00:1a.0/usb3"));
+ }
+};
+static testIsAnInterfaceOf testIsAnInterfaceOfInst;
+# endif /* __cplusplus */
+# endif /* DEBUG */
+
+
+/**
+ * Tell whether a file in /sys/bus/usb/devices is an interface rather than a
+ * device.
+ */
+static int usbsysfsAddIfInterfaceOf(const char *pszNode, USBDeviceInfo *pInfo)
+{
+ if (!usbsysfsMuiIsAnInterfaceOf(pszNode, pInfo->mSysfsPath))
+ return VINF_SUCCESS;
+
+ char *pszDup = (char *)RTStrDup(pszNode);
+ if (pszDup)
+ {
+ int vrc = VEC_PUSH_BACK_PTR(&pInfo->mvecpszInterfaces, char *, pszDup);
+ if (RT_SUCCESS(vrc))
+ return VINF_SUCCESS;
+ RTStrFree(pszDup);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Helper for usbsysfsReadFilePaths().
+ *
+ * Adds the entries from the open directory @a pDir to the vector @a pvecpchDevs
+ * using either the full path or the realpath() and skipping hidden files and
+ * files on which realpath() fails.
+ */
+static int usbsysfsReadFilePathsFromDir(const char *pszPath, DIR *pDir, VECTOR_PTR(char *) *pvecpchDevs)
+{
+ struct dirent entry, *pResult;
+ int err;
+
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+ for (err = readdir_r(pDir, &entry, &pResult); pResult;
+ err = readdir_r(pDir, &entry, &pResult))
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic pop
+#endif
+ {
+ char szPath[RTPATH_MAX + 1];
+ char szRealPath[RTPATH_MAX + 1];
+ if (entry.d_name[0] == '.')
+ continue;
+ if (snprintf(szPath, sizeof(szPath), "%s/%s", pszPath, entry.d_name) < 0)
+ return RTErrConvertFromErrno(errno); /** @todo r=bird: snprintf isn't document to set errno. Also, wouldn't it be better to continue on errors? Finally, you don't need to copy pszPath each time... */
+ if (!realpath(szPath, szRealPath))
+ return RTErrConvertFromErrno(errno);
+ char *pszPathCopy = RTStrDup(szRealPath);
+ if (!pszPathCopy)
+ return VERR_NO_MEMORY;
+ int vrc = VEC_PUSH_BACK_PTR(pvecpchDevs, char *, pszPathCopy);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+ return RTErrConvertFromErrno(err);
+}
+
+
+/**
+ * Dump the names of a directory's entries into a vector of char pointers.
+ *
+ * @returns zero on success or (positive) posix error value.
+ * @param pszPath the path to dump.
+ * @param pvecpchDevs an empty vector of char pointers - must be cleaned up
+ * by the caller even on failure.
+ * @param withRealPath whether to canonicalise the filename with realpath
+ */
+static int usbsysfsReadFilePaths(const char *pszPath, VECTOR_PTR(char *) *pvecpchDevs)
+{
+ AssertPtrReturn(pvecpchDevs, EINVAL);
+ AssertReturn(VEC_SIZE_PTR(pvecpchDevs) == 0, EINVAL);
+ AssertPtrReturn(pszPath, EINVAL);
+
+ DIR *pDir = opendir(pszPath);
+ if (!pDir)
+ return RTErrConvertFromErrno(errno);
+ int vrc = usbsysfsReadFilePathsFromDir(pszPath, pDir, pvecpchDevs);
+ if (closedir(pDir) < 0 && RT_SUCCESS(vrc))
+ vrc = RTErrConvertFromErrno(errno);
+ return vrc;
+}
+
+
+/**
+ * Logic for USBSysfsEnumerateHostDevices.
+ *
+ * @param pvecDevInfo vector of device information structures to add device
+ * information to
+ * @param pvecpchDevs empty scratch vector which will be freed by the caller,
+ * to simplify exit logic
+ */
+static int usbsysfsEnumerateHostDevicesWorker(const char *pszDevicesRoot,
+ VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo,
+ VECTOR_PTR(char *) *pvecpchDevs)
+{
+
+ AssertPtrReturn(pvecDevInfo, VERR_INVALID_POINTER);
+ LogFlowFunc (("pvecDevInfo=%p\n", pvecDevInfo));
+
+ int vrc = usbsysfsReadFilePaths("/sys/bus/usb/devices", pvecpchDevs);
+ if (RT_FAILURE(vrc))
+ return vrc;
+
+ char **ppszEntry;
+ VEC_FOR_EACH(pvecpchDevs, char *, ppszEntry)
+ {
+ vrc = usbsysfsAddIfDevice(pszDevicesRoot, *ppszEntry, pvecDevInfo);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+
+ USBDeviceInfo *pInfo;
+ VEC_FOR_EACH(pvecDevInfo, USBDeviceInfo, pInfo)
+ VEC_FOR_EACH(pvecpchDevs, char *, ppszEntry)
+ {
+ vrc = usbsysfsAddIfInterfaceOf(*ppszEntry, pInfo);
+ if (RT_FAILURE(vrc))
+ return vrc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int usbsysfsEnumerateHostDevices(const char *pszDevicesRoot, VECTOR_OBJ(USBDeviceInfo) *pvecDevInfo)
+{
+ VECTOR_PTR(char *) vecpchDevs;
+
+ AssertReturn(VEC_SIZE_OBJ(pvecDevInfo) == 0, VERR_INVALID_PARAMETER);
+ LogFlowFunc(("entered\n"));
+ VEC_INIT_PTR(&vecpchDevs, char *, RTStrFree);
+ int vrc = usbsysfsEnumerateHostDevicesWorker(pszDevicesRoot, pvecDevInfo, &vecpchDevs);
+ VEC_CLEANUP_PTR(&vecpchDevs);
+ LogFlowFunc(("vrc=%Rrc\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Helper function for extracting the port number on the parent device from
+ * the sysfs path value.
+ *
+ * The sysfs path is a chain of elements separated by forward slashes, and for
+ * USB devices, the last element in the chain takes the form
+ * <port>-<port>.[...].<port>[:<config>.<interface>]
+ * where the first <port> is the port number on the root hub, and the following
+ * (optional) ones are the port numbers on any other hubs between the device
+ * and the root hub. The last part (:<config.interface>) is only present for
+ * interfaces, not for devices. This API should only be called for devices.
+ * For compatibility with usbfs, which enumerates from zero up, we subtract one
+ * from the port number.
+ *
+ * For root hubs, the last element in the chain takes the form
+ * usb<hub number>
+ * and usbfs always returns port number zero.
+ *
+ * @returns VBox status code. pu8Port is set on success.
+ * @param pszPath The sysfs path to parse.
+ * @param pu8Port Where to store the port number.
+ */
+static int usbsysfsGetPortFromStr(const char *pszPath, uint8_t *pu8Port)
+{
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pu8Port, VERR_INVALID_POINTER);
+
+ /*
+ * This should not be possible until we get PCs with USB as their primary bus.
+ * Note: We don't assert this, as we don't expect the caller to validate the
+ * sysfs path.
+ */
+ const char *pszLastComp = strrchr(pszPath, '/');
+ if (!pszLastComp)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [1]\n", pszPath));
+ return VERR_INVALID_PARAMETER;
+ }
+ pszLastComp++; /* skip the slash */
+
+ /*
+ * This API should not be called for interfaces, so the last component
+ * of the path should not contain a colon. We *do* assert this, as it
+ * might indicate a caller bug.
+ */
+ AssertMsgReturn(strchr(pszLastComp, ':') == NULL, ("%s\n", pszPath), VERR_INVALID_PARAMETER);
+
+ /*
+ * Look for the start of the last number.
+ */
+ const char *pchDash = strrchr(pszLastComp, '-');
+ const char *pchDot = strrchr(pszLastComp, '.');
+ if (!pchDash && !pchDot)
+ {
+ /* No -/. so it must be a root hub. Check that it's usb<something>. */
+ if (strncmp(pszLastComp, RT_STR_TUPLE("usb")) != 0)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [2]\n", pszPath));
+ return VERR_INVALID_PARAMETER;
+ }
+ return VERR_NOT_SUPPORTED;
+ }
+
+ const char *pszLastPort = pchDot != NULL
+ ? pchDot + 1
+ : pchDash + 1;
+ int vrc = RTStrToUInt8Full(pszLastPort, 10, pu8Port);
+ if (vrc != VINF_SUCCESS)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [3], vrc=%Rrc\n", pszPath, vrc));
+ return VERR_INVALID_PARAMETER;
+ }
+ if (*pu8Port == 0)
+ {
+ Log(("usbGetPortFromSysfsPath(%s): failed [4]\n", pszPath));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /* usbfs compatibility, 0-based port number. */
+ *pu8Port = (uint8_t)(*pu8Port - 1);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts a sysfs BCD value into a uint16_t.
+ *
+ * In contrast to usbReadBCD() this function can handle BCD values without
+ * a decimal separator. This is necessary for parsing bcdDevice.
+ *
+ * @param pszBuf Pointer to the string buffer.
+ * @param pu15 Pointer to the return value.
+ * @returns IPRT status code.
+ */
+static int usbsysfsConvertStrToBCD(const char *pszBuf, uint16_t *pu16)
+{
+ char *pszNext;
+ int32_t i32;
+
+ pszBuf = RTStrStripL(pszBuf);
+ int vrc = RTStrToInt32Ex(pszBuf, &pszNext, 16, &i32);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_NUMBER_TOO_BIG
+ || i32 < 0)
+ return VERR_NUMBER_TOO_BIG;
+ if (*pszNext == '.')
+ {
+ if (i32 > 255)
+ return VERR_NUMBER_TOO_BIG;
+ int32_t i32Lo;
+ vrc = RTStrToInt32Ex(pszNext+1, &pszNext, 16, &i32Lo);
+ if ( RT_FAILURE(vrc)
+ || vrc == VWRN_NUMBER_TOO_BIG
+ || i32Lo > 255
+ || i32Lo < 0)
+ return VERR_NUMBER_TOO_BIG;
+ i32 = (i32 << 8) | i32Lo;
+ }
+ if ( i32 > 65535
+ || (*pszNext != '\0' && *pszNext != ' '))
+ return VERR_NUMBER_TOO_BIG;
+
+ *pu16 = (uint16_t)i32;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns the byte value for the given device property or sets the given default if an
+ * error occurs while obtaining it.
+ *
+ * @returns uint8_t value of the given property.
+ * @param uBase The base of the number in the sysfs property.
+ * @param fDef The default to set on error.
+ * @param pszFormat The format string for the property.
+ * @param ... Arguments for the format string.
+ */
+static uint8_t usbsysfsReadDevicePropertyU8Def(unsigned uBase, uint8_t fDef, const char *pszFormat, ...)
+{
+ int64_t i64Tmp = 0;
+
+ va_list va;
+ va_start(va, pszFormat);
+ int vrc = RTLinuxSysFsReadIntFileV(uBase, &i64Tmp, pszFormat, va);
+ va_end(va);
+ if (RT_SUCCESS(vrc))
+ return (uint8_t)i64Tmp;
+ return fDef;
+}
+
+
+/**
+ * Returns the uint16_t value for the given device property or sets the given default if an
+ * error occurs while obtaining it.
+ *
+ * @returns uint16_t value of the given property.
+ * @param uBase The base of the number in the sysfs property.
+ * @param u16Def The default to set on error.
+ * @param pszFormat The format string for the property.
+ * @param ... Arguments for the format string.
+ */
+static uint16_t usbsysfsReadDevicePropertyU16Def(unsigned uBase, uint16_t u16Def, const char *pszFormat, ...)
+{
+ int64_t i64Tmp = 0;
+
+ va_list va;
+ va_start(va, pszFormat);
+ int vrc = RTLinuxSysFsReadIntFileV(uBase, &i64Tmp, pszFormat, va);
+ va_end(va);
+ if (RT_SUCCESS(vrc))
+ return (uint16_t)i64Tmp;
+ return u16Def;
+}
+
+
+static void usbsysfsFillInDevice(USBDEVICE *pDev, USBDeviceInfo *pInfo)
+{
+ int vrc;
+ const char *pszSysfsPath = pInfo->mSysfsPath;
+
+ /* Fill in the simple fields */
+ pDev->enmState = USBDEVICESTATE_UNUSED;
+ pDev->bBus = (uint8_t)usbsysfsGetBusFromPath(pszSysfsPath);
+ pDev->bDeviceClass = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceClass", pszSysfsPath);
+ pDev->bDeviceSubClass = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceSubClass", pszSysfsPath);
+ pDev->bDeviceProtocol = usbsysfsReadDevicePropertyU8Def(16, 0, "%s/bDeviceProtocol", pszSysfsPath);
+ pDev->bNumConfigurations = usbsysfsReadDevicePropertyU8Def(10, 0, "%s/bNumConfigurations", pszSysfsPath);
+ pDev->idVendor = usbsysfsReadDevicePropertyU16Def(16, 0, "%s/idVendor", pszSysfsPath);
+ pDev->idProduct = usbsysfsReadDevicePropertyU16Def(16, 0, "%s/idProduct", pszSysfsPath);
+ pDev->bDevNum = usbsysfsReadDevicePropertyU8Def(10, 0, "%s/devnum", pszSysfsPath);
+
+ /* Now deal with the non-numeric bits. */
+ char szBuf[1024]; /* Should be larger than anything a sane device
+ * will need, and insane devices can be unsupported
+ * until further notice. */
+ size_t cchRead;
+
+ /* For simplicity, we just do strcmps on the next one. */
+ vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/speed", pszSysfsPath);
+ if (RT_FAILURE(vrc) || cchRead == sizeof(szBuf))
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ else
+ pDev->enmSpeed = !strcmp(szBuf, "1.5") ? USBDEVICESPEED_LOW
+ : !strcmp(szBuf, "12") ? USBDEVICESPEED_FULL
+ : !strcmp(szBuf, "480") ? USBDEVICESPEED_HIGH
+ : !strcmp(szBuf, "5000") ? USBDEVICESPEED_SUPER
+ : USBDEVICESPEED_UNKNOWN;
+
+ vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/version", pszSysfsPath);
+ if (RT_FAILURE(vrc) || cchRead == sizeof(szBuf))
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ else
+ {
+ vrc = usbsysfsConvertStrToBCD(szBuf, &pDev->bcdUSB);
+ if (RT_FAILURE(vrc))
+ {
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ pDev->bcdUSB = UINT16_MAX;
+ }
+ }
+
+ vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/bcdDevice", pszSysfsPath);
+ if (RT_FAILURE(vrc) || cchRead == sizeof(szBuf))
+ pDev->bcdDevice = UINT16_MAX;
+ else
+ {
+ vrc = usbsysfsConvertStrToBCD(szBuf, &pDev->bcdDevice);
+ if (RT_FAILURE(vrc))
+ pDev->bcdDevice = UINT16_MAX;
+ }
+
+ /* Now do things that need string duplication */
+ vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/product", pszSysfsPath);
+ if (RT_SUCCESS(vrc) && cchRead < sizeof(szBuf))
+ {
+ USBLibPurgeEncoding(szBuf);
+ pDev->pszProduct = RTStrDup(szBuf);
+ }
+
+ vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/serial", pszSysfsPath);
+ if (RT_SUCCESS(vrc) && cchRead < sizeof(szBuf))
+ {
+ USBLibPurgeEncoding(szBuf);
+ pDev->pszSerialNumber = RTStrDup(szBuf);
+ pDev->u64SerialHash = USBLibHashSerial(szBuf);
+ }
+
+ vrc = RTLinuxSysFsReadStrFile(szBuf, sizeof(szBuf), &cchRead, "%s/manufacturer", pszSysfsPath);
+ if (RT_SUCCESS(vrc) && cchRead < sizeof(szBuf))
+ {
+ USBLibPurgeEncoding(szBuf);
+ pDev->pszManufacturer = RTStrDup(szBuf);
+ }
+
+ /* Work out the port number */
+ if (RT_FAILURE(usbsysfsGetPortFromStr(pszSysfsPath, &pDev->bPort)))
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+
+ /* Check the interfaces to see if we can support the device. */
+ char **ppszIf;
+ VEC_FOR_EACH(&pInfo->mvecpszInterfaces, char *, ppszIf)
+ {
+ vrc = RTLinuxSysFsGetLinkDest(szBuf, sizeof(szBuf), NULL, "%s/driver", *ppszIf);
+ if (RT_SUCCESS(vrc) && pDev->enmState != USBDEVICESTATE_UNSUPPORTED)
+ pDev->enmState = (strcmp(szBuf, "hub") == 0)
+ ? USBDEVICESTATE_UNSUPPORTED
+ : USBDEVICESTATE_USED_BY_HOST_CAPTURABLE;
+ if (usbsysfsReadDevicePropertyU8Def(16, 9 /* bDev */, "%s/bInterfaceClass", *ppszIf) == 9 /* hub */)
+ pDev->enmState = USBDEVICESTATE_UNSUPPORTED;
+ }
+
+ /* We use a double slash as a separator in the pszAddress field. This is
+ * alright as the two paths can't contain a slash due to the way we build
+ * them. */
+ char *pszAddress = NULL;
+ RTStrAPrintf(&pszAddress, "sysfs:%s//device:%s", pszSysfsPath, pInfo->mDevice);
+ pDev->pszAddress = pszAddress;
+ pDev->pszBackend = RTStrDup("host");
+
+ /* Work out from the data collected whether we can support this device. */
+ pDev->enmState = usbDeterminState(pDev);
+ usbLogDevice(pDev);
+}
+
+
+/**
+ * USBProxyService::getDevices() implementation for sysfs.
+ */
+static PUSBDEVICE usbsysfsGetDevices(const char *pszDevicesRoot, bool fUnsupportedDevicesToo)
+{
+ /* Add each of the devices found to the chain. */
+ PUSBDEVICE pFirst = NULL;
+ PUSBDEVICE pLast = NULL;
+ VECTOR_OBJ(USBDeviceInfo) vecDevInfo;
+ USBDeviceInfo *pInfo;
+
+ VEC_INIT_OBJ(&vecDevInfo, USBDeviceInfo, usbsysfsCleanupDevInfo);
+ int vrc = usbsysfsEnumerateHostDevices(pszDevicesRoot, &vecDevInfo);
+ if (RT_FAILURE(vrc))
+ return NULL;
+ VEC_FOR_EACH(&vecDevInfo, USBDeviceInfo, pInfo)
+ {
+ USBDEVICE *pDev = (USBDEVICE *)RTMemAllocZ(sizeof(USBDEVICE));
+ if (!pDev)
+ vrc = VERR_NO_MEMORY;
+ if (RT_SUCCESS(vrc))
+ usbsysfsFillInDevice(pDev, pInfo);
+ if ( RT_SUCCESS(vrc)
+ && ( pDev->enmState != USBDEVICESTATE_UNSUPPORTED
+ || fUnsupportedDevicesToo)
+ && pDev->pszAddress != NULL
+ )
+ {
+ if (pLast != NULL)
+ {
+ pLast->pNext = pDev;
+ pLast = pLast->pNext;
+ }
+ else
+ pFirst = pLast = pDev;
+ }
+ else
+ deviceFree(pDev);
+ if (RT_FAILURE(vrc))
+ break;
+ }
+ if (RT_FAILURE(vrc))
+ deviceListFree(&pFirst);
+
+ VEC_CLEANUP_OBJ(&vecDevInfo);
+ return pFirst;
+}
+
+#endif /* VBOX_USB_WITH_SYSFS */
+#ifdef UNIT_TEST
+
+/* Set up mock functions for USBProxyLinuxCheckDeviceRoot - here dlsym and close
+ * for the inotify presence check. */
+static int testInotifyInitGood(void) { return 0; }
+static int testInotifyInitBad(void) { return -1; }
+static bool s_fHaveInotifyLibC = true;
+static bool s_fHaveInotifyKernel = true;
+
+static void *testDLSym(void *handle, const char *symbol)
+{
+ RT_NOREF(handle, symbol);
+ Assert(handle == RTLD_DEFAULT);
+ Assert(!RTStrCmp(symbol, "inotify_init"));
+ if (!s_fHaveInotifyLibC)
+ return NULL;
+ if (s_fHaveInotifyKernel)
+ return (void *)(uintptr_t)testInotifyInitGood;
+ return (void *)(uintptr_t)testInotifyInitBad;
+}
+
+void TestUSBSetInotifyAvailable(bool fHaveInotifyLibC, bool fHaveInotifyKernel)
+{
+ s_fHaveInotifyLibC = fHaveInotifyLibC;
+ s_fHaveInotifyKernel = fHaveInotifyKernel;
+}
+# define dlsym testDLSym
+# define close(a) do {} while (0)
+
+#endif /* UNIT_TEST */
+
+/**
+ * Is inotify available and working on this system?
+ *
+ * This is a requirement for using USB with sysfs
+ */
+static bool usbsysfsInotifyAvailable(void)
+{
+ int (*inotify_init)(void);
+
+ *(void **)(&inotify_init) = dlsym(RTLD_DEFAULT, "inotify_init");
+ if (!inotify_init)
+ return false;
+ int fd = inotify_init();
+ if (fd == -1)
+ return false;
+ close(fd);
+ return true;
+}
+
+#ifdef UNIT_TEST
+
+# undef dlsym
+# undef close
+
+/** Unit test list of usbfs addresses of connected devices. */
+static const char **g_papszUsbfsDeviceAddresses = NULL;
+
+static PUSBDEVICE testGetUsbfsDevices(const char *pszUsbfsRoot, bool fUnsupportedDevicesToo)
+{
+ RT_NOREF(pszUsbfsRoot, fUnsupportedDevicesToo);
+ const char **psz;
+ PUSBDEVICE pList = NULL, pTail = NULL;
+ for (psz = g_papszUsbfsDeviceAddresses; psz && *psz; ++psz)
+ {
+ PUSBDEVICE pNext = (PUSBDEVICE)RTMemAllocZ(sizeof(USBDEVICE));
+ if (pNext)
+ pNext->pszAddress = RTStrDup(*psz);
+ if (!pNext || !pNext->pszAddress)
+ {
+ if (pNext)
+ RTMemFree(pNext);
+ deviceListFree(&pList);
+ return NULL;
+ }
+ if (pTail)
+ pTail->pNext = pNext;
+ else
+ pList = pNext;
+ pTail = pNext;
+ }
+ return pList;
+}
+# define usbfsGetDevices testGetUsbfsDevices
+
+/**
+ * Specify the list of devices that will appear to be available through
+ * usbfs during unit testing (of USBProxyLinuxGetDevices)
+ * @param pacszDeviceAddresses NULL terminated array of usbfs device addresses
+ */
+void TestUSBSetAvailableUsbfsDevices(const char **papszDeviceAddresses)
+{
+ g_papszUsbfsDeviceAddresses = papszDeviceAddresses;
+}
+
+/** Unit test list of files reported as accessible by access(3). We only do
+ * accessible or not accessible. */
+static const char **g_papszAccessibleFiles = NULL;
+
+static int testAccess(const char *pszPath, int mode)
+{
+ RT_NOREF(mode);
+ const char **psz;
+ for (psz = g_papszAccessibleFiles; psz && *psz; ++psz)
+ if (!RTStrCmp(pszPath, *psz))
+ return 0;
+ return -1;
+}
+# define access testAccess
+
+
+/**
+ * Specify the list of files that access will report as accessible (at present
+ * we only do accessible or not accessible) during unit testing (of
+ * USBProxyLinuxGetDevices)
+ * @param papszAccessibleFiles NULL terminated array of file paths to be
+ * reported accessible
+ */
+void TestUSBSetAccessibleFiles(const char **papszAccessibleFiles)
+{
+ g_papszAccessibleFiles = papszAccessibleFiles;
+}
+
+
+/** The path we pretend the usbfs root is located at, or NULL. */
+const char *s_pszTestUsbfsRoot;
+/** Should usbfs be accessible to the current user? */
+bool s_fTestUsbfsAccessible;
+/** The path we pretend the device node tree root is located at, or NULL. */
+const char *s_pszTestDevicesRoot;
+/** Should the device node tree be accessible to the current user? */
+bool s_fTestDevicesAccessible;
+/** The result of the usbfs/inotify-specific init */
+int s_vrcTestMethodInitResult;
+/** The value of the VBOX_USB environment variable. */
+const char *s_pszTestEnvUsb;
+/** The value of the VBOX_USB_ROOT environment variable. */
+const char *s_pszTestEnvUsbRoot;
+
+
+/** Select which access methods will be available to the @a init method
+ * during unit testing, and (hack!) what return code it will see from
+ * the access method-specific initialisation. */
+void TestUSBSetupInit(const char *pszUsbfsRoot, bool fUsbfsAccessible,
+ const char *pszDevicesRoot, bool fDevicesAccessible,
+ int vrcMethodInitResult)
+{
+ s_pszTestUsbfsRoot = pszUsbfsRoot;
+ s_fTestUsbfsAccessible = fUsbfsAccessible;
+ s_pszTestDevicesRoot = pszDevicesRoot;
+ s_fTestDevicesAccessible = fDevicesAccessible;
+ s_vrcTestMethodInitResult = vrcMethodInitResult;
+}
+
+
+/** Specify the environment that the @a init method will see during unit
+ * testing. */
+void TestUSBSetEnv(const char *pszEnvUsb, const char *pszEnvUsbRoot)
+{
+ s_pszTestEnvUsb = pszEnvUsb;
+ s_pszTestEnvUsbRoot = pszEnvUsbRoot;
+}
+
+/* For testing we redefine anything that accesses the outside world to
+ * return test values. */
+# define RTEnvGet(a) \
+ ( !RTStrCmp(a, "VBOX_USB") ? s_pszTestEnvUsb \
+ : !RTStrCmp(a, "VBOX_USB_ROOT") ? s_pszTestEnvUsbRoot \
+ : NULL)
+# define USBProxyLinuxCheckDeviceRoot(pszPath, fUseNodes) \
+ ( ((fUseNodes) && s_fTestDevicesAccessible \
+ && !RTStrCmp(pszPath, s_pszTestDevicesRoot)) \
+ || (!(fUseNodes) && s_fTestUsbfsAccessible \
+ && !RTStrCmp(pszPath, s_pszTestUsbfsRoot)))
+# define RTDirExists(pszDir) \
+ ( (pszDir) \
+ && ( !RTStrCmp(pszDir, s_pszTestDevicesRoot) \
+ || !RTStrCmp(pszDir, s_pszTestUsbfsRoot)))
+# define RTFileExists(pszFile) \
+ ( (pszFile) \
+ && s_pszTestUsbfsRoot \
+ && !RTStrNCmp(pszFile, s_pszTestUsbfsRoot, strlen(s_pszTestUsbfsRoot)) \
+ && !RTStrCmp(pszFile + strlen(s_pszTestUsbfsRoot), "/devices"))
+
+#endif /* UNIT_TEST */
+
+/**
+ * Use USBFS-like or sysfs/device node-like access method?
+ *
+ * Selects the access method that will be used to access USB devices based on
+ * what is available on the host and what if anything the user has specified
+ * in the environment.
+ *
+ * @returns iprt status value
+ * @param pfUsingUsbfsDevices on success this will be set to true if
+ * the prefered access method is USBFS-like and to
+ * false if it is sysfs/device node-like
+ * @param ppszDevicesRoot on success the root of the tree of USBFS-like
+ * device nodes will be stored here
+ */
+int USBProxyLinuxChooseMethod(bool *pfUsingUsbfsDevices, const char **ppszDevicesRoot)
+{
+ /*
+ * We have two methods available for getting host USB device data - using
+ * USBFS and using sysfs. The default choice is sysfs; if that is not
+ * available we fall back to USBFS.
+ * In the event of both failing, an appropriate error will be returned.
+ * The user may also specify a method and root using the VBOX_USB and
+ * VBOX_USB_ROOT environment variables. In this case we don't check
+ * the root they provide for validity.
+ */
+ bool fUsbfsChosen = false;
+ bool fSysfsChosen = false;
+ const char *pszUsbFromEnv = RTEnvGet("VBOX_USB");
+ const char *pszUsbRoot = NULL;
+ if (pszUsbFromEnv)
+ {
+ bool fValidVBoxUSB = true;
+
+ pszUsbRoot = RTEnvGet("VBOX_USB_ROOT");
+ if (!RTStrICmp(pszUsbFromEnv, "USBFS"))
+ {
+ LogRel(("Default USB access method set to \"usbfs\" from environment\n"));
+ fUsbfsChosen = true;
+ }
+ else if (!RTStrICmp(pszUsbFromEnv, "SYSFS"))
+ {
+ LogRel(("Default USB method set to \"sysfs\" from environment\n"));
+ fSysfsChosen = true;
+ }
+ else
+ {
+ LogRel(("Invalid VBOX_USB environment variable setting \"%s\"\n", pszUsbFromEnv));
+ fValidVBoxUSB = false;
+ pszUsbFromEnv = NULL;
+ }
+ if (!fValidVBoxUSB && pszUsbRoot)
+ pszUsbRoot = NULL;
+ }
+ if (!pszUsbRoot)
+ {
+ if ( !fUsbfsChosen
+ && USBProxyLinuxCheckDeviceRoot("/dev/vboxusb", true))
+ {
+ fSysfsChosen = true;
+ pszUsbRoot = "/dev/vboxusb";
+ }
+ else if ( !fSysfsChosen
+ && USBProxyLinuxCheckDeviceRoot("/proc/bus/usb", false))
+ {
+ fUsbfsChosen = true;
+ pszUsbRoot = "/proc/bus/usb";
+ }
+ }
+ else if (!USBProxyLinuxCheckDeviceRoot(pszUsbRoot, fSysfsChosen))
+ pszUsbRoot = NULL;
+ if (pszUsbRoot)
+ {
+ *pfUsingUsbfsDevices = fUsbfsChosen;
+ *ppszDevicesRoot = pszUsbRoot;
+ return VINF_SUCCESS;
+ }
+ /* else */
+ return pszUsbFromEnv ? VERR_NOT_FOUND
+ : RTDirExists("/dev/vboxusb") ? VERR_VUSB_USB_DEVICE_PERMISSION
+ : RTFileExists("/proc/bus/usb/devices") ? VERR_VUSB_USBFS_PERMISSION
+ : VERR_NOT_FOUND;
+}
+
+#ifdef UNIT_TEST
+# undef RTEnvGet
+# undef USBProxyLinuxCheckDeviceRoot
+# undef RTDirExists
+# undef RTFileExists
+#endif
+
+/**
+ * Check whether a USB device tree root is usable.
+ *
+ * @param pszRoot the path to the root of the device tree
+ * @param fIsDeviceNodes whether this is a device node (or usbfs) tree
+ * @note returns a pointer into a static array so it will stay valid
+ */
+bool USBProxyLinuxCheckDeviceRoot(const char *pszRoot, bool fIsDeviceNodes)
+{
+ bool fOK = false;
+ if (!fIsDeviceNodes) /* usbfs */
+ {
+#ifdef VBOX_USB_WITH_USBFS
+ if (!access(pszRoot, R_OK | X_OK))
+ {
+ fOK = true;
+ PUSBDEVICE pDevices = usbfsGetDevices(pszRoot, true);
+ if (pDevices)
+ {
+ PUSBDEVICE pDevice;
+ for (pDevice = pDevices; pDevice && fOK; pDevice = pDevice->pNext)
+ if (access(pDevice->pszAddress, R_OK | W_OK))
+ fOK = false;
+ deviceListFree(&pDevices);
+ }
+ }
+#endif
+ }
+#ifdef VBOX_USB_WITH_SYSFS
+ /* device nodes */
+ else if (usbsysfsInotifyAvailable() && !access(pszRoot, R_OK | X_OK))
+ fOK = true;
+#endif
+ return fOK;
+}
+
+#ifdef UNIT_TEST
+# undef usbfsGetDevices
+# undef access
+#endif
+
+/**
+ * Get the list of USB devices supported by the system.
+ *
+ * Result should be freed using #deviceFree or something equivalent.
+ *
+ * @param pszDevicesRoot the path to the root of the device tree
+ * @param fUseSysfs whether to use sysfs (or usbfs) for enumeration
+ */
+PUSBDEVICE USBProxyLinuxGetDevices(const char *pszDevicesRoot, bool fUseSysfs)
+{
+ if (!fUseSysfs)
+ {
+#ifdef VBOX_USB_WITH_USBFS
+ return usbfsGetDevices(pszDevicesRoot, false);
+#else
+ return NULL;
+#endif
+ }
+
+#ifdef VBOX_USB_WITH_SYSFS
+ return usbsysfsGetDevices(pszDevicesRoot, false);
+#else
+ return NULL;
+#endif
+}
+
diff --git a/src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp b/src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp
new file mode 100644
index 00000000..bbfb7bed
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/USBProxyBackendLinux.cpp
@@ -0,0 +1,399 @@
+/* $Id: USBProxyBackendLinux.cpp $ */
+/** @file
+ * VirtualBox USB Proxy Service, Linux Specialization.
+ */
+
+/*
+ * Copyright (C) 2005-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of 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_MAIN_USBPROXYBACKEND
+#include "USBProxyService.h"
+#include "USBGetDevices.h"
+#include "LoggingNew.h"
+
+#include <VBox/usb.h>
+#include <VBox/usblib.h>
+#include <iprt/errcore.h>
+
+#include <iprt/string.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/stream.h>
+#include <iprt/linux/sysfs.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/statfs.h>
+#include <sys/poll.h>
+#ifdef VBOX_WITH_LINUX_COMPILER_H
+# include <linux/compiler.h>
+#endif
+#include <linux/usbdevice_fs.h>
+
+
+/**
+ * Initialize data members.
+ */
+USBProxyBackendLinux::USBProxyBackendLinux()
+ : USBProxyBackend(), mhFile(NIL_RTFILE), mhWakeupPipeR(NIL_RTPIPE), mhWakeupPipeW(NIL_RTPIPE), mpWaiter(NULL)
+{
+ LogFlowThisFunc(("\n"));
+}
+
+
+/**
+ * Stop all service threads and free the device chain.
+ */
+USBProxyBackendLinux::~USBProxyBackendLinux()
+{
+ LogFlowThisFunc(("\n"));
+}
+
+/**
+ * Initializes the object (called right after construction).
+ *
+ * @returns VBox status code.
+ */
+int USBProxyBackendLinux::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
+ const com::Utf8Str &strAddress, bool fLoadingSettings)
+{
+ USBProxyBackend::init(pUsbProxyService, strId, strAddress, fLoadingSettings);
+
+ unconst(m_strBackend) = Utf8Str("host");
+
+ const char *pcszDevicesRoot;
+ int vrc = USBProxyLinuxChooseMethod(&mUsingUsbfsDevices, &pcszDevicesRoot);
+ if (RT_SUCCESS(vrc))
+ {
+ mDevicesRoot = pcszDevicesRoot;
+ vrc = mUsingUsbfsDevices ? initUsbfs() : initSysfs();
+ /* For the day when we have VBoxSVC release logging... */
+ LogRel((RT_SUCCESS(vrc) ? "Successfully initialised host USB using %s\n"
+ : "Failed to initialise host USB using %s\n",
+ mUsingUsbfsDevices ? "USBFS" : "sysfs"));
+ }
+
+ return vrc;
+}
+
+void USBProxyBackendLinux::uninit()
+{
+ /*
+ * Stop the service.
+ */
+ if (isActive())
+ stop();
+
+ /*
+ * Free resources.
+ */
+ doUsbfsCleanupAsNeeded();
+#ifdef VBOX_USB_WITH_SYSFS
+ if (mpWaiter)
+ delete mpWaiter;
+#endif
+
+ USBProxyBackend::uninit();
+}
+
+
+/**
+ * Initialization routine for the usbfs based operation.
+ *
+ * @returns iprt status code.
+ */
+int USBProxyBackendLinux::initUsbfs(void)
+{
+ Assert(mUsingUsbfsDevices);
+
+ /*
+ * Open the devices file.
+ */
+ int vrc;
+ char *pszDevices = RTPathJoinA(mDevicesRoot.c_str(), "devices");
+ if (pszDevices)
+ {
+ vrc = RTFileOpen(&mhFile, pszDevices, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(vrc))
+ {
+ vrc = RTPipeCreate(&mhWakeupPipeR, &mhWakeupPipeW, 0 /*fFlags*/);
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Start the poller thread.
+ */
+ vrc = start();
+ if (RT_SUCCESS(vrc))
+ {
+ RTStrFree(pszDevices);
+ LogFlowThisFunc(("returns successfully\n"));
+ return VINF_SUCCESS;
+ }
+
+ RTPipeClose(mhWakeupPipeR);
+ RTPipeClose(mhWakeupPipeW);
+ mhWakeupPipeW = mhWakeupPipeR = NIL_RTPIPE;
+ }
+ else
+ Log(("USBProxyBackendLinux::USBProxyBackendLinux: RTFilePipe failed with vrc=%Rrc\n", vrc));
+ RTFileClose(mhFile);
+ }
+
+ RTStrFree(pszDevices);
+ }
+ else
+ {
+ vrc = VERR_NO_MEMORY;
+ Log(("USBProxyBackendLinux::USBProxyBackendLinux: out of memory!\n"));
+ }
+
+ LogFlowThisFunc(("returns failure!!! (vrc=%Rrc)\n", vrc));
+ return vrc;
+}
+
+
+/**
+ * Initialization routine for the sysfs based operation.
+ *
+ * @returns iprt status code
+ */
+int USBProxyBackendLinux::initSysfs(void)
+{
+ Assert(!mUsingUsbfsDevices);
+
+#ifdef VBOX_USB_WITH_SYSFS
+ try
+ {
+ mpWaiter = new VBoxMainHotplugWaiter(mDevicesRoot.c_str());
+ }
+ catch(std::bad_alloc &e)
+ {
+ return VERR_NO_MEMORY;
+ }
+ int vrc = mpWaiter->getStatus();
+ if (RT_SUCCESS(vrc) || vrc == VERR_TIMEOUT || vrc == VERR_TRY_AGAIN)
+ vrc = start();
+ else if (vrc == VERR_NOT_SUPPORTED)
+ /* This can legitimately happen if hal or DBus are not running, but of
+ * course we can't start in this case. */
+ vrc = VINF_SUCCESS;
+ return vrc;
+
+#else /* !VBOX_USB_WITH_SYSFS */
+ return VERR_NOT_IMPLEMENTED;
+#endif /* !VBOX_USB_WITH_SYSFS */
+}
+
+
+/**
+ * If any Usbfs-related resources are currently allocated, then free them
+ * and mark them as freed.
+ */
+void USBProxyBackendLinux::doUsbfsCleanupAsNeeded()
+{
+ /*
+ * Free resources.
+ */
+ if (mhFile != NIL_RTFILE)
+ RTFileClose(mhFile);
+ mhFile = NIL_RTFILE;
+
+ if (mhWakeupPipeR != NIL_RTPIPE)
+ RTPipeClose(mhWakeupPipeR);
+ if (mhWakeupPipeW != NIL_RTPIPE)
+ RTPipeClose(mhWakeupPipeW);
+ mhWakeupPipeW = mhWakeupPipeR = NIL_RTPIPE;
+}
+
+
+int USBProxyBackendLinux::captureDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * Don't think we need to do anything when the device is held... fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_Capturing);
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+int USBProxyBackendLinux::releaseDevice(HostUSBDevice *aDevice)
+{
+ AssertReturn(aDevice, VERR_GENERAL_FAILURE);
+ AssertReturn(!aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ LogFlowThisFunc(("aDevice=%s\n", aDevice->i_getName().c_str()));
+
+ /*
+ * We're not really holding it atm., just fake it.
+ */
+ Assert(aDevice->i_getUnistate() == kHostUSBDeviceState_ReleasingToHost);
+ devLock.release();
+ interruptWait();
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A device was added, we need to adjust mUdevPolls.
+ */
+void USBProxyBackendLinux::deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE pDev)
+{
+ AssertReturnVoid(aDevice);
+ AssertReturnVoid(!aDevice->isWriteLockOnCurrentThread());
+ AutoReadLock devLock(aDevice COMMA_LOCKVAL_SRC_POS);
+ if (pDev->enmState == USBDEVICESTATE_USED_BY_HOST)
+ {
+ LogRel(("USBProxyBackendLinux: Device %04x:%04x (%s) isn't accessible. giving udev a few seconds to fix this...\n",
+ pDev->idVendor, pDev->idProduct, pDev->pszAddress));
+ mUdevPolls = 10; /* (10 * 500ms = 5s) */
+ }
+
+ devLock.release();
+}
+
+
+bool USBProxyBackendLinux::isFakeUpdateRequired()
+{
+ return true;
+}
+
+int USBProxyBackendLinux::wait(RTMSINTERVAL aMillies)
+{
+ int vrc;
+ if (mUsingUsbfsDevices)
+ vrc = waitUsbfs(aMillies);
+ else
+ vrc = waitSysfs(aMillies);
+ return vrc;
+}
+
+
+/** String written to the wakeup pipe. */
+#define WAKE_UP_STRING "WakeUp!"
+/** Length of the string written. */
+#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
+
+int USBProxyBackendLinux::waitUsbfs(RTMSINTERVAL aMillies)
+{
+ struct pollfd PollFds[2];
+
+ /* Cap the wait interval if we're polling for udevd changing device permissions. */
+ if (aMillies > 500 && mUdevPolls > 0)
+ {
+ mUdevPolls--;
+ aMillies = 500;
+ }
+
+ RT_ZERO(PollFds);
+ PollFds[0].fd = (int)RTFileToNative(mhFile);
+ PollFds[0].events = POLLIN;
+ PollFds[1].fd = (int)RTPipeToNative(mhWakeupPipeR);
+ PollFds[1].events = POLLIN | POLLERR | POLLHUP;
+
+ int iRc = poll(&PollFds[0], 2, aMillies);
+ if (iRc == 0)
+ return VERR_TIMEOUT;
+ if (iRc > 0)
+ {
+ /* drain the pipe */
+ if (PollFds[1].revents & POLLIN)
+ {
+ char szBuf[WAKE_UP_STRING_LEN];
+ int vrc2 = RTPipeReadBlocking(mhWakeupPipeR, szBuf, sizeof(szBuf), NULL);
+ AssertRC(vrc2);
+ }
+ return VINF_SUCCESS;
+ }
+ return RTErrConvertFromErrno(errno);
+}
+
+
+int USBProxyBackendLinux::waitSysfs(RTMSINTERVAL aMillies)
+{
+#ifdef VBOX_USB_WITH_SYSFS
+ int vrc = mpWaiter->Wait(aMillies);
+ if (vrc == VERR_TRY_AGAIN)
+ {
+ RTThreadYield();
+ vrc = VINF_SUCCESS;
+ }
+ return vrc;
+#else /* !VBOX_USB_WITH_SYSFS */
+ return USBProxyService::wait(aMillies);
+#endif /* !VBOX_USB_WITH_SYSFS */
+}
+
+
+int USBProxyBackendLinux::interruptWait(void)
+{
+ AssertReturn(!isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
+
+ AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+#ifdef VBOX_USB_WITH_SYSFS
+ LogFlowFunc(("mUsingUsbfsDevices=%d\n", mUsingUsbfsDevices));
+ if (!mUsingUsbfsDevices)
+ {
+ mpWaiter->Interrupt();
+ LogFlowFunc(("Returning VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+ }
+#endif /* VBOX_USB_WITH_SYSFS */
+ int vrc = RTPipeWriteBlocking(mhWakeupPipeW, WAKE_UP_STRING, WAKE_UP_STRING_LEN, NULL);
+ if (RT_SUCCESS(vrc))
+ RTPipeFlush(mhWakeupPipeW);
+ LogFlowFunc(("returning %Rrc\n", vrc));
+ return vrc;
+}
+
+
+PUSBDEVICE USBProxyBackendLinux::getDevices(void)
+{
+ return USBProxyLinuxGetDevices(mDevicesRoot.c_str(), !mUsingUsbfsDevices);
+}
diff --git a/src/VBox/Main/src-server/linux/vbox-libhal.cpp b/src/VBox/Main/src-server/linux/vbox-libhal.cpp
new file mode 100644
index 00000000..f48182c2
--- /dev/null
+++ b/src/VBox/Main/src-server/linux/vbox-libhal.cpp
@@ -0,0 +1,105 @@
+/* $Id: vbox-libhal.cpp $ */
+/** @file
+ *
+ * Module to dynamically load libhal and libdbus and load all symbols
+ * which are needed by VirtualBox.
+ */
+
+/*
+ * 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 "vbox-libhal.h"
+
+#include <iprt/errcore.h>
+#include <iprt/ldr.h>
+
+/**
+ * Whether we have tried to load libdbus and libhal yet. This flag should only be set
+ * to "true" after we have either loaded both libraries and all symbols which we need,
+ * or failed to load something and unloaded and set back to zero pLibDBus and pLibHal.
+ */
+static bool gCheckedForLibHal = false;
+/**
+ * Pointer to the libhal shared object. This should only be set once all needed libraries
+ * and symbols have been successfully loaded.
+ */
+static RTLDRMOD ghLibHal = 0;
+
+/** The following are the symbols which we need from libdbus and libhal. */
+void (*gDBusErrorInit)(DBusError *);
+DBusConnection *(*gDBusBusGet)(DBusBusType, DBusError *);
+void (*gDBusErrorFree)(DBusError *);
+void (*gDBusConnectionUnref)(DBusConnection *);
+LibHalContext *(*gLibHalCtxNew)(void);
+dbus_bool_t (*gLibHalCtxSetDBusConnection)(LibHalContext *, DBusConnection *);
+dbus_bool_t (*gLibHalCtxInit)(LibHalContext *, DBusError *);
+char **(*gLibHalFindDeviceStringMatch)(LibHalContext *, const char *, const char *, int *,
+ DBusError *);
+char *(*gLibHalDeviceGetPropertyString)(LibHalContext *, const char *, const char *, DBusError *);
+void (*gLibHalFreeString)(char *);
+void (*gLibHalFreeStringArray)(char **);
+dbus_bool_t (*gLibHalCtxShutdown)(LibHalContext *, DBusError *);
+dbus_bool_t (*gLibHalCtxFree)(LibHalContext *);
+
+bool gLibHalCheckPresence(void)
+{
+ RTLDRMOD hLibHal;
+
+ if (ghLibHal != 0 && gCheckedForLibHal == true)
+ return true;
+ if (gCheckedForLibHal == true)
+ return false;
+ if (!RT_SUCCESS(RTLdrLoad(LIB_HAL, &hLibHal)))
+ {
+ return false;
+ }
+ if ( RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_error_init", (void **) &gDBusErrorInit))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_bus_get", (void **) &gDBusBusGet))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_error_free", (void **) &gDBusErrorFree))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "dbus_connection_unref",
+ (void **) &gDBusConnectionUnref))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_new", (void **) &gLibHalCtxNew))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_set_dbus_connection",
+ (void **) &gLibHalCtxSetDBusConnection))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_init", (void **) &gLibHalCtxInit))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_manager_find_device_string_match",
+ (void **) &gLibHalFindDeviceStringMatch))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_device_get_property_string",
+ (void **) &gLibHalDeviceGetPropertyString))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_free_string", (void **) &gLibHalFreeString))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_free_string_array",
+ (void **) &gLibHalFreeStringArray))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_shutdown", (void **) &gLibHalCtxShutdown))
+ && RT_SUCCESS(RTLdrGetSymbol(hLibHal, "libhal_ctx_free", (void **) &gLibHalCtxFree))
+ )
+ {
+ ghLibHal = hLibHal;
+ gCheckedForLibHal = true;
+ return true;
+ }
+ else
+ {
+ RTLdrClose(hLibHal);
+ gCheckedForLibHal = true;
+ return false;
+ }
+}