Construct the memory source from a file descriptor

vmie_mem could only be built from a path. Add a second input: an
already-open file descriptor, dup()'d internally (borrowed - the
caller's fd stays valid, core owns and closes its copy).

  - vmie_mem_from_fd / vmie_mem_from_fd_segs   (read-write, as the path
    constructors: PROT_RW, MAP_SHARED)
  - vmie_mem_from_ro_fd / vmie_mem_from_ro_fd_segs   (read-only: map
    PROT_READ, mark the source ro; gpa_write/gva_write return -1, every
    read path is unchanged; accepts an O_RDONLY fd)
  - vmie_win32_open_fd   (the win32 context over an fd backing file)

Factor the mmap/validate tail and the single-low segment map out of the
path constructors into shared helpers, so the path and fd inputs go
through one mmap site and one map builder each.
This commit is contained in:
2026-06-20 11:20:33 +03:00
parent f401738702
commit 5ea9a3785f
5 changed files with 227 additions and 19 deletions
+154 -18
View File
@@ -52,6 +52,9 @@ int gpa_read(vmie_mem* m, uintptr_t offs, void* buf, const size_t nmemb) {
}
int gpa_write(vmie_mem* m, uintptr_t offs, const void* src, const size_t nmemb) {
if (m->ro) {
return -1;
}
if (out_of_bounds(m, &offs, nmemb)) {
return -1;
}
@@ -88,37 +91,64 @@ static int segs_valid(const gpa_seg* segs, int nseg, uint64_t fsize) {
return 1;
}
/* Finish a vmie_mem over an ALREADY-OWNED fd (from open() or dup()): fstat it,
* validate the seg map against its real size, mmap it shared (PROT_READ when
* `ro`, else PROT_RW), and fill `m`. On ANY failure close `fd` and clean `m`
* (no partial state); on success store the fd in `m->fd`, record `ro`, and
* return 0. The fd's lifetime is now owned by `m` (closed in gpa_close). The
* single mmap/validate site shared by every constructor. */
__attribute__((cold))
int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg) {
static int gpa_map_owned_fd(vmie_mem* m, int fd, const gpa_seg* segs, int nseg, int ro) {
struct stat st;
if ((m->fd = open(path, O_RDWR)) < 0) {
goto ret_;
}
if (fstat(m->fd, &st) || !segs_valid(segs, nseg, (uint64_t)st.st_size)) {
if (fstat(fd, &st) || !segs_valid(segs, nseg, (uint64_t)st.st_size)) {
goto close_;
}
if ((m->pa = mmap(NULL, st.st_size, PROT_RW, MAP_SHARED, m->fd, 0)) == MAP_FAILED) {
const int prot = ro ? PROT_READ : PROT_RW;
if ((m->pa = mmap(NULL, st.st_size, prot, MAP_SHARED, fd, 0)) == MAP_FAILED) {
close_:
close(m->fd);
ret_:
close(fd);
clean_ctx(m);
return -1;
}
m->fd = fd;
m->fsize = st.st_size;
m->nseg = nseg;
m->ro = ro;
memcpy(m->seg, segs, (size_t)nseg * sizeof *segs);
return 0;
}
/* Convenience: the classic single-`low` QEMU map. Below the 4 GiB PCI hole the
* file maps 1:1 ([0,low)->file[0,low)); at and above 4 GiB it resumes at file
* offset low. When low >= fsize the hole is never reached, so one inert identity
* seg covering the whole image suffices. */
/* Build the classic single-`low` QEMU map into out[0..nseg) and return nseg.
* Below the 4 GiB PCI hole the file maps 1:1 ([0,low)->file[0,low)); at and
* above 4 GiB it resumes at file offset low. When low >= fsize the hole is never
* reached, so one inert identity seg covering the whole image suffices (nseg=1).
* The single low-map site shared by gpa_open (fsize from stat) and gpa_from_fd
* (fsize from fstat). */
__attribute__((cold))
static int low_segs(uint64_t fsize, uint64_t low, gpa_seg out[2]) {
if (low >= fsize) {
out[0] = (gpa_seg){ 0, fsize, 0 };
return 1;
}
out[0] = (gpa_seg){ 0, low, 0 };
out[1] = (gpa_seg){ RAM_H, fsize - low, low };
return 2;
}
__attribute__((cold))
int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg) {
const int fd = open(path, O_RDWR);
if (fd < 0) {
clean_ctx(m);
return -1;
}
return gpa_map_owned_fd(m, fd, segs, nseg, 0);
}
__attribute__((cold))
int gpa_open(vmie_mem* m, const char* path, uintptr_t low) {
struct stat st;
@@ -126,16 +156,70 @@ int gpa_open(vmie_mem* m, const char* path, uintptr_t low) {
clean_ctx(m);
return -1;
}
const uint64_t fsize = (uint64_t)st.st_size;
const gpa_seg one[1] = { { 0, fsize, 0 } };
const gpa_seg two[2] = { { 0, low, 0 }, { RAM_H, fsize > low ? fsize - low : 0, low } };
const gpa_seg* segs = low >= fsize ? one : two;
const int nseg = low >= fsize ? 1 : 2;
gpa_seg segs[2];
const int nseg = low_segs((uint64_t)st.st_size, low, segs);
return gpa_open_segs(m, path, segs, nseg);
}
/* fd-backed constructors: build a vmie_mem from an already-open file descriptor
* instead of a path. The fd is BORROWED via dup() - core owns the copy (closed
* in gpa_close); the caller's fd stays valid. The fd must reference an mmap-able
* RW object (regular file / memfd / shm); its file position is not used. */
__attribute__((cold))
int gpa_from_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg) {
const int dupfd = dup(fd);
if (dupfd < 0) {
clean_ctx(m);
return -1;
}
return gpa_map_owned_fd(m, dupfd, segs, nseg, 0);
}
__attribute__((cold))
int gpa_from_fd(vmie_mem* m, int fd, uintptr_t low) {
struct stat st;
if (fstat(fd, &st)) {
clean_ctx(m);
return -1;
}
gpa_seg segs[2];
const int nseg = low_segs((uint64_t)st.st_size, low, segs);
return gpa_from_fd_segs(m, fd, segs, nseg);
}
/* read-only twins of gpa_from_fd*: map the borrowed fd PROT_READ and mark the
* source read-only (m->ro), so gpa_write/gva_write return -1. Same dup-borrow
* semantics (core owns the copy, caller's fd stays valid). Accept O_RDONLY as
* well as O_RDWR fds - only read access is required for a PROT_READ mapping.
* Reads/gpa_ptr/scan behave exactly as on an RW source. */
__attribute__((cold))
int gpa_from_ro_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg) {
const int dupfd = dup(fd);
if (dupfd < 0) {
clean_ctx(m);
return -1;
}
return gpa_map_owned_fd(m, dupfd, segs, nseg, 1);
}
__attribute__((cold))
int gpa_from_ro_fd(vmie_mem* m, int fd, uintptr_t low) {
struct stat st;
if (fstat(fd, &st)) {
clean_ctx(m);
return -1;
}
gpa_seg segs[2];
const int nseg = low_segs((uint64_t)st.st_size, low, segs);
return gpa_from_ro_fd_segs(m, fd, segs, nseg);
}
__attribute__((cold))
void gpa_close(vmie_mem* m) {
if (m->pa) {
@@ -180,6 +264,58 @@ vmie_mem* vmie_mem_open_segs(const char* path, const gpa_seg* segs, int nseg) {
return m;
}
__attribute__((cold))
vmie_mem* vmie_mem_from_fd(int fd, uint64_t low) {
vmie_mem* m = calloc(1, sizeof *m);
if (!m) {
return NULL;
}
if (gpa_from_fd(m, fd, (uintptr_t)low)) {
free(m);
return NULL;
}
return m;
}
__attribute__((cold))
vmie_mem* vmie_mem_from_fd_segs(int fd, const gpa_seg* segs, int nseg) {
vmie_mem* m = calloc(1, sizeof *m);
if (!m) {
return NULL;
}
if (gpa_from_fd_segs(m, fd, segs, nseg)) {
free(m);
return NULL;
}
return m;
}
__attribute__((cold))
vmie_mem* vmie_mem_from_ro_fd(int fd, uint64_t low) {
vmie_mem* m = calloc(1, sizeof *m);
if (!m) {
return NULL;
}
if (gpa_from_ro_fd(m, fd, (uintptr_t)low)) {
free(m);
return NULL;
}
return m;
}
__attribute__((cold))
vmie_mem* vmie_mem_from_ro_fd_segs(int fd, const gpa_seg* segs, int nseg) {
vmie_mem* m = calloc(1, sizeof *m);
if (!m) {
return NULL;
}
if (gpa_from_ro_fd_segs(m, fd, segs, nseg)) {
free(m);
return NULL;
}
return m;
}
__attribute__((cold))
void vmie_mem_close(vmie_mem* m) {
if (!m) {
+10
View File
@@ -20,6 +20,7 @@ typedef struct vmie_mem {
int fd;
int nseg;
gpa_seg seg[VMIE_MAX_SEGS];
int ro; /* source is read-only (gpa_write/gva_write -> -1); 0 = RW */
} vmie_mem;
/* GPA <-> file-offset converters over the segment map.
@@ -40,6 +41,15 @@ static inline uintptr_t offset_gpa(const vmie_mem* m, uintptr_t off) {
/* guest-physical lifecycle + primitives (gpa.c) */
int gpa_open_segs(vmie_mem* m, const char* path, const gpa_seg* segs, int nseg);
int gpa_open (vmie_mem* m, const char* path, uintptr_t low);
/* fd-backed twins of gpa_open*: build from a borrowed fd (dup'd internally,
* caller's fd stays valid) instead of a path. fd must be an mmap-able RW object. */
int gpa_from_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg);
int gpa_from_fd (vmie_mem* m, int fd, uintptr_t low);
/* read-only twins of gpa_from_fd*: map the borrowed fd PROT_READ and mark the
* source read-only (gpa_write/gva_write -> -1). Accept O_RDONLY as well as
* O_RDWR fds (only read access is required). Reads/gpa_ptr/scan behave as RW. */
int gpa_from_ro_fd_segs(vmie_mem* m, int fd, const gpa_seg* segs, int nseg);
int gpa_from_ro_fd (vmie_mem* m, int fd, uintptr_t low);
void gpa_close(vmie_mem* m);
int gpa_read (vmie_mem* m, uintptr_t offs, void* dst, size_t nmemb);
int gpa_write(vmie_mem* m, uintptr_t offs, const void* src, size_t nmemb);
+13
View File
@@ -21,6 +21,19 @@ vmie_win32* vmie_win32_open(const char* ram_path, uint64_t low) {
return v;
}
__attribute__((cold))
vmie_win32* vmie_win32_open_fd(int fd, uint64_t low) {
vmie_win32* v = calloc(1, sizeof *v);
if (!v) {
return NULL;
}
if (gpa_from_fd(&v->mem, fd, low)) {
free(v);
return NULL;
}
return v;
}
__attribute__((cold))
void vmie_win32_close(vmie_win32* v) {
if (!v) {