VirtualBox

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

Last change on this file since 29250 was 29250, checked in by vboxsync, 15 years ago

iprt/asm*.h: split out asm-math.h, don't include asm-*.h from asm.h, don't include asm.h from sup.h. Fixed a couple file headers.

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