Skip to content

Commit 4a1976d

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 ce0fe74 commit 4a1976d

File tree

4 files changed

+161
-19
lines changed

4 files changed

+161
-19
lines changed

src/devices/virtio-blk.c

+112-17
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, accessing /dev/ block devices is not supported 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 /* !defined(__EMSCRIPTEN__) */
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,12 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
388407
exit(EXIT_FAILURE);
389408
}
390409

410+
/*
411+
* For mmap_fallback, if vblk is not specified, disk_fd should remain -1 and
412+
* no fsync should be performed on exit.
413+
*/
414+
vblk->disk_fd = -1;
415+
391416
/* Allocate memory for the private member */
392417
vblk->priv = &vblk_configs[vblk_dev_cnt++];
393418

@@ -402,30 +427,93 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
402427
/* Open disk file */
403428
int disk_fd = open(disk_file, readonly ? O_RDONLY : O_RDWR);
404429
if (disk_fd < 0) {
405-
rv_log_error("Could not open %s", disk_file);
406-
exit(EXIT_FAILURE);
430+
rv_log_error("Could not open %s: %s", disk_file, strerror(errno));
431+
goto fail;
407432
}
408433

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

414479
/* Set up the disk memory */
415480
uint32_t *disk_mem;
416481
#if HAVE_MMAP
417482
disk_mem = mmap(NULL, VBLK_PRIV(vblk)->disk_size,
418483
readonly ? PROT_READ : (PROT_READ | PROT_WRITE), MAP_SHARED,
419484
disk_fd, 0);
420-
if (disk_mem == MAP_FAILED)
421-
goto err;
422-
#else
485+
if (disk_mem == MAP_FAILED) {
486+
if (errno != EINVAL)
487+
goto disk_mem_err;
488+
/*
489+
* On Apple platforms, mmap() on block devices appears to be unsupported
490+
* and EINVAL is set to errno.
491+
*/
492+
rv_log_trace(
493+
"Fallback to malloc-based block device due to mmap() failure");
494+
goto mmap_fallback;
495+
}
496+
/*
497+
* disk_fd should be closed on exit after flushing heap data back to the
498+
* device when using mmap_fallback.
499+
*/
500+
close(disk_fd);
501+
goto disk_mem_ok;
502+
#endif
503+
504+
mmap_fallback:
423505
disk_mem = malloc(VBLK_PRIV(vblk)->disk_size);
424506
if (!disk_mem)
425-
goto err;
426-
#endif
507+
goto disk_mem_err;
508+
vblk->disk_fd = disk_fd;
509+
vblk->disk_size = disk_size;
510+
if (pread(disk_fd, disk_mem, disk_size, 0) == -1) {
511+
rv_log_error("pread block device failed: %s", strerror(errno));
512+
goto disk_mem_err;
513+
}
514+
515+
disk_mem_ok:
427516
assert(!(((uintptr_t) disk_mem) & 0b11));
428-
close(disk_fd);
429517

430518
vblk->disk = disk_mem;
431519
VBLK_PRIV(vblk)->capacity =
@@ -436,9 +524,14 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
436524

437525
return disk_mem;
438526

439-
err:
440-
rv_log_error("Could not map disk %s", disk_file);
441-
return NULL;
527+
disk_mem_err:
528+
rv_log_error("Could not map disk %s: %s", disk_file, strerror(errno));
529+
530+
disk_size_fail:
531+
close(disk_fd);
532+
533+
fail:
534+
exit(EXIT_FAILURE);
442535
}
443536

444537
virtio_blk_state_t *vblk_new()
@@ -450,10 +543,12 @@ virtio_blk_state_t *vblk_new()
450543

451544
void vblk_delete(virtio_blk_state_t *vblk)
452545
{
546+
/* mmap_fallback is used */
547+
if (vblk->disk_fd != -1)
548+
free(vblk->disk);
453549
#if HAVE_MMAP
454-
munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size);
455-
#else
456-
free(vblk->disk);
550+
else
551+
munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size);
457552
#endif
458553
free(vblk);
459554
}

src/devices/virtio.h

+2
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

+5
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

+42-2
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,43 @@ static void rv_async_block_clear()
390390
return;
391391
#endif /* !RV32_HAS(JIT) */
392392
}
393-
#endif
393+
394+
static void rv_fsync_device()
395+
{
396+
if (!rv)
397+
return;
398+
399+
vm_attr_t *attr = PRIV(rv);
400+
/*
401+
* mmap_fallback, may need to write and sync the device
402+
*
403+
* vblk is optional, so it could be NULL
404+
*/
405+
if (attr->vblk) {
406+
if (attr->vblk->disk_fd >= 3) {
407+
if (attr->vblk->device_features & VIRTIO_BLK_F_RO) /* readonly */
408+
goto end;
409+
410+
if (pwrite(attr->vblk->disk_fd, attr->vblk->disk,
411+
attr->vblk->disk_size, 0) == -1) {
412+
rv_log_error("pwrite block device failed: %s", strerror(errno));
413+
return;
414+
}
415+
416+
if (fsync(attr->vblk->disk_fd) == -1) {
417+
rv_log_error("fsync block device failed: %s", strerror(errno));
418+
return;
419+
}
420+
rv_log_info("Sync block device OK");
421+
422+
end:
423+
close(attr->vblk->disk_fd);
424+
}
425+
426+
vblk_delete(attr->vblk);
427+
}
428+
}
429+
#endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */
394430

395431
riscv_t *rv_create(riscv_user_t rv_attr)
396432
{
@@ -402,6 +438,8 @@ riscv_t *rv_create(riscv_user_t rv_attr)
402438
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
403439
/* register cleaning callback for CTRL+a+x exit */
404440
atexit(rv_async_block_clear);
441+
/* register device sync callback for CTRL+a+x exit */
442+
atexit(rv_fsync_device);
405443
#endif
406444

407445
/* copy over the attr */
@@ -549,6 +587,7 @@ riscv_t *rv_create(riscv_user_t rv_attr)
549587
attr->uart->out_fd = attr->fd_stdout;
550588

551589
/* setup virtio-blk */
590+
attr->vblk = NULL;
552591
if (attr->data.system.vblk_device) {
553592
/* Currently, only used for block image path and permission */
554593
#define MAX_OPTS 2
@@ -719,7 +758,8 @@ void rv_delete(riscv_t *rv)
719758
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
720759
u8250_delete(attr->uart);
721760
plic_delete(attr->plic);
722-
vblk_delete(attr->vblk);
761+
/* sync device, cleanup inside the callee */
762+
rv_fsync_device();
723763
#endif
724764
free(rv);
725765
}

0 commit comments

Comments
 (0)