/* $Id$ */
/** @file
* DrvHostDVD - Host DVD block driver.
*/
/*
* Copyright (C) 2006-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define __STDC_LIMIT_MACROS
#define __STDC_CONSTANT_MACROS
#ifdef RT_OS_DARWIN
# include <IOKit/IOKitLib.h>
# include <IOKit/IOCFPlugIn.h>
# include <mach/mach_error.h>
# define USE_MEDIA_POLLING
#elif defined RT_OS_LINUX
/* All the following crap is apparently not necessary anymore since Linux
* version 2.6.29. */
/* This is a hack to work around conflicts between these linux kernel headers
* and the GLIBC tcpip headers. They have different declarations of the 4
* standard byte order functions. */
# define _LINUX_BYTEORDER_GENERIC_H
/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
/* Those macros that are needed are defined in the header below. */
# include "swab.h"
# endif
# include <errno.h>
# include <limits.h>
# define USE_MEDIA_POLLING
#elif defined(RT_OS_SOLARIS)
# include <stropts.h>
# include <fcntl.h>
# include <errno.h>
# include <pwd.h>
# include <unistd.h>
# include <syslog.h>
# ifdef VBOX_WITH_SUID_WRAPPER
# include <auth_attr.h>
# endif
# define USE_MEDIA_POLLING
#elif defined(RT_OS_WINDOWS)
# define _interlockedbittestandset they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandset
# define _interlockedbittestandreset they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandreset
# define _interlockedbittestandset64 they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandset64
# define _interlockedbittestandreset64 they_messed_it_up_in_winnt_h_this_time_sigh__interlockedbittestandreset64
# include <Windows.h>
# include <winioctl.h>
# include <ntddscsi.h>
# pragma warning(default : 4163)
#elif defined(RT_OS_FREEBSD)
# include <stdio.h>
# define USE_MEDIA_POLLING
#else
# error "Unsupported Platform."
#endif
#include <iprt/critsect.h>
#include "VBoxDD.h"
#include "DrvHostBase.h"
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
#ifdef VBOX_WITH_SUID_WRAPPER
static int solarisCheckUserAuth();
#endif
/** @copydoc PDMIMOUNT::pfnUnmount */
{
/*
* Validate state.
*/
{
/* Unlock drive if necessary. */
drvHostDvdDoLock(pThis, false);
if (fEject)
{
/*
* Eject the disc.
*/
#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
{
0,0,0,0,0,0,0,0,0,0
};
#elif defined(RT_OS_LINUX)
if (rc < 0)
{
else
}
#elif defined(RT_OS_SOLARIS)
if (rc < 0)
{
else
}
#elif defined(RT_OS_WINDOWS)
rc = RTFileOpen(&hFileDevice, pThis->pszDeviceOpen, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
if (RT_SUCCESS(rc))
{
/* do ioctl */
NULL, 0,
NULL, 0, &cbReturned,
NULL))
rc = VINF_SUCCESS;
else
/* clean up handle */
}
else
#else
AssertMsgFailed(("Eject is not implemented!\n"));
rc = VINF_SUCCESS;
#endif
}
/*
* Media is no longer present.
*/
}
else
{
Log(("drvHostDvdUnmount: Locked\n"));
}
return rc;
}
/**
* Locks or unlocks the drive.
*
* @returns VBox status code.
* @param pThis The instance data.
* @param fLock True if the request is to lock the drive, false if to unlock.
*/
{
#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
{
SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
0,0,0,0,0,0,0,0,0,0
};
#elif defined(RT_OS_LINUX)
if (rc < 0)
{
else if (errno == EDRIVE_CANT_DO_THIS)
else
}
#elif defined(RT_OS_SOLARIS)
if (rc < 0)
{
else
}
#elif defined(RT_OS_WINDOWS)
int rc;
&PreventMediaRemoval, sizeof(PreventMediaRemoval),
NULL, 0, &cbReturned,
NULL))
rc = VINF_SUCCESS;
else
/** @todo figure out the return codes for already locked. */
#else
AssertMsgFailed(("Lock/Unlock is not implemented!\n"));
int rc = VINF_SUCCESS;
#endif
return rc;
}
#ifdef RT_OS_LINUX
/**
* Get the media size.
*
* @returns VBox status code.
* @param pThis The instance data.
* @param pcb Where to store the size.
*/
{
/*
* Query the media size.
*/
/* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
}
#endif /* RT_OS_LINUX */
#ifdef USE_MEDIA_POLLING
/**
* Do media change polling.
*/
{
/*
* Poll for media change.
*/
#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
#ifdef RT_OS_DARWIN
#endif
/*
* Issue a TEST UNIT READY request.
*/
bool fMediaChanged = false;
bool fMediaPresent = false;
int rc2 = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
if (RT_SUCCESS(rc2))
fMediaPresent = true;
else if ( rc2 == VERR_UNRESOLVED_ERROR
)
)
{
fMediaPresent = false;
fMediaChanged = true;
/** @todo check this media change stuff on Darwin. */
}
#elif defined(RT_OS_LINUX)
bool fMediaPresent = ioctl(RTFileToNative(pThis->hFileDevice), CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
#elif defined(RT_OS_SOLARIS)
bool fMediaPresent = false;
bool fMediaChanged = false;
/* Need to pass the previous state and DKIO_NONE for the first time. */
if (rc2 == 0)
{
if (PreviousState != s_DeviceState)
fMediaChanged = true;
}
#else
# error "Unsupported platform."
#endif
{
pThis->fMediaPresent = false;
if (fMediaPresent)
else
}
else if (fMediaPresent)
{
/*
* Poll for media change.
*/
/* taken care of above. */
#elif defined(RT_OS_LINUX)
bool fMediaChanged = ioctl(RTFileToNative(pThis->hFileDevice), CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
#else
# error "Unsupported platform."
#endif
if (fMediaChanged)
{
LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
}
}
return rc;
}
#endif /* USE_MEDIA_POLLING */
/** @copydoc PDMIBLOCK::pfnSendCmd */
{
int rc;
LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
#if defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD)
/*
* Pass the request on to the internal scsi command interface.
* The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
*/
if (enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
rc = DRVHostBaseScsiCmd(pThis, pbCmd, 12, PDMBLOCKTXDIR_FROM_DEVICE, pvBuf, pcbBuf, pabSense, cbSense, cTimeoutMillies);
if (rc == VERR_UNRESOLVED_ERROR)
/* sense information set */
#elif defined(RT_OS_LINUX)
int direction;
switch (enmTxDir)
{
case PDMBLOCKTXDIR_NONE:
break;
/* Make sure that the buffer is clear for commands reading
* data. The actually received data may be shorter than what
* we expect, and due to the unreliable feedback about how much
* data the ioctl actually transferred, it's impossible to
* prevent that. Returning previous buffer contents may cause
* security problems inside the guest OS, if users can issue
* commands to the CDROM device. */
break;
case PDMBLOCKTXDIR_TO_DEVICE:
break;
default:
AssertMsgFailed(("enmTxDir invalid!\n"));
}
if (rc < 0)
{
else
{
}
}
switch (enmTxDir)
{
break;
default:
;
}
/* The value of cgc.buflen does not reliably reflect the actual amount
* of data transferred (for packet commands with little data transfer
* it's 0). So just assume that everything worked ok. */
#elif defined(RT_OS_SOLARIS)
switch (enmTxDir)
{
case PDMBLOCKTXDIR_NONE:
/* nothing to do */
break;
/* Make sure that the buffer is clear for commands reading
* data. The actually received data may be shorter than what
* we expect, and due to the unreliable feedback about how much
* data the ioctl actually transferred, it's impossible to
* prevent that. Returning previous buffer contents may cause
* security problems inside the guest OS, if users can issue
* commands to the CDROM device. */
break;
case PDMBLOCKTXDIR_TO_DEVICE:
break;
default:
}
/* We need root privileges for user-SCSI under Solaris. */
#ifdef VBOX_WITH_SUID_WRAPPER
#endif
#ifdef VBOX_WITH_SUID_WRAPPER
#endif
if (rc < 0)
{
return VERR_PERMISSION_DENIED;
if (usc.uscsi_status)
{
}
}
Log2(("%s: after ioctl: residual buflen=%d original buflen=%d\n", __FUNCTION__, usc.uscsi_resid, usc.uscsi_buflen));
#elif defined(RT_OS_WINDOWS)
int direction;
struct _REQ
{
} Req;
switch (enmTxDir)
{
case PDMBLOCKTXDIR_NONE:
break;
/* Make sure that the buffer is clear for commands reading
* data. The actually received data may be shorter than what
* we expect, and due to the unreliable feedback about how much
* data the ioctl actually transferred, it's impossible to
* prevent that. Returning previous buffer contents may cause
* security problems inside the guest OS, if users can issue
* commands to the CDROM device. */
break;
case PDMBLOCKTXDIR_TO_DEVICE:
break;
default:
AssertMsgFailed(("enmTxDir invalid!\n"));
}
{
else
/* Windows shares the property of not properly reflecting the actually
* transferred data size. See above. Assume that everything worked ok.
* Except if there are sense information. */
}
else
Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
#else
# error "Unsupported platform."
#endif
if (pbCmd[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
{
}
return rc;
}
#ifdef VBOX_WITH_SUID_WRAPPER
/* These functions would have to go into a separate solaris binary with
* the setuid permission set, which would run the user-SCSI ioctl and
* return the value. BUT... this might be prohibitively slow.
*/
# ifdef RT_OS_SOLARIS
/**
* Checks if the current user is authorized using Solaris' role-based access control.
* Made as a separate function with so that it need not be invoked each time we need
* to gain root access.
*
* @returns VBox error code.
*/
static int solarisCheckUserAuth()
{
/* Uses Solaris' role-based access control (RBAC).*/
return VERR_PERMISSION_DENIED;
return VINF_SUCCESS;
}
/**
* Setuid wrapper to gain root access.
*
* @returns VBox error code.
* @param pEffUserID Pointer to effective user ID.
*/
{
/* Increase privilege if required */
if (*pEffUserID != 0)
{
if (seteuid(0) == 0)
{
*pEffUserID = 0;
return VINF_SUCCESS;
}
return VERR_PERMISSION_DENIED;
}
return VINF_SUCCESS;
}
/**
* Setuid wrapper to relinquish root access.
*
* @returns VBox error code.
* @param pEffUserID Pointer to effective user ID.
*/
{
/* Get back to user mode. */
if (*pEffUserID == 0)
{
{
*pEffUserID = realID;
return VINF_SUCCESS;
}
return VERR_PERMISSION_DENIED;
}
return VINF_SUCCESS;
}
# endif /* RT_OS_SOLARIS */
#endif /* VBOX_WITH_SUID_WRAPPER */
/* -=-=-=-=- driver interface -=-=-=-=- */
/** @copydoc FNPDMDRVDESTRUCT */
{
#ifdef RT_OS_LINUX
if (pThis->pbDoubleBuffer)
{
}
#endif
return DRVHostBaseDestruct(pDrvIns);
}
/**
* Construct a host dvd drive driver instance.
*
* @copydoc FNPDMDRVCONSTRUCT
*/
{
/*
* Init instance data.
*/
if (RT_SUCCESS(rc))
{
/*
* Validate configuration.
*/
if (CFGMR3AreValuesValid(pCfg, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0"))
{
/*
* Override stuff.
*/
#ifdef RT_OS_LINUX
if (!pThis->pbDoubleBuffer)
return VERR_NO_MEMORY;
#endif
bool fPassthrough;
{
/* Passthrough requires opening the device in R/W mode. */
pThis->fReadOnlyConfig = false;
#ifdef VBOX_WITH_SUID_WRAPPER /* Solaris setuid for Passthrough mode. */
rc = solarisCheckUserAuth();
if (RT_FAILURE(rc))
{
Log(("DVD: solarisCheckUserAuth failed. Permission denied!\n"));
return rc;
}
#endif /* VBOX_WITH_SUID_WRAPPER */
}
#ifdef USE_MEDIA_POLLING
if (!fPassthrough)
else
#endif
#ifdef RT_OS_LINUX
#endif
/*
* 2nd init part.
*/
}
else
{
pThis->fAttachFailError = true;
}
}
if (RT_FAILURE(rc))
{
if (!pThis->fAttachFailError)
{
/* Suppressing the attach failure error must not affect the normal
* DRVHostBaseDestruct, so reset this flag below before leaving. */
pThis->fKeepInstance = true;
rc = VINF_SUCCESS;
}
pThis->fKeepInstance = false;
}
return rc;
}
/**
* Block driver registration record.
*/
{
/* u32Version */
/* szName */
"HostDVD",
/* szRCMod */
"",
/* szR0Mod */
"",
/* pszDescription */
"Host DVD Block Driver.",
/* fFlags */
/* fClass. */
/* cMaxInstances */
~0U,
/* cbInstance */
sizeof(DRVHOSTBASE),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
NULL,
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
NULL,
/* pfnSuspend */
NULL,
/* pfnResume */
NULL,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnPowerOff */
NULL,
/* pfnSoftReset */
NULL,
/* u32EndVersion */
};