Skip to content

Commit 5163318

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, and it can also be invoked by other asynchronous exit paths. 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 5163318

File tree

4 files changed

+135
-17
lines changed

4 files changed

+135
-17
lines changed

src/devices/virtio-blk.c

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,28 @@
55

66
#include <assert.h>
77
#include <fcntl.h>
8+
#include <libgen.h>
89
#include <stdbool.h>
910
#include <stdio.h>
1011
#include <stdlib.h>
1112
#include <string.h>
13+
#include <sys/ioctl.h>
1214
#include <sys/mman.h>
1315
#include <sys/stat.h>
1416
#include <unistd.h>
1517

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

1832
#define DISK_BLK_SIZE 512
@@ -97,12 +111,16 @@ static void virtio_blk_update_status(virtio_blk_state_t *vblk, uint32_t status)
97111
uint32_t device_features = vblk->device_features;
98112
uint32_t *ram = vblk->ram;
99113
uint32_t *disk = vblk->disk;
114+
uint64_t disk_size = vblk->disk_size;
115+
int disk_fd = vblk->disk_fd;
100116
void *priv = vblk->priv;
101117
uint32_t capacity = VBLK_PRIV(vblk)->capacity;
102118
memset(vblk, 0, sizeof(*vblk));
103119
vblk->device_features = device_features;
104120
vblk->ram = ram;
105121
vblk->disk = disk;
122+
vblk->disk_size = disk_size;
123+
vblk->disk_fd = disk_fd;
106124
vblk->priv = priv;
107125
VBLK_PRIV(vblk)->capacity = capacity;
108126
}
@@ -388,6 +406,9 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
388406
exit(EXIT_FAILURE);
389407
}
390408

409+
/* For mmap_fallback */
410+
vblk->disk_fd = -1;
411+
391412
/* Allocate memory for the private member */
392413
vblk->priv = &vblk_configs[vblk_dev_cnt++];
393414

@@ -403,29 +424,81 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
403424
int disk_fd = open(disk_file, readonly ? O_RDONLY : O_RDWR);
404425
if (disk_fd < 0) {
405426
rv_log_error("Could not open %s", disk_file);
406-
exit(EXIT_FAILURE);
427+
goto fail;
407428
}
408429

409-
/* Get the disk image size */
410-
struct stat st;
411-
fstat(disk_fd, &st);
412-
VBLK_PRIV(vblk)->disk_size = st.st_size;
430+
const char *disk_file_dirname = dirname(disk_file);
431+
if (!disk_file_dirname) {
432+
rv_log_error("Fail dirname disk_file: %s", disk_file);
433+
goto disk_size_fail;
434+
}
435+
/* Get the disk size */
436+
uint64_t disk_size;
437+
if (strcmp(disk_file_dirname, "/dev") ==
438+
0) { /* 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+
rv_log_trace(
477+
"Fallback to malloc block device due to mmap failed or the "
478+
"operation is unsupported");
479+
goto mmap_fallback;
480+
}
481+
/*
482+
* disk_fd should be closed on exit after flushing heap data back to the
483+
* device when using mmap_fallback.
484+
*/
485+
close(disk_fd);
486+
goto disk_mem_ok;
487+
#endif
488+
489+
mmap_fallback:
423490
disk_mem = malloc(VBLK_PRIV(vblk)->disk_size);
424491
if (!disk_mem)
425-
goto err;
426-
#endif
492+
goto disk_mem_err;
493+
vblk->disk_fd = disk_fd;
494+
vblk->disk_size = disk_size;
495+
if (pread(disk_fd, disk_mem, disk_size, 0) == -1) {
496+
rv_log_error("pread block device failed");
497+
goto disk_mem_err;
498+
}
499+
500+
disk_mem_ok:
427501
assert(!(((uintptr_t) disk_mem) & 0b11));
428-
close(disk_fd);
429502

430503
vblk->disk = disk_mem;
431504
VBLK_PRIV(vblk)->capacity =
@@ -436,9 +509,14 @@ uint32_t *virtio_blk_init(virtio_blk_state_t *vblk,
436509

437510
return disk_mem;
438511

439-
err:
512+
disk_mem_err:
440513
rv_log_error("Could not map disk %s", disk_file);
441-
return NULL;
514+
515+
disk_size_fail:
516+
close(disk_fd);
517+
518+
fail:
519+
exit(EXIT_FAILURE);
442520
}
443521

444522
virtio_blk_state_t *vblk_new()
@@ -450,10 +528,11 @@ virtio_blk_state_t *vblk_new()
450528

451529
void vblk_delete(virtio_blk_state_t *vblk)
452530
{
531+
if (vblk->disk_fd != -1)
532+
free(vblk->disk);
453533
#if HAVE_MMAP
454-
munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size);
455-
#else
456-
free(vblk->disk);
534+
else
535+
munmap(vblk->disk, VBLK_PRIV(vblk)->disk_size);
457536
#endif
458537
free(vblk);
459538
}

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: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,35 @@ 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+
end:
403+
close(attr->vblk->disk_fd);
404+
rv_log_info("Sync block devices OK");
405+
}
406+
}
407+
#endif /* RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) */
380408

381409
riscv_t *rv_create(riscv_user_t rv_attr)
382410
{
@@ -388,6 +416,8 @@ riscv_t *rv_create(riscv_user_t rv_attr)
388416
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
389417
/* register cleaning callback for CTRL+a+x exit */
390418
atexit(rv_async_block_clear);
419+
/* register device sync callback for CTRL+a+x exit */
420+
atexit(rv_fsync_device);
391421
#endif
392422

393423
/* copy over the attr */
@@ -705,6 +735,8 @@ void rv_delete(riscv_t *rv)
705735
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
706736
u8250_delete(attr->uart);
707737
plic_delete(attr->plic);
738+
/* sync device before cleaning up */
739+
rv_fsync_device();
708740
vblk_delete(attr->vblk);
709741
#endif
710742
free(rv);

0 commit comments

Comments
 (0)