VirtualBox

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

Last change on this file since 28855 was 28800, checked in by vboxsync, 14 years ago

Automated rebranding to Oracle copyright/license strings via filemuncher

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

© 2023 Oracle
ContactPrivacy policyTerms of Use