All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/6] tests/9pfs: walk to non-existent dir
  2022-03-09 18:31 [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation Christian Schoenebeck
@ 2022-03-09 12:18 ` Christian Schoenebeck
  2022-03-11 11:25   ` Greg Kurz
  2022-03-09 13:24 ` [PATCH 2/6] tests/9pfs: Twalk with nwname=0 Christian Schoenebeck
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-09 12:18 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

Expect ENOENT Rlerror response when trying to walk to a
non-existent directory.

Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
---
 tests/qtest/virtio-9p-test.c | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
index 01ca076afe..22bdd74bc1 100644
--- a/tests/qtest/virtio-9p-test.c
+++ b/tests/qtest/virtio-9p-test.c
@@ -606,6 +606,25 @@ static uint32_t do_walk(QVirtio9P *v9p, const char *path)
     return fid;
 }
 
+/* utility function: walk to requested dir and expect passed error response */
+static void do_walk_expect_error(QVirtio9P *v9p, const char *path, uint32_t err)
+{
+    char **wnames;
+    P9Req *req;
+    uint32_t _err;
+    const uint32_t fid = genfid();
+
+    int nwnames = split(path, "/", &wnames);
+
+    req = v9fs_twalk(v9p, 0, fid, nwnames, wnames, 0);
+    v9fs_req_wait_for_reply(req, NULL);
+    v9fs_rlerror(req, &_err);
+
+    g_assert_cmpint(_err, ==, err);
+
+    split_free(&wnames);
+}
+
 static void fs_version(void *obj, void *data, QGuestAllocator *t_alloc)
 {
     alloc = t_alloc;
@@ -974,6 +993,15 @@ static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
     g_free(wnames[0]);
 }
 
+static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+    QVirtio9P *v9p = obj;
+    alloc = t_alloc;
+
+    do_attach(v9p);
+    do_walk_expect_error(v9p, "non-existent", ENOENT);
+}
+
 static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
 {
     QVirtio9P *v9p = obj;
@@ -1409,6 +1437,8 @@ static void register_virtio_9p_test(void)
                   &opts);
     qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
                  fs_walk_dotdot,  &opts);
+    qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
+                  &opts);
     qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen,  &opts);
     qos_add_test("synth/write/basic", "virtio-9p", fs_write,  &opts);
     qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 2/6] tests/9pfs: Twalk with nwname=0
  2022-03-09 18:31 [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation Christian Schoenebeck
  2022-03-09 12:18 ` [PATCH 1/6] tests/9pfs: walk to non-existent dir Christian Schoenebeck
@ 2022-03-09 13:24 ` Christian Schoenebeck
  2022-03-10  8:57   ` Christian Schoenebeck
  2022-03-09 14:49 ` [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test Christian Schoenebeck
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-09 13:24 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

Send Twalk request with nwname=0. In this case no QIDs should
be returned by 9p server; this is equivalent to walking to dot.

Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
---
 tests/qtest/virtio-9p-test.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
index 22bdd74bc1..6c00da03f4 100644
--- a/tests/qtest/virtio-9p-test.c
+++ b/tests/qtest/virtio-9p-test.c
@@ -1002,6 +1002,27 @@ static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
     do_walk_expect_error(v9p, "non-existent", ENOENT);
 }
 
+static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
+{
+    QVirtio9P *v9p = obj;
+    alloc = t_alloc;
+    v9fs_qid root_qid;
+    g_autofree v9fs_qid *wqid = NULL;
+    P9Req *req;
+
+    do_version(v9p);
+    req = v9fs_tattach(v9p, 0, getuid(), 0);
+    v9fs_req_wait_for_reply(req, NULL);
+    v9fs_rattach(req, &root_qid);
+
+    req = v9fs_twalk(v9p, 0, 1, 0, NULL, 0);
+    v9fs_req_wait_for_reply(req, NULL);
+    v9fs_rwalk(req, NULL, &wqid);
+
+    /* special case: no QID is returned if nwname=0 was sent */
+    g_assert(wqid == NULL);
+}
+
 static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
 {
     QVirtio9P *v9p = obj;
@@ -1435,6 +1456,7 @@ static void register_virtio_9p_test(void)
     qos_add_test("synth/walk/basic", "virtio-9p", fs_walk,  &opts);
     qos_add_test("synth/walk/no_slash", "virtio-9p", fs_walk_no_slash,
                   &opts);
+    qos_add_test("synth/walk/none", "virtio-9p", fs_walk_none, &opts);
     qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
                  fs_walk_dotdot,  &opts);
     qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test
  2022-03-09 18:31 [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation Christian Schoenebeck
  2022-03-09 12:18 ` [PATCH 1/6] tests/9pfs: walk to non-existent dir Christian Schoenebeck
  2022-03-09 13:24 ` [PATCH 2/6] tests/9pfs: Twalk with nwname=0 Christian Schoenebeck
@ 2022-03-09 14:49 ` Christian Schoenebeck
  2022-03-10  9:04   ` Christian Schoenebeck
  2022-03-09 17:12 ` [PATCH 4/6] 9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk() Christian Schoenebeck
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-09 14:49 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

Extend previously added fs_walk_none() test by comparing the QID
of the root fid with the QID of the cloned fid. They should be
equal.

Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
---
 tests/qtest/virtio-9p-test.c | 70 ++++++++++++++++++++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
index 6c00da03f4..9098e21173 100644
--- a/tests/qtest/virtio-9p-test.c
+++ b/tests/qtest/virtio-9p-test.c
@@ -146,6 +146,11 @@ static void v9fs_uint16_read(P9Req *req, uint16_t *val)
     le16_to_cpus(val);
 }
 
+static void v9fs_int16_read(P9Req *req, int16_t *val)
+{
+    v9fs_uint16_read(req, (uint16_t *)val);
+}
+
 static void v9fs_uint32_write(P9Req *req, uint32_t val)
 {
     uint32_t le_val = cpu_to_le32(val);
@@ -166,12 +171,22 @@ static void v9fs_uint32_read(P9Req *req, uint32_t *val)
     le32_to_cpus(val);
 }
 
+static void v9fs_int32_read(P9Req *req, int32_t *val)
+{
+    v9fs_uint32_read(req, (uint32_t *)val);
+}
+
 static void v9fs_uint64_read(P9Req *req, uint64_t *val)
 {
     v9fs_memread(req, val, 8);
     le64_to_cpus(val);
 }
 
+static void v9fs_int64_read(P9Req *req, int64_t *val)
+{
+    v9fs_uint64_read(req, (uint64_t *)val);
+}
+
 /* len[2] string[len] */
 static uint16_t v9fs_string_size(const char *string)
 {
@@ -425,6 +440,40 @@ static void v9fs_rwalk(P9Req *req, uint16_t *nwqid, v9fs_qid **wqid)
     v9fs_req_free(req);
 }
 
+/* size[4] Tstat tag[2] fid[4] */
+static P9Req *v9fs_tstat(QVirtio9P *v9p, uint32_t fid, uint16_t tag)
+{
+    P9Req *req;
+
+    req = v9fs_req_init(v9p, 4, P9_TSTAT, tag);
+    v9fs_uint32_write(req, fid);
+    v9fs_req_send(req);
+    return req;
+}
+
+/* size[4] Rstat tag[2] stat[n] */
+static void v9fs_rstat(P9Req *req, struct V9fsStat *st)
+{
+    v9fs_req_recv(req, P9_RSTAT);
+
+    v9fs_int16_read(req, &st->size);
+    v9fs_int16_read(req, &st->type);
+    v9fs_int32_read(req, &st->dev);
+    v9fs_uint8_read(req, &st->qid.type);
+    v9fs_uint32_read(req, &st->qid.version);
+    v9fs_uint64_read(req, &st->qid.path);
+    v9fs_int32_read(req, &st->mode);
+    v9fs_int32_read(req, &st->mtime);
+    v9fs_int32_read(req, &st->atime);
+    v9fs_int64_read(req, &st->length);
+    v9fs_string_read(req, &st->name.size, &st->name.data);
+    v9fs_string_read(req, &st->uid.size, &st->uid.data);
+    v9fs_string_read(req, &st->gid.size, &st->gid.data);
+    v9fs_string_read(req, &st->muid.size, &st->muid.data);
+
+    v9fs_req_free(req);
+}
+
 /* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */
 static P9Req *v9fs_treaddir(QVirtio9P *v9p, uint32_t fid, uint64_t offset,
                             uint32_t count, uint16_t tag)
@@ -1009,6 +1058,8 @@ static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
     v9fs_qid root_qid;
     g_autofree v9fs_qid *wqid = NULL;
     P9Req *req;
+    struct V9fsStat st[2];
+    int i;
 
     do_version(v9p);
     req = v9fs_tattach(v9p, 0, getuid(), 0);
@@ -1021,6 +1072,25 @@ static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
 
     /* special case: no QID is returned if nwname=0 was sent */
     g_assert(wqid == NULL);
+
+    req = v9fs_tstat(v9p, 0, 0);
+    v9fs_req_wait_for_reply(req, NULL);
+    v9fs_rstat(req, &st[0]);
+
+    req = v9fs_tstat(v9p, 1, 0);
+    v9fs_req_wait_for_reply(req, NULL);
+    v9fs_rstat(req, &st[1]);
+
+    /* don't compare QID version for checking for file ID equalness */
+    g_assert(st[0].qid.type == st[1].qid.type);
+    g_assert(st[0].qid.path == st[1].qid.path);
+
+    for (i = 0; i < 2; ++i) {
+        g_free(st[i].name.data);
+        g_free(st[i].uid.data);
+        g_free(st[i].gid.data);
+        g_free(st[i].muid.data);
+    }
 }
 
 static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 4/6] 9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk()
  2022-03-09 18:31 [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation Christian Schoenebeck
                   ` (2 preceding siblings ...)
  2022-03-09 14:49 ` [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test Christian Schoenebeck
@ 2022-03-09 17:12 ` Christian Schoenebeck
  2022-03-10  9:07   ` Christian Schoenebeck
  2022-03-09 17:57 ` [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked Christian Schoenebeck
  2022-03-09 18:21 ` [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix Christian Schoenebeck
  5 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-09 17:12 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

The local variable 'name_idx' is used in two loops in function v9fs_walk().
Let the first loop use its own variable 'nvalid' instead, which we will use
in subsequent patches as the number of (requested) path components
successfully retrieved/walked by background I/O thread.

Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
---
 hw/9pfs/9p.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index a6d6b3f835..6cdc566866 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -1764,7 +1764,7 @@ static bool same_stat_id(const struct stat *a, const struct stat *b)
 
 static void coroutine_fn v9fs_walk(void *opaque)
 {
-    int name_idx;
+    int name_idx, nvalid;
     g_autofree V9fsQID *qids = NULL;
     int i, err = 0;
     V9fsPath dpath, path;
@@ -1842,17 +1842,17 @@ static void coroutine_fn v9fs_walk(void *opaque)
             break;
         }
         stbuf = fidst;
-        for (name_idx = 0; name_idx < nwnames; name_idx++) {
+        for (nvalid = 0; nvalid < nwnames; nvalid++) {
             if (v9fs_request_cancelled(pdu)) {
                 err = -EINTR;
                 break;
             }
             if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
-                strcmp("..", wnames[name_idx].data))
+                strcmp("..", wnames[nvalid].data))
             {
                 err = s->ops->name_to_path(&s->ctx, &dpath,
-                                           wnames[name_idx].data,
-                                           &pathes[name_idx]);
+                                           wnames[nvalid].data,
+                                           &pathes[nvalid]);
                 if (err < 0) {
                     err = -errno;
                     break;
@@ -1861,13 +1861,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
                     err = -EINTR;
                     break;
                 }
-                err = s->ops->lstat(&s->ctx, &pathes[name_idx], &stbuf);
+                err = s->ops->lstat(&s->ctx, &pathes[nvalid], &stbuf);
                 if (err < 0) {
                     err = -errno;
                     break;
                 }
-                stbufs[name_idx] = stbuf;
-                v9fs_path_copy(&dpath, &pathes[name_idx]);
+                stbufs[nvalid] = stbuf;
+                v9fs_path_copy(&dpath, &pathes[nvalid]);
             }
         }
     });
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked
  2022-03-09 18:31 [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation Christian Schoenebeck
                   ` (3 preceding siblings ...)
  2022-03-09 17:12 ` [PATCH 4/6] 9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk() Christian Schoenebeck
@ 2022-03-09 17:57 ` Christian Schoenebeck
  2022-03-10  9:13   ` Christian Schoenebeck
  2022-03-09 18:21 ` [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix Christian Schoenebeck
  5 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-09 17:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

Current implementation of 'Twalk' request handling always sends an 'Rerror'
response if any error occured. The 9p2000 protocol spec sais though:

  "
  If the first element cannot be walked for any reason, Rerror is returned.
  Otherwise, the walk will return an Rwalk message containing nwqid qids
  corresponding, in order, to the files that are visited by the nwqid
  successful elementwise walks; nwqid is therefore either nwname or the index
  of the first elementwise walk that failed.
  "

  http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor33

For that reason we are no longer leaving from an error path in function
v9fs_walk(), unless really no path component could be walked successfully or
if the request has been interrupted.

Local variable 'nvalid' counts and reflects the number of path components
successfully processed by background I/O thread, whereas local variable
'name_idx' subsequently counts and reflects the number of path components
eventually accepted successfully by 9p server controller portion.

New local variable 'any_err' is an aggregate variable reflecting whether any
error occurred at all, while already existing variable 'err' only reflects
the last error.

Despite QIDs being delivered to client in a more relaxed way now, it is
important to note though that fid still must remain uneffacted if any error
occurred.

Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
---
 hw/9pfs/9p.c | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index 6cdc566866..8ccd180608 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -1766,7 +1766,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
 {
     int name_idx, nvalid;
     g_autofree V9fsQID *qids = NULL;
-    int i, err = 0;
+    int i, err = 0, any_err = 0;
     V9fsPath dpath, path;
     P9ARRAY_REF(V9fsPath) pathes = NULL;
     uint16_t nwnames;
@@ -1832,6 +1832,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
      * driver code altogether inside the following block.
      */
     v9fs_co_run_in_worker({
+        nvalid = 0;
         if (v9fs_request_cancelled(pdu)) {
             err = -EINTR;
             break;
@@ -1842,7 +1843,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
             break;
         }
         stbuf = fidst;
-        for (nvalid = 0; nvalid < nwnames; nvalid++) {
+        for (; nvalid < nwnames; nvalid++) {
             if (v9fs_request_cancelled(pdu)) {
                 err = -EINTR;
                 break;
@@ -1874,12 +1875,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
     /*
      * Handle all the rest of this Twalk request on main thread ...
      */
-    if (err < 0) {
+    if ((err < 0 && !nvalid) || err == -EINTR) {
         goto out;
     }
 
+    any_err |= err;
     err = stat_to_qid(pdu, &fidst, &qid);
-    if (err < 0) {
+    if (err < 0 && !nvalid) {
         goto out;
     }
     stbuf = fidst;
@@ -1888,20 +1890,30 @@ static void coroutine_fn v9fs_walk(void *opaque)
     v9fs_path_copy(&dpath, &fidp->path);
     v9fs_path_copy(&path, &fidp->path);
 
-    for (name_idx = 0; name_idx < nwnames; name_idx++) {
+    for (name_idx = 0; name_idx < nvalid; name_idx++) {
         if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
             strcmp("..", wnames[name_idx].data))
         {
             stbuf = stbufs[name_idx];
             err = stat_to_qid(pdu, &stbuf, &qid);
             if (err < 0) {
-                goto out;
+                break;
             }
             v9fs_path_copy(&path, &pathes[name_idx]);
             v9fs_path_copy(&dpath, &path);
         }
         memcpy(&qids[name_idx], &qid, sizeof(qid));
     }
+    any_err |= err;
+    if (any_err) {
+        if (!name_idx) {
+            /* don't send any QIDs, send Rlerror instead */
+            goto out;
+        } else {
+            /* send QIDs (not Rlerror), but fid MUST remain unaffected */
+            goto send_qids;
+        }
+    }
     if (fid == newfid) {
         if (fidp->fid_type != P9_FID_NONE) {
             err = -EINVAL;
@@ -1919,8 +1931,9 @@ static void coroutine_fn v9fs_walk(void *opaque)
         newfidp->uid = fidp->uid;
         v9fs_path_copy(&newfidp->path, &path);
     }
-    err = v9fs_walk_marshal(pdu, nwnames, qids);
-    trace_v9fs_walk_return(pdu->tag, pdu->id, nwnames, qids);
+send_qids:
+    err = v9fs_walk_marshal(pdu, name_idx, qids);
+    trace_v9fs_walk_return(pdu->tag, pdu->id, name_idx, qids);
 out:
     put_fid(pdu, fidp);
     if (newfidp) {
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix
  2022-03-09 18:31 [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation Christian Schoenebeck
                   ` (4 preceding siblings ...)
  2022-03-09 17:57 ` [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked Christian Schoenebeck
@ 2022-03-09 18:21 ` Christian Schoenebeck
  2022-03-11 10:32   ` Christian Schoenebeck
  2022-03-11 16:40   ` Greg Kurz
  5 siblings, 2 replies; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-09 18:21 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

Previous 9p patch fixed 'Twalk' request handling, which was previously not
behaving as specified by the 9p2000 protocol spec. This patch adds a new test
case which guards the new 'Twalk' behaviour in question.

More specifically: it sends a 'Twalk' request where the 1st path component
is valid, whereas the 2nd path component transmitted to server does not
exist. The expected behaviour is that 9p server would respond by sending
a 'Rwalk' response with exactly 1 QID (instead of 'Rlerror' response).

Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
---
 tests/qtest/virtio-9p-test.c | 42 +++++++++++++++++++++++++++++++++---
 1 file changed, 39 insertions(+), 3 deletions(-)

diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
index 9098e21173..f29de1ca64 100644
--- a/tests/qtest/virtio-9p-test.c
+++ b/tests/qtest/virtio-9p-test.c
@@ -638,8 +638,12 @@ static void do_version(QVirtio9P *v9p)
     g_assert_cmpmem(server_version, server_len, version, strlen(version));
 }
 
