b7d4d7
From 7b7ec67680415c22773ebb2a5daacf298b6b1e06 Mon Sep 17 00:00:00 2001
b7d4d7
From: Xavi Hernandez <xhernandez@redhat.com>
b7d4d7
Date: Sat, 13 Feb 2021 18:37:32 +0100
b7d4d7
Subject: [PATCH 537/538] cluster/afr: Fix race in lockinfo (f)getxattr
b7d4d7
b7d4d7
A shared dictionary was updated outside the lock after having updated
b7d4d7
the number of remaining answers. This means that one thread may be
b7d4d7
processing the last answer and unwinding the request before another
b7d4d7
thread completes updating the dict.
b7d4d7
b7d4d7
    Thread 1                           Thread 2
b7d4d7
b7d4d7
    LOCK()
b7d4d7
    call_cnt-- (=1)
b7d4d7
    UNLOCK()
b7d4d7
                                       LOCK()
b7d4d7
                                       call_cnt-- (=0)
b7d4d7
                                       UNLOCK()
b7d4d7
                                       update_dict(dict)
b7d4d7
                                       if (call_cnt == 0) {
b7d4d7
                                           STACK_UNWIND(dict);
b7d4d7
                                       }
b7d4d7
    update_dict(dict)
b7d4d7
    if (call_cnt == 0) {
b7d4d7
        STACK_UNWIND(dict);
b7d4d7
    }
b7d4d7
b7d4d7
The updates from thread 1 are lost.
b7d4d7
b7d4d7
This patch also reduces the work done inside the locked region and
b7d4d7
reduces code duplication.
b7d4d7
b7d4d7
Upstream-patch:
b7d4d7
> Upstream-patch-link: https://github.com/gluster/glusterfs/pull/2162
b7d4d7
> Fixes: #2161
b7d4d7
> Change-Id: Idc0d34ab19ea6031de0641f7b05c624d90fac8fa
b7d4d7
> Signed-off-by: Xavi Hernandez <xhernandez@redhat.com>
b7d4d7
b7d4d7
BUG: 1911292
b7d4d7
Change-Id: Idc0d34ab19ea6031de0641f7b05c624d90fac8fa
b7d4d7
Signed-off-by: Xavi Hernandez <xhernandez@redhat.com>
b7d4d7
Reviewed-on: https://code.engineering.redhat.com/gerrit/228924
b7d4d7
Tested-by: RHGS Build Bot <nigelb@redhat.com>
b7d4d7
Reviewed-by: Sunil Kumar Heggodu Gopala Acharya <sheggodu@redhat.com>
b7d4d7
---
b7d4d7
 xlators/cluster/afr/src/afr-inode-read.c | 254 ++++++++++++++-----------------
b7d4d7
 1 file changed, 112 insertions(+), 142 deletions(-)
b7d4d7
b7d4d7
diff --git a/xlators/cluster/afr/src/afr-inode-read.c b/xlators/cluster/afr/src/afr-inode-read.c
b7d4d7
index cf305af..98e195a 100644
b7d4d7
--- a/xlators/cluster/afr/src/afr-inode-read.c
b7d4d7
+++ b/xlators/cluster/afr/src/afr-inode-read.c
b7d4d7
@@ -15,6 +15,8 @@
b7d4d7
 #include <stdlib.h>
b7d4d7
 #include <signal.h>
b7d4d7
 
b7d4d7
+#include <urcu/uatomic.h>
b7d4d7
+
b7d4d7
 #include <glusterfs/glusterfs.h>
b7d4d7
 #include "afr.h"
b7d4d7
 #include <glusterfs/dict.h>
b7d4d7
@@ -868,188 +870,121 @@ afr_getxattr_quota_size_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
b7d4d7
     return 0;
b7d4d7
 }
b7d4d7
 
