VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/DrvHostDVD.cpp@ 63942

Last change on this file since 63942 was 63369, checked in by vboxsync, 8 years ago

warnings (gcc)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 28.2 KB
Line 
1/* $Id: DrvHostDVD.cpp 63369 2016-08-12 16:45:31Z vboxsync $ */
2/** @file
3 * DrvHostDVD - Host DVD block driver.
4 */
5
6/*
7 * Copyright (C) 2006-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. 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#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
23#define __STDC_LIMIT_MACROS
24#define __STDC_CONSTANT_MACROS
25
26#ifdef RT_OS_DARWIN
27# include <mach/mach.h>
28# include <Carbon/Carbon.h>
29# include <IOKit/IOKitLib.h>
30# include <IOKit/IOCFPlugIn.h>
31# include <IOKit/scsi/SCSITaskLib.h>
32# include <IOKit/scsi/SCSICommandOperationCodes.h>
33# include <IOKit/storage/IOStorageDeviceCharacteristics.h>
34# include <mach/mach_error.h>
35# define USE_MEDIA_POLLING
36
37#elif defined RT_OS_LINUX
38# include <sys/ioctl.h>
39# include <linux/version.h>
40/* All the following crap is apparently not necessary anymore since Linux
41 * version 2.6.29. */
42# if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
43/* This is a hack to work around conflicts between these linux kernel headers
44 * and the GLIBC tcpip headers. They have different declarations of the 4
45 * standard byte order functions. */
46# define _LINUX_BYTEORDER_GENERIC_H
47/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
48/* Those macros that are needed are defined in the header below. */
49# include "swab.h"
50# endif
51# include <linux/cdrom.h>
52# include <sys/fcntl.h>
53# include <errno.h>
54# include <limits.h>
55# include <iprt/mem.h>
56# define USE_MEDIA_POLLING
57
58#elif defined(RT_OS_SOLARIS)
59# include <stropts.h>
60# include <fcntl.h>
61# include <errno.h>
62# include <pwd.h>
63# include <unistd.h>
64# include <syslog.h>
65# ifdef VBOX_WITH_SUID_WRAPPER
66# include <auth_attr.h>
67# endif
68# include <sys/dkio.h>
69# include <sys/sockio.h>
70# include <sys/scsi/scsi.h>
71# define USE_MEDIA_POLLING
72
73#elif defined(RT_OS_WINDOWS)
74# pragma warning(disable : 4163)
75# define _interlockedbittestandset they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandset
76# define _interlockedbittestandreset they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandreset
77# define _interlockedbittestandset64 they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandset64
78# define _interlockedbittestandreset64 they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandreset64
79# include <iprt/win/windows.h>
80# include <winioctl.h>
81# include <ntddscsi.h>
82# pragma warning(default : 4163)
83# undef _interlockedbittestandset
84# undef _interlockedbittestandreset
85# undef _interlockedbittestandset64
86# undef _interlockedbittestandreset64
87# undef USE_MEDIA_POLLING
88
89#elif defined(RT_OS_FREEBSD)
90# include <sys/cdefs.h>
91# include <sys/param.h>
92# include <stdio.h>
93# include <cam/cam.h>
94# include <cam/cam_ccb.h>
95# define USE_MEDIA_POLLING
96
97#else
98# error "Unsupported Platform."
99#endif
100
101#include <iprt/asm.h>
102#include <VBox/vmm/pdmdrv.h>
103#include <VBox/vmm/pdmstorageifs.h>
104#include <iprt/asm.h>
105#include <iprt/assert.h>
106#include <iprt/file.h>
107#include <iprt/string.h>
108#include <iprt/thread.h>
109#include <iprt/critsect.h>
110#include <VBox/scsi.h>
111
112#include "VBoxDD.h"
113#include "DrvHostBase.h"
114
115
116/*********************************************************************************************************************************
117* Internal Functions *
118*********************************************************************************************************************************/
119static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock);
120#ifdef VBOX_WITH_SUID_WRAPPER
121static int solarisCheckUserAuth();
122static int solarisEnterRootMode(uid_t *pEffUserID);
123static int solarisExitRootMode(uid_t *pEffUserID);
124#endif
125
126
127/** @interface_method_impl{PDMIMOUNT,pfnUnmount} */
128static DECLCALLBACK(int) drvHostDvdUnmount(PPDMIMOUNT pInterface, bool fForce, bool fEject)
129{
130 PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
131 RTCritSectEnter(&pThis->CritSect);
132
133 /*
134 * Validate state.
135 */
136 int rc = VINF_SUCCESS;
137 if (!pThis->fLocked || fForce)
138 {
139 /* Unlock drive if necessary. */
140 if (pThis->fLocked)
141 drvHostDvdDoLock(pThis, false);
142
143 if (fEject)
144 {
145 /*
146 * Eject the disc.
147 */
148#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
149 uint8_t abCmd[16] =
150 {
151 SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
152 0,0,0,0,0,0,0,0,0,0
153 };
154 rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
155
156#elif defined(RT_OS_LINUX)
157 rc = ioctl(RTFileToNative(pThis->hFileDevice), CDROMEJECT, 0);
158 if (rc < 0)
159 {
160 if (errno == EBUSY)
161 rc = VERR_PDM_MEDIA_LOCKED;
162 else if (errno == ENOSYS)
163 rc = VERR_NOT_SUPPORTED;
164 else
165 rc = RTErrConvertFromErrno(errno);
166 }
167
168#elif defined(RT_OS_SOLARIS)
169 rc = ioctl(RTFileToNative(pThis->hFileRawDevice), DKIOCEJECT, 0);
170 if (rc < 0)
171 {
172 if (errno == EBUSY)
173 rc = VERR_PDM_MEDIA_LOCKED;
174 else if (errno == ENOSYS || errno == ENOTSUP)
175 rc = VERR_NOT_SUPPORTED;
176 else if (errno == ENODEV)
177 rc = VERR_PDM_MEDIA_NOT_MOUNTED;
178 else
179 rc = RTErrConvertFromErrno(errno);
180 }
181
182#elif defined(RT_OS_WINDOWS)
183 RTFILE hFileDevice = pThis->hFileDevice;
184 if (hFileDevice == NIL_RTFILE) /* obsolete crap */
185 rc = RTFileOpen(&hFileDevice, pThis->pszDeviceOpen, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
186 if (RT_SUCCESS(rc))
187 {
188 /* do ioctl */
189 DWORD cbReturned;
190 if (DeviceIoControl((HANDLE)RTFileToNative(hFileDevice), IOCTL_STORAGE_EJECT_MEDIA,
191 NULL, 0,
192 NULL, 0, &cbReturned,
193 NULL))
194 rc = VINF_SUCCESS;
195 else
196 rc = RTErrConvertFromWin32(GetLastError());
197
198 /* clean up handle */
199 if (hFileDevice != pThis->hFileDevice)
200 RTFileClose(hFileDevice);
201 }
202 else
203 AssertMsgFailed(("Failed to open '%s' for ejecting this tray.\n", rc));
204
205
206#else
207 AssertMsgFailed(("Eject is not implemented!\n"));
208 rc = VINF_SUCCESS;
209#endif
210 }
211
212 /*
213 * Media is no longer present.
214 */
215 DRVHostBaseMediaNotPresent(pThis); /** @todo This isn't thread safe! */
216 }
217 else
218 {
219 Log(("drvHostDvdUnmount: Locked\n"));
220 rc = VERR_PDM_MEDIA_LOCKED;
221 }
222
223 RTCritSectLeave(&pThis->CritSect);
224 LogFlow(("drvHostDvdUnmount: returns %Rrc\n", rc));
225 return rc;
226}
227
228
229/**
230 * Locks or unlocks the drive.
231 *
232 * @returns VBox status code.
233 * @param pThis The instance data.
234 * @param fLock True if the request is to lock the drive, false if to unlock.
235 */
236static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
237{
238#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
239 uint8_t abCmd[16] =
240 {
241 SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
242 0,0,0,0,0,0,0,0,0,0
243 };
244 int rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
245
246#elif defined(RT_OS_LINUX)
247 int rc = ioctl(RTFileToNative(pThis->hFileDevice), CDROM_LOCKDOOR, (int)fLock);
248 if (rc < 0)
249 {
250 if (errno == EBUSY)
251 rc = VERR_ACCESS_DENIED;
252 else if (errno == EDRIVE_CANT_DO_THIS)
253 rc = VERR_NOT_SUPPORTED;
254 else
255 rc = RTErrConvertFromErrno(errno);
256 }
257
258#elif defined(RT_OS_SOLARIS)
259 int rc = ioctl(RTFileToNative(pThis->hFileRawDevice), fLock ? DKIOCLOCK : DKIOCUNLOCK, 0);
260 if (rc < 0)
261 {
262 if (errno == EBUSY)
263 rc = VERR_ACCESS_DENIED;
264 else if (errno == ENOTSUP || errno == ENOSYS)
265 rc = VERR_NOT_SUPPORTED;
266 else
267 rc = RTErrConvertFromErrno(errno);
268 }
269
270#elif defined(RT_OS_WINDOWS)
271
272 PREVENT_MEDIA_REMOVAL PreventMediaRemoval = {fLock};
273 DWORD cbReturned;
274 int rc;
275 if (DeviceIoControl((HANDLE)RTFileToNative(pThis->hFileDevice), IOCTL_STORAGE_MEDIA_REMOVAL,
276 &PreventMediaRemoval, sizeof(PreventMediaRemoval),
277 NULL, 0, &cbReturned,
278 NULL))
279 rc = VINF_SUCCESS;
280 else
281 /** @todo figure out the return codes for already locked. */
282 rc = RTErrConvertFromWin32(GetLastError());
283
284#else
285 AssertMsgFailed(("Lock/Unlock is not implemented!\n"));
286 int rc = VINF_SUCCESS;
287
288#endif
289
290 LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Rrc\n", fLock, rc));
291 return rc;
292}
293
294
295
296#ifdef RT_OS_LINUX
297/**
298 * Get the media size.
299 *
300 * @returns VBox status code.
301 * @param pThis The instance data.
302 * @param pcb Where to store the size.
303 */
304static DECLCALLBACK(int) drvHostDvdGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
305{
306 /*
307 * Query the media size.
308 */
309 /* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
310 ioctl(RTFileToNative(pThis->hFileDevice), CDROM_MEDIA_CHANGED, CDSL_CURRENT);
311 return RTFileSeek(pThis->hFileDevice, 0, RTFILE_SEEK_END, pcb);
312
313}
314#endif /* RT_OS_LINUX */
315
316
317#ifdef USE_MEDIA_POLLING
318/**
319 * Do media change polling.
320 */
321static DECLCALLBACK(int) drvHostDvdPoll(PDRVHOSTBASE pThis)
322{
323 /*
324 * Poll for media change.
325 */
326#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
327#ifdef RT_OS_DARWIN
328 AssertReturn(pThis->ppScsiTaskDI, VERR_INTERNAL_ERROR);
329#endif
330
331 /*
332 * Issue a TEST UNIT READY request.
333 */
334 bool fMediaChanged = false;
335 bool fMediaPresent = false;
336 uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
337 uint8_t abSense[32];
338 int rc2 = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
339 if (RT_SUCCESS(rc2))
340 fMediaPresent = true;
341 else if ( rc2 == VERR_UNRESOLVED_ERROR
342 && abSense[2] == 6 /* unit attention */
343 && ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */)
344 || (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //???
345 || (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //???
346 || (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //???
347 || (abSense[12] == 0x3f && abSense[13] == 3 /* inquiry parameters changed */)
348 || (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */)
349 )
350 )
351 {
352 fMediaPresent = false;
353 fMediaChanged = true;
354 /** @todo check this media change stuff on Darwin. */
355 }
356
357#elif defined(RT_OS_LINUX)
358 bool fMediaPresent = ioctl(RTFileToNative(pThis->hFileDevice), CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
359
360#elif defined(RT_OS_SOLARIS)
361 bool fMediaPresent = false;
362 bool fMediaChanged = false;
363
364 /* Need to pass the previous state and DKIO_NONE for the first time. */
365 static dkio_state s_DeviceState = DKIO_NONE;
366 dkio_state PreviousState = s_DeviceState;
367 int rc2 = ioctl(RTFileToNative(pThis->hFileRawDevice), DKIOCSTATE, &s_DeviceState);
368 if (rc2 == 0)
369 {
370 fMediaPresent = (s_DeviceState == DKIO_INSERTED);
371 if (PreviousState != s_DeviceState)
372 fMediaChanged = true;
373 }
374
375#else
376# error "Unsupported platform."
377#endif
378
379 RTCritSectEnter(&pThis->CritSect);
380
381 int rc = VINF_SUCCESS;
382 if (pThis->fMediaPresent != fMediaPresent)
383 {
384 LogFlow(("drvHostDvdPoll: %d -> %d\n", pThis->fMediaPresent, fMediaPresent));
385 pThis->fMediaPresent = false;
386 if (fMediaPresent)
387 rc = DRVHostBaseMediaPresent(pThis);
388 else
389 DRVHostBaseMediaNotPresent(pThis);
390 }
391 else if (fMediaPresent)
392 {
393 /*
394 * Poll for media change.
395 */
396#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
397 /* taken care of above. */
398#elif defined(RT_OS_LINUX)
399 bool fMediaChanged = ioctl(RTFileToNative(pThis->hFileDevice), CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
400#else
401# error "Unsupported platform."
402#endif
403 if (fMediaChanged)
404 {
405 LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
406 DRVHostBaseMediaNotPresent(pThis);
407 rc = DRVHostBaseMediaPresent(pThis);
408 }
409 }
410
411 RTCritSectLeave(&pThis->CritSect);
412 return rc;
413}
414#endif /* USE_MEDIA_POLLING */
415
416
417/** @interface_method_impl{PDMIMEDIA,pfnSendCmd} */
418static DECLCALLBACK(int) drvHostDvdSendCmd(PPDMIMEDIA pInterface, const uint8_t *pbCmd,
419 PDMMEDIATXDIR enmTxDir, void *pvBuf, uint32_t *pcbBuf,
420 uint8_t *pabSense, size_t cbSense, uint32_t cTimeoutMillies)
421{
422 RT_NOREF(cbSense);
423 PDRVHOSTBASE pThis = PDMIMEDIA_2_DRVHOSTBASE(pInterface);
424 int rc;
425 LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
426
427#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
428 /*
429 * Pass the request on to the internal scsi command interface.
430 * The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
431 */
432 if (enmTxDir == PDMMEDIATXDIR_FROM_DEVICE)
433 memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
434 rc = DRVHostBaseScsiCmd(pThis, pbCmd, 12, PDMMEDIATXDIR_FROM_DEVICE, pvBuf, pcbBuf, pabSense, cbSense, cTimeoutMillies);
435 if (rc == VERR_UNRESOLVED_ERROR)
436 /* sense information set */
437 rc = VERR_DEV_IO_ERROR;
438
439#elif defined(RT_OS_LINUX)
440 int direction;
441 struct cdrom_generic_command cgc;
442
443 switch (enmTxDir)
444 {
445 case PDMMEDIATXDIR_NONE:
446 Assert(*pcbBuf == 0);
447 direction = CGC_DATA_NONE;
448 break;
449 case PDMMEDIATXDIR_FROM_DEVICE:
450 Assert(*pcbBuf != 0);
451 Assert(*pcbBuf <= SCSI_MAX_BUFFER_SIZE);
452 /* Make sure that the buffer is clear for commands reading
453 * data. The actually received data may be shorter than what
454 * we expect, and due to the unreliable feedback about how much
455 * data the ioctl actually transferred, it's impossible to
456 * prevent that. Returning previous buffer contents may cause
457 * security problems inside the guest OS, if users can issue
458 * commands to the CDROM device. */
459 memset(pThis->pbDoubleBuffer, '\0', *pcbBuf);
460 direction = CGC_DATA_READ;
461 break;
462 case PDMMEDIATXDIR_TO_DEVICE:
463 Assert(*pcbBuf != 0);
464 Assert(*pcbBuf <= SCSI_MAX_BUFFER_SIZE);
465 memcpy(pThis->pbDoubleBuffer, pvBuf, *pcbBuf);
466 direction = CGC_DATA_WRITE;
467 break;
468 default:
469 AssertMsgFailed(("enmTxDir invalid!\n"));
470 direction = CGC_DATA_NONE;
471 }
472 memset(&cgc, '\0', sizeof(cgc));
473 memcpy(cgc.cmd, pbCmd, CDROM_PACKET_SIZE);
474 cgc.buffer = (unsigned char *)pThis->pbDoubleBuffer;
475 cgc.buflen = *pcbBuf;
476 cgc.stat = 0;
477 Assert(cbSense >= sizeof(struct request_sense));
478 cgc.sense = (struct request_sense *)pabSense;
479 cgc.data_direction = direction;
480 cgc.quiet = false;
481 cgc.timeout = cTimeoutMillies;
482 rc = ioctl(RTFileToNative(pThis->hFileDevice), CDROM_SEND_PACKET, &cgc);
483 if (rc < 0)
484 {
485 if (errno == EBUSY)
486 rc = VERR_PDM_MEDIA_LOCKED;
487 else if (errno == ENOSYS)
488 rc = VERR_NOT_SUPPORTED;
489 else
490 {
491 rc = RTErrConvertFromErrno(errno);
492 if (rc == VERR_ACCESS_DENIED && cgc.sense->sense_key == SCSI_SENSE_NONE)
493 cgc.sense->sense_key = SCSI_SENSE_ILLEGAL_REQUEST;
494 Log2(("%s: error status %d, rc=%Rrc\n", __FUNCTION__, cgc.stat, rc));
495 }
496 }
497 switch (enmTxDir)
498 {
499 case PDMMEDIATXDIR_FROM_DEVICE:
500 memcpy(pvBuf, pThis->pbDoubleBuffer, *pcbBuf);
501 break;
502 default:
503 ;
504 }
505 Log2(("%s: after ioctl: cgc.buflen=%d txlen=%d\n", __FUNCTION__, cgc.buflen, *pcbBuf));
506 /* The value of cgc.buflen does not reliably reflect the actual amount
507 * of data transferred (for packet commands with little data transfer
508 * it's 0). So just assume that everything worked ok. */
509
510#elif defined(RT_OS_SOLARIS)
511 struct uscsi_cmd usc;
512 union scsi_cdb scdb;
513 memset(&usc, 0, sizeof(struct uscsi_cmd));
514 memset(&scdb, 0, sizeof(scdb));
515
516 switch (enmTxDir)
517 {
518 case PDMMEDIATXDIR_NONE:
519 Assert(*pcbBuf == 0);
520 usc.uscsi_flags = USCSI_READ;
521 /* nothing to do */
522 break;
523
524 case PDMMEDIATXDIR_FROM_DEVICE:
525 Assert(*pcbBuf != 0);
526 /* Make sure that the buffer is clear for commands reading
527 * data. The actually received data may be shorter than what
528 * we expect, and due to the unreliable feedback about how much
529 * data the ioctl actually transferred, it's impossible to
530 * prevent that. Returning previous buffer contents may cause
531 * security problems inside the guest OS, if users can issue
532 * commands to the CDROM device. */
533 memset(pvBuf, '\0', *pcbBuf);
534 usc.uscsi_flags = USCSI_READ;
535 break;
536 case PDMMEDIATXDIR_TO_DEVICE:
537 Assert(*pcbBuf != 0);
538 usc.uscsi_flags = USCSI_WRITE;
539 break;
540 default:
541 AssertMsgFailedReturn(("%d\n", enmTxDir), VERR_INTERNAL_ERROR);
542 }
543 usc.uscsi_flags |= USCSI_RQENABLE;
544 usc.uscsi_rqbuf = (char *)pabSense;
545 usc.uscsi_rqlen = cbSense;
546 usc.uscsi_cdb = (caddr_t)&scdb;
547 usc.uscsi_cdblen = 12;
548 memcpy (usc.uscsi_cdb, pbCmd, usc.uscsi_cdblen);
549 usc.uscsi_bufaddr = (caddr_t)pvBuf;
550 usc.uscsi_buflen = *pcbBuf;
551 usc.uscsi_timeout = (cTimeoutMillies + 999) / 1000;
552
553 /* We need root privileges for user-SCSI under Solaris. */
554#ifdef VBOX_WITH_SUID_WRAPPER
555 uid_t effUserID = geteuid();
556 solarisEnterRootMode(&effUserID); /** @todo check return code when this really works. */
557#endif
558 rc = ioctl(RTFileToNative(pThis->hFileRawDevice), USCSICMD, &usc);
559#ifdef VBOX_WITH_SUID_WRAPPER
560 solarisExitRootMode(&effUserID);
561#endif
562 if (rc < 0)
563 {
564 if (errno == EPERM)
565 return VERR_PERMISSION_DENIED;
566 if (usc.uscsi_status)
567 {
568 rc = RTErrConvertFromErrno(errno);
569 Log2(("%s: error status. rc=%Rrc\n", __FUNCTION__, rc));
570 }
571 }
572 Log2(("%s: after ioctl: residual buflen=%d original buflen=%d\n", __FUNCTION__, usc.uscsi_resid, usc.uscsi_buflen));
573
574#elif defined(RT_OS_WINDOWS)
575 int direction;
576 struct _REQ
577 {
578 SCSI_PASS_THROUGH_DIRECT spt;
579 uint8_t aSense[64];
580 } Req;
581 DWORD cbReturned = 0;
582
583 switch (enmTxDir)
584 {
585 case PDMMEDIATXDIR_NONE:
586 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
587 break;
588 case PDMMEDIATXDIR_FROM_DEVICE:
589 Assert(*pcbBuf != 0);
590 /* Make sure that the buffer is clear for commands reading
591 * data. The actually received data may be shorter than what
592 * we expect, and due to the unreliable feedback about how much
593 * data the ioctl actually transferred, it's impossible to
594 * prevent that. Returning previous buffer contents may cause
595 * security problems inside the guest OS, if users can issue
596 * commands to the CDROM device. */
597 memset(pvBuf, '\0', *pcbBuf);
598 direction = SCSI_IOCTL_DATA_IN;
599 break;
600 case PDMMEDIATXDIR_TO_DEVICE:
601 direction = SCSI_IOCTL_DATA_OUT;
602 break;
603 default:
604 AssertMsgFailed(("enmTxDir invalid!\n"));
605 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
606 }
607 memset(&Req, '\0', sizeof(Req));
608 Req.spt.Length = sizeof(Req.spt);
609 Req.spt.CdbLength = 12;
610 memcpy(Req.spt.Cdb, pbCmd, Req.spt.CdbLength);
611 Req.spt.DataBuffer = pvBuf;
612 Req.spt.DataTransferLength = *pcbBuf;
613 Req.spt.DataIn = direction;
614 Req.spt.TimeOutValue = (cTimeoutMillies + 999) / 1000; /* Convert to seconds */
615 Assert(cbSense <= sizeof(Req.aSense));
616 Req.spt.SenseInfoLength = (UCHAR)RT_MIN(sizeof(Req.aSense), cbSense);
617 Req.spt.SenseInfoOffset = RT_OFFSETOF(struct _REQ, aSense);
618 if (DeviceIoControl((HANDLE)RTFileToNative(pThis->hFileDevice), IOCTL_SCSI_PASS_THROUGH_DIRECT,
619 &Req, sizeof(Req), &Req, sizeof(Req), &cbReturned, NULL))
620 {
621 if (cbReturned > RT_OFFSETOF(struct _REQ, aSense))
622 memcpy(pabSense, Req.aSense, cbSense);
623 else
624 memset(pabSense, '\0', cbSense);
625 /* Windows shares the property of not properly reflecting the actually
626 * transferred data size. See above. Assume that everything worked ok.
627 * Except if there are sense information. */
628 rc = (pabSense[2] & 0x0f) == SCSI_SENSE_NONE
629 ? VINF_SUCCESS
630 : VERR_DEV_IO_ERROR;
631 }
632 else
633 rc = RTErrConvertFromWin32(GetLastError());
634 Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
635
636#else
637# error "Unsupported platform."
638#endif
639
640 if (pbCmd[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
641 {
642 uint8_t *pbBuf = (uint8_t*)pvBuf;
643 Log2(("Event Status Notification class=%#02x supported classes=%#02x\n", pbBuf[2], pbBuf[3]));
644 if (RT_BE2H_U16(*(uint16_t*)pbBuf) >= 6)
645 Log2((" event %#02x %#02x %#02x %#02x\n", pbBuf[4], pbBuf[5], pbBuf[6], pbBuf[7]));
646 }
647
648 LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
649 return rc;
650}
651
652
653#ifdef VBOX_WITH_SUID_WRAPPER
654/* These functions would have to go into a separate solaris binary with
655 * the setuid permission set, which would run the user-SCSI ioctl and
656 * return the value. BUT... this might be prohibitively slow.
657 */
658# ifdef RT_OS_SOLARIS
659
660/**
661 * Checks if the current user is authorized using Solaris' role-based access control.
662 * Made as a separate function with so that it need not be invoked each time we need
663 * to gain root access.
664 *
665 * @returns VBox error code.
666 */
667static int solarisCheckUserAuth()
668{
669 /* Uses Solaris' role-based access control (RBAC).*/
670 struct passwd *pPass = getpwuid(getuid());
671 if (pPass == NULL || chkauthattr("solaris.device.cdrw", pPass->pw_name) == 0)
672 return VERR_PERMISSION_DENIED;
673
674 return VINF_SUCCESS;
675}
676
677
678/**
679 * Setuid wrapper to gain root access.
680 *
681 * @returns VBox error code.
682 * @param pEffUserID Pointer to effective user ID.
683 */
684static int solarisEnterRootMode(uid_t *pEffUserID)
685{
686 /* Increase privilege if required */
687 if (*pEffUserID != 0)
688 {
689 if (seteuid(0) == 0)
690 {
691 *pEffUserID = 0;
692 return VINF_SUCCESS;
693 }
694 return VERR_PERMISSION_DENIED;
695 }
696 return VINF_SUCCESS;
697}
698
699
700/**
701 * Setuid wrapper to relinquish root access.
702 *
703 * @returns VBox error code.
704 * @param pEffUserID Pointer to effective user ID.
705 */
706static int solarisExitRootMode(uid_t *pEffUserID)
707{
708 /* Get back to user mode. */
709 if (*pEffUserID == 0)
710 {
711 uid_t realID = getuid();
712 if (seteuid(realID) == 0)
713 {
714 *pEffUserID = realID;
715 return VINF_SUCCESS;
716 }
717 return VERR_PERMISSION_DENIED;
718 }
719 return VINF_SUCCESS;
720}
721
722# endif /* RT_OS_SOLARIS */
723#endif /* VBOX_WITH_SUID_WRAPPER */
724
725
726/* -=-=-=-=- driver interface -=-=-=-=- */
727
728
729/** @copydoc FNPDMDRVDESTRUCT */
730static DECLCALLBACK(void) drvHostDvdDestruct(PPDMDRVINS pDrvIns)
731{
732#ifdef RT_OS_LINUX
733 PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
734
735 if (pThis->pbDoubleBuffer)
736 {
737 RTMemFree(pThis->pbDoubleBuffer);
738 pThis->pbDoubleBuffer = NULL;
739 }
740#endif
741 return DRVHostBaseDestruct(pDrvIns);
742}
743
744
745/**
746 * Construct a host dvd drive driver instance.
747 *
748 * @copydoc FNPDMDRVCONSTRUCT
749 */
750static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
751{
752 RT_NOREF(fFlags);
753 PDRVHOSTBASE pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTBASE);
754 LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
755
756 /*
757 * Init instance data.
758 */
759 int rc = DRVHostBaseInitData(pDrvIns, pCfg, PDMMEDIATYPE_DVD);
760 if (RT_SUCCESS(rc))
761 {
762 /*
763 * Validate configuration.
764 */
765 if (CFGMR3AreValuesValid(pCfg, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0"))
766 {
767 /*
768 * Override stuff.
769 */
770#ifdef RT_OS_LINUX
771 pThis->pbDoubleBuffer = (uint8_t *)RTMemAlloc(SCSI_MAX_BUFFER_SIZE);
772 if (!pThis->pbDoubleBuffer)
773 return VERR_NO_MEMORY;
774#endif
775
776 bool fPassthrough;
777 rc = CFGMR3QueryBool(pCfg, "Passthrough", &fPassthrough);
778 if (RT_SUCCESS(rc) && fPassthrough)
779 {
780 pThis->IMedia.pfnSendCmd = drvHostDvdSendCmd;
781 /* Passthrough requires opening the device in R/W mode. */
782 pThis->fReadOnlyConfig = false;
783#ifdef VBOX_WITH_SUID_WRAPPER /* Solaris setuid for Passthrough mode. */
784 rc = solarisCheckUserAuth();
785 if (RT_FAILURE(rc))
786 {
787 Log(("DVD: solarisCheckUserAuth failed. Permission denied!\n"));
788 return rc;
789 }
790#endif /* VBOX_WITH_SUID_WRAPPER */
791 }
792
793 pThis->IMount.pfnUnmount = drvHostDvdUnmount;
794 pThis->pfnDoLock = drvHostDvdDoLock;
795#ifdef USE_MEDIA_POLLING
796 if (!fPassthrough)
797 pThis->pfnPoll = drvHostDvdPoll;
798 else
799 pThis->pfnPoll = NULL;
800#endif
801#ifdef RT_OS_LINUX
802 pThis->pfnGetMediaSize = drvHostDvdGetMediaSize;
803#endif
804
805 /*
806 * 2nd init part.
807 */
808 rc = DRVHostBaseInitFinish(pThis);
809 }
810 else
811 {
812 pThis->fAttachFailError = true;
813 rc = VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
814 }
815 }
816 if (RT_FAILURE(rc))
817 {
818 if (!pThis->fAttachFailError)
819 {
820 /* Suppressing the attach failure error must not affect the normal
821 * DRVHostBaseDestruct, so reset this flag below before leaving. */
822 pThis->fKeepInstance = true;
823 rc = VINF_SUCCESS;
824 }
825 DRVHostBaseDestruct(pDrvIns);
826 pThis->fKeepInstance = false;
827 }
828
829 LogFlow(("drvHostDvdConstruct: returns %Rrc\n", rc));
830 return rc;
831}
832
833
834/**
835 * Block driver registration record.
836 */
837const PDMDRVREG g_DrvHostDVD =
838{
839 /* u32Version */
840 PDM_DRVREG_VERSION,
841 /* szName */
842 "HostDVD",
843 /* szRCMod */
844 "",
845 /* szR0Mod */
846 "",
847 /* pszDescription */
848 "Host DVD Block Driver.",
849 /* fFlags */
850 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
851 /* fClass. */
852 PDM_DRVREG_CLASS_BLOCK,
853 /* cMaxInstances */
854 ~0U,
855 /* cbInstance */
856 sizeof(DRVHOSTBASE),
857 /* pfnConstruct */
858 drvHostDvdConstruct,
859 /* pfnDestruct */
860 drvHostDvdDestruct,
861 /* pfnRelocate */
862 NULL,
863 /* pfnIOCtl */
864 NULL,
865 /* pfnPowerOn */
866 NULL,
867 /* pfnReset */
868 NULL,
869 /* pfnSuspend */
870 NULL,
871 /* pfnResume */
872 NULL,
873 /* pfnAttach */
874 NULL,
875 /* pfnDetach */
876 NULL,
877 /* pfnPowerOff */
878 NULL,
879 /* pfnSoftReset */
880 NULL,
881 /* u32EndVersion */
882 PDM_DRVREG_VERSION
883};
884
Note: See TracBrowser for help on using the repository browser.

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