VirtualBox

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

Last change on this file since 56292 was 56292, checked in by vboxsync, 9 years ago

Devices: Updated (C) year.

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