VirtualBox

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

Last change on this file since 45238 was 45061, checked in by vboxsync, 12 years ago

Review of PDM driver destructors making sure that variables they use are correctly initialized in the constructor. Found several RTFileClose(0) cases.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 28.0 KB
Line 
1/* $Id: DrvHostDVD.cpp 45061 2013-03-18 14:09:03Z 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 * Init instance data.
762 */
763 int rc = DRVHostBaseInitData(pDrvIns, pCfg, PDMBLOCKTYPE_DVD);
764 if (RT_SUCCESS(rc))
765 {
766 /*
767 * Validate configuration.
768 */
769 if (CFGMR3AreValuesValid(pCfg, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0"))
770 {
771 /*
772 * Override stuff.
773 */
774#ifdef RT_OS_LINUX
775 pThis->pbDoubleBuffer = (uint8_t *)RTMemAlloc(SCSI_MAX_BUFFER_SIZE);
776 if (!pThis->pbDoubleBuffer)
777 return VERR_NO_MEMORY;
778#endif
779
780#ifndef RT_OS_L4 /* Passthrough is not supported on L4 yet */
781 bool fPassthrough;
782 rc = CFGMR3QueryBool(pCfg, "Passthrough", &fPassthrough);
783 if (RT_SUCCESS(rc) && fPassthrough)
784 {
785 pThis->IBlock.pfnSendCmd = drvHostDvdSendCmd;
786 /* Passthrough requires opening the device in R/W mode. */
787 pThis->fReadOnlyConfig = false;
788# ifdef VBOX_WITH_SUID_WRAPPER /* Solaris setuid for Passthrough mode. */
789 rc = solarisCheckUserAuth();
790 if (RT_FAILURE(rc))
791 {
792 Log(("DVD: solarisCheckUserAuth failed. Permission denied!\n"));
793 return rc;
794 }
795# endif /* VBOX_WITH_SUID_WRAPPER */
796 }
797#endif /* !RT_OS_L4 */
798
799 pThis->IMount.pfnUnmount = drvHostDvdUnmount;
800 pThis->pfnDoLock = drvHostDvdDoLock;
801#ifdef USE_MEDIA_POLLING
802 if (!fPassthrough)
803 pThis->pfnPoll = drvHostDvdPoll;
804 else
805 pThis->pfnPoll = NULL;
806#endif
807#ifdef RT_OS_LINUX
808 pThis->pfnGetMediaSize = drvHostDvdGetMediaSize;
809#endif
810
811 /*
812 * 2nd init part.
813 */
814 rc = DRVHostBaseInitFinish(pThis);
815 }
816 else
817 {
818 pThis->fAttachFailError = true;
819 rc = VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
820 }
821 }
822 if (RT_FAILURE(rc))
823 {
824 if (!pThis->fAttachFailError)
825 {
826 /* Suppressing the attach failure error must not affect the normal
827 * DRVHostBaseDestruct, so reset this flag below before leaving. */
828 pThis->fKeepInstance = true;
829 rc = VINF_SUCCESS;
830 }
831 DRVHostBaseDestruct(pDrvIns);
832 pThis->fKeepInstance = false;
833 }
834
835 LogFlow(("drvHostDvdConstruct: returns %Rrc\n", rc));
836 return rc;
837}
838
839
840/**
841 * Block driver registration record.
842 */
843const PDMDRVREG g_DrvHostDVD =
844{
845 /* u32Version */
846 PDM_DRVREG_VERSION,
847 /* szName */
848 "HostDVD",
849 /* szRCMod */
850 "",
851 /* szR0Mod */
852 "",
853 /* pszDescription */
854 "Host DVD Block Driver.",
855 /* fFlags */
856 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
857 /* fClass. */
858 PDM_DRVREG_CLASS_BLOCK,
859 /* cMaxInstances */
860 ~0U,
861 /* cbInstance */
862 sizeof(DRVHOSTBASE),
863 /* pfnConstruct */
864 drvHostDvdConstruct,
865 /* pfnDestruct */
866 drvHostDvdDestruct,
867 /* pfnRelocate */
868 NULL,
869 /* pfnIOCtl */
870 NULL,
871 /* pfnPowerOn */
872 NULL,
873 /* pfnReset */
874 NULL,
875 /* pfnSuspend */
876 NULL,
877 /* pfnResume */
878 NULL,
879 /* pfnAttach */
880 NULL,
881 /* pfnDetach */
882 NULL,
883 /* pfnPowerOff */
884 NULL,
885 /* pfnSoftReset */
886 NULL,
887 /* u32EndVersion */
888 PDM_DRVREG_VERSION
889};
890
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