VirtualBox

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

Last change on this file since 33000 was 32067, checked in by vboxsync, 14 years ago

HostDVD/FreeBSD: Implement missing DoLock method

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

© 2023 Oracle
ContactPrivacy policyTerms of Use