VirtualBox

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

Last change on this file since 19223 was 18446, checked in by vboxsync, 16 years ago

DrvHostDVD: even better fix.

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