On 30/01/2026 19:30, Christian Schoenebeck wrote:
> On Wednesday, 28 January 2026 20:13:45 CET Andrey Erokhin wrote:
>>> I was trying to boot from a directory tree owned by an ordinary user,
>>> and some daemons weren't happy about non-root ownership of some files
>>>
>>> Example use:
>>> -virtfs local,path=rootfs,mount_tag=root,security_model=mapped,uid=0,gid=0
>>
>> I personally switched from fuse-overlayfs to user namespaces+kernel overlay
>> fs (for writeable overlay for rootfs) long time ago, so I do not need
>> uid=0,gid=0, I'm being mapped to 0:0 in the user namespace. I wanted to
>> publish this change to support users which can't use user namespaces, but
>> yesterday I realized I could just run QEMU (with fuse-overlayfs) under
>> fakeroot 🤦♂️
>
> Nevertheless you already came more than half way to finish this. All it would
> take is adding some lines to the command line docs.
Do you mean smth. like this?
(BTW, is it OK that there is no fmode/dmode processing in system/vl.c?)
---
fsdev/file-op-9p.h | 4 ++++
fsdev/qemu-fsdev-opts.c | 12 ++++++++++++
fsdev/qemu-fsdev.c | 2 ++
hw/9pfs/9p-local.c | 25 +++++++++++++++++++++++++
hw/9pfs/9p.c | 2 ++
qemu-options.hx | 24 ++++++++++++++++++++----
system/vl.c | 9 +++++++++
7 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/fsdev/file-op-9p.h b/fsdev/file-op-9p.h
index b9dae8c84c..10f3a7270c 100644
--- a/fsdev/file-op-9p.h
+++ b/fsdev/file-op-9p.h
@@ -94,6 +94,8 @@ typedef struct FsDriverEntry {
FsThrottle fst;
mode_t fmode;
mode_t dmode;
+ uid_t dflt_uid;
+ gid_t dflt_gid;
} FsDriverEntry;
struct FsContext {
@@ -107,6 +109,8 @@ struct FsContext {
void *private;
mode_t fmode;
mode_t dmode;
+ uid_t dflt_uid;
+ gid_t dflt_gid;
};
struct V9fsPath {
diff --git a/fsdev/qemu-fsdev-opts.c b/fsdev/qemu-fsdev-opts.c
index 07a18c6e48..c99abb3de6 100644
--- a/fsdev/qemu-fsdev-opts.c
+++ b/fsdev/qemu-fsdev-opts.c
@@ -46,6 +46,12 @@ static QemuOptsList qemu_fsdev_opts = {
}, {
.name = "dmode",
.type = QEMU_OPT_NUMBER,
+ }, {
+ .name = "uid",
+ .type = QEMU_OPT_NUMBER,
+ }, {
+ .name = "gid",
+ .type = QEMU_OPT_NUMBER,
},
THROTTLE_OPTS,
@@ -92,6 +98,12 @@ static QemuOptsList qemu_virtfs_opts = {
}, {
.name = "dmode",
.type = QEMU_OPT_NUMBER,
+ }, {
+ .name = "uid",
+ .type = QEMU_OPT_NUMBER,
+ }, {
+ .name = "gid",
+ .type = QEMU_OPT_NUMBER,
},
{ /*End of list */ }
diff --git a/fsdev/qemu-fsdev.c b/fsdev/qemu-fsdev.c
index 57877dad0a..faa84dc033 100644
--- a/fsdev/qemu-fsdev.c
+++ b/fsdev/qemu-fsdev.c
@@ -58,6 +58,8 @@ static FsDriverTable FsDrivers[] = {
"writeout",
"fmode",
"dmode",
+ "uid",
+ "gid",
"multidevs",
"throttling.bps-total",
"throttling.bps-read",
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 5ce97b76a6..f20b1c5d1a 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -198,6 +198,12 @@ static int local_lstat(FsContext *fs_ctx, V9fsPath
*fs_path, struct stat *stbuf)
if (err) {
goto err_out;
}
+ if (fs_ctx->dflt_uid != -1) {
+ stbuf->st_uid = fs_ctx->dflt_uid;
+ }
+ if (fs_ctx->dflt_gid != -1) {
+ stbuf->st_gid = fs_ctx->dflt_gid;
+ }
if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
/* Actual credentials are part of extended attrs */
uid_t tmp_uid;
@@ -788,6 +794,12 @@ static int local_fstat(FsContext *fs_ctx, int fid_type,
if (err) {
return err;
}
+ if (fs_ctx->dflt_uid != -1) {
+ stbuf->st_uid = fs_ctx->dflt_uid;
+ }
+ if (fs_ctx->dflt_gid != -1) {
+ stbuf->st_gid = fs_ctx->dflt_gid;
+ }
if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
/* Actual credentials are part of extended attrs */
uid_t tmp_uid;
@@ -1587,6 +1599,19 @@ static int local_parse_opts(QemuOpts *opts,
FsDriverEntry *fse, Error **errp)
}
}
+ if (fse->export_flags & V9FS_SM_PASSTHROUGH) {
+ if (qemu_opt_find(opts, "uid")) {
+ error_setg(errp, "uid is invalid in the passthrough security
mode");
+ return -1;
+ }
+ if (qemu_opt_find(opts, "gid")) {
+ error_setg(errp, "gid is invalid in the passthrough security
mode");
+ return -1;
+ }
+ }
+ fse->dflt_uid = qemu_opt_get_number(opts, "uid", -1);
+ fse->dflt_gid = qemu_opt_get_number(opts, "gid", -1);
+
fse->path = g_strdup(path);
return 0;
diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index acfa7db4e1..492379d361 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -4317,6 +4317,8 @@ int v9fs_device_realize_common(V9fsState *s, const
V9fsTransport *t,
s->ctx.fmode = fse->fmode;
s->ctx.dmode = fse->dmode;
+ s->ctx.dflt_uid = fse->dflt_uid;
+ s->ctx.dflt_gid = fse->dflt_gid;
s->fids = g_hash_table_new(NULL, NULL);
qemu_co_rwlock_init(&s->rename_lock);
diff --git a/qemu-options.hx b/qemu-options.hx
index ab23f14d21..84f108d9ad 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1806,7 +1806,7 @@ ERST
DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev,
"-fsdev
local,id=id,path=path,security_model=mapped-xattr|mapped-file|passthrough|none\n"
- " [,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode]\n"
+ "
[,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,uid=uid][,gid=gid]\n"
"
[[,throttling.bps-total=b]|[[,throttling.bps-read=r][,throttling.bps-write=w]]]\n"
"
[[,throttling.iops-total=i]|[[,throttling.iops-read=r][,throttling.iops-write=w]]]\n"
"
[[,throttling.bps-total-max=bm]|[[,throttling.bps-read-max=rm][,throttling.bps-write-max=wm]]]\n"
@@ -1816,7 +1816,7 @@ DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev,
QEMU_ARCH_ALL)
SRST
-``-fsdev local,id=id,path=path,security_model=security_model
[,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode]
[,throttling.option=value[,throttling.option=value[,...]]]``
+``-fsdev local,id=id,path=path,security_model=security_model
[,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode][,uid=uid][,gid=gid]
[,throttling.option=value[,throttling.option=value[,...]]]``
\
``-fsdev synth,id=id[,readonly=on]``
Define a new file system device. Valid options are:
@@ -1870,6 +1870,14 @@ SRST
host. Works only with security models "mapped-xattr" and
"mapped-file".
+ ``uid=uid``
+ Specifies the default uid for files and directories. Works with
+ security models "mapped-xattr", "mapped-file" and "none".
+
+ ``gid=gid``
+ Specifies the default gid for files and directories. Works with
+ security models "mapped-xattr", "mapped-file" and "none".
+
``throttling.bps-total=b,throttling.bps-read=r,throttling.bps-write=w``
Specify bandwidth throttling limits in bytes per second, either
for all request types or for reads or writes only.
@@ -1911,12 +1919,12 @@ ERST
DEF("virtfs", HAS_ARG, QEMU_OPTION_virtfs,
"-virtfs
local,path=path,mount_tag=tag,security_model=mapped-xattr|mapped-file|passthrough|none\n"
- "
[,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,multidevs=remap|forbid|warn]\n"
+ "
[,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,uid=uid][,gid=gid][,multidevs=remap|forbid|warn]\n"
"-virtfs synth,mount_tag=tag[,id=id][,readonly=on]\n",
QEMU_ARCH_ALL)
SRST
-``-virtfs local,path=path,mount_tag=mount_tag
,security_model=security_model[,writeout=writeout][,readonly=on]
[,fmode=fmode][,dmode=dmode][,multidevs=multidevs]``
+``-virtfs local,path=path,mount_tag=mount_tag
,security_model=security_model[,writeout=writeout][,readonly=on]
[,fmode=fmode][,dmode=dmode][,uid=uid][,gid=gid][,multidevs=multidevs]``
\
``-virtfs synth,mount_tag=mount_tag``
Define a new virtual filesystem device and expose it to the guest using
@@ -1980,6 +1988,14 @@ SRST
host. Works only with security models "mapped-xattr" and
"mapped-file".
+ ``uid=uid``
+ Specifies the default uid for files and directories. Works with
+ security models "mapped-xattr", "mapped-file" and "none".
+
+ ``gid=gid``
+ Specifies the default gid for files and directories. Works with
+ security models "mapped-xattr", "mapped-file" and "none".
+
``mount_tag=mount_tag``
Specifies the tag name to be used by the guest to mount this
export point.
diff --git a/system/vl.c b/system/vl.c
index 3b7057e6c6..d363b046a6 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -3253,6 +3253,7 @@ void qemu_init(int argc, char **argv)
QemuOpts *fsdev;
QemuOpts *device;
const char *writeout, *sock_fd, *socket, *path,
*security_model,
+ *uid, *gid,
*multidevs;
olist = qemu_find_opts("virtfs");
@@ -3301,6 +3302,14 @@ void qemu_init(int argc, char **argv)
qemu_opt_set(fsdev, "security_model", security_model,
&error_abort);
}
+ uid = qemu_opt_get(opts, "uid");
+ if (uid) {
+ qemu_opt_set(fsdev, "uid", uid, &error_abort);
+ }
+ gid = qemu_opt_get(opts, "gid");
+ if (gid) {
+ qemu_opt_set(fsdev, "gid", gid, &error_abort);
+ }
socket = qemu_opt_get(opts, "socket");
if (socket) {
qemu_opt_set(fsdev, "socket", socket, &error_abort);
--