VirtualBox

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

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

Main/linux/usb: more readable test code

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.2 KB
Line 
1/* $Id: USBProxyServiceLinux.cpp 36995 2011-05-06 22:49:41Z 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 * The user may also specify a method and root using the VBOX_USB and
117 * VBOX_USB_ROOT environment variables. In this case we don't check
118 * the root they provide for validity.
119 */
120 bool fUsbfsChosen = false, fSysfsChosen = false;
121 const char *pcszUsbFromEnv = RTEnvGet("VBOX_USB");
122 const char *pcszUsbRoot = NULL;
123 if (pcszUsbFromEnv)
124 {
125 bool fValidVBoxUSB = true;
126
127 pcszUsbRoot = RTEnvGet("VBOX_USB_ROOT");
128 if (!RTStrICmp(pcszUsbFromEnv, "USBFS"))
129 {
130 LogRel(("Default USB access method set to \"usbfs\" from environment\n"));
131 fUsbfsChosen = true;
132 }
133 else if (!RTStrICmp(pcszUsbFromEnv, "SYSFS"))
134 {
135 LogRel(("Default USB method set to \"sysfs\" from environment\n"));
136 fSysfsChosen = true;
137 }
138 else
139 {
140 LogRel(("Invalid VBOX_USB environment variable setting \"%s\"\n",
141 pcszUsbFromEnv));
142 fValidVBoxUSB = false;
143 }
144 if (!fValidVBoxUSB && pcszUsbRoot)
145 pcszUsbRoot = NULL;
146 }
147 if (!pcszUsbRoot)
148 {
149 if ( !fUsbfsChosen
150 && USBProxyLinuxCheckDeviceRoot("/dev/vboxusb", true))
151 {
152 fSysfsChosen = true;
153 pcszUsbRoot = "/dev/vboxusb";
154 }
155 else if ( !fSysfsChosen
156 && USBProxyLinuxCheckDeviceRoot("/proc/bus/usb", false))
157 {
158 fUsbfsChosen = true;
159 pcszUsbRoot = "/proc/bus/usb";
160 }
161 }
162 if (pcszUsbRoot)
163 {
164 mUsingUsbfsDevices = fUsbfsChosen;
165 mDevicesRoot = pcszUsbRoot;
166#ifndef UNIT_TEST /* Hack for now */
167 int rc = mUsingUsbfsDevices ? initUsbfs() : initSysfs();
168#else
169 int rc = mrcTestMethodInitResult;
170#endif
171 /* For the day when we have VBoxSVC release logging... */
172 LogRel((RT_SUCCESS(rc) ? "Successfully initialised host USB using %s\n"
173 : "Failed to initialise host USB using %s\n",
174 mUsingUsbfsDevices ? "USBFS" : "sysfs"));
175 mLastError = rc;
176 }
177 else
178 mLastError = RTDirExists("/dev/vboxusb") ? VERR_VUSB_USB_DEVICE_PERMISSION
179 : RTFileExists("/proc/bus/usb/devices") ? VERR_VUSB_USBFS_PERMISSION
180 : VERR_NOT_FOUND;
181 return S_OK;
182}
183
184#ifdef UNIT_TEST
185# undef RTEnvGet
186# undef USBProxyLinuxCheckDeviceRoot
187# undef RTDirExists
188# undef RTFileExists
189#endif
190
191/**
192 * Initialization routine for the usbfs based operation.
193 *
194 * @returns iprt status code.
195 */
196int USBProxyServiceLinux::initUsbfs(void)
197{
198 Assert(mUsingUsbfsDevices);
199
200 /*
201 * Open the devices file.
202 */
203 int rc;
204 char *pszDevices = RTPathJoinA(mDevicesRoot.c_str(), "devices");
205 if (pszDevices)
206 {
207 rc = RTFileOpen(&mFile, pszDevices, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
208 if (RT_SUCCESS(rc))
209 {
210 int pipes[2];
211 if (!pipe(pipes))
212 {
213 /* Set close on exec (race here!) */
214 if ( fcntl(pipes[0], F_SETFD, FD_CLOEXEC) >= 0
215 && fcntl(pipes[1], F_SETFD, FD_CLOEXEC) >= 0)
216 {
217 mWakeupPipeR = pipes[0];
218 mWakeupPipeW = pipes[1];
219 /*
220 * Start the poller thread.
221 */
222 rc = start();
223 if (RT_SUCCESS(rc))
224 {
225 RTStrFree(pszDevices);
226 LogFlowThisFunc(("returns successfully - mWakeupPipeR/W=%d/%d\n",
227 mWakeupPipeR, mWakeupPipeW));
228 return VINF_SUCCESS;
229 }
230
231 RTFileClose(mWakeupPipeR);
232 RTFileClose(mWakeupPipeW);
233 mWakeupPipeW = mWakeupPipeR = NIL_RTFILE;
234 }
235 else
236 {
237 rc = RTErrConvertFromErrno(errno);
238 Log(("USBProxyServiceLinux::USBProxyServiceLinux: fcntl failed, errno=%d\n", errno));
239 close(pipes[0]);
240 close(pipes[1]);
241 }
242 }
243 else
244 {
245 rc = RTErrConvertFromErrno(errno);
246 Log(("USBProxyServiceLinux::USBProxyServiceLinux: pipe failed, errno=%d\n", errno));
247 }
248 RTFileClose(mFile);
249 }
250
251 RTStrFree(pszDevices);
252 }
253 else
254 {
255 rc = VERR_NO_MEMORY;
256 Log(("USBProxyServiceLinux::USBProxyServiceLinux: out of memory!\n"));
257 }
258
259 LogFlowThisFunc(("returns failure!!! (rc=%Rrc)\n", rc));
260 return rc;
261}
262
263
264/**
265 * Initialization routine for the sysfs based operation.
266 *
267 * @returns iprt status code
268 */
269int USBProxyServiceLinux::initSysfs(void)
270{
271 Assert(!mUsingUsbfsDevices);
272
273#ifdef VBOX_USB_WITH_SYSFS
274 try
275 {
276 mpWaiter = new VBoxMainHotplugWaiter(mDevicesRoot.c_str());
277 }
278 catch(std::bad_alloc &e)
279 {
280 return VERR_NO_MEMORY;
281 }
282 int rc = mpWaiter->getStatus();
283 if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT || rc == VERR_TRY_AGAIN)
284 rc = start();
285 else if (rc == VERR_NOT_SUPPORTED)
286 /* This can legitimately happen if hal or DBus are not running, but of
287 * course we can't start in this case. */
288 rc = VINF_SUCCESS;
289 return rc;
290
291#else /* !VBOX_USB_WITH_SYSFS */
292 return VERR_NOT_IMPLEMENTED;
293#endif /* !VBOX_USB_WITH_SYSFS */
294}
295
296
297/**
298 * Stop all service threads and free the device chain.
299 */
300USBProxyServiceLinux::~USBProxyServiceLinux()
301{
302 LogFlowThisFunc(("\n"));
303
304 /*
305 * Stop the service.
306 */
307 if (isActive())
308 stop();
309
310 /*
311 * Free resources.
312 */
313 doUsbfsCleanupAsNeeded();
314#ifdef VBOX_USB_WITH_SYSFS
315 if (mpWaiter)
316 delete mpWaiter;
317#endif
318}
319
320
321/**
322 * If any Usbfs-related resources are currently allocated, then free them
323 * and mark them as freed.
324 */
325void USBProxyServiceLinux::doUsbfsCleanupAsNeeded()
326{
327 /*
328 * Free resources.
329 */
330 if (mFile != NIL_RTFILE)
331 {
332 RTFileClose(mFile);
333 mFile = NIL_RTFILE;
334 }
335
336 if (mWakeupPipeR != NIL_RTFILE)
337 RTFileClose(mWakeupPipeR);
338 if (mWakeupPipeW != NIL_RTFILE)
339 RTFileClose(mWakeupPipeW);
340 mWakeupPipeW = mWakeupPipeR = NIL_RTFILE;
341}
342
343
344int USBProxyServiceLinux::captureDevice(HostUSBDevice *aDevice)
345{
346 Log(("USBProxyServiceLinux::captureDevice: %p {%s}\n", aDevice, aDevice->getName().c_str()));
347 AssertReturn(aDevice, VERR_GENERAL_FAILURE);
348 AssertReturn(aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
349
350 /*
351 * Don't think we need to do anything when the device is held... fake it.
352 */
353 Assert(aDevice->getUnistate() == kHostUSBDeviceState_Capturing);
354 interruptWait();
355
356 return VINF_SUCCESS;
357}
358
359
360int USBProxyServiceLinux::releaseDevice(HostUSBDevice *aDevice)
361{
362 Log(("USBProxyServiceLinux::releaseDevice: %p\n", aDevice));
363 AssertReturn(aDevice, VERR_GENERAL_FAILURE);
364 AssertReturn(aDevice->isWriteLockOnCurrentThread(), VERR_GENERAL_FAILURE);
365
366 /*
367 * We're not really holding it atm., just fake it.
368 */
369 Assert(aDevice->getUnistate() == kHostUSBDeviceState_ReleasingToHost);
370 interruptWait();
371
372 return VINF_SUCCESS;
373}
374
375
376bool USBProxyServiceLinux::updateDeviceState(HostUSBDevice *aDevice, PUSBDEVICE aUSBDevice, bool *aRunFilters, SessionMachine **aIgnoreMachine)
377{
378 if ( aUSBDevice->enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE
379 && aDevice->mUsb->enmState == USBDEVICESTATE_USED_BY_HOST)
380 LogRel(("USBProxy: Device %04x:%04x (%s) has become accessible.\n",
381 aUSBDevice->idVendor, aUSBDevice->idProduct, aUSBDevice->pszAddress));
382 return updateDeviceStateFake(aDevice, aUSBDevice, aRunFilters, aIgnoreMachine);
383}
384
385
386/**
387 * A device was added, we need to adjust mUdevPolls.
388 *
389 * See USBProxyService::deviceAdded for details.
390 */
391void USBProxyServiceLinux::deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, SessionMachinesList &llOpenedMachines, PUSBDEVICE aUSBDevice)
392{
393 if (aUSBDevice->enmState == USBDEVICESTATE_USED_BY_HOST)
394 {
395 LogRel(("USBProxy: Device %04x:%04x (%s) isn't accessible. giving udev a few seconds to fix this...\n",
396 aUSBDevice->idVendor, aUSBDevice->idProduct, aUSBDevice->pszAddress));
397 mUdevPolls = 10; /* (10 * 500ms = 5s) */
398 }
399
400 USBProxyService::deviceAdded(aDevice, llOpenedMachines, aUSBDevice);
401}
402
403
404int USBProxyServiceLinux::wait(RTMSINTERVAL aMillies)
405{
406 int rc;
407 if (mUsingUsbfsDevices)
408 rc = waitUsbfs(aMillies);
409 else
410 rc = waitSysfs(aMillies);
411 return rc;
412}
413
414
415/** String written to the wakeup pipe. */
416#define WAKE_UP_STRING "WakeUp!"
417/** Length of the string written. */
418#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 )
419
420int USBProxyServiceLinux::waitUsbfs(RTMSINTERVAL aMillies)
421{
422 struct pollfd PollFds[2];
423
424 /* Cap the wait interval if we're polling for udevd changing device permissions. */
425 if (aMillies > 500 && mUdevPolls > 0)
426 {
427 mUdevPolls--;
428 aMillies = 500;
429 }
430
431 memset(&PollFds, 0, sizeof(PollFds));
432 PollFds[0].fd = mFile;
433 PollFds[0].events = POLLIN;
434 PollFds[1].fd = mWakeupPipeR;
435 PollFds[1].events = POLLIN | POLLERR | POLLHUP;
436
437 int rc = poll(&PollFds[0], 2, aMillies);
438 if (rc == 0)
439 return VERR_TIMEOUT;
440 if (rc > 0)
441 {
442 /* drain the pipe */
443 if (PollFds[1].revents & POLLIN)
444 {
445 char szBuf[WAKE_UP_STRING_LEN];
446 rc = RTFileRead(mWakeupPipeR, szBuf, sizeof(szBuf), NULL);
447 AssertRC(rc);
448 }
449 return VINF_SUCCESS;
450 }
451 return RTErrConvertFromErrno(errno);
452}
453
454
455int USBProxyServiceLinux::waitSysfs(RTMSINTERVAL aMillies)
456{
457#ifdef VBOX_USB_WITH_SYSFS
458 int rc = mpWaiter->Wait(aMillies);
459 if (rc == VERR_TRY_AGAIN)
460 {
461 RTThreadYield();
462 rc = VINF_SUCCESS;
463 }
464 return rc;
465#else /* !VBOX_USB_WITH_SYSFS */
466 return USBProxyService::wait(aMillies);
467#endif /* !VBOX_USB_WITH_SYSFS */
468}
469
470
471int USBProxyServiceLinux::interruptWait(void)
472{
473#ifdef VBOX_USB_WITH_SYSFS
474 LogFlowFunc(("mUsingUsbfsDevices=%d\n", mUsingUsbfsDevices));
475 if (!mUsingUsbfsDevices)
476 {
477 mpWaiter->Interrupt();
478 LogFlowFunc(("Returning VINF_SUCCESS\n"));
479 return VINF_SUCCESS;
480 }
481#endif /* VBOX_USB_WITH_SYSFS */
482 int rc = RTFileWrite(mWakeupPipeW, WAKE_UP_STRING, WAKE_UP_STRING_LEN, NULL);
483 if (RT_SUCCESS(rc))
484 RTFileFlush(mWakeupPipeW);
485 LogFlowFunc(("returning %Rrc\n", rc));
486 return rc;
487}
488
489
490PUSBDEVICE USBProxyServiceLinux::getDevices(void)
491{
492 return USBProxyLinuxGetDevices(mDevicesRoot.c_str(), !mUsingUsbfsDevices);
493}
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