Commit a99e69adc7 for qemu.org
commit a99e69adc75477caeebb3af14eed1cd21fd0c4fe
Author: Christian Schoenebeck <qemu_oss@crudebyte.com>
Date: Wed Oct 7 12:27:38 2026 +0200
tests/9p-client: add Tstat / Rstat test client functions
Add v9fs_tstat() and v9fs_rstat() functions to the 9p test client.
Since this is a request type that exists both in legacy 9p2000.u
and modern 9p2000.L protocol version, error responses types might
differ (Rerror vs. Rlerror). Therefore v9fs_tstat() is using an
error type tolerant helper function v9fs_req_check_err() for
processing expected errors with this request type.
Link: https://lore.kernel.org/qemu-devel/46192047d8b38e4d37cef7115de6cad11ea8f362.1791368962.git.qemu_oss@crudebyte.com
Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
diff --git a/tests/qtest/libqos/virtio-9p-client.c b/tests/qtest/libqos/virtio-9p-client.c
index 005ae53a3f..4a3fc1511b 100644
--- a/tests/qtest/libqos/virtio-9p-client.c
+++ b/tests/qtest/libqos/virtio-9p-client.c
@@ -306,6 +306,33 @@ void v9fs_rlerror(P9Req *req, uint32_t *err)
v9fs_req_free(req);
}
+/*
+ * Asserts for caller that the request's response returned with the passed,
+ * expected error code (in a protocol version agnostic manner).
+ */
+static void v9fs_req_check_err(P9Req *req, uint32_t expect_err)
+{
+ P9Hdr hdr;
+ uint32_t err;
+
+ /* peek the response header */
+ v9fs_memread(req, &hdr, sizeof(hdr));
+ /* reset offset after peek, so v9fs_rlerror() etc. read correctly */
+ req->r_off = 0;
+
+ if (hdr.id == P9_RLERROR) {
+ v9fs_rlerror(req, &err);
+ } else if (hdr.id == P9_RERROR) {
+ v9fs_rerror(req, &err);
+ } else {
+ g_printerr("Received response %d (%s) instead of an error response\n",
+ hdr.id, rmessage_name(hdr.id));
+ v9fs_req_free(req);
+ g_assert_not_reached();
+ }
+ g_assert_cmpint(err, ==, expect_err);
+}
+
/* size[4] Tversion tag[2] msize[4] version[s] */
TVersionRes v9fs_tversion(TVersionOpt opt)
{
@@ -1286,3 +1313,55 @@ void v9fs_rxattrwalk(P9Req *req, uint64_t *size)
}
v9fs_req_free(req);
}
+
+/* size[4] Tstat tag[2] fid[4] */
+TStatRes v9fs_tstat(TStatOpt opt)
+{
+ P9Req *req;
+
+ g_assert(opt.client);
+ /* expecting either Rstat or an error response, but obviously not both */
+ g_assert(!opt.expectErr || !opt.rstat.stat);
+
+ req = v9fs_req_init(opt.client, 4, P9_TSTAT, opt.tag);
+ v9fs_uint32_write(req, opt.fid);
+ v9fs_req_send(req);
+
+ if (!opt.requestOnly) {
+ v9fs_req_wait_for_reply(req, NULL);
+ if (opt.expectErr) {
+ v9fs_req_check_err(req, opt.expectErr);
+ } else {
+ v9fs_rstat(req, opt.rstat.stat);
+ }
+ req = NULL; /* request was freed */
+ }
+
+ return (TStatRes) { .req = req };
+}
+
+/* size[4] Rstat tag[2] stat[n] */
+void v9fs_rstat(P9Req *req, v9fs_stat *stat)
+{
+ v9fs_req_recv(req, P9_RSTAT);
+
+ v9fs_memskip(req, 2); /* protocol quirk: size appears twice */
+ v9fs_uint16_read(req, &stat->size);
+ v9fs_uint16_read(req, &stat->type);
+ v9fs_uint32_read(req, &stat->dev);
+ v9fs_memread(req, &stat->qid, 13);
+ v9fs_uint32_read(req, &stat->mode);
+ v9fs_uint32_read(req, &stat->atime);
+ v9fs_uint32_read(req, &stat->mtime);
+ v9fs_uint64_read(req, &stat->length);
+ v9fs_string_read(req, NULL, &stat->name);
+ v9fs_string_read(req, NULL, &stat->uid);
+ v9fs_string_read(req, NULL, &stat->gid);
+ v9fs_string_read(req, NULL, &stat->muid);
+ v9fs_string_read(req, NULL, &stat->extension);
+ v9fs_uint32_read(req, &stat->n_uid);
+ v9fs_uint32_read(req, &stat->n_gid);
+ v9fs_uint32_read(req, &stat->n_muid);
+
+ v9fs_req_free(req);
+}
diff --git a/tests/qtest/libqos/virtio-9p-client.h b/tests/qtest/libqos/virtio-9p-client.h
index 5a00a0870c..f604cc0ee5 100644
--- a/tests/qtest/libqos/virtio-9p-client.h
+++ b/tests/qtest/libqos/virtio-9p-client.h
@@ -63,6 +63,25 @@ typedef struct v9fs_attr {
uint64_t data_version;
} v9fs_attr;
+typedef struct v9fs_stat {
+ uint16_t size;
+ uint16_t type;
+ uint32_t dev;
+ v9fs_qid qid;
+ uint32_t mode;
+ uint32_t atime;
+ uint32_t mtime;
+ uint64_t length;
+ char *name;
+ char *uid;
+ char *gid;
+ char *muid;
+ char *extension;
+ uint32_t n_uid;
+ uint32_t n_gid;
+ uint32_t n_muid;
+} v9fs_stat;
+
#define P9_GETATTR_BASIC 0x000007ffULL /* Mask for fields up to BLOCKS */
#define P9_GETATTR_ALL 0x00003fffULL /* Mask for ALL fields */
@@ -579,6 +598,30 @@ typedef struct TXattrWalkRes {
uint64_t size;
} TXattrWalkRes;
+/* options for 'Tstat' 9p request */
+typedef struct TStatOpt {
+ /* 9P client being used (mandatory) */
+ QVirtio9P *client;
+ /* user supplied tag number being returned with response (optional) */
+ uint16_t tag;
+ /* file ID to get the Rstat for (required) */
+ uint32_t fid;
+ /* data being received from 9p server as 'Rstat' response (optional) */
+ struct {
+ v9fs_stat *stat;
+ } rstat;
+ /* only send Tstat request but not wait for a reply? (optional) */
+ bool requestOnly;
+ /* do we expect an error response, if yes which error code? (optional) */
+ uint32_t expectErr;
+} TStatOpt;
+
+/* result of 'Tstat' 9p request */
+typedef struct TStatRes {
+ /* if requestOnly was set: request object for further processing */
+ P9Req *req;
+} TStatRes;
+
void v9fs_set_allocator(QGuestAllocator *t_alloc);
void v9fs_memwrite(P9Req *req, const void *addr, size_t len);
void v9fs_memskip(P9Req *req, size_t len);
@@ -639,5 +682,7 @@ TXattrCreateRes v9fs_txattrcreate(TXattrCreateOpt opt);
void v9fs_rxattrcreate(P9Req *req);
TXattrWalkRes v9fs_txattrwalk(TXattrWalkOpt opt);
void v9fs_rxattrwalk(P9Req *req, uint64_t *size);
+TStatRes v9fs_tstat(TStatOpt opt);
+void v9fs_rstat(P9Req *req, v9fs_stat *stat);
#endif