VirtualBox

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

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

fix Linux passthrough by introducing a double buffer which is required as long as we don't use vm_insert_page()

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