VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/linux/USBProxyServiceLinux.cpp@ 36994

Last change on this file since 36994 was 36994, checked in by vboxsync, 14 years ago

Main/linux/usb: make unit tests pass and fix one test

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.0 KB
Line 
1/* $Id: USBProxyServiceLinux.cpp 36994 2011-05-06 22:20:05Z vboxsync $ */
2/** @file
3 * VirtualBox USB Proxy Service, Linux Specialization.
4 */
5
6/*
7 * Copyright (C) 2006-2011 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "USBProxyService.h"
23#include "USBGetDevices.h"
24#include "Logging.h"
25
26#include <VBox/usb.h>
27#include <VBox/usblib.h>
28#include <VBox/err.h>
29
30#include <iprt/string.h>
31#include <iprt/alloc.h>
32#include <iprt/assert.h>
33#include <iprt/ctype.h>
34#include <iprt/dir.h>
35#include <iprt/env.h>
36#include <iprt/file.h>
37#include <iprt/err.h>
38#include <iprt/mem.h>
39#include <iprt/param.h>
40#include <iprt/path.h>
41#include <iprt/stream.h>
42#include <iprt/linux/sysfs.h>
43
44#include <stdlib.h>
45#include <string.h>
46#include <stdio.h>
47#include <errno.h>
48#include <fcntl.h>
49#include <unistd.h>
50#include <sys/statfs.h>
51#include <sys/poll.h>
52#ifdef VBOX_WITH_LINUX_COMPILER_H
53# include <linux/compiler.h>
54#endif
55#include <linux/usbdevice_fs.h>
56
57
58/**
59 * Initialize data members.
60 */
61USBProxyServiceLinux::USBProxyServiceLinux(Host *aHost)
62 : USBProxyService(aHost), mFile(NIL_RTFILE), mWakeupPipeR(NIL_RTFILE),
63 mWakeupPipeW(NIL_RTFILE), mUsingUsbfsDevices(true /* see init */),
64 mUdevPolls(0), mpWaiter(NULL)
65#ifdef UNIT_TEST
66 , mpcszTestUsbfsRoot(NULL), mfTestUsbfsAccessible(false),
67 mpcszTestDevicesRoot(NULL), mfTestDevicesAccessible(false),
68 mrcTestMethodInitResult(VINF_SUCCESS), mpcszTestEnvUsb(NULL),
69 mpcszTestEnvUsbRoot(NULL)
70#endif
71{
72 LogFlowThisFunc(("aHost=%p\n", aHost));
73}
74
75#ifdef UNIT_TEST
76/* For testing we redefine anything that accesses the outside world to
77 * return test values. */
78# define RTEnvGet(a) \
79 ( !RTStrCmp(a, "VBOX_USB") ? mpcszTestEnvUsb \
80 : !RTStrCmp(a, "VBOX_USB_ROOT") ? mpcszTestEnvUsbRoot \
81 : NULL)
82# define USBProxyLinuxCheckDeviceRoot(pcszPath, fUseNodes) \
83 ( ((fUseNodes) && mfTestDevicesAccessible \
84 && !RTStrCmp(pcszPath, mpcszTestDevicesRoot)) \
85 || (!(fUseNodes) && mfTestUsbfsAccessible \
86 && !RTStrCmp(pcszPath, mpcszTestUsbfsRoot)))
87# define RTDirExists(pcszDir) \
88 ( (pcszDir) \
89 && ( !RTStrCmp(pcszDir, mpcszTestDevicesRoot) \
90 || !RTStrCmp(pcszDir, mpcszTestUsbfsRoot)))
91# define RTFileExists(pcszFile) \
92 ( (pcszFile) \
93 && mpcszTestUsbfsRoot \
94 && !RTStrNCmp(pcszFile, mpcszTestUsbfsRoot, strlen(mpcszTestUsbfsRoot)) \
95 && !RTStrCmp(pcszFile + strlen(mpcszTestUsbfsRoot), "/devices"))
96#endif
97
98/**
99 * Initializes the object (called right after construction).
100 *
101 * @returns S_OK on success and non-fatal failures, some COM error otherwise.
102 */
103HRESULT USBProxyServiceLinux::init(void)
104{
105 /*
106 * Call the superclass method first.
107 */
108 HRESULT hrc = USBProxyService::init();
109 AssertComRCReturn(hrc, hrc);
110
111 /*
112 * We have two methods available for getting host USB device data - using
113 * USBFS and using sysfs. The default choice is sysfs; if that is not
114 * available we fall back to USBFS.
115 * In the event of both failing, an appropriate error will be returned.
116 */
117 bool fUsbfsChosen = false, fSysfsChosen = false;
118 const char *pcszUsbFromEnv = RTEnvGet("VBOX_USB");
119 const char *pcszUsbRoot = NULL;
120 if (pcszUsbFromEnv)
121 {
122 bool fValidVBoxUSB = true;
123
124 pcszUsbRoot = RTEnvGet("VBOX_USB_ROOT");
125 if (!RTStrICmp(pcszUsbFromEnv, "USBFS"))
126 {
127 LogRel(("Default USB access method set to \"usbfs\" from environment\n"));
128 fUsbfsChosen = true;
129 }
130 else if (!RTStrICmp(pcszUsbFromEnv, "SYSFS"))
131 {
132 LogRel(("Default USB method set to \"sysfs\" from environment\n"));
133 fSysfsChosen = true;
134 }
135 else
136 {
137 LogRel(("Invalid VBOX_USB environment variable setting \"%s\"\n",
138 pcszUsbFromEnv));
139 fValidVBoxUSB = false;
140 }
141 if (!fValidVBoxUSB && pcszUsbRoot)
142 pcszUsbRoot = NULL;
143 }
144 if (!pcszUsbRoot)
145 {
146 if ( !fUsbfsChosen
147 && USBProxyLinuxCheckDeviceRoot("/dev/vboxusb", true))
148 {
149 fSysfsChosen = true;
150 pcszUsbRoot = "/dev/vboxusb";
151 }
152 else if ( !fSysfsChosen
153 && USBProxyLinuxCheckDeviceRoot("/proc/bus/usb", false))
154 {
155 fUsbfsChosen = true;
156 pcszUsbRoot = "/proc/bus/usb";
157 }
158 }
159 if (pcszUsbRoot)
160 {
161 mUsingUsbfsDevices = fUsbfsChosen;
162 mDevicesRoot = pcszUsbRoot;
163#ifndef UNIT_TEST /* Hack for now */
164 int rc = mUsingUsbfsDevices ? initUsbfs() : initSysfs();
165#else
166 int rc = mrcTestMethodInitResult;
167#endif
168 /* For the day when we have VBoxSVC release logging... */
169 LogRel((RT_SUCCESS(rc) ? "Successfully initialised host USB using %s\n"
170 : "Failed to initialise host USB using %s\n",
171 mUsingUsbfsDevices ? "USBFS" : "sysfs"));
172 mLastError = rc;
173 }
174 else
175 mLastError = RTDirExists("/dev/vboxusb") ? VERR_VUSB_USB_DEVICE_PERMISSION
176 : RTFileExists("/proc/bus/usb/devices") ? VERR_VUSB_USBFS_PERMISSION
177 : VERR_NOT_FOUND;
178 return S_OK;
179}
180
181#ifdef UNIT_TEST
182# undef RTEnvGet
183# undef USBProxyLinuxCheckDeviceRoot
184# undef RTDirExists
185# undef RTFileExists
186#endif
187
188/**
189 * Initialization routine for the usbfs based operation.
190 *
191 * @returns iprt status code.
192 */
193int USBProxyServiceLinux::initUsbfs(void)
194{
195 Assert(mUsingUsbfsDevices);
196
197 /*
198 * Open the devices file.
199 */
200 int rc;
201 char *pszDevices = RTPathJoinA(mDevicesRoot.c_str(), "devices");
202 if (pszDevices)
203 {
204 rc = RTFileOpen(&mFile, pszDevices, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
205 if (RT_SUCCESS(rc))
206 {
207 int pipes[2];
208 if (!pipe(pipes))
209 {
210 /* Set close on exec (race here!) */
211 if ( fcntl(pipes[0], F_SETFD, FD_CLOEXEC) >= 0
212 && fcntl(pipes[1], F_SETFD, FD_CLOEXEC) >= 0)
213 {
214 mWakeupPipeR = pipes[0];
215 mWakeupPipeW = pipes[1];
216 /*
217 * Start the poller thread.
218 */
219 rc = start();
220 if (RT_SUCCESS(rc))
221 {
222 RTStrFree(pszDevices);
223 LogFlowThisFunc(("returns successfully - mWakeupPipeR/W=%d/%d\n",
224 mWakeupPipeR, mWakeupPipeW));
225 return VINF_SUCCESS;
226 }
227
228 RTFileClose(mWakeupPipeR);
229 RTFileClose(mWakeupPipeW);
230 mWakeupPipeW = mWakeupPipeR = NIL_RTFILE;
231 }
232 else
233 {
234 rc = RTErrConvertFromErrno(errno);
235 Log(("USBProxyServiceLinux::USBProxyServiceLinux: fcntl failed, errno=%d\n", errno));
236 close(pipes[0]);
237 close(pipes[1]);
238 }
239 }
240 else
241 {
242 rc = RTErrConvertFromErrno(errno);
243 Log(("USBProxyServiceLinux::USBProxyServiceLinux: pipe failed, errno=%d\n", errno));
244 }
245 RTFileClose(mFile);
246 }
247
248 RTStrFree(pszDevices);
249 }
250 else
251 {
252 rc = VERR_NO_MEMORY;
253 Log(("USBProxyServiceLinux::USBProxyServiceLinux: out of memory!\n"));
254 }
255
256 LogFlowThisFunc(("returns failure!!! (rc=%Rrc)\n", rc));
257 return rc;
258}
259
260
261/**
262 * Initialization routine for the sysfs based operation.
263 *
264 * @returns iprt status code
265 */
266int USBProxyServiceLinux::initSysfs(void)
267{
268 Assert(!mUsingUsbfsDevices);
269
270#ifdef VBOX_USB_WITH_SYSFS
271 try
272 {
273 mpWaiter = new VBoxMainHotplugWaiter(mDevicesRoot.c_str());
274 }
275 catch(std::bad_alloc &e)
276 {
277 return VERR_NO_MEMORY;
278 }
279 int rc = mpWaiter->getStatus();
280 if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT || rc == VERR_TRY_AGAIN)
281 rc = start();
282 else if (rc == VERR_NOT_SUPPORTED)
283 /* This can legitimately happen if hal or DBus are not running, but of
284 * course we can't start in this case. */
285 rc = VINF_SUCCESS;
286 return rc;
287
288#else /* !VBOX_USB_WITH_SYSFS */
289 return VERR_NOT_IMPLEMENTED;
290#endif /* !VBOX_USB_WITH_SYSFS */
291}
292
293
294/**
295 * Stop all service threads and free the device chain.
296 */
297USBProxyServiceLinux::~USBProxyServiceLinux()
298{
299 LogFlowThisFunc(("\n"));
300
301 /*
302 * Stop the service.
303 */
304 if (isActive())
305 stop();
306
307 /*
308 * Free resources.
309 */
310 doUsbfsCleanupAsNeeded();
311#ifdef VBOX_USB_WITH_SYSFS
312 if (mpWaiter)
313 delete mpWaiter;
314#endif
315}
316
317
318/**
319 * If any Usbfs-related resources are currently allocated, then free them
320 * and mark them as freed.
321 */
322void USBProxyServiceLinux::doUsbfsCleanupAsNeeded()
323{
324 /*
325 * Free resources.
326 */
327 if (mFile != NIL_RTFILE)
328 {
329 RTFileClose(mFile);
330 mFile = NIL_RTFILE;
331 }
332
333 if (mWakeupPipeR != NIL_RTFILE)
334 RTFileClose(mWakeupPipeR);
335 if (mWakeupPipeW != NIL_RTFILE)
336 RTFileClose(mWakeupPipeW);
337 mWakeupPipeW = mWakeupPipeR = NIL_RTFILE;
338}
339
340
341int USBProxyServiceLinux::captureDevice(HostUSBDevice *aDevice)
342{
343 Log(("USBProxyServiceLinux::captureDevice: %p {%s}\n", aDevice, aDevice->getName().c_str()));
344 AssertReturn(aDevice, VERR_GENERAL_FAILURE);
345 AssertReturn(aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
346
347 /*
348 * Don't think we need to do anything when the device is held... fake it.
349 */
350 Assert(aDevice->getUnistate() == kHostUSBDeviceState_Capturing);
351 interruptWait();
352
353 return VINF_SUCCESS;
354}
355
356
357int USBProxyServiceLinux::releaseDevice(HostUSBDevice *aDevice)
358{
359 Log(("USBProxyServiceLinux::releaseDevice: %p\n", aDevice));
360 AssertReturn(aDevice, VERR_GENERAL_FAILURE);
361 AssertReturn(aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
362
363 /*
364 * We're not really holding it atm., just fake it.
365 */
366 Assert(aDevice->getUnistate() == kHostUSBDeviceState_ReleasingToHost);
367 interruptWait();
368
369 return VINF_SUCCESS;
370}
371
372
373bool USBProxyServiceLinux::updateDeviceState(HostUSBDevice *aDevice, PUSBDEVICE aUSBDevice, bool *aRunFilters, SessionMachine **aIgnoreMachine)
374{
375 if ( aUSBDevice->enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE
376 && aDevice->mUsb->enmState == USBDEVICESTATE_USED_BY_HOST)
377 LogRel(("USBProxy: Device %04x:%04x (%s) has become accessible.\n",
378 aUSBDevice->idVendor, aUSBDevice->idProduct, aUSBDevice->pszAddress));
379 return updateDeviceStateFake(aDevice, aUSBDevice, aRunFilters, aIgnoreMachine);
380}
381
382
383/**
384 * A device was added, we need to adjust mUdevPolls.
385 *
386 * See USBProxyService::deviceAdded for details.
387 */
388void USBProxyServiceLinux::deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, SessionMachinesList &llOpenedMachines, PUSBDEVICE aUSBDevice)
389{
390 if (aUSBDevice->enmState == USBDEVICESTATE_USED_BY_HOST)
391 {
392 LogRel(("USBProxy: Device %04x:%04x (%s) isn't accessible. giving udev a few seconds to fix this...\n",
393 aUSBDevice->idVendor, aUSBDevice->idProduct, aUSBDevice->pszAddress));
394 mUdevPolls = 10; /* (10 * 500ms = 5s) */
395 }
396
397 USBProxyService::deviceAdded(aDevice, llOpenedMachines, aUSBDevice);
398}
399
400
401int USBProxyServiceLinux::wait(RTMSINTERVAL aMillies)
402{
403 int rc;
404 if (mUsingUsbfsDevices)
405 rc = waitUsbfs(aMillies);
406 else
407 rc = waitSysfs(aMillies);
408 return rc;
409}
410
411
412/** String written to the wakeup pipe. */
413#define WAKE_UP_STRING "WakeUp!"
414/** Length of the string written. */
415#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
416
417int USBProxyServiceLinux::waitUsbfs(RTMSINTERVAL aMillies)
418{
419 struct pollfd PollFds[2];
420
421 /* Cap the wait interval if we're polling for udevd changing device permissions. */
422 if (aMillies > 500 && mUdevPolls > 0)
423 {
424 mUdevPolls--;
425 aMillies = 500;
426 }
427
428 memset(&PollFds, 0, sizeof(PollFds));
429 PollFds[0].fd = mFile;
430 PollFds[0].events = POLLIN;
431 PollFds[1].fd = mWakeupPipeR;
432 PollFds[1].events = POLLIN | POLLERR | POLLHUP;
433
434 int rc = poll(&PollFds[0], 2, aMillies);
435 if (rc == 0)
436 return VERR_TIMEOUT;
437 if (rc > 0)
438 {
439 /* drain the pipe */
440 if (PollFds[1].revents & POLLIN)
441 {
442 char szBuf[WAKE_UP_STRING_LEN];
443 rc = RTFileRead(mWakeupPipeR, szBuf, sizeof(szBuf), NULL);
444 AssertRC(rc);
445 }
446 return VINF_SUCCESS;
447 }
448 return RTErrConvertFromErrno(errno);
449}
450
451
452int USBProxyServiceLinux::waitSysfs(RTMSINTERVAL aMillies)
453{
454#ifdef VBOX_USB_WITH_SYSFS
455 int rc = mpWaiter->Wait(aMillies);
456 if (rc == VERR_TRY_AGAIN)
457 {
458 RTThreadYield();
459 rc = VINF_SUCCESS;
460 }
461 return rc;
462#else /* !VBOX_USB_WITH_SYSFS */
463 return USBProxyService::wait(aMillies);
464#endif /* !VBOX_USB_WITH_SYSFS */
465}
466
467
468int USBProxyServiceLinux::interruptWait(void)
469{
470#ifdef VBOX_USB_WITH_SYSFS
471 LogFlowFunc(("mUsingUsbfsDevices=%d\n", mUsingUsbfsDevices));
472 if (!mUsingUsbfsDevices)
473 {
474 mpWaiter->Interrupt();
475 LogFlowFunc(("Returning VINF_SUCCESS\n"));
476 return VINF_SUCCESS;
477 }
478#endif /* VBOX_USB_WITH_SYSFS */
479 int rc = RTFileWrite(mWakeupPipeW, WAKE_UP_STRING, WAKE_UP_STRING_LEN, NULL);
480 if (RT_SUCCESS(rc))
481 RTFileFlush(mWakeupPipeW);
482 LogFlowFunc(("returning %Rrc\n", rc));
483 return rc;
484}
485
486
487PUSBDEVICE USBProxyServiceLinux::getDevices(void)
488{
489 return USBProxyLinuxGetDevices(mDevicesRoot.c_str(), !mUsingUsbfsDevices);
490}
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette