VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/DrvHostBase-linux.cpp

Last change on this file was 106061, checked in by vboxsync, 7 weeks ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.0 KB
Line 
1/* $Id: DrvHostBase-linux.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * DrvHostBase - Host base drive access driver, Linux specifics.
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
33#include <sys/ioctl.h>
34#include <sys/fcntl.h>
35#include <errno.h>
36#include <linux/version.h>
37/* All the following crap is apparently not necessary anymore since Linux
38 * version 2.6.29. */
39#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)
40/* This is a hack to work around conflicts between these linux kernel headers
41 * and the GLIBC tcpip headers. They have different declarations of the 4
42 * standard byte order functions. */
43# define _LINUX_BYTEORDER_GENERIC_H
44/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
45/* Those macros that are needed are defined in the header below. */
46# include "swab.h"
47#endif
48#include <linux/fd.h>
49#include <linux/cdrom.h>
50#include <limits.h>
51
52#include <iprt/mem.h>
53#include <iprt/file.h>
54#include <iprt/string.h>
55#include <VBox/err.h>
56#include <VBox/scsi.h>
57
58
59/**
60 * Host backend specific data (needed by DrvHostBase.h).
61 */
62typedef struct DRVHOSTBASEOS
63{
64 /** The filehandle of the device. */
65 RTFILE hFileDevice;
66 /** Double buffer required for ioctl with the Linux kernel as long as we use
67 * remap_pfn_range() instead of vm_insert_page(). */
68 uint8_t *pbDoubleBuffer;
69 /** Previous disk inserted indicator for the media polling on floppy drives. */
70 bool fPrevDiskIn;
71} DRVHOSTBASEOS;
72/** Pointer to the host backend specific data. */
73typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
74AssertCompile(sizeof(DRVHOSTBASEOS) <= 64);
75
76#define DRVHOSTBASE_OS_INT_DECLARED
77#include "DrvHostBase.h"
78
79
80/*********************************************************************************************************************************
81* Defined Constants And Macros *
82*********************************************************************************************************************************/
83/** Maximum buffer size supported by the kernel interface. */
84#define LNX_SCSI_MAX_BUFFER_SIZE (100 * _1K)
85
86
87
88
89
90DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
91 void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
92{
93 /*
94 * Minimal input validation.
95 */
96 Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE);
97 Assert(!pvBuf || pcbBuf);
98 Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE);
99 Assert(pbSense || !cbSense); RT_NOREF(cbSense);
100 AssertPtr(pbCmd);
101 Assert(cbCmd <= 16 && cbCmd >= 1);
102
103 /* Allocate the temporary buffer lazily. */
104 if(RT_UNLIKELY(!pThis->Os.pbDoubleBuffer))
105 {
106 pThis->Os.pbDoubleBuffer = (uint8_t *)RTMemAlloc(LNX_SCSI_MAX_BUFFER_SIZE);
107 if (!pThis->Os.pbDoubleBuffer)
108 return VERR_NO_MEMORY;
109 }
110
111 int rc = VERR_GENERAL_FAILURE;
112 int direction;
113 struct cdrom_generic_command cgc;
114
115 switch (enmTxDir)
116 {
117 case PDMMEDIATXDIR_NONE:
118 Assert(*pcbBuf == 0);
119 direction = CGC_DATA_NONE;
120 break;
121 case PDMMEDIATXDIR_FROM_DEVICE:
122 Assert(*pcbBuf != 0);
123 Assert(*pcbBuf <= LNX_SCSI_MAX_BUFFER_SIZE);
124 /* Make sure that the buffer is clear for commands reading
125 * data. The actually received data may be shorter than what
126 * we expect, and due to the unreliable feedback about how much
127 * data the ioctl actually transferred, it's impossible to
128 * prevent that. Returning previous buffer contents may cause
129 * security problems inside the guest OS, if users can issue
130 * commands to the CDROM device. */
131 memset(pThis->Os.pbDoubleBuffer, '\0', *pcbBuf);
132 direction = CGC_DATA_READ;
133 break;
134 case PDMMEDIATXDIR_TO_DEVICE:
135 Assert(*pcbBuf != 0);
136 Assert(*pcbBuf <= LNX_SCSI_MAX_BUFFER_SIZE);
137 memcpy(pThis->Os.pbDoubleBuffer, pvBuf, *pcbBuf);
138 direction = CGC_DATA_WRITE;
139 break;
140 default:
141 AssertMsgFailed(("enmTxDir invalid!\n"));
142 direction = CGC_DATA_NONE;
143 }
144 memset(&cgc, '\0', sizeof(cgc));
145 memcpy(cgc.cmd, pbCmd, RT_MIN(CDROM_PACKET_SIZE, cbCmd));
146 cgc.buffer = (unsigned char *)pThis->Os.pbDoubleBuffer;
147 cgc.buflen = *pcbBuf;
148 cgc.stat = 0;
149 Assert(cbSense >= sizeof(struct request_sense));
150 cgc.sense = (struct request_sense *)pbSense;
151 cgc.data_direction = direction;
152 cgc.quiet = false;
153 cgc.timeout = cTimeoutMillies;
154 rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_SEND_PACKET, &cgc);
155 if (rc < 0)
156 {
157 if (errno == EBUSY)
158 rc = VERR_PDM_MEDIA_LOCKED;
159 else if (errno == ENOSYS)
160 rc = VERR_NOT_SUPPORTED;
161 else
162 {
163 rc = RTErrConvertFromErrno(errno);
164 if (rc == VERR_ACCESS_DENIED && cgc.sense->sense_key == SCSI_SENSE_NONE)
165 cgc.sense->sense_key = SCSI_SENSE_ILLEGAL_REQUEST;
166 Log2(("%s: error status %d, rc=%Rrc\n", __FUNCTION__, cgc.stat, rc));
167 }
168 }
169 switch (enmTxDir)
170 {
171 case PDMMEDIATXDIR_FROM_DEVICE:
172 memcpy(pvBuf, pThis->Os.pbDoubleBuffer, *pcbBuf);
173 break;
174 default:
175 ;
176 }
177 Log2(("%s: after ioctl: cgc.buflen=%d txlen=%d\n", __FUNCTION__, cgc.buflen, *pcbBuf));
178 /* The value of cgc.buflen does not reliably reflect the actual amount
179 * of data transferred (for packet commands with little data transfer
180 * it's 0). So just assume that everything worked ok. */
181
182 return rc;
183}
184
185
186DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
187{
188 RT_NOREF(pThis);
189
190 return LNX_SCSI_MAX_BUFFER_SIZE;
191}
192
193
194DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb)
195{
196 int rc = VERR_INVALID_STATE;
197
198 if (PDMMEDIATYPE_IS_FLOPPY(pThis->enmType))
199 {
200 rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), FDFLUSH);
201 if (rc)
202 {
203 rc = RTErrConvertFromErrno (errno);
204 Log(("DrvHostFloppy: FDFLUSH ioctl(%s) failed, errno=%d rc=%Rrc\n", pThis->pszDevice, errno, rc));
205 return rc;
206 }
207
208 floppy_drive_struct DrvStat;
209 rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), FDGETDRVSTAT, &DrvStat);
210 if (rc)
211 {
212 rc = RTErrConvertFromErrno(errno);
213 Log(("DrvHostFloppy: FDGETDRVSTAT ioctl(%s) failed, errno=%d rc=%Rrc\n", pThis->pszDevice, errno, rc));
214 return rc;
215 }
216 pThis->fReadOnly = !(DrvStat.flags & FD_DISK_WRITABLE);
217 rc = RTFileSeek(pThis->Os.hFileDevice, 0, RTFILE_SEEK_END, pcb);
218 }
219 else if (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
220 {
221 /* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
222 ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_MEDIA_CHANGED, CDSL_CURRENT);
223 rc = RTFileSeek(pThis->Os.hFileDevice, 0, RTFILE_SEEK_END, pcb);
224 }
225
226 return rc;
227}
228
229
230DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead)
231{
232 return RTFileReadAt(pThis->Os.hFileDevice, off, pvBuf, cbRead, NULL);
233}
234
235
236DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite)
237{
238 return RTFileWriteAt(pThis->Os.hFileDevice, off, pvBuf, cbWrite, NULL);
239}
240
241
242DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
243{
244 return RTFileFlush(pThis->Os.hFileDevice);
245}
246
247
248DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock)
249{
250 int rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_LOCKDOOR, (int)fLock);
251 if (rc < 0)
252 {
253 if (errno == EBUSY)
254 rc = VERR_ACCESS_DENIED;
255 else if (errno == EDRIVE_CANT_DO_THIS)
256 rc = VERR_NOT_SUPPORTED;
257 else
258 rc = RTErrConvertFromErrno(errno);
259 }
260
261 return rc;
262}
263
264
265DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis)
266{
267 int rc = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROMEJECT, 0);
268 if (rc < 0)
269 {
270 if (errno == EBUSY)
271 rc = VERR_PDM_MEDIA_LOCKED;
272 else if (errno == ENOSYS)
273 rc = VERR_NOT_SUPPORTED;
274 else
275 rc = RTErrConvertFromErrno(errno);
276 }
277
278 return rc;
279}
280
281
282DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent)
283{
284 int rc = VINF_SUCCESS;
285
286 if (PDMMEDIATYPE_IS_FLOPPY(pThis->enmType))
287 {
288 floppy_drive_struct DrvStat;
289 int rcLnx = ioctl(RTFileToNative(pThis->Os.hFileDevice), FDPOLLDRVSTAT, &DrvStat);
290 if (!rcLnx)
291 {
292 bool fDiskIn = !(DrvStat.flags & (FD_VERIFY | FD_DISK_NEWCHANGE));
293 *pfMediaPresent = fDiskIn;
294
295 if (fDiskIn != pThis->Os.fPrevDiskIn)
296 *pfMediaChanged = true;
297
298 pThis->Os.fPrevDiskIn = fDiskIn;
299 }
300 else
301 rc = RTErrConvertFromErrno(errno);
302 }
303 else
304 {
305 *pfMediaPresent = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
306 *pfMediaChanged = false;
307 if (pThis->fMediaPresent != *pfMediaPresent)
308 *pfMediaChanged = ioctl(RTFileToNative(pThis->Os.hFileDevice), CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
309 }
310
311 return rc;
312}
313
314
315DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis)
316{
317 pThis->Os.hFileDevice = NIL_RTFILE;
318 pThis->Os.pbDoubleBuffer = NULL;
319 pThis->Os.fPrevDiskIn = false;
320}
321
322
323DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly)
324{
325 uint32_t fFlags = (fReadOnly ? RTFILE_O_READ : RTFILE_O_READWRITE) | RTFILE_O_OPEN | RTFILE_O_DENY_NONE | RTFILE_O_NON_BLOCK;
326 return RTFileOpen(&pThis->Os.hFileDevice, pThis->pszDevice, fFlags);
327}
328
329
330DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
331{
332 /*
333 * Need to re-open the device because it will kill off any cached data
334 * that Linux for some peculiar reason thinks should survive a media change.
335 */
336 if (pThis->Os.hFileDevice != NIL_RTFILE)
337 {
338 RTFileClose(pThis->Os.hFileDevice);
339 pThis->Os.hFileDevice = NIL_RTFILE;
340 }
341 int rc = drvHostBaseOpenOs(pThis, pThis->fReadOnlyConfig);
342 if (RT_FAILURE(rc))
343 {
344 if (!pThis->fReadOnlyConfig)
345 {
346 LogFlow(("%s-%d: drvHostBaseMediaRefreshOs: '%s' - retry readonly (%Rrc)\n",
347 pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->pszDevice, rc));
348 rc = drvHostBaseOpenOs(pThis, true);
349 }
350 if (RT_FAILURE(rc))
351 {
352 LogFlow(("%s-%d: failed to open device '%s', rc=%Rrc\n",
353 pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance, pThis->pszDevice, rc));
354 return rc;
355 }
356 pThis->fReadOnly = true;
357 }
358 else
359 pThis->fReadOnly = pThis->fReadOnlyConfig;
360
361 return rc;
362}
363
364
365DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis)
366{
367 RT_NOREF(pThis);
368 return true; /* On Linux we always use media polling. */
369}
370
371
372DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis)
373{
374 /*
375 * Unlock the drive if we've locked it or we're in passthru mode.
376 */
377 if ( pThis->fLocked
378 && pThis->Os.hFileDevice != NIL_RTFILE
379 && pThis->pfnDoLock)
380 {
381 int rc = pThis->pfnDoLock(pThis, false);
382 if (RT_SUCCESS(rc))
383 pThis->fLocked = false;
384 }
385
386 if (pThis->Os.pbDoubleBuffer)
387 {
388 RTMemFree(pThis->Os.pbDoubleBuffer);
389 pThis->Os.pbDoubleBuffer = NULL;
390 }
391
392 if (pThis->Os.hFileDevice != NIL_RTFILE)
393 {
394 int rc = RTFileClose(pThis->Os.hFileDevice);
395 AssertRC(rc);
396 pThis->Os.hFileDevice = NIL_RTFILE;
397 }
398}
399
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