-/* utility function: walk to requested dir and return fid for that dir */
-static uint32_t do_walk(QVirtio9P *v9p, const char *path)
+/*
+ * utility function: walk to requested dir and return fid for that dir and
+ * the QIDs of server response
+ */
+static uint32_t do_walk_rqids(QVirtio9P *v9p, const char *path, uint16_t *nwqid,
+                              v9fs_qid **wqid)
 {
     char **wnames;
     P9Req *req;
@@ -649,12 +653,18 @@ static uint32_t do_walk(QVirtio9P *v9p, const char *path)
 
     req = v9fs_twalk(v9p, 0, fid, nwnames, wnames, 0);
     v9fs_req_wait_for_reply(req, NULL);
-    v9fs_rwalk(req, NULL, NULL);
+    v9fs_rwalk(req, nwqid, wqid);
 
     split_free(&wnames);
     return fid;
 }
 
+/* utility function: walk to requested dir and return fid for that dir */
+static uint32_t do_walk(QVirtio9P *v9p, const char *path)
+{
+    return do_walk_rqids(v9p, path, NULL, NULL);
+}
+
 /* utility function: walk to requested dir and expect passed error response */
 static void do_walk_expect_error(QVirtio9P *v9p, const char *path, uint32_t err)
 {
@@ -1048,9 +1058,33 @@ static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
     alloc = t_alloc;
 
     do_attach(v9p);
+    /*
+     * The 9p2000 protocol spec sais: "If the first element cannot be walked
+     * for any reason, Rerror is returned."
+     */
     do_walk_expect_error(v9p, "non-existent", ENOENT);
 }
 
+static void fs_walk_2nd_nonexistent(void *obj, void *data,
+                                    QGuestAllocator *t_alloc)
+{
+    QVirtio9P *v9p = obj;
+    alloc = t_alloc;
+    uint16_t nwqid;
+    g_autofree v9fs_qid *wqid = NULL;
+    g_autofree char *path = g_strdup_printf(
+        QTEST_V9FS_SYNTH_WALK_FILE "/non-existent", 0
+    );
+
+    do_attach(v9p);
+    do_walk_rqids(v9p, path, &nwqid, &wqid);
+    /*
+     * The 9p2000 protocol spec sais: "nwqid is therefore either nwname or the
+     * index of the first elementwise walk that failed."
+     */
+    assert(nwqid == 1);
+}
+
 static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
 {
     QVirtio9P *v9p = obj;
@@ -1531,6 +1565,8 @@ static void register_virtio_9p_test(void)
                  fs_walk_dotdot,  &opts);
     qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
                   &opts);
+    qos_add_test("synth/walk/2nd_non_existent", "virtio-9p",
+                 fs_walk_2nd_nonexistent, &opts);
     qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen,  &opts);
     qos_add_test("synth/write/basic", "virtio-9p", fs_write,  &opts);
     qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,
-- 
2.30.2



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation
@ 2022-03-09 18:31 Christian Schoenebeck
  2022-03-09 12:18 ` [PATCH 1/6] tests/9pfs: walk to non-existent dir Christian Schoenebeck
                   ` (5 more replies)
  0 siblings, 6 replies; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-09 18:31 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

Currently the implementation of 'Twalk' does not behave exactly as specified
by the 9p2000 protocol specification. Actual fix is patch 5; see the
description of that patch for details of what this overall fix and series is
about.

Patch 4 is a preparatory (pure) refactoring change to make patch 5 better
readable.

All the other patches are just additional test cases for guarding 'Twalk'
behaviour.

Christian Schoenebeck (6):
  tests/9pfs: walk to non-existent dir
  tests/9pfs: Twalk with nwname=0
  tests/9pfs: compare QIDs in fs_walk_none() test
  9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk()
  9pfs: fix 'Twalk' to only send error if no component walked
  tests/9pfs: guard recent 'Twalk' behaviour fix

 hw/9pfs/9p.c                 |  43 ++++++----
 tests/qtest/virtio-9p-test.c | 162 ++++++++++++++++++++++++++++++++++-
 2 files changed, 188 insertions(+), 17 deletions(-)

-- 
2.30.2



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/6] tests/9pfs: Twalk with nwname=0
  2022-03-09 13:24 ` [PATCH 2/6] tests/9pfs: Twalk with nwname=0 Christian Schoenebeck
@ 2022-03-10  8:57   ` Christian Schoenebeck
  2022-03-11 11:41     ` Greg Kurz
  0 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-10  8:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Mittwoch, 9. März 2022 14:24:24 CET Christian Schoenebeck wrote:
> Send Twalk request with nwname=0. In this case no QIDs should
> be returned by 9p server; this is equivalent to walking to dot.
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---
>  tests/qtest/virtio-9p-test.c | 22 ++++++++++++++++++++++
>  1 file changed, 22 insertions(+)
> 
> diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> index 22bdd74bc1..6c00da03f4 100644
> --- a/tests/qtest/virtio-9p-test.c
> +++ b/tests/qtest/virtio-9p-test.c
> @@ -1002,6 +1002,27 @@ static void fs_walk_nonexistent(void *obj, void
> *data, QGuestAllocator *t_alloc) do_walk_expect_error(v9p, "non-existent",
> ENOENT);
>  }
> 
> +static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
> +{

Or maybe calling this function fs_walk_clone_fid and the test case name
"synth/walk/clone_fid" respectively instead?

> +    QVirtio9P *v9p = obj;
> +    alloc = t_alloc;
> +    v9fs_qid root_qid;
> +    g_autofree v9fs_qid *wqid = NULL;
> +    P9Req *req;
> +
> +    do_version(v9p);
> +    req = v9fs_tattach(v9p, 0, getuid(), 0);
> +    v9fs_req_wait_for_reply(req, NULL);
> +    v9fs_rattach(req, &root_qid);
> +
> +    req = v9fs_twalk(v9p, 0, 1, 0, NULL, 0);
> +    v9fs_req_wait_for_reply(req, NULL);
> +    v9fs_rwalk(req, NULL, &wqid);
> +
> +    /* special case: no QID is returned if nwname=0 was sent */
> +    g_assert(wqid == NULL);
> +}
> +
>  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
> {
>      QVirtio9P *v9p = obj;
> @@ -1435,6 +1456,7 @@ static void register_virtio_9p_test(void)
>      qos_add_test("synth/walk/basic", "virtio-9p", fs_walk,  &opts);
>      qos_add_test("synth/walk/no_slash", "virtio-9p", fs_walk_no_slash,
>                    &opts);
> +    qos_add_test("synth/walk/none", "virtio-9p", fs_walk_none, &opts);
>      qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
>                   fs_walk_dotdot,  &opts);
>      qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test
  2022-03-09 14:49 ` [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test Christian Schoenebeck
@ 2022-03-10  9:04   ` Christian Schoenebeck
  2022-03-11 16:11     ` Greg Kurz
  0 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-10  9:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Mittwoch, 9. März 2022 15:49:04 CET Christian Schoenebeck wrote:
> Extend previously added fs_walk_none() test by comparing the QID
> of the root fid with the QID of the cloned fid. They should be
> equal.
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---
>  tests/qtest/virtio-9p-test.c | 70 ++++++++++++++++++++++++++++++++++++
>  1 file changed, 70 insertions(+)
> 
> diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> index 6c00da03f4..9098e21173 100644
> --- a/tests/qtest/virtio-9p-test.c
> +++ b/tests/qtest/virtio-9p-test.c
> @@ -146,6 +146,11 @@ static void v9fs_uint16_read(P9Req *req, uint16_t *val)
> le16_to_cpus(val);
>  }
> 
> +static void v9fs_int16_read(P9Req *req, int16_t *val)
> +{
> +    v9fs_uint16_read(req, (uint16_t *)val);
> +}
> +
>  static void v9fs_uint32_write(P9Req *req, uint32_t val)
>  {
>      uint32_t le_val = cpu_to_le32(val);
> @@ -166,12 +171,22 @@ static void v9fs_uint32_read(P9Req *req, uint32_t
> *val) le32_to_cpus(val);
>  }
> 
> +static void v9fs_int32_read(P9Req *req, int32_t *val)
> +{
> +    v9fs_uint32_read(req, (uint32_t *)val);
> +}
> +
>  static void v9fs_uint64_read(P9Req *req, uint64_t *val)
>  {
>      v9fs_memread(req, val, 8);
>      le64_to_cpus(val);
>  }
> 
> +static void v9fs_int64_read(P9Req *req, int64_t *val)
> +{
> +    v9fs_uint64_read(req, (uint64_t *)val);
> +}
> +
>  /* len[2] string[len] */
>  static uint16_t v9fs_string_size(const char *string)
>  {
> @@ -425,6 +440,40 @@ static void v9fs_rwalk(P9Req *req, uint16_t *nwqid,
> v9fs_qid **wqid) v9fs_req_free(req);
>  }
> 
> +/* size[4] Tstat tag[2] fid[4] */
> +static P9Req *v9fs_tstat(QVirtio9P *v9p, uint32_t fid, uint16_t tag)
> +{
> +    P9Req *req;
> +
> +    req = v9fs_req_init(v9p, 4, P9_TSTAT, tag);
> +    v9fs_uint32_write(req, fid);
> +    v9fs_req_send(req);
> +    return req;
> +}
> +
> +/* size[4] Rstat tag[2] stat[n] */
> +static void v9fs_rstat(P9Req *req, struct V9fsStat *st)
> +{
> +    v9fs_req_recv(req, P9_RSTAT);
> +
> +    v9fs_int16_read(req, &st->size);
> +    v9fs_int16_read(req, &st->type);
> +    v9fs_int32_read(req, &st->dev);
> +    v9fs_uint8_read(req, &st->qid.type);
> +    v9fs_uint32_read(req, &st->qid.version);
> +    v9fs_uint64_read(req, &st->qid.path);
> +    v9fs_int32_read(req, &st->mode);
> +    v9fs_int32_read(req, &st->mtime);
> +    v9fs_int32_read(req, &st->atime);
> +    v9fs_int64_read(req, &st->length);
> +    v9fs_string_read(req, &st->name.size, &st->name.data);
> +    v9fs_string_read(req, &st->uid.size, &st->uid.data);
> +    v9fs_string_read(req, &st->gid.size, &st->gid.data);
> +    v9fs_string_read(req, &st->muid.size, &st->muid.data);
> +
> +    v9fs_req_free(req);
> +}
> +
>  /* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */
>  static P9Req *v9fs_treaddir(QVirtio9P *v9p, uint32_t fid, uint64_t offset,
>                              uint32_t count, uint16_t tag)
> @@ -1009,6 +1058,8 @@ static void fs_walk_none(void *obj, void *data,
> QGuestAllocator *t_alloc) v9fs_qid root_qid;
>      g_autofree v9fs_qid *wqid = NULL;
>      P9Req *req;
> +    struct V9fsStat st[2];
> +    int i;
> 
>      do_version(v9p);
>      req = v9fs_tattach(v9p, 0, getuid(), 0);
> @@ -1021,6 +1072,25 @@ static void fs_walk_none(void *obj, void *data,
> QGuestAllocator *t_alloc)
> 
>      /* special case: no QID is returned if nwname=0 was sent */
>      g_assert(wqid == NULL);
> +
> +    req = v9fs_tstat(v9p, 0, 0);
> +    v9fs_req_wait_for_reply(req, NULL);
> +    v9fs_rstat(req, &st[0]);

Probably stat-ing the root fid (0) should happen before sending Twalk, to 
better counter the 1st fid (0) having become potentially mutated?

> +
> +    req = v9fs_tstat(v9p, 1, 0);
> +    v9fs_req_wait_for_reply(req, NULL);
> +    v9fs_rstat(req, &st[1]);
> +
> +    /* don't compare QID version for checking for file ID equalness */
> +    g_assert(st[0].qid.type == st[1].qid.type);
> +    g_assert(st[0].qid.path == st[1].qid.path);

I could add a helper function is_same_qid() for this if desired.

> +
> +    for (i = 0; i < 2; ++i) {
> +        g_free(st[i].name.data);
> +        g_free(st[i].uid.data);
> +        g_free(st[i].gid.data);
> +        g_free(st[i].muid.data);
> +    }

I didn't find a more elegant way to do this cleanup.

