VirtualBox

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

Last change on this file since 44004 was 40282, checked in by vboxsync, 13 years ago

*: gcc-4.7: ~0 => ~0U in initializers (warning: narrowing conversion of -1' from int' to `unsigned int' inside { } is ill-formed in C++11 [-Wnarrowing])

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