VirtualBox

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

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

PDM: introduced fEject parameter to PDMIMOUNT::pfnUnmount which is false if we don't need to eject the medium during unmount

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