b7d4d7
-int32_t
b7d4d7
-afr_getxattr_lockinfo_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
b7d4d7
-                          int32_t op_ret, int32_t op_errno, dict_t *dict,
b7d4d7
-                          dict_t *xdata)
b7d4d7
+static int32_t
b7d4d7
+afr_update_local_dicts(call_frame_t *frame, dict_t *dict, dict_t *xdata)
b7d4d7
 {
b7d4d7
-    int call_cnt = 0, len = 0;
b7d4d7
-    char *lockinfo_buf = NULL;
b7d4d7
-    dict_t *lockinfo = NULL, *newdict = NULL;
b7d4d7
-    afr_local_t *local = NULL;
b7d4d7
+    afr_local_t *local;
b7d4d7
+    dict_t *local_dict;
b7d4d7
+    dict_t *local_xdata;
b7d4d7
+    int32_t ret;
b7d4d7
 
b7d4d7
-    LOCK(&frame->lock);
b7d4d7
-    {
b7d4d7
-        local = frame->local;
b7d4d7
+    local = frame->local;
b7d4d7
+    local_dict = NULL;
b7d4d7
+    local_xdata = NULL;
b7d4d7
 
b7d4d7
-        call_cnt = --local->call_count;
b7d4d7
+    ret = -ENOMEM;
b7d4d7
 
b7d4d7
-        if ((op_ret < 0) || (!dict && !xdata)) {
b7d4d7
-            goto unlock;
b7d4d7
-        }
b7d4d7
-
b7d4d7
-        if (xdata) {
b7d4d7
-            if (!local->xdata_rsp) {
b7d4d7
-                local->xdata_rsp = dict_new();
b7d4d7
-                if (!local->xdata_rsp) {
b7d4d7
-                    local->op_ret = -1;
b7d4d7
-                    local->op_errno = ENOMEM;
b7d4d7
-                    goto unlock;
b7d4d7
-                }
b7d4d7
-            }
b7d4d7
+    if ((dict != NULL) && (local->dict == NULL)) {
b7d4d7
+        local_dict = dict_new();
b7d4d7
+        if (local_dict == NULL) {
b7d4d7
+            goto done;
b7d4d7
         }
b7d4d7
+    }
b7d4d7
 
b7d4d7
-        if (!dict) {
b7d4d7
-            goto unlock;
b7d4d7
+    if ((xdata != NULL) && (local->xdata_rsp == NULL)) {
b7d4d7
+        local_xdata = dict_new();
b7d4d7
+        if (local_xdata == NULL) {
b7d4d7
+            goto done;
b7d4d7
         }
b7d4d7
+    }
b7d4d7
 
b7d4d7
-        op_ret = dict_get_ptr_and_len(dict, GF_XATTR_LOCKINFO_KEY,
b7d4d7
-                                      (void **)&lockinfo_buf, &len;;
b7d4d7
+    if ((local_dict != NULL) || (local_xdata != NULL)) {
b7d4d7
+        /* TODO: Maybe it would be better to preallocate both dicts before
b7d4d7
+         *       sending the requests. This way we don't need to use a LOCK()
b7d4d7
+         *       here. */
b7d4d7
+        LOCK(&frame->lock);
b7d4d7
 
b7d4d7
-        if (!lockinfo_buf) {
b7d4d7
-            goto unlock;
b7d4d7
+        if ((local_dict != NULL) && (local->dict == NULL)) {
b7d4d7
+            local->dict = local_dict;
b7d4d7
+            local_dict = NULL;
b7d4d7
         }
b7d4d7
 
b7d4d7
-        if (!local->dict) {
b7d4d7
-            local->dict = dict_new();
b7d4d7
-            if (!local->dict) {
b7d4d7
-                local->op_ret = -1;
b7d4d7
-                local->op_errno = ENOMEM;
b7d4d7
-                goto unlock;
b7d4d7
-            }
b7d4d7
+        if ((local_xdata != NULL) && (local->xdata_rsp == NULL)) {
b7d4d7
+            local->xdata_rsp = local_xdata;
b7d4d7
+            local_xdata = NULL;
b7d4d7
         }
b7d4d7
-    }
b7d4d7
-unlock:
b7d4d7
-    UNLOCK(&frame->lock);
b7d4d7
 
b7d4d7
-    if (lockinfo_buf != NULL) {
b7d4d7
-        lockinfo = dict_new();
b7d4d7
-        if (lockinfo == NULL) {
b7d4d7
-            local->op_ret = -1;
b7d4d7
-            local->op_errno = ENOMEM;
b7d4d7
-        } else {
b7d4d7
-            op_ret = dict_unserialize(lockinfo_buf, len, &lockinfo);
b7d4d7
-
b7d4d7
-            if (lockinfo && local->dict) {
b7d4d7
-                dict_copy(lockinfo, local->dict);
b7d4d7
-            }
b7d4d7
-        }
b7d4d7
-    }
b7d4d7
-
b7d4d7
-    if (xdata && local->xdata_rsp) {
b7d4d7
-        dict_copy(xdata, local->xdata_rsp);
b7d4d7
+        UNLOCK(&frame->lock);
b7d4d7
     }
b7d4d7
 
b7d4d7
-    if (!call_cnt) {
b7d4d7
-        newdict = dict_new();
b7d4d7
-        if (!newdict) {
b7d4d7
-            local->op_ret = -1;
b7d4d7
-            local->op_errno = ENOMEM;
b7d4d7
-            goto unwind;
b7d4d7
+    if (dict != NULL) {
b7d4d7
+        if (dict_copy(dict, local->dict) < 0) {
b7d4d7
+            goto done;
b7d4d7
         }
b7d4d7
+    }
b7d4d7
 
b7d4d7
-        op_ret = dict_allocate_and_serialize(
b7d4d7
-            local->dict, (char **)&lockinfo_buf, (unsigned int *)&len;;
b7d4d7
-        if (op_ret != 0) {
b7d4d7
-            local->op_ret = -1;
b7d4d7
-            goto unwind;
b7d4d7
+    if (xdata != NULL) {
b7d4d7
+        if (dict_copy(xdata, local->xdata_rsp) < 0) {
b7d4d7
+            goto done;
b7d4d7
         }
b7d4d7
+    }
b7d4d7
 
b7d4d7
-        op_ret = dict_set_dynptr(newdict, GF_XATTR_LOCKINFO_KEY,
b7d4d7
-                                 (void *)lockinfo_buf, len);
b7d4d7
-        if (op_ret < 0) {
b7d4d7
-            local->op_ret = -1;
b7d4d7
-            local->op_errno = -op_ret;
b7d4d7
-            goto unwind;
b7d4d7
-        }
b7d4d7
+    ret = 0;
b7d4d7
 
b7d4d7
-    unwind:
b7d4d7
-        AFR_STACK_UNWIND(getxattr, frame, op_ret, op_errno, newdict,
b7d4d7
-                         local->xdata_rsp);
b7d4d7
+done:
b7d4d7
+    if (local_dict != NULL) {
b7d4d7
+        dict_unref(local_dict);
b7d4d7
     }
b7d4d7
 
b7d4d7
-    dict_unref(lockinfo);
b7d4d7
+    if (local_xdata != NULL) {
b7d4d7
+        dict_unref(local_xdata);
b7d4d7
+    }
b7d4d7
 
b7d4d7
-    return 0;
b7d4d7
+    return ret;
b7d4d7
 }
b7d4d7
 
b7d4d7
-int32_t
b7d4d7
-afr_fgetxattr_lockinfo_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
b7d4d7
-                           int32_t op_ret, int32_t op_errno, dict_t *dict,
b7d4d7
-                           dict_t *xdata)
b7d4d7
+static void
b7d4d7
+afr_getxattr_lockinfo_cbk_common(call_frame_t *frame, int32_t op_ret,
b7d4d7
+                                 int32_t op_errno, dict_t *dict, dict_t *xdata,
b7d4d7
+                                 bool is_fgetxattr)
b7d4d7
 {
b7d4d7
-    int call_cnt = 0, len = 0;
b7d4d7
+    int len = 0;
b7d4d7
     char *lockinfo_buf = NULL;
b7d4d7
     dict_t *lockinfo = NULL, *newdict = NULL;
b7d4d7
     afr_local_t *local = NULL;
b7d4d7
 
b7d4d7
-    LOCK(&frame->lock);
b7d4d7
-    {
b7d4d7
-        local = frame->local;
b7d4d7
-
b7d4d7
-        call_cnt = --local->call_count;
b7d4d7
-
b7d4d7
-        if ((op_ret < 0) || (!dict && !xdata)) {
b7d4d7
-            goto unlock;
b7d4d7
-        }
b7d4d7
-
b7d4d7
-        if (xdata) {
b7d4d7
-            if (!local->xdata_rsp) {
b7d4d7
-                local->xdata_rsp = dict_new();
b7d4d7
-                if (!local->xdata_rsp) {
b7d4d7
-                    local->op_ret = -1;
b7d4d7
-                    local->op_errno = ENOMEM;
b7d4d7
-                    goto unlock;
b7d4d7
-                }
b7d4d7
-            }
b7d4d7
-        }
b7d4d7
-
b7d4d7
-        if (!dict) {
b7d4d7
-            goto unlock;
b7d4d7
-        }
b7d4d7
+    local = frame->local;
b7d4d7
 
b7d4d7
+    if ((op_ret >= 0) && (dict != NULL)) {
b7d4d7
         op_ret = dict_get_ptr_and_len(dict, GF_XATTR_LOCKINFO_KEY,
b7d4d7
                                       (void **)&lockinfo_buf, &len;;
b7d4d7
-
b7d4d7
-        if (!lockinfo_buf) {
b7d4d7
-            goto unlock;
b7d4d7
-        }
b7d4d7
-
b7d4d7
-        if (!local->dict) {
b7d4d7
-            local->dict = dict_new();
b7d4d7
-            if (!local->dict) {
b7d4d7
-                local->op_ret = -1;
b7d4d7
-                local->op_errno = ENOMEM;
b7d4d7
-                goto unlock;
b7d4d7
+        if (lockinfo_buf != NULL) {
b7d4d7
+            lockinfo = dict_new();
b7d4d7
+            if (lockinfo == NULL) {
b7d4d7
+                op_ret = -1;
b7d4d7
+            } else {
b7d4d7
+                op_ret = dict_unserialize(lockinfo_buf, len, &lockinfo);
b7d4d7
             }
b7d4d7
         }
b7d4d7
     }
b7d4d7
-unlock:
b7d4d7
-    UNLOCK(&frame->lock);
b7d4d7
 
b7d4d7
-    if (lockinfo_buf != NULL) {
b7d4d7
-        lockinfo = dict_new();
b7d4d7
-        if (lockinfo == NULL) {
b7d4d7
-            local->op_ret = -1;
b7d4d7
-            local->op_errno = ENOMEM;
b7d4d7
-        } else {
b7d4d7
-            op_ret = dict_unserialize(lockinfo_buf, len, &lockinfo);
b7d4d7
-
b7d4d7
-            if (lockinfo && local->dict) {
b7d4d7
-                dict_copy(lockinfo, local->dict);
b7d4d7
-            }
b7d4d7
+    if ((op_ret >= 0) && ((lockinfo != NULL) || (xdata != NULL))) {
b7d4d7
+        op_ret = afr_update_local_dicts(frame, lockinfo, xdata);
b7d4d7
+        if (lockinfo != NULL) {
b7d4d7
+            dict_unref(lockinfo);
b7d4d7
         }
b7d4d7
     }
b7d4d7
 
b7d4d7
-    if (xdata && local->xdata_rsp) {
b7d4d7
-        dict_copy(xdata, local->xdata_rsp);
b7d4d7
+    if (op_ret < 0) {
b7d4d7
+        local->op_ret = -1;
b7d4d7
+        local->op_errno = ENOMEM;
b7d4d7
     }
b7d4d7
 
b7d4d7
-    if (!call_cnt) {
b7d4d7
+    if (uatomic_sub_return(&local->call_count, 1) == 0) {
b7d4d7
         newdict = dict_new();
b7d4d7
         if (!newdict) {
b7d4d7
             local->op_ret = -1;
b7d4d7
-            local->op_errno = ENOMEM;
b7d4d7
+            local->op_errno = op_errno = ENOMEM;
b7d4d7
             goto unwind;
b7d4d7
         }
b7d4d7
 
b7d4d7
@@ -1057,23 +992,58 @@ unlock:
b7d4d7
             local->dict, (char **)&lockinfo_buf, (unsigned int *)&len;;
b7d4d7
         if (op_ret != 0) {
b7d4d7
             local->op_ret = -1;
b7d4d7
+            local->op_errno = op_errno = ENOMEM;
b7d4d7
             goto unwind;
b7d4d7
         }
b7d4d7
 
b7d4d7
         op_ret = dict_set_dynptr(newdict, GF_XATTR_LOCKINFO_KEY,
b7d4d7
                                  (void *)lockinfo_buf, len);
b7d4d7
         if (op_ret < 0) {
b7d4d7
-            local->op_ret = -1;
b7d4d7
-            local->op_errno = -op_ret;
b7d4d7
+            GF_FREE(lockinfo_buf);
b7d4d7
+            local->op_ret = op_ret = -1;
b7d4d7
+            local->op_errno = op_errno = -op_ret;
b7d4d7
             goto unwind;
b7d4d7
         }
b7d4d7
 
b7d4d7
     unwind:
b7d4d7
-        AFR_STACK_UNWIND(fgetxattr, frame, op_ret, op_errno, newdict,
b7d4d7
-                         local->xdata_rsp);
b7d4d7
+        /* TODO: These unwinds use op_ret and op_errno instead of local->op_ret
b7d4d7
+         *       and local->op_errno. This doesn't seem right because any
b7d4d7
+         *       failure during processing of each answer could be silently
b7d4d7
+         *       ignored. This is kept this was the old behavior and because
b7d4d7
+         *       local->op_ret is initialized as -1 and local->op_errno is
b7d4d7
+         *       initialized as EUCLEAN, which makes these values useless. */
b7d4d7
+        if (is_fgetxattr) {
b7d4d7
+            AFR_STACK_UNWIND(fgetxattr, frame, op_ret, op_errno, newdict,
b7d4d7
+                             local->xdata_rsp);
b7d4d7
+        } else {
b7d4d7
+            AFR_STACK_UNWIND(getxattr, frame, op_ret, op_errno, newdict,
b7d4d7
+                             local->xdata_rsp);
b7d4d7
+        }
b7d4d7
+
b7d4d7
+        if (newdict != NULL) {
b7d4d7
+            dict_unref(newdict);
b7d4d7
+        }
b7d4d7
     }
b7d4d7
+}
b7d4d7
+
b7d4d7
+static int32_t
b7d4d7
+afr_getxattr_lockinfo_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
b7d4d7
+                          int32_t op_ret, int32_t op_errno, dict_t *dict,
b7d4d7
+                          dict_t *xdata)
b7d4d7
+{
b7d4d7
+    afr_getxattr_lockinfo_cbk_common(frame, op_ret, op_errno, dict, xdata,
b7d4d7
+                                     false);
b7d4d7
 
b7d4d7
-    dict_unref(lockinfo);
b7d4d7
+    return 0;
b7d4d7
+}
b7d4d7
+
b7d4d7
+static int32_t
b7d4d7
+afr_fgetxattr_lockinfo_cbk(call_frame_t *frame, void *cookie, xlator_t *this,
b7d4d7
+                           int32_t op_ret, int32_t op_errno, dict_t *dict,
b7d4d7
+                           dict_t *xdata)
b7d4d7
+{
b7d4d7
+    afr_getxattr_lockinfo_cbk_common(frame, op_ret, op_errno, dict, xdata,
b7d4d7
+                                     true);
b7d4d7
 
b7d4d7
     return 0;
b7d4d7
 }
b7d4d7
-- 
b7d4d7
1.8.3.1
b7d4d7