>  }
> 
>  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 4/6] 9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk()
  2022-03-09 17:12 ` [PATCH 4/6] 9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk() Christian Schoenebeck
@ 2022-03-10  9:07   ` Christian Schoenebeck
  2022-03-11 16:16     ` Greg Kurz
  0 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-10  9:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Mittwoch, 9. März 2022 18:12:17 CET Christian Schoenebeck wrote:
> The local variable 'name_idx' is used in two loops in function v9fs_walk().
> Let the first loop use its own variable 'nvalid' instead, which we will use
> in subsequent patches as the number of (requested) path components
> successfully retrieved/walked by background I/O thread.
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---
>  hw/9pfs/9p.c | 16 ++++++++--------
>  1 file changed, 8 insertions(+), 8 deletions(-)
> 
> diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> index a6d6b3f835..6cdc566866 100644
> --- a/hw/9pfs/9p.c
> +++ b/hw/9pfs/9p.c
> @@ -1764,7 +1764,7 @@ static bool same_stat_id(const struct stat *a, const
> struct stat *b)
> 
>  static void coroutine_fn v9fs_walk(void *opaque)
>  {
> -    int name_idx;
> +    int name_idx, nvalid;

Or rather renaming this nvalid -> nfetched?

>      g_autofree V9fsQID *qids = NULL;
>      int i, err = 0;
>      V9fsPath dpath, path;
> @@ -1842,17 +1842,17 @@ static void coroutine_fn v9fs_walk(void *opaque)
>              break;
>          }
>          stbuf = fidst;
> -        for (name_idx = 0; name_idx < nwnames; name_idx++) {
> +        for (nvalid = 0; nvalid < nwnames; nvalid++) {
>              if (v9fs_request_cancelled(pdu)) {
>                  err = -EINTR;
>                  break;
>              }
>              if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
> -                strcmp("..", wnames[name_idx].data))
> +                strcmp("..", wnames[nvalid].data))
>              {
>                  err = s->ops->name_to_path(&s->ctx, &dpath,
> -                                           wnames[name_idx].data,
> -                                           &pathes[name_idx]);
> +                                           wnames[nvalid].data,
> +                                           &pathes[nvalid]);
>                  if (err < 0) {
>                      err = -errno;
>                      break;
> @@ -1861,13 +1861,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
>                      err = -EINTR;
>                      break;
>                  }
> -                err = s->ops->lstat(&s->ctx, &pathes[name_idx], &stbuf);
> +                err = s->ops->lstat(&s->ctx, &pathes[nvalid], &stbuf);
>                  if (err < 0) {
>                      err = -errno;
>                      break;
>                  }
> -                stbufs[name_idx] = stbuf;
> -                v9fs_path_copy(&dpath, &pathes[name_idx]);
> +                stbufs[nvalid] = stbuf;
> +                v9fs_path_copy(&dpath, &pathes[nvalid]);
>              }
>          }
>      });




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked
  2022-03-09 17:57 ` [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked Christian Schoenebeck
@ 2022-03-10  9:13   ` Christian Schoenebeck
  2022-03-11 16:35     ` Greg Kurz
  0 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-10  9:13 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Mittwoch, 9. März 2022 18:57:39 CET Christian Schoenebeck wrote:
> Current implementation of 'Twalk' request handling always sends an 'Rerror'
> response if any error occured. The 9p2000 protocol spec sais though:
> 
>   "
>   If the first element cannot be walked for any reason, Rerror is returned.
>   Otherwise, the walk will return an Rwalk message containing nwqid qids
>   corresponding, in order, to the files that are visited by the nwqid
>   successful elementwise walks; nwqid is therefore either nwname or the
> index of the first elementwise walk that failed.
>   "
> 
>   http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor33
> 
> For that reason we are no longer leaving from an error path in function
> v9fs_walk(), unless really no path component could be walked successfully or
> if the request has been interrupted.
> 
> Local variable 'nvalid' counts and reflects the number of path components
> successfully processed by background I/O thread, whereas local variable
> 'name_idx' subsequently counts and reflects the number of path components
> eventually accepted successfully by 9p server controller portion.
> 
> New local variable 'any_err' is an aggregate variable reflecting whether any
> error occurred at all, while already existing variable 'err' only reflects
> the last error.
> 
> Despite QIDs being delivered to client in a more relaxed way now, it is
> important to note though that fid still must remain uneffacted if any error

Typo: should be "unaffected".

> occurred.
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---
>  hw/9pfs/9p.c | 29 +++++++++++++++++++++--------
>  1 file changed, 21 insertions(+), 8 deletions(-)
> 
> diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> index 6cdc566866..8ccd180608 100644
> --- a/hw/9pfs/9p.c
> +++ b/hw/9pfs/9p.c
> @@ -1766,7 +1766,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
>  {
>      int name_idx, nvalid;
>      g_autofree V9fsQID *qids = NULL;
> -    int i, err = 0;
> +    int i, err = 0, any_err = 0;
>      V9fsPath dpath, path;
>      P9ARRAY_REF(V9fsPath) pathes = NULL;
>      uint16_t nwnames;
> @@ -1832,6 +1832,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
>       * driver code altogether inside the following block.
>       */
>      v9fs_co_run_in_worker({
> +        nvalid = 0;
>          if (v9fs_request_cancelled(pdu)) {
>              err = -EINTR;
>              break;
> @@ -1842,7 +1843,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
>              break;
>          }
>          stbuf = fidst;
> -        for (nvalid = 0; nvalid < nwnames; nvalid++) {
> +        for (; nvalid < nwnames; nvalid++) {
>              if (v9fs_request_cancelled(pdu)) {
>                  err = -EINTR;
>                  break;
> @@ -1874,12 +1875,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
>      /*
>       * Handle all the rest of this Twalk request on main thread ...
>       */
> -    if (err < 0) {
> +    if ((err < 0 && !nvalid) || err == -EINTR) {
>          goto out;
>      }
> 
> +    any_err |= err;
>      err = stat_to_qid(pdu, &fidst, &qid);
> -    if (err < 0) {
> +    if (err < 0 && !nvalid) {
>          goto out;
>      }
>      stbuf = fidst;
> @@ -1888,20 +1890,30 @@ static void coroutine_fn v9fs_walk(void *opaque)
>      v9fs_path_copy(&dpath, &fidp->path);
>      v9fs_path_copy(&path, &fidp->path);
> 
> -    for (name_idx = 0; name_idx < nwnames; name_idx++) {
> +    for (name_idx = 0; name_idx < nvalid; name_idx++) {
>          if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
>              strcmp("..", wnames[name_idx].data))
>          {
>              stbuf = stbufs[name_idx];
>              err = stat_to_qid(pdu, &stbuf, &qid);
>              if (err < 0) {
> -                goto out;
> +                break;
>              }
>              v9fs_path_copy(&path, &pathes[name_idx]);
>              v9fs_path_copy(&dpath, &path);
>          }
>          memcpy(&qids[name_idx], &qid, sizeof(qid));
>      }
> +    any_err |= err;
> +    if (any_err) {


Not sure if there is ever the case err > 0, but as we are already comparing 
for "if (err < 0)" everywhere, we should probably also do the same comparison 
for the aggregate error variable here, right?

    if (any_err < 0) {
        ...

> +        if (!name_idx) {
> +            /* don't send any QIDs, send Rlerror instead */
> +            goto out;
> +        } else {
> +            /* send QIDs (not Rlerror), but fid MUST remain unaffected */
> +            goto send_qids;
> +        }
> +    }
>      if (fid == newfid) {
>          if (fidp->fid_type != P9_FID_NONE) {
>              err = -EINVAL;
> @@ -1919,8 +1931,9 @@ static void coroutine_fn v9fs_walk(void *opaque)
>          newfidp->uid = fidp->uid;
>          v9fs_path_copy(&newfidp->path, &path);
>      }
> -    err = v9fs_walk_marshal(pdu, nwnames, qids);
> -    trace_v9fs_walk_return(pdu->tag, pdu->id, nwnames, qids);
> +send_qids:
> +    err = v9fs_walk_marshal(pdu, name_idx, qids);
> +    trace_v9fs_walk_return(pdu->tag, pdu->id, name_idx, qids);
>  out:
>      put_fid(pdu, fidp);
>      if (newfidp) {




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix
  2022-03-09 18:21 ` [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix Christian Schoenebeck
@ 2022-03-11 10:32   ` Christian Schoenebeck
  2022-03-11 16:40   ` Greg Kurz
  1 sibling, 0 replies; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-11 10:32 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Mittwoch, 9. März 2022 19:21:18 CET Christian Schoenebeck wrote:
> Previous 9p patch fixed 'Twalk' request handling, which was previously not
> behaving as specified by the 9p2000 protocol spec. This patch adds a new
> test case which guards the new 'Twalk' behaviour in question.
> 
> More specifically: it sends a 'Twalk' request where the 1st path component
> is valid, whereas the 2nd path component transmitted to server does not
> exist. The expected behaviour is that 9p server would respond by sending
> a 'Rwalk' response with exactly 1 QID (instead of 'Rlerror' response).
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---
>  tests/qtest/virtio-9p-test.c | 42 +++++++++++++++++++++++++++++++++---
>  1 file changed, 39 insertions(+), 3 deletions(-)
> 
> diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> index 9098e21173..f29de1ca64 100644
> --- a/tests/qtest/virtio-9p-test.c
> +++ b/tests/qtest/virtio-9p-test.c
> @@ -638,8 +638,12 @@ static void do_version(QVirtio9P *v9p)
>      g_assert_cmpmem(server_version, server_len, version, strlen(version));
>  }
> 
> -/* utility function: walk to requested dir and return fid for that dir */
> -static uint32_t do_walk(QVirtio9P *v9p, const char *path)
> +/*
> + * utility function: walk to requested dir and return fid for that dir and
> + * the QIDs of server response
> + */
> +static uint32_t do_walk_rqids(QVirtio9P *v9p, const char *path, uint16_t
> *nwqid, +                              v9fs_qid **wqid)
>  {
>      char **wnames;
>      P9Req *req;
> @@ -649,12 +653,18 @@ static uint32_t do_walk(QVirtio9P *v9p, const char
> *path)
> 
>      req = v9fs_twalk(v9p, 0, fid, nwnames, wnames, 0);
>      v9fs_req_wait_for_reply(req, NULL);
> -    v9fs_rwalk(req, NULL, NULL);
> +    v9fs_rwalk(req, nwqid, wqid);
> 
>      split_free(&wnames);
>      return fid;
>  }
> 
> +/* utility function: walk to requested dir and return fid for that dir */
> +static uint32_t do_walk(QVirtio9P *v9p, const char *path)
> +{
> +    return do_walk_rqids(v9p, path, NULL, NULL);
> +}
> +
>  /* utility function: walk to requested dir and expect passed error response
> */ static void do_walk_expect_error(QVirtio9P *v9p, const char *path,
> uint32_t err) {
> @@ -1048,9 +1058,33 @@ static void fs_walk_nonexistent(void *obj, void
> *data, QGuestAllocator *t_alloc) alloc = t_alloc;
> 
>      do_attach(v9p);
> +    /*
> +     * The 9p2000 protocol spec sais: "If the first element cannot be
> walked +     * for any reason, Rerror is returned."
> +     */
>      do_walk_expect_error(v9p, "non-existent", ENOENT);
>  }
> 
> +static void fs_walk_2nd_nonexistent(void *obj, void *data,
> +                                    QGuestAllocator *t_alloc)
> +{
> +    QVirtio9P *v9p = obj;
> +    alloc = t_alloc;
> +    uint16_t nwqid;
> +    g_autofree v9fs_qid *wqid = NULL;
> +    g_autofree char *path = g_strdup_printf(
> +        QTEST_V9FS_SYNTH_WALK_FILE "/non-existent", 0
> +    );
> +
> +    do_attach(v9p);
> +    do_walk_rqids(v9p, path, &nwqid, &wqid);
> +    /*
> +     * The 9p2000 protocol spec sais: "nwqid is therefore either nwname or
> the +     * index of the first elementwise walk that failed."
> +     */
> +    assert(nwqid == 1);
> +}

I should probably extend this test case with a separate 7th patch to check 
that fid was really unaffected by Twalk, by comparing the QID before vs. after 
the walk (similar to what patch 3 does).

> +
>  static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
>  {
>      QVirtio9P *v9p = obj;
> @@ -1531,6 +1565,8 @@ static void register_virtio_9p_test(void)
>                   fs_walk_dotdot,  &opts);
>      qos_add_test("synth/walk/non_existent", "virtio-9p",
> fs_walk_nonexistent, &opts);
> +    qos_add_test("synth/walk/2nd_non_existent", "virtio-9p",
> +                 fs_walk_2nd_nonexistent, &opts);
>      qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen,  &opts);
>      qos_add_test("synth/write/basic", "virtio-9p", fs_write,  &opts);
>      qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 1/6] tests/9pfs: walk to non-existent dir
  2022-03-09 12:18 ` [PATCH 1/6] tests/9pfs: walk to non-existent dir Christian Schoenebeck
@ 2022-03-11 11:25   ` Greg Kurz
  0 siblings, 0 replies; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 11:25 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Wed, 9 Mar 2022 13:18:50 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> Expect ENOENT Rlerror response when trying to walk to a
> non-existent directory.
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---

Reviewed-by: Greg Kurz <groug@kaod.org>

>  tests/qtest/virtio-9p-test.c | 30 ++++++++++++++++++++++++++++++
>  1 file changed, 30 insertions(+)
> 
> diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> index 01ca076afe..22bdd74bc1 100644
> --- a/tests/qtest/virtio-9p-test.c
> +++ b/tests/qtest/virtio-9p-test.c
> @@ -606,6 +606,25 @@ static uint32_t do_walk(QVirtio9P *v9p, const char *path)
>      return fid;
>  }
>  
> +/* utility function: walk to requested dir and expect passed error response */
> +static void do_walk_expect_error(QVirtio9P *v9p, const char *path, uint32_t err)
> +{
> +    char **wnames;
> +    P9Req *req;
> +    uint32_t _err;
> +    const uint32_t fid = genfid();
> +
> +    int nwnames = split(path, "/", &wnames);
> +
> +    req = v9fs_twalk(v9p, 0, fid, nwnames, wnames, 0);
> +    v9fs_req_wait_for_reply(req, NULL);
> +    v9fs_rlerror(req, &_err);
> +
> +    g_assert_cmpint(_err, ==, err);
> +
> +    split_free(&wnames);
> +}
> +
>  static void fs_version(void *obj, void *data, QGuestAllocator *t_alloc)
>  {
>      alloc = t_alloc;
> @@ -974,6 +993,15 @@ static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
>      g_free(wnames[0]);
>  }
>  
> +static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
> +{
> +    QVirtio9P *v9p = obj;
> +    alloc = t_alloc;
> +
> +    do_attach(v9p);
> +    do_walk_expect_error(v9p, "non-existent", ENOENT);
> +}
> +
>  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
>  {
>      QVirtio9P *v9p = obj;
> @@ -1409,6 +1437,8 @@ static void register_virtio_9p_test(void)
>                    &opts);
>      qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
>                   fs_walk_dotdot,  &opts);
> +    qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
> +                  &opts);
>      qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen,  &opts);
>      qos_add_test("synth/write/basic", "virtio-9p", fs_write,  &opts);
>      qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/6] tests/9pfs: Twalk with nwname=0
  2022-03-10  8:57   ` Christian Schoenebeck
@ 2022-03-11 11:41     ` Greg Kurz
  2022-03-11 13:33       ` Christian Schoenebeck
  0 siblings, 1 reply; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 11:41 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Thu, 10 Mar 2022 09:57:25 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> On Mittwoch, 9. März 2022 14:24:24 CET Christian Schoenebeck wrote:
> > Send Twalk request with nwname=0. In this case no QIDs should
> > be returned by 9p server; this is equivalent to walking to dot.
> > 
> > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > ---
> >  tests/qtest/virtio-9p-test.c | 22 ++++++++++++++++++++++
> >  1 file changed, 22 insertions(+)
> > 
> > diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> > index 22bdd74bc1..6c00da03f4 100644
> > --- a/tests/qtest/virtio-9p-test.c
> > +++ b/tests/qtest/virtio-9p-test.c
> > @@ -1002,6 +1002,27 @@ static void fs_walk_nonexistent(void *obj, void
> > *data, QGuestAllocator *t_alloc) do_walk_expect_error(v9p, "non-existent",
> > ENOENT);
> >  }
> > 
> > +static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
> > +{
> 
> Or maybe calling this function fs_walk_clone_fid and the test case name
> "synth/walk/clone_fid" respectively instead?
> 

I agree that Twalk with nwname=0 does clone the fid in practice but
the test doesn't explicitly check that. In its present form, I'd
suggest a "no_names" wording but it is already fine as is, so:

Reviewed-by: Greg Kurz <groug@kaod.org>

> > +    QVirtio9P *v9p = obj;
> > +    alloc = t_alloc;
> > +    v9fs_qid root_qid;
> > +    g_autofree v9fs_qid *wqid = NULL;
> > +    P9Req *req;
> > +
> > +    do_version(v9p);
> > +    req = v9fs_tattach(v9p, 0, getuid(), 0);
> > +    v9fs_req_wait_for_reply(req, NULL);
> > +    v9fs_rattach(req, &root_qid);
> > +
> > +    req = v9fs_twalk(v9p, 0, 1, 0, NULL, 0);
> > +    v9fs_req_wait_for_reply(req, NULL);
> > +    v9fs_rwalk(req, NULL, &wqid);
> > +
> > +    /* special case: no QID is returned if nwname=0 was sent */
> > +    g_assert(wqid == NULL);
> > +}
> > +
> >  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
> > {
> >      QVirtio9P *v9p = obj;
> > @@ -1435,6 +1456,7 @@ static void register_virtio_9p_test(void)
> >      qos_add_test("synth/walk/basic", "virtio-9p", fs_walk,  &opts);
> >      qos_add_test("synth/walk/no_slash", "virtio-9p", fs_walk_no_slash,
> >                    &opts);
> > +    qos_add_test("synth/walk/none", "virtio-9p", fs_walk_none, &opts);
> >      qos_add_test("synth/walk/dotdot_from_root", "virtio-9p",
> >                   fs_walk_dotdot,  &opts);
> >      qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
> 
> 



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 2/6] tests/9pfs: Twalk with nwname=0
  2022-03-11 11:41     ` Greg Kurz
@ 2022-03-11 13:33       ` Christian Schoenebeck
  0 siblings, 0 replies; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-11 13:33 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Freitag, 11. März 2022 12:41:32 CET Greg Kurz wrote:
> On Thu, 10 Mar 2022 09:57:25 +0100
> 
> Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > On Mittwoch, 9. März 2022 14:24:24 CET Christian Schoenebeck wrote:
> > > Send Twalk request with nwname=0. In this case no QIDs should
> > > be returned by 9p server; this is equivalent to walking to dot.
> > > 
> > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > ---
> > > 
> > >  tests/qtest/virtio-9p-test.c | 22 ++++++++++++++++++++++
> > >  1 file changed, 22 insertions(+)
> > > 
> > > diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> > > index 22bdd74bc1..6c00da03f4 100644
> > > --- a/tests/qtest/virtio-9p-test.c
> > > +++ b/tests/qtest/virtio-9p-test.c
> > > @@ -1002,6 +1002,27 @@ static void fs_walk_nonexistent(void *obj, void
> > > *data, QGuestAllocator *t_alloc) do_walk_expect_error(v9p,
> > > "non-existent",
> > > ENOENT);
> > > 
> > >  }
> > > 
> > > +static void fs_walk_none(void *obj, void *data, QGuestAllocator
> > > *t_alloc)
> > > +{
> > 
> > Or maybe calling this function fs_walk_clone_fid and the test case name
> > "synth/walk/clone_fid" respectively instead?
> 
> I agree that Twalk with nwname=0 does clone the fid in practice but
> the test doesn't explicitly check that. In its present form, I'd
> suggest a "no_names" wording but it is already fine as is, so:

It actually does; not with this patch 2 yet, but with patch 3 (which compares 
QIDs).

> Reviewed-by: Greg Kurz <groug@kaod.org>

Thanks!

Best regards,
Christian Schoenebeck




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test
  2022-03-10  9:04   ` Christian Schoenebeck
@ 2022-03-11 16:11     ` Greg Kurz
  2022-03-11 16:39       ` Christian Schoenebeck
  0 siblings, 1 reply; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 16:11 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Thu, 10 Mar 2022 10:04:50 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> On Mittwoch, 9. März 2022 15:49:04 CET Christian Schoenebeck wrote:
> > Extend previously added fs_walk_none() test by comparing the QID
> > of the root fid with the QID of the cloned fid. They should be
> > equal.
> > 

Ha, I understand your suggestion of changing the name now :-) but I'll
personally leave it named according to the test scenario of "sending
a Twalk with no names" and checking everything that is expected in this
case.

> > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > ---
> >  tests/qtest/virtio-9p-test.c | 70 ++++++++++++++++++++++++++++++++++++
> >  1 file changed, 70 insertions(+)
> > 
> > diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> > index 6c00da03f4..9098e21173 100644
> > --- a/tests/qtest/virtio-9p-test.c
> > +++ b/tests/qtest/virtio-9p-test.c
> > @@ -146,6 +146,11 @@ static void v9fs_uint16_read(P9Req *req, uint16_t *val)
> > le16_to_cpus(val);
> >  }
> > 
> > +static void v9fs_int16_read(P9Req *req, int16_t *val)
> > +{
> > +    v9fs_uint16_read(req, (uint16_t *)val);
> > +}
> > +
> >  static void v9fs_uint32_write(P9Req *req, uint32_t val)
> >  {
> >      uint32_t le_val = cpu_to_le32(val);
> > @@ -166,12 +171,22 @@ static void v9fs_uint32_read(P9Req *req, uint32_t
> > *val) le32_to_cpus(val);
> >  }
> > 
> > +static void v9fs_int32_read(P9Req *req, int32_t *val)
> > +{
> > +    v9fs_uint32_read(req, (uint32_t *)val);
> > +}
> > +
> >  static void v9fs_uint64_read(P9Req *req, uint64_t *val)
> >  {
> >      v9fs_memread(req, val, 8);
> >      le64_to_cpus(val);
> >  }
> > 
> > +static void v9fs_int64_read(P9Req *req, int64_t *val)
> > +{
> > +    v9fs_uint64_read(req, (uint64_t *)val);
> > +}
> > +
> >  /* len[2] string[len] */
> >  static uint16_t v9fs_string_size(const char *string)
> >  {
> > @@ -425,6 +440,40 @@ static void v9fs_rwalk(P9Req *req, uint16_t *nwqid,
> > v9fs_qid **wqid) v9fs_req_free(req);
> >  }
> > 
> > +/* size[4] Tstat tag[2] fid[4] */
> > +static P9Req *v9fs_tstat(QVirtio9P *v9p, uint32_t fid, uint16_t tag)

Tstat/Rstat aren't part of 9p2000.L, you should use Tgetattr/Rgetattr
instead (see https://github.com/chaos/diod/blob/master/protocol.md).

> > +{
> > +    P9Req *req;
> > +
> > +    req = v9fs_req_init(v9p, 4, P9_TSTAT, tag);
> > +    v9fs_uint32_write(req, fid);
> > +    v9fs_req_send(req);
> > +    return req;
> > +}
> > +
> > +/* size[4] Rstat tag[2] stat[n] */
> > +static void v9fs_rstat(P9Req *req, struct V9fsStat *st)
> > +{
> > +    v9fs_req_recv(req, P9_RSTAT);
> > +

For the records, this is a stat[n], i.e. "n[2] followed by n bytes of
data forming the parameter", so you should read an uint16_t first.

> > +    v9fs_int16_read(req, &st->size);
> > +    v9fs_int16_read(req, &st->type);
> > +    v9fs_int32_read(req, &st->dev);
> > +    v9fs_uint8_read(req, &st->qid.type);
> > +    v9fs_uint32_read(req, &st->qid.version);
> > +    v9fs_uint64_read(req, &st->qid.path);
> > +    v9fs_int32_read(req, &st->mode);
> > +    v9fs_int32_read(req, &st->mtime);
> > +    v9fs_int32_read(req, &st->atime);
> > +    v9fs_int64_read(req, &st->length);
> > +    v9fs_string_read(req, &st->name.size, &st->name.data);
> > +    v9fs_string_read(req, &st->uid.size, &st->uid.data);
> > +    v9fs_string_read(req, &st->gid.size, &st->gid.data);
> > +    v9fs_string_read(req, &st->muid.size, &st->muid.data);
> > +
> > +    v9fs_req_free(req);
> > +}
> > +
> >  /* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */
> >  static P9Req *v9fs_treaddir(QVirtio9P *v9p, uint32_t fid, uint64_t offset,
> >                              uint32_t count, uint16_t tag)
> > @@ -1009,6 +1058,8 @@ static void fs_walk_none(void *obj, void *data,
> > QGuestAllocator *t_alloc) v9fs_qid root_qid;
> >      g_autofree v9fs_qid *wqid = NULL;
> >      P9Req *req;
> > +    struct V9fsStat st[2];
> > +    int i;
> > 
> >      do_version(v9p);
> >      req = v9fs_tattach(v9p, 0, getuid(), 0);
> > @@ -1021,6 +1072,25 @@ static void fs_walk_none(void *obj, void *data,
> > QGuestAllocator *t_alloc)
> > 
> >      /* special case: no QID is returned if nwname=0 was sent */
> >      g_assert(wqid == NULL);
> > +
> > +    req = v9fs_tstat(v9p, 0, 0);
> > +    v9fs_req_wait_for_reply(req, NULL);
> > +    v9fs_rstat(req, &st[0]);
> 
> Probably stat-ing the root fid (0) should happen before sending Twalk, to 
> better counter the 1st fid (0) having become potentially mutated?
> 

You already have the root qid from Rattach, no need to stat.

> > +
> > +    req = v9fs_tstat(v9p, 1, 0);
> > +    v9fs_req_wait_for_reply(req, NULL);
> > +    v9fs_rstat(req, &st[1]);
> > +
> > +    /* don't compare QID version for checking for file ID equalness */
> > +    g_assert(st[0].qid.type == st[1].qid.type);
> > +    g_assert(st[0].qid.path == st[1].qid.path);
> 
> I could add a helper function is_same_qid() for this if desired.
> 

Rgetattr provides a qid[13] like Rattach. Since we control everything,
the version bits won't change and I think is_same_qid() could be
something as simple as:

static inline bool is_same_qid(v9fs_qid qid1, v9fs_qid qid2)
{
    return memcmp(qid1, qid2, 13) == 0;
}

> > +
> > +    for (i = 0; i < 2; ++i) {
> > +        g_free(st[i].name.data);
> > +        g_free(st[i].uid.data);
> > +        g_free(st[i].gid.data);
> > +        g_free(st[i].muid.data);
> > +    }
> 
> I didn't find a more elegant way to do this cleanup.
> 

You won't need that with Tgetattr.
 
> >  }
> > 
> >  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator *t_alloc)
> 
> 



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 4/6] 9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk()
  2022-03-10  9:07   ` Christian Schoenebeck
@ 2022-03-11 16:16     ` Greg Kurz
  0 siblings, 0 replies; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 16:16 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Thu, 10 Mar 2022 10:07:04 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> On Mittwoch, 9. März 2022 18:12:17 CET Christian Schoenebeck wrote:
> > The local variable 'name_idx' is used in two loops in function v9fs_walk().
> > Let the first loop use its own variable 'nvalid' instead, which we will use
> > in subsequent patches as the number of (requested) path components
> > successfully retrieved/walked by background I/O thread.

I think walked is clear enough.

> > 
> > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > ---
> >  hw/9pfs/9p.c | 16 ++++++++--------
> >  1 file changed, 8 insertions(+), 8 deletions(-)
> > 
> > diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> > index a6d6b3f835..6cdc566866 100644
> > --- a/hw/9pfs/9p.c
> > +++ b/hw/9pfs/9p.c
> > @@ -1764,7 +1764,7 @@ static bool same_stat_id(const struct stat *a, const
> > struct stat *b)
> > 
> >  static void coroutine_fn v9fs_walk(void *opaque)
> >  {
> > -    int name_idx;
> > +    int name_idx, nvalid;
> 
> Or rather renaming this nvalid -> nfetched?
> 

or simply nwalked ?

Anyway,

Reviewed-by: Greg Kurz <groug@kaod.org>

> >      g_autofree V9fsQID *qids = NULL;
> >      int i, err = 0;
> >      V9fsPath dpath, path;
> > @@ -1842,17 +1842,17 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >              break;
> >          }
> >          stbuf = fidst;
> > -        for (name_idx = 0; name_idx < nwnames; name_idx++) {
> > +        for (nvalid = 0; nvalid < nwnames; nvalid++) {
> >              if (v9fs_request_cancelled(pdu)) {
> >                  err = -EINTR;
> >                  break;
> >              }
> >              if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
> > -                strcmp("..", wnames[name_idx].data))
> > +                strcmp("..", wnames[nvalid].data))
> >              {
> >                  err = s->ops->name_to_path(&s->ctx, &dpath,
> > -                                           wnames[name_idx].data,
> > -                                           &pathes[name_idx]);
> > +                                           wnames[nvalid].data,
> > +                                           &pathes[nvalid]);
> >                  if (err < 0) {
> >                      err = -errno;
> >                      break;
> > @@ -1861,13 +1861,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >                      err = -EINTR;
> >                      break;
> >                  }
> > -                err = s->ops->lstat(&s->ctx, &pathes[name_idx], &stbuf);
> > +                err = s->ops->lstat(&s->ctx, &pathes[nvalid], &stbuf);
> >                  if (err < 0) {
> >                      err = -errno;
> >                      break;
> >                  }
> > -                stbufs[name_idx] = stbuf;
> > -                v9fs_path_copy(&dpath, &pathes[name_idx]);
> > +                stbufs[nvalid] = stbuf;
> > +                v9fs_path_copy(&dpath, &pathes[nvalid]);
> >              }
> >          }
> >      });
> 
> 



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked
  2022-03-10  9:13   ` Christian Schoenebeck
@ 2022-03-11 16:35     ` Greg Kurz
  2022-03-11 16:44       ` Christian Schoenebeck
  0 siblings, 1 reply; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 16:35 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Thu, 10 Mar 2022 10:13:33 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> On Mittwoch, 9. März 2022 18:57:39 CET Christian Schoenebeck wrote:
> > Current implementation of 'Twalk' request handling always sends an 'Rerror'
> > response if any error occured. The 9p2000 protocol spec sais though:
> > 
> >   "
> >   If the first element cannot be walked for any reason, Rerror is returned.
> >   Otherwise, the walk will return an Rwalk message containing nwqid qids
> >   corresponding, in order, to the files that are visited by the nwqid
> >   successful elementwise walks; nwqid is therefore either nwname or the
> > index of the first elementwise walk that failed.
> >   "
> > 
> >   http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor33
> > 
> > For that reason we are no longer leaving from an error path in function
> > v9fs_walk(), unless really no path component could be walked successfully or
> > if the request has been interrupted.
> > 
> > Local variable 'nvalid' counts and reflects the number of path components
> > successfully processed by background I/O thread, whereas local variable
> > 'name_idx' subsequently counts and reflects the number of path components
> > eventually accepted successfully by 9p server controller portion.
> > 
> > New local variable 'any_err' is an aggregate variable reflecting whether any
> > error occurred at all, while already existing variable 'err' only reflects
> > the last error.
> > 
> > Despite QIDs being delivered to client in a more relaxed way now, it is
> > important to note though that fid still must remain uneffacted if any error
> 
> Typo: should be "unaffected".
> 
> > occurred.
> > 
> > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > ---
> >  hw/9pfs/9p.c | 29 +++++++++++++++++++++--------
> >  1 file changed, 21 insertions(+), 8 deletions(-)
> > 
> > diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> > index 6cdc566866..8ccd180608 100644
> > --- a/hw/9pfs/9p.c
> > +++ b/hw/9pfs/9p.c
> > @@ -1766,7 +1766,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >  {
> >      int name_idx, nvalid;
> >      g_autofree V9fsQID *qids = NULL;
> > -    int i, err = 0;
> > +    int i, err = 0, any_err = 0;
> >      V9fsPath dpath, path;
> >      P9ARRAY_REF(V9fsPath) pathes = NULL;
> >      uint16_t nwnames;
> > @@ -1832,6 +1832,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >       * driver code altogether inside the following block.
> >       */
> >      v9fs_co_run_in_worker({
> > +        nvalid = 0;
> >          if (v9fs_request_cancelled(pdu)) {
> >              err = -EINTR;
> >              break;
> > @@ -1842,7 +1843,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >              break;
> >          }
> >          stbuf = fidst;
> > -        for (nvalid = 0; nvalid < nwnames; nvalid++) {
> > +        for (; nvalid < nwnames; nvalid++) {
> >              if (v9fs_request_cancelled(pdu)) {
> >                  err = -EINTR;
> >                  break;
> > @@ -1874,12 +1875,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >      /*
> >       * Handle all the rest of this Twalk request on main thread ...
> >       */
> > -    if (err < 0) {
> > +    if ((err < 0 && !nvalid) || err == -EINTR) {
> >          goto out;
> >      }
> > 
> > +    any_err |= err;
> >      err = stat_to_qid(pdu, &fidst, &qid);
> > -    if (err < 0) {
> > +    if (err < 0 && !nvalid) {
> >          goto out;
> >      }
> >      stbuf = fidst;
> > @@ -1888,20 +1890,30 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >      v9fs_path_copy(&dpath, &fidp->path);
> >      v9fs_path_copy(&path, &fidp->path);
> > 
> > -    for (name_idx = 0; name_idx < nwnames; name_idx++) {
> > +    for (name_idx = 0; name_idx < nvalid; name_idx++) {
> >          if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
> >              strcmp("..", wnames[name_idx].data))
> >          {
> >              stbuf = stbufs[name_idx];
> >              err = stat_to_qid(pdu, &stbuf, &qid);
> >              if (err < 0) {
> > -                goto out;
> > +                break;
> >              }
> >              v9fs_path_copy(&path, &pathes[name_idx]);
> >              v9fs_path_copy(&dpath, &path);
> >          }
> >          memcpy(&qids[name_idx], &qid, sizeof(qid));
> >      }
> > +    any_err |= err;
> > +    if (any_err) {
> 
> 
> Not sure if there is ever the case err > 0, but as we are already comparing 
> for "if (err < 0)" everywhere, we should probably also do the same comparison 
> for the aggregate error variable here, right?
> 

It seems that you could drop any_err and just check name_idx != nwnames ?

>     if (any_err < 0) {
>         ...
> 
> > +        if (!name_idx) {
> > +            /* don't send any QIDs, send Rlerror instead */
> > +            goto out;
> > +        } else {
> > +            /* send QIDs (not Rlerror), but fid MUST remain unaffected */
> > +            goto send_qids;
> > +        }
> > +    }
> >      if (fid == newfid) {
> >          if (fidp->fid_type != P9_FID_NONE) {
> >              err = -EINVAL;
> > @@ -1919,8 +1931,9 @@ static void coroutine_fn v9fs_walk(void *opaque)
> >          newfidp->uid = fidp->uid;
> >          v9fs_path_copy(&newfidp->path, &path);
> >      }
> > -    err = v9fs_walk_marshal(pdu, nwnames, qids);
> > -    trace_v9fs_walk_return(pdu->tag, pdu->id, nwnames, qids);
> > +send_qids:
> > +    err = v9fs_walk_marshal(pdu, name_idx, qids);
> > +    trace_v9fs_walk_return(pdu->tag, pdu->id, name_idx, qids);
> >  out:
> >      put_fid(pdu, fidp);
> >      if (newfidp) {
> 
> 



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test
  2022-03-11 16:11     ` Greg Kurz
@ 2022-03-11 16:39       ` Christian Schoenebeck
  2022-03-11 17:02         ` Greg Kurz
  0 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-11 16:39 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Freitag, 11. März 2022 17:11:24 CET Greg Kurz wrote:
> On Thu, 10 Mar 2022 10:04:50 +0100
> 
> Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > On Mittwoch, 9. März 2022 15:49:04 CET Christian Schoenebeck wrote:
> > > Extend previously added fs_walk_none() test by comparing the QID
> > > of the root fid with the QID of the cloned fid. They should be
> > > equal.
> 
> Ha, I understand your suggestion of changing the name now :-) but I'll
> personally leave it named according to the test scenario of "sending
> a Twalk with no names" and checking everything that is expected in this
> case.

NP

> > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > ---
> > > 
> > >  tests/qtest/virtio-9p-test.c | 70 ++++++++++++++++++++++++++++++++++++
> > >  1 file changed, 70 insertions(+)
> > > 
> > > diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> > > index 6c00da03f4..9098e21173 100644
> > > --- a/tests/qtest/virtio-9p-test.c
> > > +++ b/tests/qtest/virtio-9p-test.c
> > > @@ -146,6 +146,11 @@ static void v9fs_uint16_read(P9Req *req, uint16_t
> > > *val) le16_to_cpus(val);
> > > 
> > >  }
> > > 
> > > +static void v9fs_int16_read(P9Req *req, int16_t *val)
> > > +{
> > > +    v9fs_uint16_read(req, (uint16_t *)val);
> > > +}
> > > +
> > > 
> > >  static void v9fs_uint32_write(P9Req *req, uint32_t val)
> > >  {
> > >  
> > >      uint32_t le_val = cpu_to_le32(val);
> > > 
> > > @@ -166,12 +171,22 @@ static void v9fs_uint32_read(P9Req *req, uint32_t
> > > *val) le32_to_cpus(val);
> > > 
> > >  }
> > > 
> > > +static void v9fs_int32_read(P9Req *req, int32_t *val)
> > > +{
> > > +    v9fs_uint32_read(req, (uint32_t *)val);
> > > +}
> > > +
> > > 
> > >  static void v9fs_uint64_read(P9Req *req, uint64_t *val)
> > >  {
> > >  
> > >      v9fs_memread(req, val, 8);
> > >      le64_to_cpus(val);
> > >  
> > >  }
> > > 
> > > +static void v9fs_int64_read(P9Req *req, int64_t *val)
> > > +{
> > > +    v9fs_uint64_read(req, (uint64_t *)val);
> > > +}
> > > +
> > > 
> > >  /* len[2] string[len] */
> > >  static uint16_t v9fs_string_size(const char *string)
> > >  {
> > > 
> > > @@ -425,6 +440,40 @@ static void v9fs_rwalk(P9Req *req, uint16_t *nwqid,
> > > v9fs_qid **wqid) v9fs_req_free(req);
> > > 
> > >  }
> > > 
> > > +/* size[4] Tstat tag[2] fid[4] */
> > > +static P9Req *v9fs_tstat(QVirtio9P *v9p, uint32_t fid, uint16_t tag)
> 
> Tstat/Rstat aren't part of 9p2000.L, you should use Tgetattr/Rgetattr
> instead (see https://github.com/chaos/diod/blob/master/protocol.md).

Ah right, I forgot.

> > > +{
> > > +    P9Req *req;
> > > +
> > > +    req = v9fs_req_init(v9p, 4, P9_TSTAT, tag);
> > > +    v9fs_uint32_write(req, fid);
> > > +    v9fs_req_send(req);
> > > +    return req;
> > > +}
> > > +
> > > +/* size[4] Rstat tag[2] stat[n] */
> > > +static void v9fs_rstat(P9Req *req, struct V9fsStat *st)
> > > +{
> > > +    v9fs_req_recv(req, P9_RSTAT);
> > > +
> 
> For the records, this is a stat[n], i.e. "n[2] followed by n bytes of
> data forming the parameter", so you should read an uint16_t first.
> 
> > > +    v9fs_int16_read(req, &st->size);

Which I did here? --^

> > > +    v9fs_int16_read(req, &st->type);
> > > +    v9fs_int32_read(req, &st->dev);
> > > +    v9fs_uint8_read(req, &st->qid.type);
> > > +    v9fs_uint32_read(req, &st->qid.version);
> > > +    v9fs_uint64_read(req, &st->qid.path);
> > > +    v9fs_int32_read(req, &st->mode);
> > > +    v9fs_int32_read(req, &st->mtime);
> > > +    v9fs_int32_read(req, &st->atime);
> > > +    v9fs_int64_read(req, &st->length);
> > > +    v9fs_string_read(req, &st->name.size, &st->name.data);
> > > +    v9fs_string_read(req, &st->uid.size, &st->uid.data);
> > > +    v9fs_string_read(req, &st->gid.size, &st->gid.data);
> > > +    v9fs_string_read(req, &st->muid.size, &st->muid.data);
> > > +
> > > +    v9fs_req_free(req);
> > > +}
> > > +
> > > 
> > >  /* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */
> > >  static P9Req *v9fs_treaddir(QVirtio9P *v9p, uint32_t fid, uint64_t
> > >  offset,
> > >  
> > >                              uint32_t count, uint16_t tag)
> > > 
> > > @@ -1009,6 +1058,8 @@ static void fs_walk_none(void *obj, void *data,
> > > QGuestAllocator *t_alloc) v9fs_qid root_qid;
> > > 
> > >      g_autofree v9fs_qid *wqid = NULL;
> > >      P9Req *req;
> > > 
> > > +    struct V9fsStat st[2];
> > > +    int i;
> > > 
> > >      do_version(v9p);
> > >      req = v9fs_tattach(v9p, 0, getuid(), 0);
> > > 
> > > @@ -1021,6 +1072,25 @@ static void fs_walk_none(void *obj, void *data,
> > > QGuestAllocator *t_alloc)
> > > 
> > >      /* special case: no QID is returned if nwname=0 was sent */
> > >      g_assert(wqid == NULL);
> > > 
> > > +
> > > +    req = v9fs_tstat(v9p, 0, 0);
> > > +    v9fs_req_wait_for_reply(req, NULL);
> > > +    v9fs_rstat(req, &st[0]);
> > 
> > Probably stat-ing the root fid (0) should happen before sending Twalk, to
> > better counter the 1st fid (0) having become potentially mutated?
> 
> You already have the root qid from Rattach, no need to stat.

Yes, this was about easy comparison with qid.version in mind, i.e. ...

> 
> > > +
> > > +    req = v9fs_tstat(v9p, 1, 0);
> > > +    v9fs_req_wait_for_reply(req, NULL);
> > > +    v9fs_rstat(req, &st[1]);
> > > +
> > > +    /* don't compare QID version for checking for file ID equalness */
> > > +    g_assert(st[0].qid.type == st[1].qid.type);
> > > +    g_assert(st[0].qid.path == st[1].qid.path);
> > 
> > I could add a helper function is_same_qid() for this if desired.
> 
> Rgetattr provides a qid[13] like Rattach. Since we control everything,
> the version bits won't change and I think is_same_qid() could be
> something as simple as:
> 
> static inline bool is_same_qid(v9fs_qid qid1, v9fs_qid qid2)
> {
>     return memcmp(qid1, qid2, 13) == 0;
> }

Yes I know, the version definitely won't change with the synth driver. But I 
thought to add code so it could be used for 'local' driver tests as well in 
future.

> > > +
> > > +    for (i = 0; i < 2; ++i) {
> > > +        g_free(st[i].name.data);
> > > +        g_free(st[i].uid.data);
> > > +        g_free(st[i].gid.data);
> > > +        g_free(st[i].muid.data);
> > > +    }
> > 
> > I didn't find a more elegant way to do this cleanup.
> 
> You won't need that with Tgetattr.
> 
> > >  }
> > >  
> > >  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator
> > >  *t_alloc)




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix
  2022-03-09 18:21 ` [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix Christian Schoenebeck
  2022-03-11 10:32   ` Christian Schoenebeck
@ 2022-03-11 16:40   ` Greg Kurz
  1 sibling, 0 replies; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 16:40 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Wed, 9 Mar 2022 19:21:18 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> Previous 9p patch fixed 'Twalk' request handling, which was previously not
> behaving as specified by the 9p2000 protocol spec. This patch adds a new test
> case which guards the new 'Twalk' behaviour in question.
> 
> More specifically: it sends a 'Twalk' request where the 1st path component
> is valid, whereas the 2nd path component transmitted to server does not
> exist. The expected behaviour is that 9p server would respond by sending
> a 'Rwalk' response with exactly 1 QID (instead of 'Rlerror' response).
> 
> Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> ---
>  tests/qtest/virtio-9p-test.c | 42 +++++++++++++++++++++++++++++++++---
>  1 file changed, 39 insertions(+), 3 deletions(-)
> 
> diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> index 9098e21173..f29de1ca64 100644
> --- a/tests/qtest/virtio-9p-test.c
> +++ b/tests/qtest/virtio-9p-test.c
> @@ -638,8 +638,12 @@ static void do_version(QVirtio9P *v9p)
>      g_assert_cmpmem(server_version, server_len, version, strlen(version));
>  }
>  
> -/* utility function: walk to requested dir and return fid for that dir */
> -static uint32_t do_walk(QVirtio9P *v9p, const char *path)
> +/*
> + * utility function: walk to requested dir and return fid for that dir and
> + * the QIDs of server response
> + */
> +static uint32_t do_walk_rqids(QVirtio9P *v9p, const char *path, uint16_t *nwqid,
> +                              v9fs_qid **wqid)
>  {
>      char **wnames;
>      P9Req *req;
> @@ -649,12 +653,18 @@ static uint32_t do_walk(QVirtio9P *v9p, const char *path)
>  
>      req = v9fs_twalk(v9p, 0, fid, nwnames, wnames, 0);
>      v9fs_req_wait_for_reply(req, NULL);
> -    v9fs_rwalk(req, NULL, NULL);
> +    v9fs_rwalk(req, nwqid, wqid);
>  
>      split_free(&wnames);
>      return fid;
>  }
>  
> +/* utility function: walk to requested dir and return fid for that dir */
> +static uint32_t do_walk(QVirtio9P *v9p, const char *path)
> +{
> +    return do_walk_rqids(v9p, path, NULL, NULL);
> +}
> +
>  /* utility function: walk to requested dir and expect passed error response */
>  static void do_walk_expect_error(QVirtio9P *v9p, const char *path, uint32_t err)
>  {
> @@ -1048,9 +1058,33 @@ static void fs_walk_nonexistent(void *obj, void *data, QGuestAllocator *t_alloc)
>      alloc = t_alloc;
>  
>      do_attach(v9p);
> +    /*
> +     * The 9p2000 protocol spec sais: "If the first element cannot be walked

s/sais/says/ and elsewhere

LGTM.

Reviewed-by: Greg Kurz <groug@kaod.org>

> +     * for any reason, Rerror is returned."
> +     */
>      do_walk_expect_error(v9p, "non-existent", ENOENT);
>  }
>  
> +static void fs_walk_2nd_nonexistent(void *obj, void *data,
> +                                    QGuestAllocator *t_alloc)
> +{
> +    QVirtio9P *v9p = obj;
> +    alloc = t_alloc;
> +    uint16_t nwqid;
> +    g_autofree v9fs_qid *wqid = NULL;
> +    g_autofree char *path = g_strdup_printf(
> +        QTEST_V9FS_SYNTH_WALK_FILE "/non-existent", 0
> +    );
> +
> +    do_attach(v9p);
> +    do_walk_rqids(v9p, path, &nwqid, &wqid);
> +    /*
> +     * The 9p2000 protocol spec sais: "nwqid is therefore either nwname or the
> +     * index of the first elementwise walk that failed."
> +     */
> +    assert(nwqid == 1);
> +}
> +
>  static void fs_walk_none(void *obj, void *data, QGuestAllocator *t_alloc)
>  {
>      QVirtio9P *v9p = obj;
> @@ -1531,6 +1565,8 @@ static void register_virtio_9p_test(void)
>                   fs_walk_dotdot,  &opts);
>      qos_add_test("synth/walk/non_existent", "virtio-9p", fs_walk_nonexistent,
>                    &opts);
> +    qos_add_test("synth/walk/2nd_non_existent", "virtio-9p",
> +                 fs_walk_2nd_nonexistent, &opts);
>      qos_add_test("synth/lopen/basic", "virtio-9p", fs_lopen,  &opts);
>      qos_add_test("synth/write/basic", "virtio-9p", fs_write,  &opts);
>      qos_add_test("synth/flush/success", "virtio-9p", fs_flush_success,



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked
  2022-03-11 16:35     ` Greg Kurz
@ 2022-03-11 16:44       ` Christian Schoenebeck
  2022-03-11 17:08         ` Greg Kurz
  0 siblings, 1 reply; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-11 16:44 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Freitag, 11. März 2022 17:35:41 CET Greg Kurz wrote:
> On Thu, 10 Mar 2022 10:13:33 +0100
> 
> Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > On Mittwoch, 9. März 2022 18:57:39 CET Christian Schoenebeck wrote:
> > > Current implementation of 'Twalk' request handling always sends an
> > > 'Rerror'
> > > 
> > > response if any error occured. The 9p2000 protocol spec sais though:
> > >   "
> > >   If the first element cannot be walked for any reason, Rerror is
> > >   returned.
> > >   Otherwise, the walk will return an Rwalk message containing nwqid qids
> > >   corresponding, in order, to the files that are visited by the nwqid
> > >   successful elementwise walks; nwqid is therefore either nwname or the
> > > 
> > > index of the first elementwise walk that failed.
> > > 
> > >   "
> > >   
> > >   http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor33
> > > 
> > > For that reason we are no longer leaving from an error path in function
> > > v9fs_walk(), unless really no path component could be walked
> > > successfully or if the request has been interrupted.
> > > 
> > > Local variable 'nvalid' counts and reflects the number of path
> > > components
> > > successfully processed by background I/O thread, whereas local variable
> > > 'name_idx' subsequently counts and reflects the number of path
> > > components
> > > eventually accepted successfully by 9p server controller portion.
> > > 
> > > New local variable 'any_err' is an aggregate variable reflecting whether
> > > any error occurred at all, while already existing variable 'err' only
> > > reflects the last error.
> > > 
> > > Despite QIDs being delivered to client in a more relaxed way now, it is
> > > important to note though that fid still must remain uneffacted if any
> > > error
> > 
> > Typo: should be "unaffected".
> > 
> > > occurred.
> > > 
> > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > ---
> > > 
> > >  hw/9pfs/9p.c | 29 +++++++++++++++++++++--------
> > >  1 file changed, 21 insertions(+), 8 deletions(-)
> > > 
> > > diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> > > index 6cdc566866..8ccd180608 100644
> > > --- a/hw/9pfs/9p.c
> > > +++ b/hw/9pfs/9p.c
> > > @@ -1766,7 +1766,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > 
> > >  {
> > >  
> > >      int name_idx, nvalid;
> > >      g_autofree V9fsQID *qids = NULL;
> > > 
> > > -    int i, err = 0;
> > > +    int i, err = 0, any_err = 0;
> > > 
> > >      V9fsPath dpath, path;
> > >      P9ARRAY_REF(V9fsPath) pathes = NULL;
> > >      uint16_t nwnames;
> > > 
> > > @@ -1832,6 +1832,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > 
> > >       * driver code altogether inside the following block.
> > >       */
> > >      
> > >      v9fs_co_run_in_worker({
> > > 
> > > +        nvalid = 0;
> > > 
> > >          if (v9fs_request_cancelled(pdu)) {
> > >          
> > >              err = -EINTR;
> > >              break;
> > > 
> > > @@ -1842,7 +1843,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > 
> > >              break;
> > >          
> > >          }
> > >          stbuf = fidst;
> > > 
> > > -        for (nvalid = 0; nvalid < nwnames; nvalid++) {
> > > +        for (; nvalid < nwnames; nvalid++) {
> > > 
> > >              if (v9fs_request_cancelled(pdu)) {
> > >              
> > >                  err = -EINTR;
> > >                  break;
> > > 
> > > @@ -1874,12 +1875,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > 
> > >      /*
> > >      
> > >       * Handle all the rest of this Twalk request on main thread ...
> > >       */
> > > 
> > > -    if (err < 0) {
> > > +    if ((err < 0 && !nvalid) || err == -EINTR) {
> > > 
> > >          goto out;
> > >      
> > >      }
> > > 
> > > +    any_err |= err;
> > > 
> > >      err = stat_to_qid(pdu, &fidst, &qid);
> > > 
> > > -    if (err < 0) {
> > > +    if (err < 0 && !nvalid) {
> > > 
> > >          goto out;
> > >      
> > >      }
> > >      stbuf = fidst;
> > > 
> > > @@ -1888,20 +1890,30 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > 
> > >      v9fs_path_copy(&dpath, &fidp->path);
> > >      v9fs_path_copy(&path, &fidp->path);
> > > 
> > > -    for (name_idx = 0; name_idx < nwnames; name_idx++) {
> > > +    for (name_idx = 0; name_idx < nvalid; name_idx++) {
> > > 
> > >          if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
> > >          
> > >              strcmp("..", wnames[name_idx].data))
> > >          
> > >          {
> > >          
> > >              stbuf = stbufs[name_idx];
> > >              err = stat_to_qid(pdu, &stbuf, &qid);
> > >              if (err < 0) {
> > > 
> > > -                goto out;
> > > +                break;
> > > 
> > >              }
> > >              v9fs_path_copy(&path, &pathes[name_idx]);
> > >              v9fs_path_copy(&dpath, &path);
> > >          
> > >          }
> > >          memcpy(&qids[name_idx], &qid, sizeof(qid));
> > >      
> > >      }
> > > 
> > > +    any_err |= err;
> > > +    if (any_err) {
> > 
> > Not sure if there is ever the case err > 0, but as we are already
> > comparing
> > for "if (err < 0)" everywhere, we should probably also do the same
> > comparison for the aggregate error variable here, right?
> 
> It seems that you could drop any_err and just check name_idx != nwnames ?

Mmm, what about the special case 'Twalk nwnames=0' (i.e. fid cloning), that 
implementation would then skip errors, no?

> >     if (any_err < 0) {
> >     
> >         ...
> > > 
> > > +        if (!name_idx) {
> > > +            /* don't send any QIDs, send Rlerror instead */
> > > +            goto out;
> > > +        } else {
> > > +            /* send QIDs (not Rlerror), but fid MUST remain unaffected
> > > */
> > > +            goto send_qids;
> > > +        }
> > > +    }
> > > 
> > >      if (fid == newfid) {
> > >      
> > >          if (fidp->fid_type != P9_FID_NONE) {
> > >          
> > >              err = -EINVAL;
> > > 
> > > @@ -1919,8 +1931,9 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > 
> > >          newfidp->uid = fidp->uid;
> > >          v9fs_path_copy(&newfidp->path, &path);
> > >      
> > >      }
> > > 
> > > -    err = v9fs_walk_marshal(pdu, nwnames, qids);
> > > -    trace_v9fs_walk_return(pdu->tag, pdu->id, nwnames, qids);
> > > +send_qids:
> > > +    err = v9fs_walk_marshal(pdu, name_idx, qids);
> > > +    trace_v9fs_walk_return(pdu->tag, pdu->id, name_idx, qids);
> > > 
> > >  out:
> > >      put_fid(pdu, fidp);
> > >      if (newfidp) {




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test
  2022-03-11 16:39       ` Christian Schoenebeck
@ 2022-03-11 17:02         ` Greg Kurz
  2022-03-11 17:23           ` Christian Schoenebeck
  0 siblings, 1 reply; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 17:02 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Fri, 11 Mar 2022 17:39:56 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> On Freitag, 11. März 2022 17:11:24 CET Greg Kurz wrote:
> > On Thu, 10 Mar 2022 10:04:50 +0100
> > 
> > Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > > On Mittwoch, 9. März 2022 15:49:04 CET Christian Schoenebeck wrote:
> > > > Extend previously added fs_walk_none() test by comparing the QID
> > > > of the root fid with the QID of the cloned fid. They should be
> > > > equal.
> > 
> > Ha, I understand your suggestion of changing the name now :-) but I'll
> > personally leave it named according to the test scenario of "sending
> > a Twalk with no names" and checking everything that is expected in this
> > case.
> 
> NP
> 
> > > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > > ---
> > > > 
> > > >  tests/qtest/virtio-9p-test.c | 70 ++++++++++++++++++++++++++++++++++++
> > > >  1 file changed, 70 insertions(+)
> > > > 
> > > > diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
> > > > index 6c00da03f4..9098e21173 100644
> > > > --- a/tests/qtest/virtio-9p-test.c
> > > > +++ b/tests/qtest/virtio-9p-test.c
> > > > @@ -146,6 +146,11 @@ static void v9fs_uint16_read(P9Req *req, uint16_t
> > > > *val) le16_to_cpus(val);
> > > > 
> > > >  }
> > > > 
> > > > +static void v9fs_int16_read(P9Req *req, int16_t *val)
> > > > +{
> > > > +    v9fs_uint16_read(req, (uint16_t *)val);
> > > > +}
> > > > +
> > > > 
> > > >  static void v9fs_uint32_write(P9Req *req, uint32_t val)
> > > >  {
> > > >  
> > > >      uint32_t le_val = cpu_to_le32(val);
> > > > 
> > > > @@ -166,12 +171,22 @@ static void v9fs_uint32_read(P9Req *req, uint32_t
> > > > *val) le32_to_cpus(val);
> > > > 
> > > >  }
> > > > 
> > > > +static void v9fs_int32_read(P9Req *req, int32_t *val)
> > > > +{
> > > > +    v9fs_uint32_read(req, (uint32_t *)val);
> > > > +}
> > > > +
> > > > 
> > > >  static void v9fs_uint64_read(P9Req *req, uint64_t *val)
> > > >  {
> > > >  
> > > >      v9fs_memread(req, val, 8);
> > > >      le64_to_cpus(val);
> > > >  
> > > >  }
> > > > 
> > > > +static void v9fs_int64_read(P9Req *req, int64_t *val)
> > > > +{
> > > > +    v9fs_uint64_read(req, (uint64_t *)val);
> > > > +}
> > > > +
> > > > 
> > > >  /* len[2] string[len] */
> > > >  static uint16_t v9fs_string_size(const char *string)
> > > >  {
> > > > 
> > > > @@ -425,6 +440,40 @@ static void v9fs_rwalk(P9Req *req, uint16_t *nwqid,
> > > > v9fs_qid **wqid) v9fs_req_free(req);
> > > > 
> > > >  }
> > > > 
> > > > +/* size[4] Tstat tag[2] fid[4] */
> > > > +static P9Req *v9fs_tstat(QVirtio9P *v9p, uint32_t fid, uint16_t tag)
> > 
> > Tstat/Rstat aren't part of 9p2000.L, you should use Tgetattr/Rgetattr
> > instead (see https://github.com/chaos/diod/blob/master/protocol.md).
> 
> Ah right, I forgot.
> 
> > > > +{
> > > > +    P9Req *req;
> > > > +
> > > > +    req = v9fs_req_init(v9p, 4, P9_TSTAT, tag);
> > > > +    v9fs_uint32_write(req, fid);
> > > > +    v9fs_req_send(req);
> > > > +    return req;
> > > > +}
> > > > +
> > > > +/* size[4] Rstat tag[2] stat[n] */
> > > > +static void v9fs_rstat(P9Req *req, struct V9fsStat *st)
> > > > +{
> > > > +    v9fs_req_recv(req, P9_RSTAT);
> > > > +
> > 
> > For the records, this is a stat[n], i.e. "n[2] followed by n bytes of
> > data forming the parameter", so you should read an uint16_t first.
> > 
> > > > +    v9fs_int16_read(req, &st->size);
> 
> Which I did here? --^
> 

From the BUGS section of https://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor32 :

     BUGS
          To make the contents of a directory, such as returned by
          read(5), easy to parse, each directory entry begins with a
          size field.  For consistency, the entries in Twstat and
          Rstat messages also contain their size, which means the size
                                                                  ^^^^
          appears twice.  For example, the Rstat message is formatted
          ^^^^^^^^^^^^^
          as ``(4+1+2+2+n)[4] Rstat tag[2] n[2] (n-2)[2] type[2]
          dev[4]...,'' where n is the value returned by Styx->packdir.

I realized that when giving a try to convert a v9fs_qid to a V9fsQID on
top of this patch.

> > > > +    v9fs_int16_read(req, &st->type);
> > > > +    v9fs_int32_read(req, &st->dev);
> > > > +    v9fs_uint8_read(req, &st->qid.type);
> > > > +    v9fs_uint32_read(req, &st->qid.version);
> > > > +    v9fs_uint64_read(req, &st->qid.path);
> > > > +    v9fs_int32_read(req, &st->mode);
> > > > +    v9fs_int32_read(req, &st->mtime);
> > > > +    v9fs_int32_read(req, &st->atime);
> > > > +    v9fs_int64_read(req, &st->length);
> > > > +    v9fs_string_read(req, &st->name.size, &st->name.data);
> > > > +    v9fs_string_read(req, &st->uid.size, &st->uid.data);
> > > > +    v9fs_string_read(req, &st->gid.size, &st->gid.data);
> > > > +    v9fs_string_read(req, &st->muid.size, &st->muid.data);
> > > > +
> > > > +    v9fs_req_free(req);
> > > > +}
> > > > +
> > > > 
> > > >  /* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */
> > > >  static P9Req *v9fs_treaddir(QVirtio9P *v9p, uint32_t fid, uint64_t
> > > >  offset,
> > > >  
> > > >                              uint32_t count, uint16_t tag)
> > > > 
> > > > @@ -1009,6 +1058,8 @@ static void fs_walk_none(void *obj, void *data,
> > > > QGuestAllocator *t_alloc) v9fs_qid root_qid;
> > > > 
> > > >      g_autofree v9fs_qid *wqid = NULL;
> > > >      P9Req *req;
> > > > 
> > > > +    struct V9fsStat st[2];
> > > > +    int i;
> > > > 
> > > >      do_version(v9p);
> > > >      req = v9fs_tattach(v9p, 0, getuid(), 0);
> > > > 
> > > > @@ -1021,6 +1072,25 @@ static void fs_walk_none(void *obj, void *data,
> > > > QGuestAllocator *t_alloc)
> > > > 
> > > >      /* special case: no QID is returned if nwname=0 was sent */
> > > >      g_assert(wqid == NULL);
> > > > 
> > > > +
> > > > +    req = v9fs_tstat(v9p, 0, 0);
> > > > +    v9fs_req_wait_for_reply(req, NULL);
> > > > +    v9fs_rstat(req, &st[0]);
> > > 
> > > Probably stat-ing the root fid (0) should happen before sending Twalk, to
> > > better counter the 1st fid (0) having become potentially mutated?
> > 
> > You already have the root qid from Rattach, no need to stat.
> 
> Yes, this was about easy comparison with qid.version in mind, i.e. ...
> 
> > 
> > > > +
> > > > +    req = v9fs_tstat(v9p, 1, 0);
> > > > +    v9fs_req_wait_for_reply(req, NULL);
> > > > +    v9fs_rstat(req, &st[1]);
> > > > +
> > > > +    /* don't compare QID version for checking for file ID equalness */
> > > > +    g_assert(st[0].qid.type == st[1].qid.type);
> > > > +    g_assert(st[0].qid.path == st[1].qid.path);
> > > 
> > > I could add a helper function is_same_qid() for this if desired.
> > 
> > Rgetattr provides a qid[13] like Rattach. Since we control everything,
> > the version bits won't change and I think is_same_qid() could be
> > something as simple as:
> > 
> > static inline bool is_same_qid(v9fs_qid qid1, v9fs_qid qid2)
> > {
> >     return memcmp(qid1, qid2, 13) == 0;
> > }
> 
> Yes I know, the version definitely won't change with the synth driver. But I 
> thought to add code so it could be used for 'local' driver tests as well in 
> future.
> 

FWIW, even when using local, only an external cause could do that, which
would mean that the test environment is compromised, no ?

You can also ignore version and just compare the first byte and the 8 last
ones.

> > > > +
> > > > +    for (i = 0; i < 2; ++i) {
> > > > +        g_free(st[i].name.data);
> > > > +        g_free(st[i].uid.data);
> > > > +        g_free(st[i].gid.data);
> > > > +        g_free(st[i].muid.data);
> > > > +    }
> > > 
> > > I didn't find a more elegant way to do this cleanup.
> > 
> > You won't need that with Tgetattr.
> > 
> > > >  }
> > > >  
> > > >  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator
> > > >  *t_alloc)
> 
> 



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked
  2022-03-11 16:44       ` Christian Schoenebeck
@ 2022-03-11 17:08         ` Greg Kurz
  2022-03-11 17:36           ` Christian Schoenebeck
  0 siblings, 1 reply; 25+ messages in thread
From: Greg Kurz @ 2022-03-11 17:08 UTC (permalink / raw)
  To: Christian Schoenebeck; +Cc: qemu-devel

On Fri, 11 Mar 2022 17:44:54 +0100
Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:

> On Freitag, 11. März 2022 17:35:41 CET Greg Kurz wrote:
> > On Thu, 10 Mar 2022 10:13:33 +0100
> > 
> > Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > > On Mittwoch, 9. März 2022 18:57:39 CET Christian Schoenebeck wrote:
> > > > Current implementation of 'Twalk' request handling always sends an
> > > > 'Rerror'
> > > > 
> > > > response if any error occured. The 9p2000 protocol spec sais though:
> > > >   "
> > > >   If the first element cannot be walked for any reason, Rerror is
> > > >   returned.
> > > >   Otherwise, the walk will return an Rwalk message containing nwqid qids
> > > >   corresponding, in order, to the files that are visited by the nwqid
> > > >   successful elementwise walks; nwqid is therefore either nwname or the
> > > > 
> > > > index of the first elementwise walk that failed.
> > > > 
> > > >   "
> > > >   
> > > >   http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor33
> > > > 
> > > > For that reason we are no longer leaving from an error path in function
> > > > v9fs_walk(), unless really no path component could be walked
> > > > successfully or if the request has been interrupted.
> > > > 
> > > > Local variable 'nvalid' counts and reflects the number of path
> > > > components
> > > > successfully processed by background I/O thread, whereas local variable
> > > > 'name_idx' subsequently counts and reflects the number of path
> > > > components
> > > > eventually accepted successfully by 9p server controller portion.
> > > > 
> > > > New local variable 'any_err' is an aggregate variable reflecting whether
> > > > any error occurred at all, while already existing variable 'err' only
> > > > reflects the last error.
> > > > 
> > > > Despite QIDs being delivered to client in a more relaxed way now, it is
> > > > important to note though that fid still must remain uneffacted if any
> > > > error
> > > 
> > > Typo: should be "unaffected".
> > > 
> > > > occurred.
> > > > 
> > > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > > ---
> > > > 
> > > >  hw/9pfs/9p.c | 29 +++++++++++++++++++++--------
> > > >  1 file changed, 21 insertions(+), 8 deletions(-)
> > > > 
> > > > diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> > > > index 6cdc566866..8ccd180608 100644
> > > > --- a/hw/9pfs/9p.c
> > > > +++ b/hw/9pfs/9p.c
> > > > @@ -1766,7 +1766,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > > 
> > > >  {
> > > >  
> > > >      int name_idx, nvalid;
> > > >      g_autofree V9fsQID *qids = NULL;
> > > > 
> > > > -    int i, err = 0;
> > > > +    int i, err = 0, any_err = 0;
> > > > 
> > > >      V9fsPath dpath, path;
> > > >      P9ARRAY_REF(V9fsPath) pathes = NULL;
> > > >      uint16_t nwnames;
> > > > 
> > > > @@ -1832,6 +1832,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > > 
> > > >       * driver code altogether inside the following block.
> > > >       */
> > > >      
> > > >      v9fs_co_run_in_worker({
> > > > 
> > > > +        nvalid = 0;
> > > > 
> > > >          if (v9fs_request_cancelled(pdu)) {
> > > >          
> > > >              err = -EINTR;
> > > >              break;
> > > > 
> > > > @@ -1842,7 +1843,7 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > > 
> > > >              break;
> > > >          
> > > >          }
> > > >          stbuf = fidst;
> > > > 
> > > > -        for (nvalid = 0; nvalid < nwnames; nvalid++) {
> > > > +        for (; nvalid < nwnames; nvalid++) {
> > > > 
> > > >              if (v9fs_request_cancelled(pdu)) {
> > > >              
> > > >                  err = -EINTR;
> > > >                  break;
> > > > 
> > > > @@ -1874,12 +1875,13 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > > 
> > > >      /*
> > > >      
> > > >       * Handle all the rest of this Twalk request on main thread ...
> > > >       */
> > > > 
> > > > -    if (err < 0) {
> > > > +    if ((err < 0 && !nvalid) || err == -EINTR) {
> > > > 
> > > >          goto out;
> > > >      
> > > >      }
> > > > 
> > > > +    any_err |= err;
> > > > 
> > > >      err = stat_to_qid(pdu, &fidst, &qid);
> > > > 
> > > > -    if (err < 0) {
> > > > +    if (err < 0 && !nvalid) {
> > > > 
> > > >          goto out;
> > > >      
> > > >      }
> > > >      stbuf = fidst;
> > > > 
> > > > @@ -1888,20 +1890,30 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > > 
> > > >      v9fs_path_copy(&dpath, &fidp->path);
> > > >      v9fs_path_copy(&path, &fidp->path);
> > > > 
> > > > -    for (name_idx = 0; name_idx < nwnames; name_idx++) {
> > > > +    for (name_idx = 0; name_idx < nvalid; name_idx++) {
> > > > 
> > > >          if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
> > > >          
> > > >              strcmp("..", wnames[name_idx].data))
> > > >          
> > > >          {
> > > >          
> > > >              stbuf = stbufs[name_idx];
> > > >              err = stat_to_qid(pdu, &stbuf, &qid);
> > > >              if (err < 0) {
> > > > 
> > > > -                goto out;
> > > > +                break;
> > > > 
> > > >              }
> > > >              v9fs_path_copy(&path, &pathes[name_idx]);
> > > >              v9fs_path_copy(&dpath, &path);
> > > >          
> > > >          }
> > > >          memcpy(&qids[name_idx], &qid, sizeof(qid));
> > > >      
> > > >      }
> > > > 
> > > > +    any_err |= err;
> > > > +    if (any_err) {
> > > 
> > > Not sure if there is ever the case err > 0, but as we are already
> > > comparing
> > > for "if (err < 0)" everywhere, we should probably also do the same
> > > comparison for the aggregate error variable here, right?
> > 
> > It seems that you could drop any_err and just check name_idx != nwnames ?
> 
> Mmm, what about the special case 'Twalk nwnames=0' (i.e. fid cloning), that 
> implementation would then skip errors, no?
> 

Ouch you're right... honestly, v9fs_walk() is really a mess and it
is getting harder to fix. What about having a totally separate
path for the cloning case (as a preparatory patch) ?

> > >     if (any_err < 0) {
> > >     
> > >         ...
> > > > 
> > > > +        if (!name_idx) {
> > > > +            /* don't send any QIDs, send Rlerror instead */
> > > > +            goto out;
> > > > +        } else {
> > > > +            /* send QIDs (not Rlerror), but fid MUST remain unaffected
> > > > */
> > > > +            goto send_qids;
> > > > +        }
> > > > +    }
> > > > 
> > > >      if (fid == newfid) {
> > > >      
> > > >          if (fidp->fid_type != P9_FID_NONE) {
> > > >          
> > > >              err = -EINVAL;
> > > > 
> > > > @@ -1919,8 +1931,9 @@ static void coroutine_fn v9fs_walk(void *opaque)
> > > > 
> > > >          newfidp->uid = fidp->uid;
> > > >          v9fs_path_copy(&newfidp->path, &path);
> > > >      
> > > >      }
> > > > 
> > > > -    err = v9fs_walk_marshal(pdu, nwnames, qids);
> > > > -    trace_v9fs_walk_return(pdu->tag, pdu->id, nwnames, qids);
> > > > +send_qids:
> > > > +    err = v9fs_walk_marshal(pdu, name_idx, qids);
> > > > +    trace_v9fs_walk_return(pdu->tag, pdu->id, name_idx, qids);
> > > > 
> > > >  out:
> > > >      put_fid(pdu, fidp);
> > > >      if (newfidp) {
> 
> 



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test
  2022-03-11 17:02         ` Greg Kurz
@ 2022-03-11 17:23           ` Christian Schoenebeck
  0 siblings, 0 replies; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-11 17:23 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Freitag, 11. März 2022 18:02:36 CET Greg Kurz wrote:
> On Fri, 11 Mar 2022 17:39:56 +0100
> 
> Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > On Freitag, 11. März 2022 17:11:24 CET Greg Kurz wrote:
> > > On Thu, 10 Mar 2022 10:04:50 +0100
> > > 
> > > Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > > > On Mittwoch, 9. März 2022 15:49:04 CET Christian Schoenebeck wrote:
> > > > > Extend previously added fs_walk_none() test by comparing the QID
> > > > > of the root fid with the QID of the cloned fid. They should be
> > > > > equal.
> > > 
> > > Ha, I understand your suggestion of changing the name now :-) but I'll
> > > personally leave it named according to the test scenario of "sending
> > > a Twalk with no names" and checking everything that is expected in this
> > > case.
> > 
> > NP
> > 
> > > > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > > > ---
> > > > > 
> > > > >  tests/qtest/virtio-9p-test.c | 70
> > > > >  ++++++++++++++++++++++++++++++++++++
> > > > >  1 file changed, 70 insertions(+)
> > > > > 
> > > > > diff --git a/tests/qtest/virtio-9p-test.c
> > > > > b/tests/qtest/virtio-9p-test.c
> > > > > index 6c00da03f4..9098e21173 100644
> > > > > --- a/tests/qtest/virtio-9p-test.c
> > > > > +++ b/tests/qtest/virtio-9p-test.c
> > > > > @@ -146,6 +146,11 @@ static void v9fs_uint16_read(P9Req *req,
> > > > > uint16_t
> > > > > *val) le16_to_cpus(val);
> > > > > 
> > > > >  }
> > > > > 
> > > > > +static void v9fs_int16_read(P9Req *req, int16_t *val)
> > > > > +{
> > > > > +    v9fs_uint16_read(req, (uint16_t *)val);
> > > > > +}
> > > > > +
> > > > > 
> > > > >  static void v9fs_uint32_write(P9Req *req, uint32_t val)
> > > > >  {
> > > > >  
> > > > >      uint32_t le_val = cpu_to_le32(val);
> > > > > 
> > > > > @@ -166,12 +171,22 @@ static void v9fs_uint32_read(P9Req *req,
> > > > > uint32_t
> > > > > *val) le32_to_cpus(val);
> > > > > 
> > > > >  }
> > > > > 
> > > > > +static void v9fs_int32_read(P9Req *req, int32_t *val)
> > > > > +{
> > > > > +    v9fs_uint32_read(req, (uint32_t *)val);
> > > > > +}
> > > > > +
> > > > > 
> > > > >  static void v9fs_uint64_read(P9Req *req, uint64_t *val)
> > > > >  {
> > > > >  
> > > > >      v9fs_memread(req, val, 8);
> > > > >      le64_to_cpus(val);
> > > > >  
> > > > >  }
> > > > > 
> > > > > +static void v9fs_int64_read(P9Req *req, int64_t *val)
> > > > > +{
> > > > > +    v9fs_uint64_read(req, (uint64_t *)val);
> > > > > +}
> > > > > +
> > > > > 
> > > > >  /* len[2] string[len] */
> > > > >  static uint16_t v9fs_string_size(const char *string)
> > > > >  {
> > > > > 
> > > > > @@ -425,6 +440,40 @@ static void v9fs_rwalk(P9Req *req, uint16_t
> > > > > *nwqid,
> > > > > v9fs_qid **wqid) v9fs_req_free(req);
> > > > > 
> > > > >  }
> > > > > 
> > > > > +/* size[4] Tstat tag[2] fid[4] */
> > > > > +static P9Req *v9fs_tstat(QVirtio9P *v9p, uint32_t fid, uint16_t
> > > > > tag)
> > > 
> > > Tstat/Rstat aren't part of 9p2000.L, you should use Tgetattr/Rgetattr
> > > instead (see https://github.com/chaos/diod/blob/master/protocol.md).
> > 
> > Ah right, I forgot.
> > 
> > > > > +{
> > > > > +    P9Req *req;
> > > > > +
> > > > > +    req = v9fs_req_init(v9p, 4, P9_TSTAT, tag);
> > > > > +    v9fs_uint32_write(req, fid);
> > > > > +    v9fs_req_send(req);
> > > > > +    return req;
> > > > > +}
> > > > > +
> > > > > +/* size[4] Rstat tag[2] stat[n] */
> > > > > +static void v9fs_rstat(P9Req *req, struct V9fsStat *st)
> > > > > +{
> > > > > +    v9fs_req_recv(req, P9_RSTAT);
> > > > > +
> > > 
> > > For the records, this is a stat[n], i.e. "n[2] followed by n bytes of
> > > data forming the parameter", so you should read an uint16_t first.
> > > 
> > > > > +    v9fs_int16_read(req, &st->size);
> > 
> > Which I did here? --^
> 
> From the BUGS section of
> https://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor32 :
> 
>      BUGS
>           To make the contents of a directory, such as returned by
>           read(5), easy to parse, each directory entry begins with a
>           size field.  For consistency, the entries in Twstat and
>           Rstat messages also contain their size, which means the size
>                                                                   ^^^^
>           appears twice.  For example, the Rstat message is formatted
>           ^^^^^^^^^^^^^
>           as ``(4+1+2+2+n)[4] Rstat tag[2] n[2] (n-2)[2] type[2]
>           dev[4]...,'' where n is the value returned by Styx->packdir.
> 
> I realized that when giving a try to convert a v9fs_qid to a V9fsQID on
> top of this patch.

Ouch, what a trap. Yeah, I didn't realize that.

> > > > > +    v9fs_int16_read(req, &st->type);
> > > > > +    v9fs_int32_read(req, &st->dev);
> > > > > +    v9fs_uint8_read(req, &st->qid.type);
> > > > > +    v9fs_uint32_read(req, &st->qid.version);
> > > > > +    v9fs_uint64_read(req, &st->qid.path);
> > > > > +    v9fs_int32_read(req, &st->mode);
> > > > > +    v9fs_int32_read(req, &st->mtime);
> > > > > +    v9fs_int32_read(req, &st->atime);
> > > > > +    v9fs_int64_read(req, &st->length);
> > > > > +    v9fs_string_read(req, &st->name.size, &st->name.data);
> > > > > +    v9fs_string_read(req, &st->uid.size, &st->uid.data);
> > > > > +    v9fs_string_read(req, &st->gid.size, &st->gid.data);
> > > > > +    v9fs_string_read(req, &st->muid.size, &st->muid.data);
> > > > > +
> > > > > +    v9fs_req_free(req);
> > > > > +}
> > > > > +
> > > > > 
> > > > >  /* size[4] Treaddir tag[2] fid[4] offset[8] count[4] */
> > > > >  static P9Req *v9fs_treaddir(QVirtio9P *v9p, uint32_t fid, uint64_t
> > > > >  offset,
> > > > >  
> > > > >                              uint32_t count, uint16_t tag)
> > > > > 
> > > > > @@ -1009,6 +1058,8 @@ static void fs_walk_none(void *obj, void
> > > > > *data,
> > > > > QGuestAllocator *t_alloc) v9fs_qid root_qid;
> > > > > 
> > > > >      g_autofree v9fs_qid *wqid = NULL;
> > > > >      P9Req *req;
> > > > > 
> > > > > +    struct V9fsStat st[2];
> > > > > +    int i;
> > > > > 
> > > > >      do_version(v9p);
> > > > >      req = v9fs_tattach(v9p, 0, getuid(), 0);
> > > > > 
> > > > > @@ -1021,6 +1072,25 @@ static void fs_walk_none(void *obj, void
> > > > > *data,
> > > > > QGuestAllocator *t_alloc)
> > > > > 
> > > > >      /* special case: no QID is returned if nwname=0 was sent */
> > > > >      g_assert(wqid == NULL);
> > > > > 
> > > > > +
> > > > > +    req = v9fs_tstat(v9p, 0, 0);
> > > > > +    v9fs_req_wait_for_reply(req, NULL);
> > > > > +    v9fs_rstat(req, &st[0]);
> > > > 
> > > > Probably stat-ing the root fid (0) should happen before sending Twalk,
> > > > to
> > > > better counter the 1st fid (0) having become potentially mutated?
> > > 
> > > You already have the root qid from Rattach, no need to stat.
> > 
> > Yes, this was about easy comparison with qid.version in mind, i.e. ...
> > 
> > > > > +
> > > > > +    req = v9fs_tstat(v9p, 1, 0);
> > > > > +    v9fs_req_wait_for_reply(req, NULL);
> > > > > +    v9fs_rstat(req, &st[1]);
> > > > > +
> > > > > +    /* don't compare QID version for checking for file ID equalness
> > > > > */
> > > > > +    g_assert(st[0].qid.type == st[1].qid.type);
> > > > > +    g_assert(st[0].qid.path == st[1].qid.path);
> > > > 
> > > > I could add a helper function is_same_qid() for this if desired.
> > > 
> > > Rgetattr provides a qid[13] like Rattach. Since we control everything,
> > > the version bits won't change and I think is_same_qid() could be
> > > something as simple as:
> > > 
> > > static inline bool is_same_qid(v9fs_qid qid1, v9fs_qid qid2)
> > > {
> > > 
> > >     return memcmp(qid1, qid2, 13) == 0;
> > > 
> > > }
> > 
> > Yes I know, the version definitely won't change with the synth driver. But
> > I thought to add code so it could be used for 'local' driver tests as
> > well in future.
> 
> FWIW, even when using local, only an external cause could do that, which
> would mean that the test environment is compromised, no ?
> 
> You can also ignore version and just compare the first byte and the 8 last
> ones.

OK, I'll come up with some more simple code (raw byte comparison) in the way 
suggested by you. No big deal.

Thanks!

> > > > > +
> > > > > +    for (i = 0; i < 2; ++i) {
> > > > > +        g_free(st[i].name.data);
> > > > > +        g_free(st[i].uid.data);
> > > > > +        g_free(st[i].gid.data);
> > > > > +        g_free(st[i].muid.data);
> > > > > +    }
> > > > 
> > > > I didn't find a more elegant way to do this cleanup.
> > > 
> > > You won't need that with Tgetattr.
> > > 
> > > > >  }
> > > > >  
> > > > >  static void fs_walk_dotdot(void *obj, void *data, QGuestAllocator
> > > > >  *t_alloc)




^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked
  2022-03-11 17:08         ` Greg Kurz
@ 2022-03-11 17:36           ` Christian Schoenebeck
  0 siblings, 0 replies; 25+ messages in thread
From: Christian Schoenebeck @ 2022-03-11 17:36 UTC (permalink / raw)
  To: qemu-devel; +Cc: Greg Kurz

On Freitag, 11. März 2022 18:08:38 CET Greg Kurz wrote:
> On Fri, 11 Mar 2022 17:44:54 +0100
> 
> Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > On Freitag, 11. März 2022 17:35:41 CET Greg Kurz wrote:
> > > On Thu, 10 Mar 2022 10:13:33 +0100
> > > 
> > > Christian Schoenebeck <qemu_oss@crudebyte.com> wrote:
> > > > On Mittwoch, 9. März 2022 18:57:39 CET Christian Schoenebeck wrote:
> > > > > Current implementation of 'Twalk' request handling always sends an
> > > > > 'Rerror'
> > > > > 
> > > > > response if any error occured. The 9p2000 protocol spec sais though:
> > > > >   "
> > > > >   If the first element cannot be walked for any reason, Rerror is
> > > > >   returned.
> > > > >   Otherwise, the walk will return an Rwalk message containing nwqid
> > > > >   qids
> > > > >   corresponding, in order, to the files that are visited by the
> > > > >   nwqid
> > > > >   successful elementwise walks; nwqid is therefore either nwname or
> > > > >   the
> > > > > 
> > > > > index of the first elementwise walk that failed.
> > > > > 
> > > > >   "
> > > > >   
> > > > >   http://ericvh.github.io/9p-rfc/rfc9p2000.html#anchor33
> > > > > 
> > > > > For that reason we are no longer leaving from an error path in
> > > > > function
> > > > > v9fs_walk(), unless really no path component could be walked
> > > > > successfully or if the request has been interrupted.
> > > > > 
> > > > > Local variable 'nvalid' counts and reflects the number of path
> > > > > components
> > > > > successfully processed by background I/O thread, whereas local
> > > > > variable
> > > > > 'name_idx' subsequently counts and reflects the number of path
> > > > > components
> > > > > eventually accepted successfully by 9p server controller portion.
> > > > > 
> > > > > New local variable 'any_err' is an aggregate variable reflecting
> > > > > whether
> > > > > any error occurred at all, while already existing variable 'err'
> > > > > only
> > > > > reflects the last error.
> > > > > 
> > > > > Despite QIDs being delivered to client in a more relaxed way now, it
> > > > > is
> > > > > important to note though that fid still must remain uneffacted if
> > > > > any
> > > > > error
> > > > 
> > > > Typo: should be "unaffected".
> > > > 
> > > > > occurred.
> > > > > 
> > > > > Signed-off-by: Christian Schoenebeck <qemu_oss@crudebyte.com>
> > > > > ---
> > > > > 
> > > > >  hw/9pfs/9p.c | 29 +++++++++++++++++++++--------
> > > > >  1 file changed, 21 insertions(+), 8 deletions(-)
> > > > > 
> > > > > diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
> > > > > index 6cdc566866..8ccd180608 100644
> > > > > --- a/hw/9pfs/9p.c
> > > > > +++ b/hw/9pfs/9p.c
> > > > > @@ -1766,7 +1766,7 @@ static void coroutine_fn v9fs_walk(void
> > > > > *opaque)
> > > > > 
> > > > >  {
> > > > >  
> > > > >      int name_idx, nvalid;
> > > > >      g_autofree V9fsQID *qids = NULL;
> > > > > 
> > > > > -    int i, err = 0;
> > > > > +    int i, err = 0, any_err = 0;
> > > > > 
> > > > >      V9fsPath dpath, path;
> > > > >      P9ARRAY_REF(V9fsPath) pathes = NULL;
> > > > >      uint16_t nwnames;
> > > > > 
> > > > > @@ -1832,6 +1832,7 @@ static void coroutine_fn v9fs_walk(void
> > > > > *opaque)
> > > > > 
> > > > >       * driver code altogether inside the following block.
> > > > >       */
> > > > >      
> > > > >      v9fs_co_run_in_worker({
> > > > > 
> > > > > +        nvalid = 0;
> > > > > 
> > > > >          if (v9fs_request_cancelled(pdu)) {
> > > > >          
> > > > >              err = -EINTR;
> > > > >              break;
> > > > > 
> > > > > @@ -1842,7 +1843,7 @@ static void coroutine_fn v9fs_walk(void
> > > > > *opaque)
> > > > > 
> > > > >              break;
> > > > >          
> > > > >          }
> > > > >          stbuf = fidst;
> > > > > 
> > > > > -        for (nvalid = 0; nvalid < nwnames; nvalid++) {
> > > > > +        for (; nvalid < nwnames; nvalid++) {
> > > > > 
> > > > >              if (v9fs_request_cancelled(pdu)) {
> > > > >              
> > > > >                  err = -EINTR;
> > > > >                  break;
> > > > > 
> > > > > @@ -1874,12 +1875,13 @@ static void coroutine_fn v9fs_walk(void
> > > > > *opaque)
> > > > > 
> > > > >      /*
> > > > >      
> > > > >       * Handle all the rest of this Twalk request on main thread ...
> > > > >       */
> > > > > 
> > > > > -    if (err < 0) {
> > > > > +    if ((err < 0 && !nvalid) || err == -EINTR) {
> > > > > 
> > > > >          goto out;
> > > > >      
> > > > >      }
> > > > > 
> > > > > +    any_err |= err;
> > > > > 
> > > > >      err = stat_to_qid(pdu, &fidst, &qid);
> > > > > 
> > > > > -    if (err < 0) {
> > > > > +    if (err < 0 && !nvalid) {
> > > > > 
> > > > >          goto out;
> > > > >      
> > > > >      }
> > > > >      stbuf = fidst;
> > > > > 
> > > > > @@ -1888,20 +1890,30 @@ static void coroutine_fn v9fs_walk(void
> > > > > *opaque)
> > > > > 
> > > > >      v9fs_path_copy(&dpath, &fidp->path);
> > > > >      v9fs_path_copy(&path, &fidp->path);
> > > > > 
> > > > > -    for (name_idx = 0; name_idx < nwnames; name_idx++) {
> > > > > +    for (name_idx = 0; name_idx < nvalid; name_idx++) {
> > > > > 
> > > > >          if (!same_stat_id(&pdu->s->root_st, &stbuf) ||
> > > > >          
> > > > >              strcmp("..", wnames[name_idx].data))
> > > > >          
> > > > >          {
> > > > >          
> > > > >              stbuf = stbufs[name_idx];
> > > > >              err = stat_to_qid(pdu, &stbuf, &qid);
> > > > >              if (err < 0) {
> > > > > 
> > > > > -                goto out;
> > > > > +                break;
> > > > > 
> > > > >              }
> > > > >              v9fs_path_copy(&path, &pathes[name_idx]);
> > > > >              v9fs_path_copy(&dpath, &path);
> > > > >          
> > > > >          }
> > > > >          memcpy(&qids[name_idx], &qid, sizeof(qid));
> > > > >      
> > > > >      }
> > > > > 
> > > > > +    any_err |= err;
> > > > > +    if (any_err) {
> > > > 
> > > > Not sure if there is ever the case err > 0, but as we are already
> > > > comparing
> > > > for "if (err < 0)" everywhere, we should probably also do the same
> > > > comparison for the aggregate error variable here, right?
> > > 
> > > It seems that you could drop any_err and just check name_idx != nwnames
> > > ?
> > 
> > Mmm, what about the special case 'Twalk nwnames=0' (i.e. fid cloning),
> > that
> > implementation would then skip errors, no?
> 
> Ouch you're right... honestly, v9fs_walk() is really a mess and it
> is getting harder to fix. What about having a totally separate
> path for the cloning case (as a preparatory patch) ?

You suggested that before, somehow I did not get to simpler code when trying 
that, rather the opposite. But you definitely have a better eye on identifying 
redundant pathes than me.

There are some things that can still be wiped away, like the path vs. dpath 
variables, but I thought to do that a bit later.

Best regards,
Christian Schoenebeck




^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2022-03-11 17:37 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-09 18:31 [PATCH 0/6] 9pfs: fix 'Twalk' protocol violation Christian Schoenebeck
2022-03-09 12:18 ` [PATCH 1/6] tests/9pfs: walk to non-existent dir Christian Schoenebeck
2022-03-11 11:25   ` Greg Kurz
2022-03-09 13:24 ` [PATCH 2/6] tests/9pfs: Twalk with nwname=0 Christian Schoenebeck
2022-03-10  8:57   ` Christian Schoenebeck
2022-03-11 11:41     ` Greg Kurz
2022-03-11 13:33       ` Christian Schoenebeck
2022-03-09 14:49 ` [PATCH 3/6] tests/9pfs: compare QIDs in fs_walk_none() test Christian Schoenebeck
2022-03-10  9:04   ` Christian Schoenebeck
2022-03-11 16:11     ` Greg Kurz
2022-03-11 16:39       ` Christian Schoenebeck
2022-03-11 17:02         ` Greg Kurz
2022-03-11 17:23           ` Christian Schoenebeck
2022-03-09 17:12 ` [PATCH 4/6] 9pfs: refactor 'name_idx' -> 'nvalid' in v9fs_walk() Christian Schoenebeck
2022-03-10  9:07   ` Christian Schoenebeck
2022-03-11 16:16     ` Greg Kurz
2022-03-09 17:57 ` [PATCH 5/6] 9pfs: fix 'Twalk' to only send error if no component walked Christian Schoenebeck
2022-03-10  9:13   ` Christian Schoenebeck
2022-03-11 16:35     ` Greg Kurz
2022-03-11 16:44       ` Christian Schoenebeck
2022-03-11 17:08         ` Greg Kurz
2022-03-11 17:36           ` Christian Schoenebeck
2022-03-09 18:21 ` [PATCH 6/6] tests/9pfs: guard recent 'Twalk' behaviour fix Christian Schoenebeck
2022-03-11 10:32   ` Christian Schoenebeck
2022-03-11 16:40   ` Greg Kurz

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.