Skip to content

Commit 9494c40

Browse files
committed
Enable VirtIO block to access hostOS /dev/ block devices
The user may not always have a disk image but might have a /dev/x block device, such as a USB drive that they want to share with the guest OS. So, allowing this type of virtio-blk source is intuitive. To support this, ioctl is used to retrieve the actual size of the /dev/x block device. This implementation supports both Apple and Linux platforms. On Apple platforms, mmap() on block devices appears to be unsupported with various flag combinations. To address this, a fallback mechanism is added and used when mmap() fails, using malloc() along with pread(), pwrite() and fsync() on the block device fd to emulate the behavior of mmap(). Additionally, the initial fallback was incomplete, as it only allocated heap memory without loading the block device's content into memory. This commit resolves the issue by properly reading the device contents into the allocated buffer. Since there may be asynchronous exits, a new rv_fsync_device() function is introduced to ensure the block device is properly synchronized during such exits. To fully support this fallback, disk_fd and disk_size are now stored in the vblk state during its initialization. Close #544
1 parent c9e2b5f commit 9494c40

File tree

4 files changed

+143
-18
lines changed

4 files changed

+143
-18
lines changed

src/devices/virtio-blk.c

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,30 @@
44
*/
55

66
#include <assert.h>
7+
#include <errno.h>
78
#include <fcntl.h>
9+
#include <libgen.h>
810
#include <stdbool.h>
911
#include <stdio.h>
1012
#include <stdlib.h>
1113
#include <string.h>
14+
#include <sys/ioctl.h>
1215
#include <sys/mman.h>
1316
#include <sys/stat.h>
1417
#include <unistd.h>
1518

19+
/*
20+
* The /dev/ block devices cannot be embedded to the part of the wasm.
21+
* Thus, no support access /dev/ block devices for wasm.
22+
*/
23+
#if !defined(__EMSCRIPTEN__)
24+
#if defined(__APPLE__)
25+
#include <sys/disk.h> /* DKIOCGETBLOCKCOUNT and DKIOCGETBLOCKSIZE */
26+
#else
27+
#include <linux/fs.h> /* BLKGETSIZE64 */
28+
#endif
29+
#endif
30+
1631
#include "virtio.h"
1732

1833
#define DISK_BLK_SIZE 512
@@ -97,12 +112,16 @@ static void virtio_blk_update_status(virtio_blk_state_t *vblk, uint32_t status)
97112
uint32_t device_features = vblk->device_features;
98113
uint32_t *ram = vblk->ram;
99114
uint32_t *disk = vblk->disk;
115+
uint64_t disk_size = vblk->disk_size;
116+
int disk_fd = vblk->disk_fd;
100117
void *priv = vblk->priv;
101118
uint32_t capacity = VBLK_PRIV(vblk)->capacity;
102119
memset(vblk, 0, sizeof(*vblk));
103120
vblk->device_features = device_features;
104121
vblk->ram = ram;
105122
vblk->disk = disk;
123+
vblk->disk_size = disk_size;
124+
vblk->disk_fd = disk_fd;
106125
vblk->priv = priv;
107126
VBLK_PRIV(vblk)->capacity = capacity;
108127
}
@@ -388,6 +407,9 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
388407
exit(EXIT_FAILURE);
389408
}
390409

410+
/* For mmap_fallback */
411+
vblk->disk_fd = -1;
412+
391413
/* Allocate memory for the private member */
392414
vblk->priv = &vblk_configs[vblk_dev_cnt++];
393415

@@ -403,29 +425,85 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
403425
int disk_fd = open(disk_file, readonly ? O_RDONLY : O_RDWR);
404426
if (disk_fd < 0) {
405427
rv_log_error("Could not open %s", disk_file);
406-
exit(EXIT_FAILURE);
428+
goto fail;
407429
}
408430

409-
/* Get the disk image size */
410-
struct stat st;
411-
fstat(disk_fd, &st);
412-
VBLK_PRIV(vblk)->disk_size = st.st_size;
431+
const char *disk_file_dirname = dirname(disk_file);
432+
if (!disk_file_dirname) {
433+
rv_log_error("Fail dirname disk_file: %s", disk_file);
434+
goto disk_size_fail;
435+
}
436+
/* Get the disk size */
437+
uint64_t disk_size;
438+
if (!strcmp(disk_file_dirname, "/dev")) { /* from /dev/, leverage ioctl */
439+
#if !defined(__EMSCRIPTEN__)
440+
#if defined(__APPLE__)
441+
uint32_t block_size;
442+
uint64_t block_count;
443+
if (ioctl(disk_fd, DKIOCGETBLOCKCOUNT, &block_count) == -1) {
444+
rv_log_error("DKIOCGETBLOCKCOUNT failed");
445+
goto disk_size_fail;
446+
}
447+
if (ioctl(disk_fd, DKIOCGETBLOCKSIZE, &block_size) == -1) {
448+
rv_log_error("DKIOCGETBLOCKSIZE failed");
449+
goto disk_size_fail;
450+
}
451+
disk_size = block_count * block_size;
452+
#else /* Linux */
453+
if (ioctl(disk_fd, BLKGETSIZE64, &disk_size) == -1) {
454+
rv_log_error("BLKGETSIZE64 failed");
455+
goto disk_size_fail;
456+
}
457+
#endif
458+
#endif /* !defined(__EMSCRIPTEN__) */
459+
} else { /* other path, stat it as normal file */
460+
struct stat st;
461+
if (fstat(disk_fd, &st) == -1) {
462+
rv_log_error("fstat failed");
463+
goto disk_size_fail;
464+
}
465+
disk_size = st.st_size;
466+
}
467+
VBLK_PRIV(vblk)->disk_size = disk_size;
413468

