diff options
author | Anoop C S <anoopcs@redhat.com> | 2015-02-04 10:34:33 +0530 |
---|---|---|
committer | Pranith Kumar Karampuri <pkarampu@redhat.com> | 2016-05-02 04:18:44 -0700 |
commit | 4517bf8dd6de310950cc5a612955aa3a2fddb57e (patch) | |
tree | 2cdf4e8ec5c9362a325d5a48e07778ea44e1fdfe | |
parent | 78c1c6002f0b11afa997a14f8378c04f257ea1c5 (diff) |
features/locks: Implement mandatory locks
Initial change to fix/enable the mandatory locking support in GlusterFS
as per the following design:
https://review.gluster.org/#/c/12014/
Accordingly 'locks.mandatory-locking' option is available as part of this
change which will accept one among the following values:
* off
* file
* forced
* optimal
See design doc for more details
Change-Id: I14c489b3f8af5ebcbfa155a03f0c175e9558ac46
BUG: 762184
Signed-off-by: Anoop C S <anoopcs@redhat.com>
Reviewed-on: http://review.gluster.org/9768
Smoke: Gluster Build System <jenkins@build.gluster.com>
CentOS-regression: Gluster Build System <jenkins@build.gluster.com>
NetBSD-regression: NetBSD Build System <jenkins@build.gluster.org>
Reviewed-by: Poornima G <pgurusid@redhat.com>
Reviewed-by: Raghavendra Talur <rtalur@redhat.com>
Reviewed-by: Rajesh Joseph <rjoseph@redhat.com>
Reviewed-by: Pranith Kumar Karampuri <pkarampu@redhat.com>
-rw-r--r-- | libglusterfs/src/glusterfs.h | 5 | ||||
-rw-r--r-- | tests/features/mandatory-lock-forced.c | 138 | ||||
-rw-r--r-- | tests/features/mandatory-lock-forced.t | 80 | ||||
-rw-r--r-- | xlators/features/locks/src/common.c | 12 | ||||
-rw-r--r-- | xlators/features/locks/src/common.h | 4 | ||||
-rw-r--r-- | xlators/features/locks/src/locks.h | 14 | ||||
-rw-r--r-- | xlators/features/locks/src/posix.c | 688 | ||||
-rw-r--r-- | xlators/mgmt/glusterd/src/glusterd-volume-set.c | 35 |
8 files changed, 823 insertions, 153 deletions
diff --git a/libglusterfs/src/glusterfs.h b/libglusterfs/src/glusterfs.h index 86c3b2b81d9..2efa3a8f470 100644 --- a/libglusterfs/src/glusterfs.h +++ b/libglusterfs/src/glusterfs.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2008-2015 Red Hat, Inc. <http://www.redhat.com> + Copyright (c) 2008-2016 Red Hat, Inc. <http://www.redhat.com> This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser @@ -273,6 +273,9 @@ #define GF_BACKTRACE_LEN 4096 #define GF_BACKTRACE_FRAME_COUNT 7 +#define GF_LK_ADVISORY 0 +#define GF_LK_MANDATORY 1 + const char *fop_enum_to_pri_string (glusterfs_fop_t fop); const char *fop_enum_to_string (glusterfs_fop_t fop); diff --git a/tests/features/mandatory-lock-forced.c b/tests/features/mandatory-lock-forced.c new file mode 100644 index 00000000000..f37206845f1 --- /dev/null +++ b/tests/features/mandatory-lock-forced.c @@ -0,0 +1,138 @@ +#include <stdio.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> + +#define LOG_ERR(func, err) do { \ + fprintf (stderr, "%s : returned error (%s)\n", func, strerror(err)); \ + exit (err); \ +} while (0) + +int fd; +struct flock lock; +char *buf = "ten bytes!"; +char *fname = "/mnt/glusterfs/0/mand.lock"; +int open_flags, child, err, status, blocked = 0; + +int do_child (char *argv[]) { + /* Initialize file open flags */ + if (strcmp (argv[2], "BLOCK") == 0) + open_flags = O_RDWR; + else if (strcmp (argv[2], "TRUNC") == 0) + open_flags = O_RDWR | O_TRUNC | O_NONBLOCK; + else if (strcmp (argv[2], "NONE") == 0) + open_flags = O_RDWR | O_NONBLOCK; + else + LOG_ERR ("Invalid option:", EINVAL); + + /* Open the file */ + fd = open (fname, open_flags); + if (fd == -1) + LOG_ERR ("Child open", errno); + + /* Perform the file operation*/ + if (strcmp (argv[3], "READ") == 0) { + buf = NULL; + err = read (fd, buf, 10); + if (err == -1) + LOG_ERR ("Child read", errno); + } else if (strcmp (argv[3], "WRITE") == 0) { + err = write (fd, buf, 10); + if (err == -1) + LOG_ERR ("Child write", errno); + } else if (strcmp (argv[3], "FTRUNCATE") == 0) { + err = ftruncate (fd, 5); + if (err) + LOG_ERR ("Child ftruncate", errno); + } else + LOG_ERR ("Invalid operation:", EINVAL); + + /* Close child fd */ + err = close (fd); + if (err) + LOG_ERR ("Child close", errno); + + /* Exit success */ + exit (0); +} + +int main (int argc, char *argv[]) { + if (argc < 4) { + fprintf (stderr, "Wrong usage: Use as ./mandatory-lock " + "<RD_LCK/WR_LCK> <BLOCK/TRUNC/NONE> " + "<READ/WRITE/FTRUNCATE\n"); + exit(EINVAL); + } + /* Create an empty lock file */ + fd = open (fname, O_CREAT | O_RDWR, 0755); + if (fd == -1) + LOG_ERR ("Parent create", errno); + + /* Determine the type of lock */ + if (strcmp (argv[1], "RD_LCK") == 0) + lock.l_type = F_RDLCK; + else if (strcmp (argv[1], "WR_LCK") == 0) + lock.l_type = F_WRLCK; + else + LOG_ERR ("Parent lock type", EINVAL); + + lock.l_whence = SEEK_SET; + lock.l_start = 0L; + lock.l_len = 0L; + + /* Let parent acquire the initial lock */ + err = fcntl (fd, F_SETLK, &lock); + if (err) + LOG_ERR ("Parent lock", errno); + + /* Now fork a child */ + child = fork (); + if (child == 0) + /* Perform the child operations */ + do_child (argv); + else { + /* If blocking mode, then sleep for 2 seconds + * and wait for the child */ + if (strcmp (argv[2], "NONE") != 0) { + sleep (2); + if (waitpid (child, &status, WNOHANG) == 0) + blocked = 1; + /* Release the parent lock so that the + * child can terminate */ + lock.l_type = F_UNLCK; + err = fcntl (fd, F_SETLK, &lock); + if (err) + LOG_ERR ("Parent unlock", errno); + } + + /* Wait for child to finish */ + waitpid (child, &status, 0); + + /* Close the parent fd */ + err = close (fd); + if (err) + LOG_ERR ("Parent close", errno); + + /* Remove the lock file*/ + err = unlink (fname); + if (err) + LOG_ERR ("Parent unlink", errno); + + /* If not blocked, exit with child exit status*/ + errno = WEXITSTATUS(status); + + /* If blocked, exit with corresponding + * error code */ + if (blocked) + errno = EWOULDBLOCK; + + if (errno != 0) + printf ("%s\n", strerror(errno)); + + exit (errno); + + } +} diff --git a/tests/features/mandatory-lock-forced.t b/tests/features/mandatory-lock-forced.t new file mode 100644 index 00000000000..563669c6774 --- /dev/null +++ b/tests/features/mandatory-lock-forced.t @@ -0,0 +1,80 @@ +#!/bin/bash + +. $(dirname $0)/../include.rc +. $(dirname $0)/../volume.rc + +cleanup; +# Start glusterd [1] +TEST glusterd + +# Create and verify the volume information [2-4] +TEST $CLI volume create $V0 $H0:$B0/${V0}{1,2}; +EXPECT "$V0" volinfo_field $V0 'Volume Name'; +EXPECT 'Created' volinfo_field $V0 'Status'; + +# Turn off the performance translators [5-9] +TEST $CLI volume set $V0 performance.open-behind off +TEST $CLI volume set $V0 performance.write-behind off +TEST $CLI volume set $V0 performance.quick-read off +TEST $CLI volume set $V0 performance.io-cache off +TEST $CLI volume set $V0 performance.read-ahead off + +# Start and mount the volume [10-11] +TEST $CLI volume start $V0; +TEST $GFS --volfile-id=/$V0 --volfile-server=$H0 $M0 + +build_tester $(dirname $0)/mandatory-lock-forced.c -o $(dirname $0)/mandatory-lock + +# Various read/write tests without enabling mandatory-locking [12-18] +$(dirname $0)/mandatory-lock RD_LCK NONE READ +TEST [ $? -eq 0 ] + +$(dirname $0)/mandatory-lock RD_LCK NONE WRITE +TEST [ $? -eq 0 ] + +$(dirname $0)/mandatory-lock WR_LCK NONE READ +TEST [ $? -eq 0 ] + +$(dirname $0)/mandatory-lock WR_LCK NONE WRITE +TEST [ $? -eq 0 ] + +# Specifies O_TRUNC during open +$(dirname $0)/mandatory-lock RD_LCK TRUNC READ +TEST [ $? -eq 0 ] + +$(dirname $0)/mandatory-lock RD_LCK NONE FTRUNCATE +TEST [ $? -eq 0 ] + +$(dirname $0)/mandatory-lock RD_LCK BLOCK WRITE +TEST [ $? -eq 0 ] + +# Enable mandatory-locking [19] +TEST $CLI volume set $V0 mandatory-locking forced + +# Restart the volume to take the change into effect [20-23] +TEST umount $M0 +TEST $CLI volume stop $V0 +TEST $CLI volume start $V0 +TEST $GFS --volfile-id=/$V0 --volfile-server=$H0 $M0 + +# Repeat the above tests with mandatory-locking [24-30] +$(dirname $0)/mandatory-lock RD_LCK NONE READ +TEST [ $? -eq 0 ] + +EXPECT "Resource temporarily unavailable" $(dirname $0)/mandatory-lock RD_LCK NONE WRITE + +EXPECT "Resource temporarily unavailable" $(dirname $0)/mandatory-lock WR_LCK NONE READ + +EXPECT "Resource temporarily unavailable" $(dirname $0)/mandatory-lock WR_LCK NONE WRITE + +EXPECT "Resource temporarily unavailable" $(dirname $0)/mandatory-lock RD_LCK TRUNC READ + +EXPECT "Resource temporarily unavailable" $(dirname $0)/mandatory-lock RD_LCK NONE FTRUNCATE + +EXPECT "Resource temporarily unavailable" $(dirname $0)/mandatory-lock RD_LCK BLOCK WRITE + +rm -rf $(dirname $0)/mandatory-lock + +cleanup + +#G_TESTDEF_TEST_STATUS_NETBSD7=KNOWN_ISSUE,BUG=1326464 diff --git a/xlators/features/locks/src/common.c b/xlators/features/locks/src/common.c index c6db18f6ba8..facb078612f 100644 --- a/xlators/features/locks/src/common.c +++ b/xlators/features/locks/src/common.c @@ -1,5 +1,5 @@ /* - Copyright (c) 2006-2012 Red Hat, Inc. <http://www.redhat.com> + Copyright (c) 2006-2012, 2015-2016 Red Hat, Inc. <http://www.redhat.com> This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser @@ -446,7 +446,7 @@ unlock: /* Create a new posix_lock_t */ posix_lock_t * new_posix_lock (struct gf_flock *flock, client_t *client, pid_t client_pid, - gf_lkowner_t *owner, fd_t *fd) + gf_lkowner_t *owner, fd_t *fd, uint32_t lk_flags) { posix_lock_t *lock = NULL; @@ -480,6 +480,7 @@ new_posix_lock (struct gf_flock *flock, client_t *client, pid_t client_pid, lock->fd = fd; lock->client_pid = client_pid; lock->owner = *owner; + lock->lk_flags = lk_flags; INIT_LIST_HEAD (&lock->list); @@ -799,7 +800,8 @@ __insert_and_merge (pl_inode_t *pl_inode, posix_lock_t *lock) continue; if (same_owner (conf, lock)) { - if (conf->fl_type == lock->fl_type) { + if (conf->fl_type == lock->fl_type && + conf->lk_flags == lock->lk_flags) { sum = add_locks (lock, conf); sum->fl_type = lock->fl_type; @@ -810,6 +812,7 @@ __insert_and_merge (pl_inode_t *pl_inode, posix_lock_t *lock) sum->fd_num = lock->fd_num; sum->client_pid = lock->client_pid; sum->owner = lock->owner; + sum->lk_flags = lock->lk_flags; __delete_lock (conf); __destroy_lock (conf); @@ -832,6 +835,7 @@ __insert_and_merge (pl_inode_t *pl_inode, posix_lock_t *lock) sum->fd_num = conf->fd_num; sum->client_pid = conf->client_pid; sum->owner = conf->owner; + sum->lk_flags = conf->lk_flags; v = subtract_locks (sum, lock); @@ -988,7 +992,7 @@ pl_send_prelock_unlock (xlator_t *this, pl_inode_t *pl_inode, unlock_lock = new_posix_lock (&flock, old_lock->client, old_lock->client_pid, &old_lock->owner, - old_lock->fd); + old_lock->fd, old_lock->lk_flags); GF_VALIDATE_OR_GOTO (this->name, unlock_lock, out); ret = 0; diff --git a/xlators/features/locks/src/common.h b/xlators/features/locks/src/common.h index be13d29362b..44f5a8484c5 100644 --- a/xlators/features/locks/src/common.h +++ b/xlators/features/locks/src/common.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006-2012 Red Hat, Inc. <http://www.redhat.com> + Copyright (c) 2006-2012, 2015-2016 Red Hat, Inc. <http://www.redhat.com> This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser @@ -35,7 +35,7 @@ posix_lock_t * new_posix_lock (struct gf_flock *flock, client_t *client, pid_t client_pid, - gf_lkowner_t *owner, fd_t *fd); + gf_lkowner_t *owner, fd_t *fd, uint32_t lk_flags); pl_inode_t * pl_inode_get (xlator_t *this, inode_t *inode); diff --git a/xlators/features/locks/src/locks.h b/xlators/features/locks/src/locks.h index 3480027c4c9..b8763091d00 100644 --- a/xlators/features/locks/src/locks.h +++ b/xlators/features/locks/src/locks.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2006-2012 Red Hat, Inc. <http://www.redhat.com> + Copyright (c) 2006-2012, 2015-2016 Red Hat, Inc. <http://www.redhat.com> This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser @@ -18,6 +18,13 @@ #include "lkowner.h" +typedef enum { + MLK_NONE, + MLK_FILE_BASED, + MLK_FORCED, + MLK_OPTIMAL +} mlk_mode_t; /* defines different mandatory locking modes*/ + struct __pl_fd; struct __posix_lock { @@ -26,6 +33,7 @@ struct __posix_lock { short fl_type; off_t fl_start; off_t fl_end; + uint32_t lk_flags; short blocked; /* waiting to acquire */ struct gf_flock user_flock; /* the flock supplied by the user */ @@ -161,7 +169,7 @@ typedef struct __pl_inode pl_inode_t; typedef struct { - gf_boolean_t mandatory; /* if mandatory locking is enabled */ + mlk_mode_t mandatory_mode; /* holds current mandatory locking mode */ gf_boolean_t trace; /* trace lock requests in and out */ char *brickname; } posix_locks_private_t; @@ -178,7 +186,7 @@ typedef struct { loc_t loc[2]; fd_t *fd; off_t offset; - enum {TRUNCATE, FTRUNCATE} op; + glusterfs_fop_t op; } pl_local_t; diff --git a/xlators/features/locks/src/posix.c b/xlators/features/locks/src/posix.c index 1d40c154162..2ff7655a170 100644 --- a/xlators/features/locks/src/posix.c +++ b/xlators/features/locks/src/posix.c @@ -1,5 +1,5 @@ /* - Copyright (c) 2006-2012 Red Hat, Inc. <http://www.redhat.com> + Copyright (c) 2006-2012, 2016 Red Hat, Inc. <http://www.redhat.com> This file is part of GlusterFS. This file is licensed to you under your choice of the GNU Lesser @@ -33,7 +33,6 @@ /* Forward declarations */ - void do_blocked_rw (pl_inode_t *); static int __rw_allowable (pl_inode_t *, posix_lock_t *, glusterfs_fop_t); static int format_brickname(char *); @@ -352,6 +351,49 @@ pl_set_xdata_response (xlator_t *this, pl_local_t *local, inode_t *parent, pl_posixlk_xattr_fill (this, inode, xdata, max_lock); } +/* Return true in case we need to ensure mandatory-locking + * semnatics under different modes. + */ +gf_boolean_t +pl_is_mandatory_locking_enabled (pl_inode_t *pl_inode) +{ + posix_locks_private_t *priv = NULL; + + priv = THIS->private; + + if (priv->mandatory_mode == MLK_FILE_BASED && pl_inode->mandatory) + return _gf_true; + else if (priv->mandatory_mode == MLK_FORCED || + priv->mandatory_mode == MLK_OPTIMAL) + return _gf_true; + + return _gf_false; +} + +/* Checks whether the region where fop is acting upon conflicts + * with existing locks. If there is no conflict function returns + * 1 else returns 0 with can_block boolean set accordingly to + * indicate block/fail the fop. + */ +int +pl_is_fop_allowed (pl_inode_t *pl_inode, posix_lock_t *region, fd_t *fd, + glusterfs_fop_t op, gf_boolean_t *can_block) +{ + int ret = 0; + + if (!__rw_allowable (pl_inode, region, op)) { + if ((!fd) || (fd && (fd->flags & O_NONBLOCK))) { + gf_log ("locks", GF_LOG_TRACE, "returning EAGAIN" + " because fd is O_NONBLOCK"); + *can_block = _gf_false; + } else + *can_block = _gf_true; + } else + ret = 1; + + return ret; +} + static pl_fdctx_t * pl_new_fdctx () { @@ -402,6 +444,214 @@ out: return fdctx; } +int32_t +pl_discard_cbk (call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + STACK_UNWIND_STRICT (discard, frame, op_ret, op_errno, prebuf, + postbuf, xdata); + return 0; +} + +int +pl_discard_cont (call_frame_t *frame, xlator_t *this, fd_t *fd, off_t offset, + size_t len, dict_t *xdata) +{ + STACK_WIND (frame, pl_discard_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->discard, fd, offset, len, xdata); + return 0; +} + +int32_t +pl_discard (call_frame_t *frame, xlator_t *this, fd_t *fd, off_t offset, + size_t len, dict_t *xdata) +{ + pl_inode_t *pl_inode = NULL; + pl_rw_req_t *rw = NULL; + posix_lock_t region = {.list = {0, }, }; + gf_boolean_t enabled = _gf_false; + gf_boolean_t can_block = _gf_true; + int op_ret = 0; + int op_errno = 0; + int allowed = 1; + + GF_VALIDATE_OR_GOTO ("locks", this, unwind); + + pl_inode = pl_inode_get (this, fd->inode); + if (!pl_inode) { + op_ret = -1; + op_errno = ENOMEM; + goto unwind; + } + + enabled = pl_is_mandatory_locking_enabled (pl_inode); + + if (frame->root->pid < 0) + enabled = _gf_false; + + if (enabled) { + region.fl_start = offset; + region.fl_end = offset + len - 1; + region.client = frame->root->client; + region.fd_num = fd_to_fdnum(fd); + region.client_pid = frame->root->pid; + region.owner = frame->root->lk_owner; + + pthread_mutex_lock (&pl_inode->mutex); + { + allowed = pl_is_fop_allowed (pl_inode, ®ion, fd, + GF_FOP_DISCARD, + &can_block); + if (allowed == 1) + goto unlock; + else if (!can_block) { + op_errno = EAGAIN; + op_ret = -1; + goto unlock; + } + + rw = GF_CALLOC (1, sizeof (*rw), + gf_locks_mt_pl_rw_req_t); + if (!rw) { + op_errno = ENOMEM; + op_ret = -1; + goto unlock; + } + + rw->stub = fop_discard_stub (frame, pl_discard_cont, + fd, offset, len, xdata); + if (!rw->stub) { + op_errno = ENOMEM; + op_ret = -1; + GF_FREE (rw); + goto unlock; + } + + rw->region = region; + + list_add_tail (&rw->list, &pl_inode->rw_list); + } + unlock: + pthread_mutex_unlock (&pl_inode->mutex); + } + + if (allowed == 1) + STACK_WIND (frame, pl_discard_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->discard, fd, offset, + len, xdata); +unwind: + if (op_ret == -1) + STACK_UNWIND_STRICT (discard, frame, op_ret, op_errno, + NULL, NULL, NULL); + + return 0; +} + +int32_t +pl_zerofill_cbk (call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, struct iatt *prebuf, + struct iatt *postbuf, dict_t *xdata) +{ + STACK_UNWIND_STRICT (zerofill, frame, op_ret, op_errno, prebuf, + postbuf, xdata); + return 0; +} + +int +pl_zerofill_cont (call_frame_t *frame, xlator_t *this, fd_t *fd, off_t offset, + off_t len, dict_t *xdata) +{ + STACK_WIND (frame, pl_zerofill_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->zerofill, fd, offset, len, xdata); + return 0; +} + +int32_t +pl_zerofill (call_frame_t *frame, xlator_t *this, fd_t *fd, off_t offset, + off_t len, dict_t *xdata) +{ + pl_inode_t *pl_inode = NULL; + pl_rw_req_t *rw = NULL; + posix_lock_t region = {.list = {0, }, }; + gf_boolean_t enabled = _gf_false; + gf_boolean_t can_block = _gf_true; + int op_ret = 0; + int op_errno = 0; + int allowed = 1; + + GF_VALIDATE_OR_GOTO ("locks", this, unwind); + + pl_inode = pl_inode_get (this, fd->inode); + if (!pl_inode) { + op_ret = -1; + op_errno = ENOMEM; + goto unwind; + } + + enabled = pl_is_mandatory_locking_enabled (pl_inode); + + if (frame->root->pid < 0) + enabled = _gf_false; + + if (enabled) { + region.fl_start = offset; + region.fl_end = offset + len - 1; + region.client = frame->root->client; + region.fd_num = fd_to_fdnum(fd); + region.client_pid = frame->root->pid; + region.owner = frame->root->lk_owner; + + pthread_mutex_lock (&pl_inode->mutex); + { + allowed = pl_is_fop_allowed (pl_inode, ®ion, fd, + GF_FOP_ZEROFILL, + &can_block); + if (allowed == 1) + goto unlock; + else if (!can_block) { + op_errno = EAGAIN; + op_ret = -1; + goto unlock; + } + + rw = GF_CALLOC (1, sizeof (*rw), + gf_locks_mt_pl_rw_req_t); + if (!rw) { + op_errno = ENOMEM; + op_ret = -1; + goto unlock; + } + + rw->stub = fop_zerofill_stub (frame, pl_zerofill_cont, + fd, offset, len, xdata); + if (!rw->stub) { + op_errno = ENOMEM; + op_ret = -1; + GF_FREE (rw); + goto unlock; + } + + rw->region = region; + + list_add_tail (&rw->list, &pl_inode->rw_list); + } + unlock: + pthread_mutex_unlock (&pl_inode->mutex); + } + + if (allowed == 1) + STACK_WIND (frame, pl_zerofill_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->zerofill, fd, offset, + len, xdata); +unwind: + if (op_ret == -1) + STACK_UNWIND_STRICT (zerofill, frame, op_ret, op_errno, + NULL, NULL, NULL); + + return 0; +} + int pl_truncate_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *prebuf, @@ -411,7 +661,7 @@ pl_truncate_cbk (call_frame_t *frame, void *cookie, xlator_t *this, local = frame->local; - if (local->op == TRUNCATE) + if (local->op == GF_FOP_TRUNCATE) loc_wipe (&local->loc[0]); if (local->xdata) @@ -419,58 +669,48 @@ pl_truncate_cbk (call_frame_t *frame, void *cookie, xlator_t *this, if (local->fd) fd_unref (local->fd); - STACK_UNWIND_STRICT (truncate, frame, op_ret, op_errno, - prebuf, postbuf, xdata); + if (local->op == GF_FOP_TRUNCATE) + STACK_UNWIND_STRICT (truncate, frame, op_ret, op_errno, + prebuf, postbuf, xdata); + else + STACK_UNWIND_STRICT (ftruncate, frame, op_ret, op_errno, + prebuf, postbuf, xdata); return 0; } - -static int -truncate_allowed (pl_inode_t *pl_inode, - client_t *client, pid_t client_pid, - gf_lkowner_t *owner, off_t offset) +int +pl_ftruncate_cont (call_frame_t *frame, xlator_t *this, fd_t *fd, + off_t offset, dict_t *xdata) { - posix_lock_t *l = NULL; - posix_lock_t region = {.list = {0, }, }; - int ret = 1; - - region.fl_start = offset; - region.fl_end = LLONG_MAX; - region.client = client; - region.client_pid = client_pid; - region.owner = *owner; - - pthread_mutex_lock (&pl_inode->mutex); - { - list_for_each_entry (l, &pl_inode->ext_list, list) { - if (!l->blocked - && locks_overlap (®ion, l) - && !same_owner (®ion, l)) { - ret = 0; - gf_log ("posix-locks", GF_LOG_TRACE, "Truncate " - "allowed"); - break; - } - } - } - pthread_mutex_unlock (&pl_inode->mutex); - - return ret; + STACK_WIND (frame, pl_truncate_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->ftruncate, fd, offset, xdata); + return 0; } +int +pl_truncate_cont (call_frame_t *frame, xlator_t *this, loc_t *loc, + off_t offset, dict_t *xdata) +{ + STACK_WIND (frame, pl_truncate_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->truncate, loc, offset, xdata); + return 0; +} static int truncate_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf, dict_t *xdata) { - posix_locks_private_t *priv = NULL; pl_local_t *local = NULL; inode_t *inode = NULL; pl_inode_t *pl_inode = NULL; + pl_rw_req_t *rw = NULL; + posix_lock_t region = {.list = {0, }, }; + gf_boolean_t enabled = _gf_false; + gf_boolean_t can_block = _gf_true; + int allowed = 1; - - priv = this->private; + GF_VALIDATE_OR_GOTO ("locks", this, unwind); local = frame->local; if (op_ret != 0) { @@ -480,7 +720,7 @@ truncate_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, goto unwind; } - if (local->op == TRUNCATE) + if (local->op == GF_FOP_TRUNCATE) inode = local->loc[0].inode; else inode = local->fd->inode; @@ -492,56 +732,119 @@ truncate_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, goto unwind; } - if (priv->mandatory - && pl_inode->mandatory - && !truncate_allowed (pl_inode, frame->root->client, - frame->root->pid, &frame->root->lk_owner, - local->offset)) { - op_ret = -1; - op_errno = EAGAIN; - goto unwind; - } + enabled = pl_is_mandatory_locking_enabled (pl_inode); - switch (local->op) { - case TRUNCATE: - STACK_WIND (frame, pl_truncate_cbk, FIRST_CHILD (this), - FIRST_CHILD (this)->fops->truncate, - &local->loc[0], local->offset, local->xdata); - break; - case FTRUNCATE: - STACK_WIND (frame, pl_truncate_cbk, FIRST_CHILD (this), - FIRST_CHILD (this)->fops->ftruncate, - local->fd, local->offset, local->xdata); - break; - } + if (frame->root->pid < 0) + enabled = _gf_false; - return 0; + if (enabled) { + region.fl_start = local->offset; + region.fl_end = LLONG_MAX; + region.client = frame->root->client; + region.fd_num = fd_to_fdnum(local->fd); + region.client_pid = frame->root->pid; + region.owner = frame->root->lk_owner; + pthread_mutex_lock (&pl_inode->mutex); + { + allowed = pl_is_fop_allowed (pl_inode, ®ion, + local->fd, local->op, + &can_block); -unwind: - gf_log (this->name, GF_LOG_ERROR, "truncate failed with ret: %d, " - "error: %s", op_ret, strerror (op_errno)); - if (local->op == TRUNCATE) - loc_wipe (&local->loc[0]); - if (local->xdata) - dict_unref (local->xdata); - if (local->fd) - fd_unref (local->fd); + if (allowed == 1) + goto unlock; + else if (!can_block) { + op_errno = EAGAIN; + op_ret = -1; + goto unlock; + } + + rw = GF_CALLOC (1, sizeof (*rw), + gf_locks_mt_pl_rw_req_t); + if (!rw) { + op_errno = ENOMEM; + op_ret = -1; + goto unlock; + } + + if (local->op == GF_FOP_TRUNCATE) + rw->stub = fop_truncate_stub (frame, + pl_truncate_cont, &local->loc[0], + local->offset, local->xdata); + else + rw->stub = fop_ftruncate_stub (frame, + pl_ftruncate_cont, local->fd, + local->offset, local->xdata); + if (!rw->stub) { + op_errno = ENOMEM; + op_ret = -1; + GF_FREE (rw); + goto unlock; + } + + rw->region = region; + + list_add_tail (&rw->list, &pl_inode->rw_list); + } + unlock: + pthread_mutex_unlock (&pl_inode->mutex); + } - STACK_UNWIND_STRICT (truncate, frame, op_ret, op_errno, buf, NULL, xdata); + if (allowed == 1) { + switch (local->op) { + case GF_FOP_TRUNCATE: + STACK_WIND (frame, pl_truncate_cbk, FIRST_CHILD (this), + FIRST_CHILD (this)->fops->truncate, + &local->loc[0], local->offset, local->xdata); + break; + case GF_FOP_FTRUNCATE: + STACK_WIND (frame, pl_truncate_cbk, FIRST_CHILD (this), + FIRST_CHILD (this)->fops->ftruncate, + local->fd, local->offset, local->xdata); + break; + default: + break; + } + } +unwind: + if (op_ret == -1) { + gf_log (this->name, GF_LOG_ERROR, "truncate failed with " + "ret: %d, error: %s", op_ret, strerror (op_errno)); + if (local->op == GF_FOP_TRUNCATE) + loc_wipe (&local->loc[0]); + if (local->xdata) + dict_unref (local->xdata); + if (local->fd) + fd_unref (local->fd); + + switch (local->op) { + case GF_FOP_TRUNCATE: + STACK_UNWIND_STRICT (truncate, frame, op_ret, + op_errno, buf, NULL, xdata); + break; + case GF_FOP_FTRUNCATE: + STACK_UNWIND_STRICT (ftruncate, frame, op_ret, + op_errno, buf, NULL, xdata); + break; + default: + break; + } + } return 0; } - int pl_truncate (call_frame_t *frame, xlator_t *this, loc_t *loc, off_t offset, dict_t *xdata) { pl_local_t *local = NULL; + int ret = -1; + + GF_VALIDATE_OR_GOTO ("locks", this, unwind); local = mem_get0 (this->local_pool); GF_VALIDATE_OR_GOTO (this->name, local, unwind); - local->op = TRUNCATE; + local->op = GF_FOP_TRUNCATE; local->offset = offset; loc_copy (&local->loc[0], loc); if (xdata) @@ -551,28 +854,30 @@ pl_truncate (call_frame_t *frame, xlator_t *this, STACK_WIND (frame, truncate_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, loc, NULL); - - return 0; - + ret = 0; unwind: - gf_log (this->name, GF_LOG_ERROR, "truncate for %s failed with ret: %d, " - "error: %s", loc->path, -1, strerror (ENOMEM)); - STACK_UNWIND_STRICT (truncate, frame, -1, ENOMEM, NULL, NULL, NULL); - + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, "truncate on %s failed with" + " ret: %d, error: %s", loc->path, -1, + strerror (ENOMEM)); + STACK_UNWIND_STRICT (truncate, frame, -1, ENOMEM, + NULL, NULL, NULL); + } return 0; } - int pl_ftruncate (call_frame_t *frame, xlator_t *this, fd_t *fd, off_t offset, dict_t *xdata) { pl_local_t *local = NULL; + int ret = -1; + GF_VALIDATE_OR_GOTO ("locks", this, unwind); local = mem_get0 (this->local_pool); GF_VALIDATE_OR_GOTO (this->name, local, unwind); - local->op = FTRUNCATE; + local->op = GF_FOP_FTRUNCATE; local->offset = offset; local->fd = fd_ref (fd); if (xdata) @@ -582,13 +887,14 @@ pl_ftruncate (call_frame_t *frame, xlator_t *this, STACK_WIND (frame, truncate_stat_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->fstat, fd, xdata); - return 0; - + ret = 0; unwind: - gf_log (this->name, GF_LOG_ERROR, "ftruncate failed with ret: %d, " - "error: %s", -1, strerror (ENOMEM)); - STACK_UNWIND_STRICT (ftruncate, frame, -1, ENOMEM, NULL, NULL, NULL); - + if (ret == -1) { + gf_log (this->name, GF_LOG_ERROR, "ftruncate failed with" + " ret: %d, error: %s", -1, strerror (ENOMEM)); + STACK_UNWIND_STRICT (ftruncate, frame, -1, ENOMEM, + NULL, NULL, NULL); + } return 0; } @@ -1336,10 +1642,62 @@ int pl_open (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, fd_t *fd, dict_t *xdata) { - STACK_WIND (frame, pl_open_cbk, - FIRST_CHILD(this), FIRST_CHILD(this)->fops->open, - loc, flags, fd, xdata); + int op_ret = -1; + int op_errno = EINVAL; + pl_inode_t *pl_inode = NULL; + posix_lock_t *l = NULL; + posix_locks_private_t *priv = NULL; + + priv = this->private; + + GF_VALIDATE_OR_GOTO ("locks", this, unwind); + + op_ret = 0, op_errno = 0; + pl_inode = pl_inode_get (this, fd->inode); + + /* As per design, under forced and file-based mandatory locking modes + * it doesn't matter whether inodes's lock list contain advisory or + * mandatory type locks. So we just check whether inode's lock list is + * empty or not to make sure that no locks are being held for the file. + * Whereas under optimal mandatory locking mode, we strictly fail open + * if and only if lock list contain mandatory locks. + */ + if (((priv->mandatory_mode == MLK_FILE_BASED) && pl_inode->mandatory) || + priv->mandatory_mode == MLK_FORCED) { + if (fd->flags & O_TRUNC) { + pthread_mutex_lock (&pl_inode->mutex); + { + if (!list_empty (&pl_inode->ext_list)) { + op_ret = -1; + op_errno = EAGAIN; + } + } + pthread_mutex_unlock (&pl_inode->mutex); + } + } else if (priv->mandatory_mode == MLK_OPTIMAL) { + if (fd->flags & O_TRUNC) { + pthread_mutex_lock (&pl_inode->mutex); + { + list_for_each_entry (l, &pl_inode->ext_list, list) { + if ((l->lk_flags & GF_LK_MANDATORY)) { + op_ret = -1; + op_errno = EAGAIN; + break; + } + } + } + pthread_mutex_unlock (&pl_inode->mutex); + } + } +unwind: + if (op_ret == -1) + STACK_UNWIND_STRICT (open, frame, op_ret, op_errno, + NULL, NULL); + else + STACK_WIND (frame, pl_open_cbk, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->open, + loc, flags, fd, xdata); return 0; } @@ -1455,18 +1813,26 @@ do_blocked_rw (pl_inode_t *pl_inode) return; } - static int __rw_allowable (pl_inode_t *pl_inode, posix_lock_t *region, glusterfs_fop_t op) { posix_lock_t *l = NULL; + posix_locks_private_t *priv = NULL; int ret = 1; + priv = THIS->private; + list_for_each_entry (l, &pl_inode->ext_list, list) { - if (locks_overlap (l, region) && !same_owner (l, region)) { + if (!l->blocked && locks_overlap (l, region) + && !same_owner (l, region)) { if ((op == GF_FOP_READ) && (l->fl_type != F_WRLCK)) continue; + /* Check for mandatory lock under optimal + * mandatory-locking mode */ + if (priv->mandatory_mode == MLK_OPTIMAL + && !(l->lk_flags & GF_LK_MANDATORY)) + continue; ret = 0; break; } @@ -1475,7 +1841,6 @@ __rw_allowable (pl_inode_t *pl_inode, posix_lock_t *region, return ret; } - int pl_readv_cont (call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size, off_t offset, uint32_t flags, dict_t *xdata) @@ -1487,26 +1852,35 @@ pl_readv_cont (call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size, return 0; } - int pl_readv (call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size, off_t offset, uint32_t flags, dict_t *xdata) { - posix_locks_private_t *priv = NULL; pl_inode_t *pl_inode = NULL; pl_rw_req_t *rw = NULL; posix_lock_t region = {.list = {0, }, }; + gf_boolean_t enabled = _gf_false; + gf_boolean_t can_block = _gf_true; int op_ret = 0; int op_errno = 0; - char wind_needed = 1; + int allowed = 1; + GF_VALIDATE_OR_GOTO ("locks", this, unwind); - priv = this->private; pl_inode = pl_inode_get (this, fd->inode); + if (!pl_inode) { + op_ret = -1; + op_errno = ENOMEM; + goto unwind; + } PL_LOCAL_GET_REQUESTS (frame, this, xdata, fd, NULL, NULL); + enabled = pl_is_mandatory_locking_enabled (pl_inode); + + if (frame->root->pid < 0) + enabled = _gf_false; - if (priv->mandatory && pl_inode->mandatory) { + if (enabled) { region.fl_start = offset; region.fl_end = offset + size - 1; region.client = frame->root->client; @@ -1516,15 +1890,11 @@ pl_readv (call_frame_t *frame, xlator_t *this, pthread_mutex_lock (&pl_inode->mutex); { - wind_needed = __rw_allowable (pl_inode, ®ion, - GF_FOP_READ); - if (wind_needed) { + allowed = pl_is_fop_allowed (pl_inode, ®ion, fd, + GF_FOP_READ, &can_block); + if (allowed == 1) goto unlock; - } - - if (fd->flags & O_NONBLOCK) { - gf_log (this->name, GF_LOG_TRACE, - "returning EAGAIN as fd is O_NONBLOCK"); + else if (!can_block) { op_errno = EAGAIN; op_ret = -1; goto unlock; @@ -1556,21 +1926,19 @@ pl_readv (call_frame_t *frame, xlator_t *this, pthread_mutex_unlock (&pl_inode->mutex); } - - if (wind_needed) { + if (allowed == 1) { STACK_WIND (frame, pl_readv_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->readv, fd, size, offset, flags, xdata); } - +unwind: if (op_ret == -1) - STACK_UNWIND_STRICT (readv, frame, -1, op_errno, + STACK_UNWIND_STRICT (readv, frame, op_ret, op_errno, NULL, 0, NULL, NULL, NULL); return 0; } - int pl_writev_cont (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *vector, int count, off_t offset, @@ -1583,26 +1951,36 @@ pl_writev_cont (call_frame_t *frame, xlator_t *this, fd_t *fd, return 0; } - int pl_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *vector, int32_t count, off_t offset, uint32_t flags, struct iobref *iobref, dict_t *xdata) { - posix_locks_private_t *priv = NULL; pl_inode_t *pl_inode = NULL; pl_rw_req_t *rw = NULL; posix_lock_t region = {.list = {0, }, }; + gf_boolean_t enabled = _gf_false; + gf_boolean_t can_block = _gf_true; int op_ret = 0; int op_errno = 0; - char wind_needed = 1; + int allowed = 1; + + GF_VALIDATE_OR_GOTO ("locks", this, unwind); - priv = this->private; pl_inode = pl_inode_get (this, fd->inode); + if (!pl_inode) { + op_ret = -1; + op_errno = ENOMEM; + goto unwind; + } PL_LOCAL_GET_REQUESTS (frame, this, xdata, fd, NULL, NULL); + enabled = pl_is_mandatory_locking_enabled (pl_inode); + + if (frame->root->pid < 0) + enabled = _gf_false; - if (priv->mandatory && pl_inode->mandatory) { + if (enabled) { region.fl_start = offset; region.fl_end = offset + iov_length (vector, count) - 1; region.client = frame->root->client; @@ -1612,15 +1990,11 @@ pl_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, pthread_mutex_lock (&pl_inode->mutex); { - wind_needed = __rw_allowable (pl_inode, ®ion, - GF_FOP_WRITE); - if (wind_needed) + allowed = pl_is_fop_allowed (pl_inode, ®ion, fd, + GF_FOP_WRITE, &can_block); + if (allowed == 1) goto unlock; - - if (fd->flags & O_NONBLOCK) { - gf_log (this->name, GF_LOG_TRACE, - "returning EAGAIN because fd is " - "O_NONBLOCK"); + else if (!can_block) { op_errno = EAGAIN; op_ret = -1; goto unlock; @@ -1652,16 +2026,15 @@ pl_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, pthread_mutex_unlock (&pl_inode->mutex); } - - if (wind_needed) { + if (allowed == 1) { STACK_WIND (frame, pl_writev_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->writev, fd, vector, count, offset, flags, iobref, xdata); } - +unwind: if (op_ret == -1) - STACK_UNWIND_STRICT (writev, frame, -1, op_errno, NULL, NULL, - NULL); + STACK_UNWIND_STRICT (writev, frame, op_ret, op_errno, + NULL, NULL, NULL); return 0; } @@ -1689,7 +2062,7 @@ lock_dup (posix_lock_t *lock) new_lock = new_posix_lock (&lock->user_flock, lock->client, lock->client_pid, &lock->owner, - (fd_t *)lock->fd_num); + (fd_t *)lock->fd_num, lock->lk_flags); return new_lock; } @@ -1854,6 +2227,21 @@ pl_lk (call_frame_t *frame, xlator_t *this, posix_lock_t *reqlock = NULL; posix_lock_t *conf = NULL; int ret = 0; + uint32_t lk_flags = 0; + posix_locks_private_t *priv = NULL; + + priv = this->private; + + ret = dict_get_uint32 (xdata, "lkmode", &lk_flags); + if (ret == 0) { + if (priv->mandatory_mode == MLK_NONE) + gf_log (this->name, GF_LOG_DEBUG, "Lock flags received " + "in a non-mandatory locking environment, " + "continuing"); + else + gf_log (this->name, GF_LOG_DEBUG, "Lock flags received, " + "continuing"); + } if ((flock->l_start < 0) || ((flock->l_start + flock->l_len) < 0)) { @@ -1880,7 +2268,7 @@ pl_lk (call_frame_t *frame, xlator_t *this, } reqlock = new_posix_lock (flock, frame->root->client, frame->root->pid, - &frame->root->lk_owner, fd); + &frame->root->lk_owner, fd, lk_flags); if (!reqlock) { op_ret = -1; @@ -2856,7 +3244,7 @@ init (xlator_t *this) { posix_locks_private_t *priv = NULL; xlator_list_t *trav = NULL; - data_t *mandatory = NULL; + char *tmp_str = NULL; int ret = -1; if (!this->children || this->children->next) { @@ -2884,10 +3272,16 @@ init (xlator_t *this) priv = GF_CALLOC (1, sizeof (*priv), gf_locks_mt_posix_locks_private_t); - mandatory = dict_get (this->options, "mandatory-locks"); - if (mandatory) - gf_log (this->name, GF_LOG_WARNING, - "mandatory locks not supported in this minor release."); + GF_OPTION_INIT ("mandatory-locking", tmp_str, str, out); + if (!strcmp (tmp_str, "forced")) + priv->mandatory_mode = MLK_FORCED; + else if (!strcmp (tmp_str, "file")) + priv->mandatory_mode = MLK_FILE_BASED; + else if (!strcmp (tmp_str, "optimal")) + priv->mandatory_mode = MLK_OPTIMAL; + else + priv->mandatory_mode = MLK_NONE; + tmp_str = NULL; GF_OPTION_INIT ("trace", priv->trace, bool, out); @@ -3095,6 +3489,8 @@ struct xlator_fops fops = { .fstat = pl_fstat, .truncate = pl_truncate, .ftruncate = pl_ftruncate, + .discard = pl_discard, + .zerofill = pl_zerofill, .open = pl_open, .readv = pl_readv, .writev = pl_writev, @@ -3126,13 +3522,21 @@ struct xlator_cbks cbks = { .client_disconnect = pl_client_disconnect_cbk, }; - struct volume_options options[] = { - { .key = { "mandatory-locks", "mandatory" }, - .type = GF_OPTION_TYPE_BOOL + { .key = { "mandatory-locking" }, + .type = GF_OPTION_TYPE_STR, + .default_value = "off", + .description = "Specifies the mandatory-locking mode. Valid options " + "are 'file' to use linux style mandatory locks, " + "'forced' to use volume striclty under mandatory lock " + "semantics only and 'optimal' to treat advisory and " + "mandatory locks separately on their own." }, { .key = { "trace" }, - .type = GF_OPTION_TYPE_BOOL + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "Trace the different lock requests " + "to logs." }, { .key = {NULL} }, }; diff --git a/xlators/mgmt/glusterd/src/glusterd-volume-set.c b/xlators/mgmt/glusterd/src/glusterd-volume-set.c index 68dec22ecaf..008a4589e10 100644 --- a/xlators/mgmt/glusterd/src/glusterd-volume-set.c +++ b/xlators/mgmt/glusterd/src/glusterd-volume-set.c @@ -791,6 +791,35 @@ validate_replica_heal_enable_disable (glusterd_volinfo_t *volinfo, dict_t *dict, } static int +validate_mandatory_locking (glusterd_volinfo_t *volinfo, dict_t *dict, + char *key, char *value, char **op_errstr) +{ + char errstr[2048] = ""; + int ret = 0; + xlator_t *this = NULL; + + this = THIS; + GF_ASSERT (this); + + if (strcmp (value, "off") != 0 && strcmp (value, "file") != 0 && + strcmp(value, "forced") != 0 && + strcmp(value, "optimal") != 0) { + snprintf (errstr, sizeof(errstr), "Invalid option value '%s':" + " Available options are 'off', 'file', " + "'forced' or 'optimal'", value); + gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_INVALID_ENTRY, + "%s", errstr); + *op_errstr = gf_strdup (errstr); + ret = -1; + goto out; + } +out: + gf_msg_debug (this->name, 0, "Returning %d", ret); + + return ret; +} + +static int validate_disperse_heal_enable_disable (glusterd_volinfo_t *volinfo, dict_t *dict, char *key, char *value, char **op_errstr) @@ -2789,9 +2818,13 @@ struct volopt_map_entry glusterd_volopt_map[] = { #endif /* USE_GFDB */ { .key = "locks.trace", .voltype = "features/locks", - .type = NO_DOC, .op_version = GD_OP_VERSION_3_7_0, }, + { .key = "locks.mandatory-locking", + .voltype = "features/locks", + .op_version = GD_OP_VERSION_4_0_0, + .validate_fn = validate_mandatory_locking, + }, { .key = "cluster.disperse-self-heal-daemon", .voltype = "cluster/disperse", .type = NO_DOC, |