VirtualBox

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

Last change on this file since 1019 was 1, checked in by vboxsync, 55 years ago

import

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.2 KB
Line 
1/** @file
2 *
3 * VBox storage devices:
4 * Host DVD block driver
5 */
6
7/*
8 * Copyright (C) 2006 InnoTek Systemberatung GmbH
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License as published by the Free Software Foundation,
14 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
15 * distribution. VirtualBox OSE is distributed in the hope that it will
16 * be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * If you received this file as part of a commercial VirtualBox
19 * distribution, then only the terms of your commercial VirtualBox
20 * license agreement apply instead of the previous paragraph.
21 */
22
23
24/*******************************************************************************
25* Header Files *
26*******************************************************************************/
27#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
28#ifdef __LINUX__
29# include <sys/ioctl.h>
30/* This is a hack to work around conflicts between these linux kernel headers
31 * and the GLIBC tcpip headers. They have different declarations of the 4
32 * standard byte order functions. */
33# define _LINUX_BYTEORDER_GENERIC_H
34/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
35# define _LINUX_BYTEORDER_SWAB_H
36/* Those macros that are needed are defined in the header below */
37# include "swab.h"
38# include <linux/cdrom.h>
39# include <sys/fcntl.h>
40# include <errno.h>
41
42#elif defined(__WIN__)
43# include <Windows.h>
44# include <winioctl.h>
45# include <ntddscsi.h>
46
47#elif defined(__L4ENV__)
48
49#else /* !__WIN__ nor __LINUX__ nor __L4ENV__ */
50# error "Unsupported Platform."
51#endif /* !__WIN__ nor __LINUX__ nor __L4ENV__ */
52
53#include <VBox/pdm.h>
54#include <VBox/cfgm.h>
55#include <VBox/err.h>
56
57#include <VBox/log.h>
58#include <iprt/assert.h>
59#include <iprt/file.h>
60#include <iprt/string.h>
61#include <iprt/thread.h>
62#include <iprt/critsect.h>
63#include <VBox/scsi.h>
64
65#include "Builtins.h"
66#include "DrvHostBase.h"
67
68
69
70
71/** @copydoc PDMIMOUNT::pfnUnmount */
72static DECLCALLBACK(int) drvHostDvdUnmount(PPDMIMOUNT pInterface)
73{
74 PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
75 RTCritSectEnter(&pThis->CritSect);
76
77 /*
78 * Validate state.
79 */
80 int rc = VINF_SUCCESS;
81 if (!pThis->fLocked)
82 {
83 /*
84 * Eject the disc.
85 */
86#ifdef __LINUX__
87 rc = ioctl(pThis->FileDevice, CDROMEJECT, 0);
88 if (rc < 0)
89 {
90 if (errno == EBUSY)
91 rc = VERR_PDM_MEDIA_LOCKED;
92 else if (errno == ENOSYS)
93 rc = VERR_NOT_SUPPORTED;
94 else
95 rc = RTErrConvertFromErrno(errno);
96 }
97
98#elif defined(__WIN__)
99 RTFILE FileDevice = pThis->FileDevice;
100 if (FileDevice == NIL_RTFILE) /* obsolete crap */
101 rc = RTFileOpen(&FileDevice, pThis->pszDeviceOpen, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
102 if (VBOX_SUCCESS(rc))
103 {
104 /* do ioctl */
105 DWORD cbReturned;
106 if (DeviceIoControl((HANDLE)FileDevice, IOCTL_STORAGE_EJECT_MEDIA,
107 NULL, 0,
108 NULL, 0, &cbReturned,
109 NULL))
110 rc = VINF_SUCCESS;
111 else
112 rc = RTErrConvertFromWin32(GetLastError());
113
114 /* clean up handle */
115 if (FileDevice != pThis->FileDevice)
116 RTFileClose(FileDevice);
117 }
118 else
119 AssertMsgFailed(("Failed to open '%s' for ejecting this tray.\n", rc));
120
121
122#else
123 AssertMsgFailed(("Eject is not implemented!\n"));
124 rc = VINF_SUCCESS;
125#endif
126
127 /*
128 * Media is no longer present.
129 */
130 DRVHostBaseMediaNotPresent(pThis); /** @todo This isn't thread safe! */
131 }
132 else
133 {
134 Log(("drvHostDvdUnmount: Locked\n"));
135 rc = VERR_PDM_MEDIA_LOCKED;
136 }
137
138 RTCritSectLeave(&pThis->CritSect);
139 LogFlow(("drvHostDvdUnmount: returns %Vrc\n", rc));
140 return rc;
141}
142
143
144/**
145 * Locks or unlocks the drive.
146 *
147 * @returns VBox status code.
148 * @param pThis The instance data.
149 * @param fLock True if the request is to lock the drive, false if to unlock.
150 */
151static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
152{
153#ifdef __LINUX__
154 int rc = ioctl(pThis->FileDevice, CDROM_LOCKDOOR, (int)fLock);
155 if (rc < 0)
156 {
157 if (errno == EBUSY)
158 rc = VERR_ACCESS_DENIED;
159 else if (errno == EDRIVE_CANT_DO_THIS)
160 rc = VERR_NOT_SUPPORTED;
161 else
162 rc = RTErrConvertFromErrno(errno);
163 }
164
165#elif defined(__WIN__)
166
167 PREVENT_MEDIA_REMOVAL PreventMediaRemoval = {fLock};
168 DWORD cbReturned;
169 int rc;
170 if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_STORAGE_MEDIA_REMOVAL,
171 &PreventMediaRemoval, sizeof(PreventMediaRemoval),
172 NULL, 0, &cbReturned,
173 NULL))
174 rc = VINF_SUCCESS;
175 else
176 /** @todo figure out the return codes for already locked. */
177 rc = RTErrConvertFromWin32(GetLastError());
178
179#else
180 AssertMsgFailed(("Lock/Unlock is not implemented!\n"));
181 int rc = VINF_SUCCESS;
182
183#endif
184
185 LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Vrc\n", fLock, rc));
186 return rc;
187}
188
189
190
191#ifdef __LINUX__
192/**
193 * Get the media size.
194 *
195 * @returns VBox status code.
196 * @param pThis The instance data.
197 * @param pcb Where to store the size.
198 */
199static int drvHostDvdGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
200{
201 /*
202 * Query the media size.
203 */
204 /* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
205 ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT);
206 return RTFileSeek(pThis->FileDevice, 0, RTFILE_SEEK_END, pcb);
207
208}
209#endif /* __LINUX__ */
210
211
212#ifdef __LINUX__
213/**
214 * Do media change polling.
215 */
216DECLCALLBACK(int) drvHostDvdPoll(PDRVHOSTBASE pThis)
217{
218 /*
219 * Poll for media change.
220 */
221#ifdef __LINUX__
222 bool fMediaPresent = ioctl(pThis->FileDevice, CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
223
224#else
225# error "Unsupported platform."
226#endif
227
228 RTCritSectEnter(&pThis->CritSect);
229
230 int rc = VINF_SUCCESS;
231 if (pThis->fMediaPresent != fMediaPresent)
232 {
233 LogFlow(("drvHostDvdPoll: %d -> %d\n", pThis->fMediaPresent, fMediaPresent));
234 pThis->fMediaPresent = false;
235 if (fMediaPresent)
236 rc = DRVHostBaseMediaPresent(pThis);
237 else
238 DRVHostBaseMediaNotPresent(pThis);
239 }
240 else if (fMediaPresent)
241 {
242 /*
243 * Poll for media change.
244 */
245 bool fMediaChanged;
246#ifdef __LINUX__
247 fMediaChanged = ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
248
249#else
250# error "Unsupported platform."
251#endif
252 if (fMediaChanged)
253 {
254 int rc;
255 LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
256 DRVHostBaseMediaNotPresent(pThis);
257 rc = DRVHostBaseMediaPresent(pThis);
258 }
259 }
260
261 RTCritSectLeave(&pThis->CritSect);
262 return rc;
263}
264#endif /* __LINUX__ */
265
266
267/** @copydoc PDMIBLOCK::pfnSendCmd */
268static int drvHostDvdSendCmd(PPDMIBLOCK pInterface, const uint8_t *pbCmd, PDMBLOCKTXDIR enmTxDir, void *pvBuf, size_t *pcbBuf, uint8_t *pbStat, uint32_t cTimeoutMillies)
269{
270 PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
271 int direction;
272 int rc;
273 LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
274#ifdef __LINUX__
275 struct cdrom_generic_command cgc;
276 request_sense sense;
277
278 switch (enmTxDir)
279 {
280 case PDMBLOCKTXDIR_NONE:
281 Assert(*pcbBuf == 0);
282 direction = CGC_DATA_NONE;
283 break;
284 case PDMBLOCKTXDIR_FROM_DEVICE:
285 Assert(*pcbBuf != 0);
286 /* Make sure that the buffer is clear for commands reading
287 * data. The actually received data may be shorter than what
288 * we expect, and due to the unreliable feedback about how much
289 * data the ioctl actually transferred, it's impossible to
290 * prevent that. Returning previous buffer contents may cause
291 * security problems inside the guest OS, if users can issue
292 * commands to the CDROM device. */
293 memset(pvBuf, '\0', *pcbBuf);
294 direction = CGC_DATA_READ;
295 break;
296 case PDMBLOCKTXDIR_TO_DEVICE:
297 Assert(*pcbBuf != 0);
298 direction = CGC_DATA_WRITE;
299 break;
300 default:
301 AssertMsgFailed(("enmTxDir invalid!\n"));
302 direction = CGC_DATA_NONE;
303 }
304 memset(&cgc, '\0', sizeof(cgc));
305 memcpy(cgc.cmd, pbCmd, CDROM_PACKET_SIZE);
306 cgc.buffer = (unsigned char *)pvBuf;
307 cgc.buflen = *pcbBuf;
308 cgc.stat = 0;
309 cgc.sense = &sense;
310 cgc.data_direction = direction;
311 cgc.quiet = false;
312 cgc.timeout = cTimeoutMillies;
313 rc = ioctl(pThis->FileDevice, CDROM_SEND_PACKET, &cgc);
314 if (rc < 0)
315 {
316 if (errno == EBUSY)
317 rc = VERR_PDM_MEDIA_LOCKED;
318 else if (errno == ENOSYS)
319 rc = VERR_NOT_SUPPORTED;
320 else
321 {
322 if (rc == VERR_ACCESS_DENIED && cgc.sense->sense_key == SCSI_SENSE_NONE)
323 cgc.sense->sense_key = SCSI_SENSE_ILLEGAL_REQUEST;
324 *pbStat = cgc.sense->sense_key;
325 rc = RTErrConvertFromErrno(errno);
326 Log2(("%s: error status %d, rc=%Vrc\n", __FUNCTION__, cgc.stat, rc));
327 }
328 }
329 Log2(("%s: after ioctl: cgc.buflen=%d txlen=%d\n", __FUNCTION__, cgc.buflen, *pcbBuf));
330 /* The value of cgc.buflen does not reliably reflect the actual amount
331 * of data transferred (for packet commands with little data transfer
332 * it's 0). So just assume that everything worked ok. */
333#elif defined(__WIN__)
334 struct _REQ {
335 SCSI_PASS_THROUGH_DIRECT spt;
336 uint8_t aSense[18];
337 } Req;
338 DWORD cbReturned = 0;
339
340 switch (enmTxDir)
341 {
342 case PDMBLOCKTXDIR_NONE:
343 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
344 break;
345 case PDMBLOCKTXDIR_FROM_DEVICE:
346 Assert(*pcbBuf != 0);
347 /* Make sure that the buffer is clear for commands reading
348 * data. The actually received data may be shorter than what
349 * we expect, and due to the unreliable feedback about how much
350 * data the ioctl actually transferred, it's impossible to
351 * prevent that. Returning previous buffer contents may cause
352 * security problems inside the guest OS, if users can issue
353 * commands to the CDROM device. */
354 memset(pvBuf, '\0', *pcbBuf);
355 direction = SCSI_IOCTL_DATA_IN;
356 break;
357 case PDMBLOCKTXDIR_TO_DEVICE:
358 direction = SCSI_IOCTL_DATA_OUT;
359 break;
360 default:
361 AssertMsgFailed(("enmTxDir invalid!\n"));
362 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
363 }
364 memset(&Req, '\0', sizeof(Req));
365 Req.spt.Length = sizeof(Req.spt);
366 Req.spt.CdbLength = 12;
367 memcpy(Req.spt.Cdb, pbCmd, Req.spt.CdbLength);
368 Req.spt.DataBuffer = pvBuf;
369 Req.spt.DataTransferLength = *pcbBuf;
370 Req.spt.DataIn = direction;
371 Req.spt.TimeOutValue = (cTimeoutMillies + 999) / 1000; /* Convert to seconds */
372 Req.spt.SenseInfoLength = sizeof(Req.aSense);
373 Req.spt.SenseInfoOffset = RT_OFFSETOF(struct _REQ, aSense);
374 if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT,
375 &Req, sizeof(Req), &Req, sizeof(Req), &cbReturned, NULL))
376 {
377 if (cbReturned > RT_OFFSETOF(struct _REQ, aSense))
378 *pbStat = Req.aSense[2] & 0x0f;
379 else
380 *pbStat = 0;
381 /* Windows shares the property of not properly reflecting the actually
382 * transferred data size. See above. Assume that everything worked ok. */
383 rc = VINF_SUCCESS;
384 }
385 else
386 rc = RTErrConvertFromWin32(GetLastError());
387 Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
388#elif defined(__L4ENV__)
389 /* L4 is silently unsupported. */
390#else
391# error "Unsupported platform."
392#endif
393 LogFlow(("%s: rc=%Vrc\n", __FUNCTION__, rc));
394 return rc;
395}
396
397
398/* -=-=-=-=- driver interface -=-=-=-=- */
399
400
401/**
402 * Construct a host dvd drive driver instance.
403 *
404 * @returns VBox status.
405 * @param pDrvIns The driver instance data.
406 * If the registration structure is needed, pDrvIns->pDrvReg points to it.
407 * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
408 * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
409 * iInstance it's expected to be used a bit in this function.
410 */
411static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
412{
413 PDRVHOSTBASE pThis = PDMINS2DATA(pDrvIns, PDRVHOSTBASE);
414 LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
415
416 /*
417 * Validate configuration.
418 */
419 if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0Interval\0Locked\0BIOSVisible\0Passthrough\0"))
420 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
421
422
423 /*
424 * Init instance data.
425 */
426 int rc = DRVHostBaseInitData(pDrvIns, pCfgHandle, PDMBLOCKTYPE_DVD);
427 if (VBOX_SUCCESS(rc))
428 {
429 /*
430 * Override stuff.
431 */
432
433#ifndef __L4ENV__ /* Passthrough is not supported on L4 yet */
434 bool fPassthrough;
435 rc = CFGMR3QueryBool(pCfgHandle, "Passthrough", &fPassthrough);
436 if (VBOX_SUCCESS(rc) && fPassthrough)
437 {
438 pThis->IBlock.pfnSendCmd = drvHostDvdSendCmd;
439 /* Passthrough requires opening the device in R/W mode. */
440 pThis->fReadOnlyConfig = false;
441 }
442#endif /* !__L4ENV__ */
443
444 pThis->IMount.pfnUnmount = drvHostDvdUnmount;
445 pThis->pfnDoLock = drvHostDvdDoLock;
446#ifdef __LINUX__
447 if (!fPassthrough)
448 pThis->pfnPoll = drvHostDvdPoll;
449 else
450 pThis->pfnPoll = NULL;
451 pThis->pfnGetMediaSize = drvHostDvdGetMediaSize;
452#endif
453
454 /*
455 * 2nd init part.
456 */
457 rc = DRVHostBaseInitFinish(pThis);
458 if (VBOX_SUCCESS(rc))
459 {
460 LogFlow(("drvHostDvdConstruct: return %Vrc\n", rc));
461 return rc;
462 }
463 }
464 DRVHostBaseDestruct(pDrvIns);
465
466 LogFlow(("drvHostDvdConstruct: returns %Vrc\n", rc));
467 return rc;
468}
469
470
471/**
472 * Block driver registration record.
473 */
474const PDMDRVREG g_DrvHostDVD =
475{
476 /* u32Version */
477 PDM_DRVREG_VERSION,
478 /* szDriverName */
479 "HostDVD",
480 /* pszDescription */
481 "Host DVD Block Driver.",
482 /* fFlags */
483 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
484 /* fClass. */
485 PDM_DRVREG_CLASS_BLOCK,
486 /* cMaxInstances */
487 ~0,
488 /* cbInstance */
489 sizeof(DRVHOSTBASE),
490 /* pfnConstruct */
491 drvHostDvdConstruct,
492 /* pfnDestruct */
493 DRVHostBaseDestruct,
494 /* pfnIOCtl */
495 NULL,
496 /* pfnPowerOn */
497 NULL,
498 /* pfnReset */
499 NULL,
500 /* pfnSuspend */
501 NULL,
502 /* pfnResume */
503 NULL,
504 /* pfnDetach */
505 NULL
506};
507
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