414469
/* Set up the disk memory */
415470
uint32_t *disk_mem;
416471
#if HAVE_MMAP
417472
disk_mem = mmap(NULL, VBLK_PRIV(vblk)->disk_size,
418473
readonly ? PROT_READ : (PROT_READ | PROT_WRITE), MAP_SHARED,
419474
disk_fd, 0);
420-
if (disk_mem == MAP_FAILED)
421-
goto err;
422-
#else
475+
if (disk_mem == MAP_FAILED) {
476+
if (errno != EINVAL)
477+
goto disk_mem_err;
478+
/*
479+
* On Apple platforms, mmap() on block devices appears to be unsupported
480+
* and EINVAL is set to errno.
481+
*/
482+
rv_log_trace(
483+
"Fallback to malloc-based block device due to mmap() failure");
484+
goto mmap_fallback;
485+
}
486+
/*
487+
* disk_fd should be closed on exit after flushing heap data back to the
488+
* device when using mmap_fallback.
489+
*/
490+
close(disk_fd);
491+
goto disk_mem_ok;
492+
#endif
493+
494+
mmap_fallback:
423495
disk_mem = malloc(VBLK_PRIV(vblk)->disk_size);
424496
if (!disk_mem)
425-
goto err;
426-
#endif
497+
goto disk_mem_err;
498+
vblk->disk_fd = disk_fd;
499+
vblk->disk_size = disk_size;
500+
if (pread(disk_fd, disk_mem, disk_size, 0) == -1) {
501+
rv_log_error("pread block device failed");
502+
goto disk_mem_err;
503+
}
504+
505+
disk_mem_ok:
427506
assert(!(((uintptr_t) disk_mem) & 0b11));
428-
close(disk_fd);
429507

430508
vblk->disk = disk_mem;
431509
VBLK_PRIV(vblk)->capacity =
@@ -436,9 +514,15 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
436514

437515
return disk_mem;
438516

439-
err:
440-
rv_log_error("Could not map disk %s", disk_file);
441-
return NULL;
517+
disk_mem_err:
518+
rv_log_error("Could not map disk %s(reason: %s)", disk_file,
519+
strerror(errno));
520+
521+
disk_size_fail:
522+
close(disk_fd);
523+
524+
fail:
525+
exit(EXIT_FAILURE);
442526
}
443527

444528
virtio_blk_state_t *vblk_new()
@@ -450,10 +534,11 @@ virtio_blk_state_t *vblk_new()
450534

451535
void vblk_delete(virtio_blk_state_t *vblk)
452536
{
537+
if (vblk->disk_fd != -1)
538+
free(vblk->disk);
453539
#if HAVE_MMAP
454-
munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size);
455-
#else
456-
free(vblk->disk);
540+
else
541+
munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size);
457542
#endif
458543
free(vblk);
459544
}

src/devices/virtio.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ typedef struct {
103103
/* supplied by environment */
104104
uint32_t *ram;
105105
uint32_t *disk;
106+
uint64_t disk_size;
107+
int disk_fd;
106108
/* implementation-specific */
107109
void *priv;
108110
} virtio_blk_state_t;

src/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ int main(int argc, char **args)
298298

299299
/* finalize the RISC-V runtime */
300300
rv_delete(rv);
301+
/*
302+
* Other translation units cannot update the pointer, update it here
303+
* to prevent multiple atexit()'s callback be called.
304+
*/
305+
rv = NULL;
301306
rv_log_info("RISC-V emulator is destroyed");
302307

303308
end:

src/riscv.c

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,36 @@ static void rv_async_block_clear()
376376
return;
377377
#endif /* !RV32_HAS(JIT) */
378378
}
379-
#endif
379+
380+
static void rv_fsync_device()
381+
{
382+
if (!rv)
383+
return;
384+
385+
/* mmap_fallback, need to write and sync the device */
386+
vm_attr_t *attr = PRIV(rv);
387+
if (attr->vblk && attr->vblk->disk_fd >= 3) {
388+
if (attr->vblk->device_features & VIRTIO_BLK_F_RO) /* readonly */
389+
goto end;
390+
391+
if (pwrite(attr->vblk->disk_fd, attr->vblk->disk, attr->vblk->disk_size,
392+
0) == -1) {
393+
rv_log_error("pwrite block device failed");
394+
return;
395+
}
396+
397+
if (fsync(attr->vblk->disk_fd) == -1) {
398+
rv_log_error("fsync block device failed");
399+
return;
400+
}
401+
}
402+
403+
end:
404+
close(attr->vblk->disk_fd);
405+
vblk_delete(attr->vblk);
406+
rv_log_info("Sync block devices OK");
407+
}
408+
#endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */
380409

381410
riscv_t *rv_create(riscv_user_t rv_attr)
382411
{
@@ -388,6 +417,8 @@ riscv_t *rv_create(riscv_user_t rv_attr)
388417
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
389418
/* register cleaning callback for CTRL+a+x exit */
390419
atexit(rv_async_block_clear);
420+
/* register device sync callback for CTRL+a+x exit */
421+
atexit(rv_fsync_device);
391422
#endif
392423

393424
/* copy over the attr */
@@ -705,6 +736,8 @@ void rv_delete(riscv_t *rv)
705736
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
706737
u8250_delete(attr->uart);
707738
plic_delete(attr->plic);
739+
/* sync device before cleaning up */
740+
rv_fsync_device();
708741
vblk_delete(attr->vblk);
709742
#endif
710743
free(rv);

0 commit comments

Comments
 (0)