get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.1/patches/2227580/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2227580,
    "url": "http://patchwork.ozlabs.org/api/1.1/patches/2227580/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260424-master-v2-1-8b50b5c063ed@gmail.com/",
    "project": {
        "id": 14,
        "url": "http://patchwork.ozlabs.org/api/1.1/projects/14/?format=api",
        "name": "QEMU Development",
        "link_name": "qemu-devel",
        "list_id": "qemu-devel.nongnu.org",
        "list_email": "qemu-devel@nongnu.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20260424-master-v2-1-8b50b5c063ed@gmail.com>",
    "date": "2026-04-23T21:36:38",
    "name": "[v2,1/3] linux-user: Filter /proc/*/task/ to hide QEMU-internal threads",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "c083155aaf1f3aaef02aa91d3ab3c1eaf9cd950b",
    "submitter": {
        "id": 93154,
        "url": "http://patchwork.ozlabs.org/api/1.1/people/93154/?format=api",
        "name": "Ali Raza",
        "email": "elirazamumtaz@gmail.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260424-master-v2-1-8b50b5c063ed@gmail.com/mbox/",
    "series": [
        {
            "id": 501265,
            "url": "http://patchwork.ozlabs.org/api/1.1/series/501265/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=501265",
            "date": "2026-04-23T21:36:37",
            "name": "linux-user: Filter /proc/*/task/ and validate tkill targets",
            "version": 2,
            "mbox": "http://patchwork.ozlabs.org/series/501265/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2227580/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2227580/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>",
        "X-Original-To": "incoming@patchwork.ozlabs.org",
        "Delivered-To": "patchwork-incoming@legolas.ozlabs.org",
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=h7QPoEsF;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists1p.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)"
        ],
        "Received": [
            "from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g1vNl3yMmz1xvV\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 24 Apr 2026 10:44:23 +1000 (AEST)",
            "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists1p.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1wG4ct-0004TK-El; Thu, 23 Apr 2026 20:42:35 -0400",
            "from eggs.gnu.org ([2001:470:142:3::10])\n by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <elirazamumtaz@gmail.com>)\n id 1wG1jl-0002Zb-O6\n for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:29 -0400",
            "from mail-wm1-x330.google.com ([2a00:1450:4864:20::330])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)\n (Exim 4.90_1) (envelope-from <elirazamumtaz@gmail.com>)\n id 1wG1jj-00017U-6d\n for qemu-devel@nongnu.org; Thu, 23 Apr 2026 17:37:29 -0400",
            "by mail-wm1-x330.google.com with SMTP id\n 5b1f17b1804b1-4891c569cb1so7275175e9.2\n for <qemu-devel@nongnu.org>; Thu, 23 Apr 2026 14:37:26 -0700 (PDT)",
            "from [10.94.10.196] ([223.123.19.204])\n by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-48919f54572sm138755225e9.26.2026.04.23.14.37.22\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Thu, 23 Apr 2026 14:37:24 -0700 (PDT)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1776980246; x=1777585046; darn=nongnu.org;\n h=cc:to:in-reply-to:references:message-id:content-transfer-encoding\n :mime-version:subject:date:from:from:to:cc:subject:date:message-id\n :reply-to; bh=uA+3oshFCqGtJFNZ2+y4Ee11ofsxVMEKZKGagbGyBPI=;\n b=h7QPoEsFz+c550CVT9mFvjMSK8viWS6jD1E+VaX17WgYxwJJTYPx5H/f9xOWKSrEi3\n 0Fv5tEekXtRaCww8G8vOVSSjYyjzMc2u7Nep2DC+SIE5QX2crpZYJwjYtzicz4rJng+N\n ZywE+1Q6C3oPAjl4h2imZGQ9YJlMHG2C8skR33vKSvnULC5EMnUp56neqDxCMUeybm0d\n jiydv3g3HBhR9b5fLqTKEG+yDvXZKKf0X/MR/+e6escq4NtjPECiaqXpEjxCSMC6C1Ps\n 614iz5KsoKXx6SoNjfkSncz+4hF/RFrfulS6P9ybpJoyQQXCkAZlw6XE7+lOZduCrd5x\n rOXw==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776980246; x=1777585046;\n h=cc:to:in-reply-to:references:message-id:content-transfer-encoding\n :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to\n :cc:subject:date:message-id:reply-to;\n bh=uA+3oshFCqGtJFNZ2+y4Ee11ofsxVMEKZKGagbGyBPI=;\n b=eIDby3b9Na6o+8djwW4sLDEtK/N9CKSnURdL9lpfiViydOe0vGeJ7WiEGceeLVQmit\n dOxjHXE+ybvfJPRlGUiaAWyjcWfq6HGpOY3hO85Bh7TQDTtWLHb5hfYRX3sKzI0Pc4t8\n 5PQQK3n9vW0iiFIy906SYoXL6zypQeZE4pNOJ1uldc+zDsgGjSUb6L63rLo0HCu1wdpB\n hTuehaqjmSNvXn5jjOsjLnMCiFXXFaeL3iepDM2vrPKTIdte2KYSiC0Y9CncktZrzQrM\n gz/nsC68AzE1MM4/2SE1CEvb7Fll9qcAgm22uIG4JC3KZ0VMXHQvakn1FiRLi7+1q3hW\n gRJQ==",
        "X-Gm-Message-State": "AOJu0YwGhrEsnA/uktpQ4R1iJ2mzlU3C+j1IkTezkFK73f1LIiPFN/N1\n gVGxw9zlIrg/DfuvB2jdszi1b0S2moHvkzx/m/hk+X6oxPukEiuJG3ZCDKZBZGMD",
        "X-Gm-Gg": "AeBDievndWLorc8/6XhkmY5xxa8lVO8VER13NXi4rej92CLB546xc6xkbgTkWKn3gXw\n VQnqeY8wYZmVOmPuKs6NkyX4RpXY3QDZXR7n0Tp0jfAzNrNJpZdMN/8Ruq8JNrcGc00R2Ope5K0\n gDCL6qCqIxy8S7QGgvmWpx0gVwsE0KloCxCt/bpkERkLxtjsaQenx2sMO7cI8W1JtD+7dDgYwHD\n eyT3fEOIsmmYiPi7yIoSmtivXe+KPzOI9BmA6MN2DjsZBLS1OIYnI1N6dk5GsEuKaeS9IiMWUVy\n /+ZPFA6YMaUIv/IWWx6pisyptCW3qBWADLAXuJuqDDs4AmAh09Mm/We67X3xRH5e5loN2ynKcRU\n po6bwOzzWYjBPz2l3JEeG79jMdpkRXOy8GunOqzUCIQj6GhPZXWNNa2bdcBP0ezTBW0g4c1RBTk\n saYagmwfhiS/yvAoBsx8KfGa4hzg2oXg/V1GLLlfGmUQ==",
        "X-Received": "by 2002:a05:600c:1f82:b0:48a:5546:619e with SMTP id\n 5b1f17b1804b1-48a55466251mr92148725e9.4.1776980245251;\n Thu, 23 Apr 2026 14:37:25 -0700 (PDT)",
        "From": "Ali Raza <elirazamumtaz@gmail.com>",
        "Date": "Fri, 24 Apr 2026 02:36:38 +0500",
        "Subject": "[PATCH v2 1/3] linux-user: Filter /proc/*/task/ to hide\n QEMU-internal threads",
        "MIME-Version": "1.0",
        "Content-Type": "text/plain; charset=\"utf-8\"",
        "Content-Transfer-Encoding": "7bit",
        "Message-Id": "<20260424-master-v2-1-8b50b5c063ed@gmail.com>",
        "References": "<20260424-master-v2-0-8b50b5c063ed@gmail.com>",
        "In-Reply-To": "<20260424-master-v2-0-8b50b5c063ed@gmail.com>",
        "To": "qemu-devel@nongnu.org",
        "Cc": "Ali Raza <elirazamumtaz@gmail.com>, Laurent Vivier <laurent@vivier.eu>,\n  Pierrick Bouvier <pierrick.bouvier@linaro.org>,\n =?utf-8?q?Alex_Benn=C3=A9e?= <alex.bennee@linaro.org>, deller@gmx.de",
        "X-Mailer": "b4 0.15.2",
        "Received-SPF": "pass client-ip=2a00:1450:4864:20::330;\n envelope-from=elirazamumtaz@gmail.com; helo=mail-wm1-x330.google.com",
        "X-Spam_score_int": "-20",
        "X-Spam_score": "-2.1",
        "X-Spam_bar": "--",
        "X-Spam_report": "(-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001,\n RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001,\n SPF_PASS=-0.001 autolearn=ham autolearn_force=no",
        "X-Spam_action": "no action",
        "X-Mailman-Approved-At": "Thu, 23 Apr 2026 20:42:33 -0400",
        "X-BeenThere": "qemu-devel@nongnu.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "qemu development <qemu-devel.nongnu.org>",
        "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>",
        "List-Post": "<mailto:qemu-devel@nongnu.org>",
        "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>",
        "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>",
        "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org",
        "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org"
    },
    "content": "When a guest process reads /proc/<pid>/task/ via getdents/getdents64,\nthe host kernel returns directory entries for all host threads in the\nprocess, including QEMU-internal threads (RCU, TCG workers) that have\nno guest CPUState.  This causes problems for guest libraries like\nlibcap's PSX that enumerate threads via /proc/self/task/ and send\nsignals to each one -- signals sent to QEMU-internal threads are never\nhandled, leading to deadlocks.\n\nAdd filtering to do_getdents() and do_getdents64() that detects when\nthe fd refers to a /proc/<pid>/task/ directory and skips entries whose\nTID does not belong to a guest thread.  Guest thread TIDs are\nidentified by iterating the CPU list via CPU_FOREACH(), matching the\nexisting pattern used for /proc/self/stat num_threads.\n\nThis fixes the psx_test hang reported in containers using QEMU\nuser-mode emulation for cross-architecture builds.\n\nResolves: https://github.com/AndrewGMorgan/libcap_mirror/issues/6\nSigned-off-by: Ali Raza (@locus-x64)\n\n---\nChanges in v2:\n- Use pid_t instead of long for the parsed tid in do_getdents() and\n  do_getdents64() (suggested by Helge Deller).\n- Replace the ad-hoc readlink-at-getdents check with fd_trans-based\n  tagging at open time, eliminating a TOCTOU race and a broad-prefix\n  match that also matched /proc/<pid>/task/<tid>/fd.\n- Retry sys_getdents when an entire host batch is filtered out, so\n  the guest does not see a false EOF.\n- Parse the dirent TID with qemu_strtoi() instead of strtol() so\n  out-of-range values cannot wrap silently into pid_t.\n---\n linux-user/fd-trans.c |   6 +\n linux-user/fd-trans.h |  18 +++\n linux-user/syscall.c  | 298 +++++++++++++++++++++++++++++++++++++-------------\n 3 files changed, 244 insertions(+), 78 deletions(-)",
    "diff": "diff --git a/linux-user/fd-trans.c b/linux-user/fd-trans.c\nindex 64dd0745d2..6bad892b78 100644\n--- a/linux-user/fd-trans.c\n+++ b/linux-user/fd-trans.c\n@@ -1881,3 +1881,9 @@ static abi_long host_to_target_data_inotify(void *buf, size_t len)\n TargetFdTrans target_inotify_trans = {\n     .host_to_target_data = host_to_target_data_inotify,\n };\n+\n+/*\n+ * No callbacks: this trans entry is purely a marker to identify fds\n+ * that refer to our own /proc/<pid>/task directory.  See getdents().\n+ */\n+TargetFdTrans target_proc_pid_task_trans;\ndiff --git a/linux-user/fd-trans.h b/linux-user/fd-trans.h\nindex 3bbc15fa1f..76b8c35374 100644\n--- a/linux-user/fd-trans.h\n+++ b/linux-user/fd-trans.h\n@@ -142,4 +142,22 @@ extern TargetFdTrans target_signalfd_trans;\n extern TargetFdTrans target_eventfd_trans;\n extern TargetFdTrans target_timerfd_trans;\n extern TargetFdTrans target_inotify_trans;\n+\n+/*\n+ * Marker (no callbacks) used to tag fds that refer to /proc/<pid>/task\n+ * for our own process.  Used by getdents to filter out QEMU-internal\n+ * host threads (RCU, TCG workers) that have no guest CPUState.\n+ */\n+extern TargetFdTrans target_proc_pid_task_trans;\n+\n+static inline bool fd_trans_is_proc_pid_task(int fd)\n+{\n+    if (fd < 0) {\n+        return false;\n+    }\n+\n+    QEMU_LOCK_GUARD(&target_fd_trans_lock);\n+    return fd < target_fd_max\n+           && target_fd_trans[fd] == &target_proc_pid_task_trans;\n+}\n #endif\ndiff --git a/linux-user/syscall.c b/linux-user/syscall.c\nindex f4b74ad350..44f2cd851f 100644\n--- a/linux-user/syscall.c\n+++ b/linux-user/syscall.c\n@@ -8870,6 +8870,7 @@ static int do_openat2(CPUArchState *cpu_env, abi_long dirfd,\n     }\n \n     fd_trans_unregister(ret);\n+    maybe_tag_proc_pid_task(ret);\n     unlock_user(pathname, guest_pathname, 0);\n     return ret;\n }\n@@ -9108,6 +9109,58 @@ static int host_to_target_cpu_mask(const unsigned long *host_mask,\n     return 0;\n }\n \n+/*\n+ * If the freshly-opened host fd refers to our own /proc/<pid>/task\n+ * directory, register a marker on it via fd_trans so getdents() can\n+ * later filter out QEMU-internal host threads (RCU, TCG workers) that\n+ * have no guest CPUState.  Tagging at open time (rather than at\n+ * getdents time) avoids TOCTOU on the fd's resolved path and lets us\n+ * piggy-back on the existing fd_trans infrastructure for dup/close.\n+ */\n+static void maybe_tag_proc_pid_task(int fd)\n+{\n+    char link_path[64];\n+    char link_target[PATH_MAX];\n+    char expected[64];\n+    ssize_t len;\n+    int expected_len;\n+\n+    if (fd < 0) {\n+        return;\n+    }\n+\n+    snprintf(link_path, sizeof(link_path), \"/proc/self/fd/%d\", fd);\n+    len = readlink(link_path, link_target, sizeof(link_target) - 1);\n+    if (len < 0) {\n+        return;\n+    }\n+    link_target[len] = '\\0';\n+\n+    expected_len = snprintf(expected, sizeof(expected),\n+                            \"/proc/%d/task\", getpid());\n+    if (expected_len > 0 && (ssize_t)expected_len == len\n+        && memcmp(link_target, expected, expected_len) == 0) {\n+        fd_trans_register(fd, &target_proc_pid_task_trans);\n+    }\n+}\n+\n+/*\n+ * Check if a given host TID belongs to a guest thread by looking it up\n+ * in the CPU list.  Must be called under RCU read lock.\n+ */\n+static bool is_guest_tid(pid_t tid)\n+{\n+    CPUState *cpu;\n+\n+    CPU_FOREACH(cpu) {\n+        TaskState *ts = get_task_state(cpu);\n+        if (ts->ts_tid == tid) {\n+            return true;\n+        }\n+    }\n+    return false;\n+}\n+\n #ifdef TARGET_NR_getdents\n static int do_getdents(abi_long dirfd, abi_long arg2, abi_long count)\n {\n@@ -9116,78 +9169,123 @@ static int do_getdents(abi_long dirfd, abi_long arg2, abi_long count)\n     int hlen, hoff, toff;\n     int hreclen, treclen;\n     off_t prev_diroff = 0;\n+    bool filter_task = fd_trans_is_proc_pid_task(dirfd);\n+    bool stop = false;\n \n     hdirp = g_try_malloc(count);\n     if (!hdirp) {\n         return -TARGET_ENOMEM;\n     }\n \n-#ifdef EMULATE_GETDENTS_WITH_GETDENTS\n-    hlen = sys_getdents(dirfd, hdirp, count);\n-#else\n-    hlen = sys_getdents64(dirfd, hdirp, count);\n-#endif\n-\n-    hlen = get_errno(hlen);\n-    if (is_error(hlen)) {\n-        return hlen;\n-    }\n-\n     tdirp = lock_user(VERIFY_WRITE, arg2, count, 0);\n     if (!tdirp) {\n         return -TARGET_EFAULT;\n     }\n \n-    for (hoff = toff = 0; hoff < hlen; hoff += hreclen, toff += treclen) {\n+    toff = 0;\n+    /*\n+     * Loop until we either produce a visible target entry, hit a\n+     * real EOF (host returns 0), or hit an error.  Without this\n+     * retry, a host batch consisting entirely of filtered entries\n+     * would be reported to the guest as EOF.\n+     */\n+    while (!stop) {\n #ifdef EMULATE_GETDENTS_WITH_GETDENTS\n-        struct linux_dirent *hde = hdirp + hoff;\n+        hlen = sys_getdents(dirfd, hdirp, count);\n #else\n-        struct linux_dirent64 *hde = hdirp + hoff;\n+        hlen = sys_getdents64(dirfd, hdirp, count);\n #endif\n-        struct target_dirent *tde = tdirp + toff;\n-        int namelen;\n-        uint8_t type;\n+        hlen = get_errno(hlen);\n+        if (is_error(hlen)) {\n+            unlock_user(tdirp, arg2, 0);\n+            return hlen;\n+        }\n+        if (hlen == 0) {\n+            break; /* real EOF */\n+        }\n \n-        namelen = strlen(hde->d_name);\n-        hreclen = hde->d_reclen;\n-        treclen = offsetof(struct target_dirent, d_name) + namelen + 2;\n-        treclen = QEMU_ALIGN_UP(treclen, __alignof(struct target_dirent));\n+        for (hoff = 0; hoff < hlen; hoff += hreclen, toff += treclen) {\n+#ifdef EMULATE_GETDENTS_WITH_GETDENTS\n+            struct linux_dirent *hde = hdirp + hoff;\n+#else\n+            struct linux_dirent64 *hde = hdirp + hoff;\n+#endif\n+            struct target_dirent *tde = tdirp + toff;\n+            int namelen;\n+            uint8_t type;\n+\n+            namelen = strlen(hde->d_name);\n+            hreclen = hde->d_reclen;\n \n-        if (toff + treclen > count) {\n             /*\n-             * If the host struct is smaller than the target struct, or\n-             * requires less alignment and thus packs into less space,\n-             * then the host can return more entries than we can pass\n-             * on to the guest.\n+             * Filter /proc/<pid>/task/ listings to hide QEMU-internal\n+             * host threads (RCU, TCG) that have no guest CPUState.\n              */\n-            if (toff == 0) {\n-                toff = -TARGET_EINVAL; /* result buffer is too small */\n+            if (filter_task) {\n+                int tid_int;\n+                if (qemu_strtoi(hde->d_name, NULL, 10, &tid_int) == 0\n+                    && tid_int > 0) {\n+                    pid_t tid = (pid_t)tid_int;\n+                    bool drop;\n+\n+                    WITH_RCU_READ_LOCK_GUARD() {\n+                        drop = !is_guest_tid(tid);\n+                    }\n+                    if (drop) {\n+                        treclen = 0;\n+                        continue;\n+                    }\n+                }\n+            }\n+            treclen = offsetof(struct target_dirent, d_name) + namelen + 2;\n+            treclen = QEMU_ALIGN_UP(treclen, __alignof(struct target_dirent));\n+\n+            if (toff + treclen > count) {\n+                /*\n+                 * If the host struct is smaller than the target struct, or\n+                 * requires less alignment and thus packs into less space,\n+                 * then the host can return more entries than we can pass\n+                 * on to the guest.\n+                 */\n+                if (toff == 0) {\n+                    toff = -TARGET_EINVAL; /* result buffer is too small */\n+                    stop = true;\n+                    break;\n+                }\n+                /*\n+                 * Return what we have, resetting the file pointer to the\n+                 * location of the first record not returned.\n+                 */\n+                lseek(dirfd, prev_diroff, SEEK_SET);\n+                stop = true;\n                 break;\n             }\n+\n+            prev_diroff = hde->d_off;\n+            tde->d_ino = tswapal(hde->d_ino);\n+            tde->d_off = tswapal(hde->d_off);\n+            tde->d_reclen = tswap16(treclen);\n+            memcpy(tde->d_name, hde->d_name, namelen + 1);\n+\n             /*\n-             * Return what we have, resetting the file pointer to the\n-             * location of the first record not returned.\n+             * The getdents type is in what was formerly a padding byte at the\n+             * end of the structure.\n              */\n-            lseek(dirfd, prev_diroff, SEEK_SET);\n-            break;\n+#ifdef EMULATE_GETDENTS_WITH_GETDENTS\n+            type = *((uint8_t *)hde + hreclen - 1);\n+#else\n+            type = hde->d_type;\n+#endif\n+            *((uint8_t *)tde + treclen - 1) = type;\n         }\n \n-        prev_diroff = hde->d_off;\n-        tde->d_ino = tswapal(hde->d_ino);\n-        tde->d_off = tswapal(hde->d_off);\n-        tde->d_reclen = tswap16(treclen);\n-        memcpy(tde->d_name, hde->d_name, namelen + 1);\n-\n         /*\n-         * The getdents type is in what was formerly a padding byte at the\n-         * end of the structure.\n+         * If we produced any visible target entry, return; otherwise\n+         * loop again to fetch more host entries (avoid false EOF).\n          */\n-#ifdef EMULATE_GETDENTS_WITH_GETDENTS\n-        type = *((uint8_t *)hde + hreclen - 1);\n-#else\n-        type = hde->d_type;\n-#endif\n-        *((uint8_t *)tde + treclen - 1) = type;\n+        if (toff != 0) {\n+            break;\n+        }\n     }\n \n     unlock_user(tdirp, arg2, toff);\n@@ -9203,57 +9301,99 @@ static int do_getdents64(abi_long dirfd, abi_long arg2, abi_long count)\n     int hlen, hoff, toff;\n     int hreclen, treclen;\n     off_t prev_diroff = 0;\n+    bool filter_task = fd_trans_is_proc_pid_task(dirfd);\n+    bool stop = false;\n \n     hdirp = g_try_malloc(count);\n     if (!hdirp) {\n         return -TARGET_ENOMEM;\n     }\n \n-    hlen = get_errno(sys_getdents64(dirfd, hdirp, count));\n-    if (is_error(hlen)) {\n-        return hlen;\n-    }\n-\n     tdirp = lock_user(VERIFY_WRITE, arg2, count, 0);\n     if (!tdirp) {\n         return -TARGET_EFAULT;\n     }\n \n-    for (hoff = toff = 0; hoff < hlen; hoff += hreclen, toff += treclen) {\n-        struct linux_dirent64 *hde = hdirp + hoff;\n-        struct target_dirent64 *tde = tdirp + toff;\n-        int namelen;\n+    toff = 0;\n+    /*\n+     * Loop until we either produce a visible target entry, hit a\n+     * real EOF (host returns 0), or hit an error.  Without this\n+     * retry, a host batch consisting entirely of filtered entries\n+     * would be reported to the guest as EOF.\n+     */\n+    while (!stop) {\n+        hlen = get_errno(sys_getdents64(dirfd, hdirp, count));\n+        if (is_error(hlen)) {\n+            unlock_user(tdirp, arg2, 0);\n+            return hlen;\n+        }\n+        if (hlen == 0) {\n+            break; /* real EOF */\n+        }\n+\n+        for (hoff = 0; hoff < hlen; hoff += hreclen, toff += treclen) {\n+            struct linux_dirent64 *hde = hdirp + hoff;\n+            struct target_dirent64 *tde = tdirp + toff;\n+            int namelen;\n \n-        namelen = strlen(hde->d_name) + 1;\n-        hreclen = hde->d_reclen;\n-        treclen = offsetof(struct target_dirent64, d_name) + namelen;\n-        treclen = QEMU_ALIGN_UP(treclen, __alignof(struct target_dirent64));\n+            namelen = strlen(hde->d_name) + 1;\n+            hreclen = hde->d_reclen;\n \n-        if (toff + treclen > count) {\n             /*\n-             * If the host struct is smaller than the target struct, or\n-             * requires less alignment and thus packs into less space,\n-             * then the host can return more entries than we can pass\n-             * on to the guest.\n+             * Filter /proc/<pid>/task/ listings to hide QEMU-internal\n+             * host threads (RCU, TCG) that have no guest CPUState.\n              */\n-            if (toff == 0) {\n-                toff = -TARGET_EINVAL; /* result buffer is too small */\n+            if (filter_task) {\n+                int tid_int;\n+                if (qemu_strtoi(hde->d_name, NULL, 10, &tid_int) == 0\n+                    && tid_int > 0) {\n+                    pid_t tid = (pid_t)tid_int;\n+                    bool drop;\n+\n+                    WITH_RCU_READ_LOCK_GUARD() {\n+                        drop = !is_guest_tid(tid);\n+                    }\n+                    if (drop) {\n+                        treclen = 0;\n+                        continue;\n+                    }\n+                }\n+            }\n+            treclen = offsetof(struct target_dirent64, d_name) + namelen;\n+            treclen = QEMU_ALIGN_UP(treclen, __alignof(struct target_dirent64));\n+\n+            if (toff + treclen > count) {\n+                /*\n+                 * If the host struct is smaller than the target struct, or\n+                 * requires less alignment and thus packs into less space,\n+                 * then the host can return more entries than we can pass\n+                 * on to the guest.\n+                 */\n+                if (toff == 0) {\n+                    toff = -TARGET_EINVAL; /* result buffer is too small */\n+                    stop = true;\n+                    break;\n+                }\n+                /*\n+                 * Return what we have, resetting the file pointer to the\n+                 * location of the first record not returned.\n+                 */\n+                lseek(dirfd, prev_diroff, SEEK_SET);\n+                stop = true;\n                 break;\n             }\n-            /*\n-             * Return what we have, resetting the file pointer to the\n-             * location of the first record not returned.\n-             */\n-            lseek(dirfd, prev_diroff, SEEK_SET);\n-            break;\n+\n+            prev_diroff = hde->d_off;\n+            tde->d_ino = tswap64(hde->d_ino);\n+            tde->d_off = tswap64(hde->d_off);\n+            tde->d_reclen = tswap16(treclen);\n+            tde->d_type = hde->d_type;\n+            memcpy(tde->d_name, hde->d_name, namelen);\n         }\n \n-        prev_diroff = hde->d_off;\n-        tde->d_ino = tswap64(hde->d_ino);\n-        tde->d_off = tswap64(hde->d_off);\n-        tde->d_reclen = tswap16(treclen);\n-        tde->d_type = hde->d_type;\n-        memcpy(tde->d_name, hde->d_name, namelen);\n+        if (toff != 0) {\n+            break;\n+        }\n     }\n \n     unlock_user(tdirp, arg2, toff);\n@@ -9732,6 +9872,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,\n                                   target_to_host_bitmask(arg2, fcntl_flags_tbl),\n                                   arg3, true));\n         fd_trans_unregister(ret);\n+        maybe_tag_proc_pid_task(ret);\n         unlock_user(p, arg1, 0);\n         return ret;\n #endif\n@@ -9742,6 +9883,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,\n                                   target_to_host_bitmask(arg3, fcntl_flags_tbl),\n                                   arg4, true));\n         fd_trans_unregister(ret);\n+        maybe_tag_proc_pid_task(ret);\n         unlock_user(p, arg2, 0);\n         return ret;\n     case TARGET_NR_openat2:\n",
    "prefixes": [
        "v2",
        "1/3"
    ]
}