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);
--

Reply via email to