diff options
-rwxr-xr-x | tests/features/worm.t | 54 | ||||
-rw-r--r-- | xlators/features/read-only/src/Makefile.am | 4 | ||||
-rw-r--r-- | xlators/features/read-only/src/read-only.h | 17 | ||||
-rw-r--r-- | xlators/features/read-only/src/worm-helper.c | 422 | ||||
-rw-r--r-- | xlators/features/read-only/src/worm-helper.h | 36 | ||||
-rw-r--r-- | xlators/features/read-only/src/worm.c | 483 | ||||
-rw-r--r-- | xlators/mgmt/glusterd/src/glusterd-volgen.c | 10 | ||||
-rw-r--r-- | xlators/mgmt/glusterd/src/glusterd-volume-set.c | 113 |
8 files changed, 1098 insertions, 41 deletions
diff --git a/tests/features/worm.t b/tests/features/worm.t new file mode 100755 index 00000000000..407b49a79ce --- /dev/null +++ b/tests/features/worm.t @@ -0,0 +1,54 @@ +#!/bin/bash + +. $(dirname $0)/../include.rc +. $(dirname $0)/../volume.rc + +cleanup; + +TEST glusterd +TEST pidof glusterd + +TEST $CLI volume create $V0 $H0:$B0/${V0}1 + +EXPECT "$V0" volinfo_field $V0 'Volume Name' +EXPECT 'Created' volinfo_field $V0 'Status' + +TEST $CLI volume start $V0 +EXPECT 'Started' volinfo_field $V0 'Status' + +## Mount FUSE with caching disabled (read-write) +TEST $GFS -s $H0 --volfile-id $V0 $M0 + +## Tests for the volume level WORM +TEST `echo "File 1" > $M0/file1` +TEST touch $M0/file2 + +## Enable the volume level WORM +TEST $CLI volume set $V0 features.worm 1 +TEST ! mv $M0/file1 $M0/file11 +TEST `echo "block" > $M0/file2` + +## Disable the volume level WORM and delete the legacy files +TEST $CLI volume set $V0 features.worm 0 +TEST rm -f $M0/* + +## Enable file level WORM +TEST $CLI volume set $V0 features.worm-file-level 1 +TEST $CLI volume set $V0 features.default-retention-period 10 +TEST $CLI volume set $V0 features.auto-commit-period 5 + +## Tests for manual transition to WORM/Retained state +TEST `echo "worm 1" > $M0/file1` +TEST chmod 0444 $M0/file1 +sleep 5 +TEST `echo "line 1" > $M0/file1` +TEST ! mv $M0/file1 $M0/file2 +sleep 10 +TEST ! link $M0/file1 $M0/file2 +sleep 5 +TEST rm -f $M0/file1 + +TEST $CLI volume stop $V0 +EXPECT 'Stopped' volinfo_field $V0 'Status' + +cleanup; diff --git a/xlators/features/read-only/src/Makefile.am b/xlators/features/read-only/src/Makefile.am index 5aad7079344..3edac3f8a1d 100644 --- a/xlators/features/read-only/src/Makefile.am +++ b/xlators/features/read-only/src/Makefile.am @@ -2,7 +2,7 @@ xlator_LTLIBRARIES = read-only.la worm.la xlatordir = $(libdir)/glusterfs/$(PACKAGE_VERSION)/xlator/features -noinst_HEADERS = read-only.h read-only-mem-types.h read-only-common.h +noinst_HEADERS = read-only.h read-only-mem-types.h read-only-common.h worm-helper.h read_only_la_LDFLAGS = -module $(GF_XLATOR_DEFAULT_LDFLAGS) @@ -11,7 +11,7 @@ read_only_la_LIBADD = $(top_builddir)/libglusterfs/src/libglusterfs.la worm_la_LDFLAGS = -module $(GF_XLATOR_DEFAULT_LDFLAGS) -worm_la_SOURCES = read-only-common.c worm.c +worm_la_SOURCES = read-only-common.c worm-helper.c worm.c worm_la_LIBADD = $(top_builddir)/libglusterfs/src/libglusterfs.la AM_CPPFLAGS = $(GF_CPPFLAGS) -I$(top_srcdir)/libglusterfs/src diff --git a/xlators/features/read-only/src/read-only.h b/xlators/features/read-only/src/read-only.h index 8e7e1b68081..3178bb26715 100644 --- a/xlators/features/read-only/src/read-only.h +++ b/xlators/features/read-only/src/read-only.h @@ -15,7 +15,22 @@ #include "xlator.h" typedef struct { - gf_boolean_t readonly_or_worm_enabled; + uint8_t worm : 1; + uint8_t retain : 1; + uint8_t legal_hold :1; + uint8_t ret_mode : 1; + uint64_t ret_period; + uint64_t auto_commit_period; +} worm_reten_state_t; + + +typedef struct { + gf_boolean_t readonly_or_worm_enabled; + gf_boolean_t worm_file; + uint64_t reten_period; + uint64_t com_period; + char *reten_mode; + time_t start_time; } read_only_priv_t; #endif diff --git a/xlators/features/read-only/src/worm-helper.c b/xlators/features/read-only/src/worm-helper.c new file mode 100644 index 00000000000..b5b1c628d73 --- /dev/null +++ b/xlators/features/read-only/src/worm-helper.c @@ -0,0 +1,422 @@ +/* + Copyright (c) 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 + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ +#include "read-only-mem-types.h" +#include "read-only.h" +#include "xlator.h" +#include "syncop.h" +#include "worm-helper.h" + +/*Function to check whether file is read-only. + * The input *stbuf contains the attributes of the file, which is used to check + * the write protection bits for all the users of the file. + * Return true if all the write bits are disabled,false otherwise*/ +gf_boolean_t +is_write_disabled (struct iatt *stbuf) +{ + gf_boolean_t ret = _gf_false; + + GF_VALIDATE_OR_GOTO ("worm", stbuf, out); + + if (stbuf->ia_prot.owner.write == 0 && + stbuf->ia_prot.group.write == 0 && + stbuf->ia_prot.other.write == 0) + ret = _gf_true; +out: + return ret; +} + + +int32_t +worm_init_state (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr) +{ + int ret = -1; + uint64_t start_time = 0; + dict_t *dict = NULL; + + GF_VALIDATE_OR_GOTO ("worm", this, out); + GF_VALIDATE_OR_GOTO (this->name, file_ptr, out); + + start_time = time (NULL); + dict = dict_new (); + if (!dict) { + gf_log (this->name, GF_LOG_ERROR, "Error creating the dict"); + goto out; + } + ret = dict_set_uint64 (dict, "trusted.start_time", start_time); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, "Error in setting the dict"); + goto out; + } + if (fop_with_fd) + ret = syncop_fsetxattr (this, (fd_t *)file_ptr, dict, 0, + NULL, NULL); + else + ret = syncop_setxattr (this, (loc_t *)file_ptr, dict, 0, NULL, + NULL); +out: + if (dict) + dict_destroy (dict); + return ret; +} + + +/*Function to set the retention state for a file. + * It loads the WORM/Retention state into the retention_state pointer.*/ +int32_t +worm_set_state (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr, + worm_reten_state_t *retention_state, struct iatt *stbuf) +{ + read_only_priv_t *priv = NULL; + struct iatt stpre = {0,}; + int ret = -1; + + GF_VALIDATE_OR_GOTO ("worm", this, out); + GF_VALIDATE_OR_GOTO (this->name, file_ptr, out); + GF_VALIDATE_OR_GOTO (this->name, retention_state, out); + GF_VALIDATE_OR_GOTO (this->name, stbuf, out); + + priv = this->private; + GF_ASSERT (priv); + retention_state->worm = 1; + retention_state->retain = 1; + retention_state->legal_hold = 0; + if (strcmp (priv->reten_mode, "relax") == 0) + retention_state->ret_mode = 0; + else + retention_state->ret_mode = 1; + retention_state->ret_period = priv->reten_period; + retention_state->auto_commit_period = priv->com_period; + if (fop_with_fd) + ret = syncop_fstat (this, (fd_t *)file_ptr, &stpre, NULL, NULL); + else + ret = syncop_stat (this, (loc_t *)file_ptr, &stpre, NULL, NULL); + if (ret) + goto out; + stbuf->ia_mtime = stpre.ia_mtime; + stbuf->ia_atime = time (NULL) + retention_state->ret_period; + + if (fop_with_fd) + ret = syncop_fsetattr (this, (fd_t *)file_ptr, stbuf, + GF_SET_ATTR_ATIME, NULL, NULL, + NULL, NULL); + else + ret = syncop_setattr (this, (loc_t *)file_ptr, stbuf, + GF_SET_ATTR_ATIME, NULL, NULL, + NULL, NULL); + if (ret) + goto out; + + ret = set_xattr (this, retention_state, fop_with_fd, file_ptr); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, "Error setting xattr"); + goto out; + } + ret = 0; +out: + return ret; +} + + +/*This function gets the state of the WORM/Retention xattr and loads it in the + * dict pointer.*/ +int32_t +worm_get_state (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr, + worm_reten_state_t *reten_state) +{ + dict_t *dict = NULL; + char *val = NULL; + int ret = -1; + + GF_VALIDATE_OR_GOTO ("worm", this, out); + GF_VALIDATE_OR_GOTO (this->name, file_ptr, out); + GF_VALIDATE_OR_GOTO (this->name, reten_state, out); + + if (fop_with_fd) + ret = syncop_fgetxattr (this, (fd_t *)file_ptr, &dict, + "trusted.reten_state", NULL, NULL); + else + ret = syncop_getxattr (this, (loc_t *)file_ptr, &dict, + "trusted.reten_state", NULL, NULL); + if (ret < 0 || !dict) { + ret = -1; + goto out; + } + ret = dict_get_str (dict, "trusted.reten_state", &val); + if (ret) { + ret = -2; + gf_log (this->name, GF_LOG_ERROR, "Empty val"); + } + deserialize_state (val, reten_state); +out: + if (dict) + dict_unref (dict); + return ret; +} + + +/*Function to lookup the current state of the WORM/Retention profile. + * Based on the retain value and the access time of the file, the transition + * from WORM/Retention to WORM is made.*/ +void +state_lookup (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr, + worm_reten_state_t *reten_state) +{ + int ret = -1; + struct iatt stbuf = {0,}; + + GF_VALIDATE_OR_GOTO ("worm", this, out); + GF_VALIDATE_OR_GOTO (this->name, file_ptr, out); + GF_VALIDATE_OR_GOTO (this->name, reten_state, out); + + if (fop_with_fd) + ret = syncop_fstat (this, (fd_t *)file_ptr, &stbuf, NULL, NULL); + else + ret = syncop_stat (this, (loc_t *)file_ptr, &stbuf, NULL, NULL); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, "Stat lookup error: %s", + strerror (-ret)); + goto out; + } + if (time (NULL) < stbuf.ia_atime) + goto out; + + stbuf.ia_atime -= reten_state->ret_period; + reten_state->retain = 0; + reten_state->ret_period = 0; + reten_state->auto_commit_period = 0; + ret = set_xattr (this, reten_state, fop_with_fd, file_ptr); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, "Error setting xattr"); + goto out; + } + + if (fop_with_fd) + ret = syncop_fsetattr (this, (fd_t *)file_ptr, &stbuf, + GF_SET_ATTR_ATIME, NULL, NULL, + NULL, NULL); + else + ret = syncop_setattr (this, (loc_t *)file_ptr, &stbuf, + GF_SET_ATTR_ATIME, NULL, NULL, + NULL, NULL); + if (ret) + goto out; + gf_log (this->name, GF_LOG_INFO, "Retention state reset"); +out: + return; +} + + +/*This function serializes and stores the WORM/Retention state of a file in an + * uint64_t variable by setting the bits using the bitwise operations.*/ +void +serialize_state (worm_reten_state_t *reten_state, char *val) +{ + uint32_t state = 0; + + GF_VALIDATE_OR_GOTO ("worm", reten_state, out); + GF_VALIDATE_OR_GOTO ("worm", val, out); + + state |= reten_state->worm << 0; + state |= reten_state->retain << 1; + state |= reten_state->legal_hold << 2; + state |= reten_state->ret_mode << 3; + sprintf (val, "%d/%ld/%ld", state, reten_state->ret_period, + reten_state->auto_commit_period); + +out: + return; +} + + +/*This function deserializes the data stored in the xattr of the file and loads + * the value to the reten_state structure.*/ +void deserialize_state (char *val, worm_reten_state_t *reten_state) +{ + char *token = NULL; + uint32_t state = 0; + + GF_VALIDATE_OR_GOTO ("worm", val, out); + GF_VALIDATE_OR_GOTO ("worm", reten_state, out); + + token = strtok (val, "/"); + state = atoi (token); + reten_state->worm = (state >> 0) & 1; + reten_state->retain = (state >> 1) & 1; + reten_state->legal_hold = (state >> 2) & 1; + reten_state->ret_mode = (state >> 3) & 1; + token = strtok (NULL, "/"); + reten_state->ret_period = atoi (token); + token = strtok (NULL, "/"); + reten_state->auto_commit_period = atoi (token); + +out: + return; +} + + +/*Function to set the xattr for a file. + * If the xattr is already present then it will replace that.*/ +int32_t +set_xattr (xlator_t *this, worm_reten_state_t *reten_state, + gf_boolean_t fop_with_fd, void *file_ptr) +{ + char val[100] = ""; + int ret = -1; + dict_t *dict = NULL; + + GF_VALIDATE_OR_GOTO ("worm", this, out); + GF_VALIDATE_OR_GOTO (this->name, reten_state, out); + GF_VALIDATE_OR_GOTO (this->name, file_ptr, out); + + serialize_state (reten_state, val); + dict = dict_new (); + if (!dict) { + gf_log (this->name, GF_LOG_ERROR, "Error creating the dict"); + goto out; + } + ret = dict_set_str (dict, "trusted.reten_state", val); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, "Error in setting the dict"); + goto out; + } + if (fop_with_fd) + ret = syncop_fsetxattr (this, (fd_t *)file_ptr, dict, 0, + NULL, NULL); + else + ret = syncop_setxattr (this, (loc_t *)file_ptr, dict, 0, NULL, + NULL); +out: + if (dict) + dict_destroy (dict); + return ret; +} + + +/*This function checks whether a file's timeout is happend for the state + * transition and if yes, then it will do the transition from the current state + * to the appropriate state. It also decides whether to continue or to block + * the FOP. + * Return: + * 0 : If the FOP should continue i.e., if the file is not in the WORM-Retained + * state or if the FOP is unlink and the file is not in the Retained state. + * 1: If the FOP sholud block i.e., if the file is in WORM-Retained/WORM state. + * 2: Blocks the FOP if any operation fails while doing the state transition or + * fails to get the state of the file.*/ +int32_t +state_transition (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr, + glusterfs_fop_t op, int *ret_val) +{ + int label = -1; + int ret = -1; + uint64_t com_period = 0; + uint64_t ret_period = 0; + uint64_t start_time = 0; + dict_t *dict = NULL; + worm_reten_state_t reten_state = {0,}; + read_only_priv_t *priv = NULL; + struct iatt stbuf = {0,}; + + priv = this->private; + GF_ASSERT (priv); + + if (fop_with_fd) + ret = syncop_fgetxattr (this, (fd_t *)file_ptr, &dict, + "trusted.start_time", NULL, NULL); + else + ret = syncop_getxattr (this, (loc_t *)file_ptr, &dict, + "trusted.start_time", NULL, NULL); + if (ret < 0 || !dict) { + ret = -2; + label = 2; + goto out; + } + ret = dict_get_uint64 (dict, "trusted.start_time", &start_time); + if (ret) { + label = 2; + goto out; + } + + ret = worm_get_state (this, fop_with_fd, file_ptr, &reten_state); + if (ret == -2) { + ret = -1; + label = 2; + goto out; + } + com_period = priv->com_period; + if (ret == -1 && (time (NULL) - start_time) >= com_period) { + if (fop_with_fd) + ret = syncop_fstat (this, (fd_t *)file_ptr, &stbuf, + NULL, NULL); + else + ret = syncop_stat (this, (loc_t *)file_ptr, &stbuf, + NULL, NULL); + if (ret) { + label = 2; + goto out; + } + ret_period = priv->reten_period; + if ((time (NULL) - stbuf.ia_mtime) >= ret_period) { + ret = worm_set_state(this, fop_with_fd, file_ptr, + &reten_state, &stbuf); + if (ret) { + label = 2; + goto out; + } + label = 1; + goto out; + } else { + label = 0; + goto out; + } + } else if (ret == -1 && (time (NULL) - start_time) + < com_period) { + label = 0; + goto out; + } else if (reten_state.retain && + (time (NULL) - start_time) >= + reten_state.auto_commit_period) { + state_lookup (this, fop_with_fd, file_ptr, &reten_state); + } + if (reten_state.retain) + label = 1; + else if (reten_state.worm && !reten_state.retain && + op == GF_FOP_UNLINK) + label = 0; + else + label = 1; + +out: + if (dict) + dict_unref (dict); + *ret_val = ret; + return label; +} + + +/*Function to check whether a file is independently WORMed (i.e., file level + * WORM is set on the file). */ +int32_t +is_wormfile (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr) +{ + int ret = -1; + dict_t *dict = NULL; + + if (fop_with_fd) + ret = syncop_fgetxattr (this, (fd_t *)file_ptr, &dict, + "trusted.worm_file", NULL, NULL); + else + ret = syncop_getxattr (this, (loc_t *)file_ptr, &dict, + "trusted.worm_file", NULL, NULL); + if (dict) { + ret = 0; + dict_unref (dict); + } + return ret; +}
\ No newline at end of file diff --git a/xlators/features/read-only/src/worm-helper.h b/xlators/features/read-only/src/worm-helper.h new file mode 100644 index 00000000000..a06a1d38063 --- /dev/null +++ b/xlators/features/read-only/src/worm-helper.h @@ -0,0 +1,36 @@ +/* + Copyright (c) 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 + General Public License, version 3 or any later version (LGPLv3 or + later), or the GNU General Public License, version 2 (GPLv2), in all + cases as published by the Free Software Foundation. +*/ + +gf_boolean_t is_write_disabled (struct iatt *stbuf); + +int32_t worm_init_state (xlator_t *this, gf_boolean_t fop_with_fd, + void *file_ptr); + +int32_t worm_set_state (xlator_t *this, gf_boolean_t fop_with_fd, + void *file_ptr, worm_reten_state_t *retention_state, + struct iatt *stbuf); + +int32_t worm_get_state (xlator_t *this, gf_boolean_t fop_with_fd, + void *file_ptr, worm_reten_state_t *reten_state); + +void state_lookup (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr, + worm_reten_state_t *reten_state); + +void serialize_state (worm_reten_state_t *reten_state, char *val); + +void deserialize_state (char *val, worm_reten_state_t *reten_state); + +int32_t set_xattr (xlator_t *this, worm_reten_state_t *reten_state, + gf_boolean_t fop_with_fd, void *file_ptr); + +int32_t state_transition (xlator_t *this, gf_boolean_t fop_with_fd, + void *file_ptr, glusterfs_fop_t op, int *ret_val); + +int32_t is_wormfile (xlator_t *this, gf_boolean_t fop_with_fd, void *file_ptr); diff --git a/xlators/features/read-only/src/worm.c b/xlators/features/read-only/src/worm.c index f117e206285..2f35f1fc9f1 100644 --- a/xlators/features/read-only/src/worm.c +++ b/xlators/features/read-only/src/worm.c @@ -1,5 +1,5 @@ /* - Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com> + Copyright (c) 2008-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 @@ -12,6 +12,9 @@ #include "read-only-common.h" #include "read-only-mem-types.h" #include "read-only.h" +#include "syncop.h" +#include "worm-helper.h" + int32_t mem_acct_init (xlator_t *this) @@ -26,35 +29,393 @@ mem_acct_init (xlator_t *this) return ret; } -static int32_t -worm_open_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, - int32_t op_errno, fd_t *fd, dict_t *xdata) -{ - STACK_UNWIND_STRICT (open, frame, op_ret, op_errno, fd, xdata); - return 0; -} int32_t worm_open (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, fd_t *fd, dict_t *xdata) { if (is_readonly_or_worm_enabled (this) && - ((((flags & O_ACCMODE) == O_WRONLY) || - ((flags & O_ACCMODE) == O_RDWR)) && - !(flags & O_APPEND))) { + (flags & (O_WRONLY | O_RDWR | O_APPEND))) { STACK_UNWIND_STRICT (open, frame, -1, EROFS, NULL, NULL); return 0; } - STACK_WIND (frame, worm_open_cbk, FIRST_CHILD(this), + STACK_WIND_TAIL (frame, FIRST_CHILD(this), FIRST_CHILD(this)->fops->open, loc, flags, fd, xdata); return 0; } + +int32_t +worm_link (call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc, + dict_t *xdata) +{ + int ret = -1; + int label = -1; + + if (is_readonly_or_worm_enabled (this)) + goto unwind; + gf_uuid_copy (oldloc->gfid, oldloc->inode->gfid); + if (is_wormfile (this, _gf_false, oldloc)) + goto wind; + label = state_transition (this, _gf_false, oldloc, GF_FOP_LINK, &ret); + if (label == 0) + goto wind; + if (label == 1) + goto unwind; + if (label == 2) + goto out; + +unwind: + STACK_UNWIND_STRICT (link, frame, -1, EROFS, NULL, NULL, NULL, NULL, + NULL); + ret = 0; + goto out; +wind: + STACK_WIND_TAIL (frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->link, + oldloc, newloc, xdata); + ret = 0; +out: + if (label == 2) + STACK_UNWIND_STRICT (link, frame, -1, ret, NULL, NULL, + NULL, NULL, NULL); + return ret; +} + + +int32_t +worm_unlink (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, + dict_t *xdata) +{ + int ret = -1; + int label = -1; + + if (is_readonly_or_worm_enabled (this)) + goto unwind; + gf_uuid_copy (loc->gfid, loc->inode->gfid); + if (is_wormfile (this, _gf_false, loc)) + goto wind; + label = state_transition (this, _gf_false, loc, GF_FOP_UNLINK, &ret); + if (label == 0) + goto wind; + if (label == 1) + goto unwind; + if (label == 2) + goto out; + +unwind: + STACK_UNWIND_STRICT (unlink, frame, -1, EROFS, NULL, NULL, NULL); + ret = 0; + goto out; +wind: + STACK_WIND_TAIL (frame, FIRST_CHILD(this), + FIRST_CHILD(this)->fops->unlink, + loc, flags, xdata); + ret = 0; +out: + if (label == 2) + STACK_UNWIND_STRICT (unlink, frame, -1, ret, + NULL, NULL, NULL); + return ret; +} + + +int32_t +worm_rename (call_frame_t *frame, xlator_t *this, + loc_t *oldloc, loc_t *newloc, dict_t *xdata) +{ + int ret = -1; + int label = -1; + + if (is_readonly_or_worm_enabled (this)) + goto unwind; + gf_uuid_copy (oldloc->gfid, oldloc->inode->gfid); + if (is_wormfile (this, _gf_false, oldloc)) + goto wind; + label = state_transition (this, _gf_false, oldloc, GF_FOP_RENAME, + &ret); + if (label == 0) + goto wind; + if (label == 1) + goto unwind; + if (label == 2) + goto out; + +unwind: + STACK_UNWIND_STRICT (rename, frame, -1, EROFS, NULL, + NULL, NULL, NULL, NULL, NULL); + ret = 0; + goto out; +wind: + STACK_WIND_TAIL (frame, FIRST_CHILD (this), + FIRST_CHILD (this)->fops->rename, + oldloc, newloc, xdata); + ret = 0; +out: + if (label == 2) + STACK_UNWIND_STRICT (rename, frame, -1, ret, NULL, + NULL, NULL, NULL, NULL, NULL); + return ret; +} + + +int32_t +worm_truncate (call_frame_t *frame, xlator_t *this, loc_t *loc, off_t offset, + dict_t *xdata) +{ + int ret = -1; + int label = -1; + + if (is_readonly_or_worm_enabled (this)) + goto unwind; + if (is_wormfile (this, _gf_false, loc)) + goto wind; + label = state_transition (this, _gf_false, loc, GF_FOP_TRUNCATE, + &ret); + if (label == 0) + goto wind; + if (label == 1) + goto unwind; + if (label == 2) + goto out; + +unwind: + STACK_UNWIND_STRICT (truncate, frame, -1, EROFS, + NULL, NULL, NULL); + ret = 0; + goto out; + +wind: + STACK_WIND_TAIL (frame, + FIRST_CHILD (this), + FIRST_CHILD (this)->fops->truncate, + loc, offset, xdata); + ret = 0; +out: + if (label == 2) + STACK_UNWIND_STRICT (truncate, frame, -1, ret, + NULL, NULL, NULL); + return ret; +} + + +int32_t +worm_setattr (call_frame_t *frame, xlator_t *this, loc_t *loc, + struct iatt *stbuf, int32_t valid, dict_t *xdata) +{ + gf_boolean_t rd_only = _gf_false; + worm_reten_state_t reten_state = {0,}; + struct iatt stpre = {0,}; + int ret = -1; + + if (is_wormfile (this, _gf_false, loc)) + goto wind; + if (valid & GF_SET_ATTR_MODE) { + rd_only = is_write_disabled (stbuf); + if (!rd_only) + goto wind; + + ret = worm_set_state (this, _gf_false, loc, + &reten_state, stbuf); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, + "Error setting worm state"); + goto out; + } + } else if (valid & GF_SET_ATTR_ATIME) { + ret = worm_get_state (this, _gf_false, loc, &reten_state); + if (ret) + goto wind; + if (reten_state.retain) { + ret = syncop_stat (this, loc, &stpre, NULL, NULL); + if (ret) + goto out; + if (reten_state.ret_mode == 0) { + if (stbuf->ia_atime < stpre.ia_mtime) { + gf_log (this->name, GF_LOG_ERROR, + "Cannot set atime less than " + "the mtime for a WORM-Retained " + "file"); + goto unwind; + } + } else { + if (stbuf->ia_atime < stpre.ia_atime) { + gf_log (this->name, GF_LOG_ERROR, + "Cannot decrease the atime of a" + " WORM-Retained file in " + "Enterprise mode"); + goto unwind; + } + } + stbuf->ia_mtime = stpre.ia_mtime; + } + } + +wind: + STACK_WIND_TAIL (frame, FIRST_CHILD (this), + FIRST_CHILD (this)->fops->setattr, + loc, stbuf, valid, xdata); + ret = 0; + goto out; +unwind: + STACK_UNWIND_STRICT (setattr, frame, -1, EROFS, NULL, NULL, NULL); +out: + return ret; +} + + +int32_t +worm_fsetattr (call_frame_t *frame, xlator_t *this, fd_t *fd, + struct iatt *stbuf, int32_t valid, dict_t *xdata) +{ + gf_boolean_t rd_only = _gf_false; + worm_reten_state_t reten_state = {0,}; + struct iatt stpre = {0,}; + int ret = -1; + + if (is_wormfile (this, _gf_true, fd)) + goto wind; + if (valid & GF_SET_ATTR_MODE) { + rd_only = is_write_disabled (stbuf); + if (!rd_only) + goto wind; + + ret = worm_set_state (this, _gf_true, fd, + &reten_state, stbuf); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, + "Error setting worm state"); + goto out; + } + } else if (valid & GF_SET_ATTR_ATIME) { + ret = worm_get_state (this, _gf_true, fd, &reten_state); + if (ret) + goto wind; + if (reten_state.retain) { + ret = syncop_fstat (this, fd, &stpre, NULL, NULL); + if (ret) + goto out; + if (reten_state.ret_mode == 0) { + if (stbuf->ia_atime < stpre.ia_mtime) { + gf_log (this->name, GF_LOG_ERROR, + "Cannot set atime less than " + "the mtime for a WORM-Retained " + "file"); + goto unwind; + } + } else { + if (stbuf->ia_atime < stpre.ia_atime) { + gf_log (this->name, GF_LOG_ERROR, + "Cannot decrease the atime of a" + " WORM-Retained file in " + "Enterprise mode"); + goto unwind; + } + } + stbuf->ia_mtime = stpre.ia_mtime; + } + } + +wind: + STACK_WIND_TAIL (frame, FIRST_CHILD (this), + FIRST_CHILD (this)->fops->fsetattr, + fd, stbuf, valid, xdata); + ret = 0; + goto out; +unwind: + STACK_UNWIND_STRICT (fsetattr, frame, -1, EROFS, NULL, NULL, NULL); +out: + return ret; +} + + +int32_t +worm_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) +{ + worm_reten_state_t reten_state = {0,}; + int ret = -1; + + if (is_readonly_or_worm_enabled (this)) + goto unwind; + if (is_wormfile (this, _gf_true, fd)) + goto wind; + ret = worm_get_state (this, _gf_true, fd, &reten_state); + if (!reten_state.worm) + goto wind; + +unwind: + STACK_UNWIND_STRICT (writev, frame, -1, EROFS, NULL, NULL, NULL); + goto out; + +wind: + STACK_WIND_TAIL (frame, + FIRST_CHILD (this), + FIRST_CHILD (this)->fops->writev, + fd, vector, count, offset, flags, + iobref, xdata); + gf_log (this->name, GF_LOG_INFO, "WORM writev"); + ret = 0; + +out: + return ret; +} + + +int32_t +worm_create (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, + mode_t mode, mode_t umask, fd_t *fd, dict_t *xdata) +{ + int ret = -1; + read_only_priv_t *priv = NULL; + dict_t *dict = NULL; + + STACK_WIND_TAIL (frame, FIRST_CHILD (this), + FIRST_CHILD(this)->fops->create, loc, flags, + mode, umask, fd, xdata); + priv = this->private; + GF_ASSERT (priv); + + if (priv->worm_file) { + dict = dict_new (); + if (!dict) { + gf_log (this->name, GF_LOG_ERROR, "Error creating the " + "dict"); + goto out; + } + GF_VALIDATE_OR_GOTO (this->name, dict, out); + ret = dict_set_int8 (dict, "trusted.worm_file", 1); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, "Error in setting " + "the dict"); + goto out; + } + ret = syncop_fsetxattr (this, fd, dict, 0, NULL, NULL); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, + "Error setting xattr"); + goto out; + } + ret = worm_init_state (this, _gf_true, fd); + if (ret) { + gf_log (this->name, GF_LOG_ERROR, + "Error initializing state"); + } + } + +out: + if (dict) + dict_destroy (dict); + return ret; +} + + int32_t init (xlator_t *this) { - int ret = -1; + int ret = -1; read_only_priv_t *priv = NULL; if (!this->children || this->children->next) { @@ -68,11 +429,34 @@ init (xlator_t *this) "dangling volume. check volfile "); } - priv = GF_CALLOC (1, sizeof (*priv), gf_read_only_mt_priv_t); - if (!priv) + this->local_pool = mem_pool_new (read_only_priv_t, 64); + if (!this->local_pool) { + ret = -1; + gf_log (this->name, GF_LOG_ERROR, + "failed to create read_only_priv_t's memory pool"); + goto out; + } + + priv = mem_get0 (this->local_pool); + if (!priv) { + gf_log (this->name, GF_LOG_ERROR, "Error allocating priv"); + goto out; + } + + priv->reten_mode = mem_get0 (this->local_pool); + if (!priv->reten_mode) { + gf_log (this->name, GF_LOG_ERROR, "Error allocating " + "reten_mode"); goto out; + } - GF_OPTION_INIT ("worm", priv->readonly_or_worm_enabled, bool, out); + GF_OPTION_INIT ("worm", priv->readonly_or_worm_enabled, + bool, out); + GF_OPTION_INIT ("worm-file-level", priv->worm_file, bool, out); + GF_OPTION_INIT ("default-retention-period", priv->reten_period, + uint64, out); + GF_OPTION_INIT ("auto-commit-period", priv->com_period, uint64, out); + GF_OPTION_INIT ("retention-mode", priv->reten_mode, str, out); this->private = priv; ret = 0; @@ -80,25 +464,33 @@ out: return ret; } + int reconfigure (xlator_t *this, dict_t *options) { - read_only_priv_t *priv = NULL; + read_only_priv_t *priv = NULL; int ret = -1; - gf_boolean_t readonly_or_worm_enabled = _gf_false; priv = this->private; GF_ASSERT (priv); - GF_OPTION_RECONF ("worm", readonly_or_worm_enabled, options, bool, out); - - priv->readonly_or_worm_enabled = readonly_or_worm_enabled; + GF_OPTION_RECONF ("worm", priv->readonly_or_worm_enabled, + options, bool, out); + GF_OPTION_RECONF ("worm-file-level", priv->worm_file, options, bool, + out); + GF_OPTION_RECONF ("default-retention-period", priv->reten_period, + options, uint64, out); + GF_OPTION_RECONF ("retention-mode", priv->reten_mode, options, str, + out); + GF_OPTION_RECONF ("auto-commit-period", priv->com_period, options, + uint64, out); ret = 0; out: gf_log (this->name, GF_LOG_DEBUG, "returning %d", ret); return ret; } + void fini (xlator_t *this) { @@ -106,21 +498,31 @@ fini (xlator_t *this) priv = this->private; if (!priv) - return; - + goto out; + if (priv->reten_mode != NULL) { + mem_put (priv->reten_mode); + priv->reten_mode = NULL; + } + mem_put (priv); this->private = NULL; - GF_FREE (priv); - + mem_pool_destroy (this->local_pool); +out: return; } + struct xlator_fops fops = { .open = worm_open, + .writev = worm_writev, + .setattr = worm_setattr, + .fsetattr = worm_fsetattr, + .rename = worm_rename, + .link = worm_link, + .unlink = worm_unlink, + .truncate = worm_truncate, + .create = worm_create, - .unlink = ro_unlink, .rmdir = ro_rmdir, - .rename = ro_rename, - .truncate = ro_truncate, .removexattr = ro_removexattr, .fsyncdir = ro_fsyncdir, .xattrop = ro_xattrop, @@ -131,8 +533,10 @@ struct xlator_fops fops = { .lk = ro_lk, }; + struct xlator_cbks cbks; + struct volume_options options[] = { { .key = {"worm"}, .type = GF_OPTION_TYPE_BOOL, @@ -140,5 +544,26 @@ struct volume_options options[] = { .description = "When \"on\", makes a volume get write once read many " " feature. It is turned \"off\" by default." }, + { .key = {"worm-file-level"}, + .type = GF_OPTION_TYPE_BOOL, + .default_value = "off", + .description = "When \"on\", activates the file level worm. " + "It is turned \"off\" by default." + }, + { .key = {"default-retention-period"}, + .type = GF_OPTION_TYPE_TIME, + .default_value = "120", + .description = "The default retention period for the files." + }, + { .key = {"retention-mode"}, + .type = GF_OPTION_TYPE_STR, + .default_value = "relax", + .description = "The mode of retention (relax/enterprise). " + "It is relax by default." + }, + { .key = {"auto-commit-period"}, + .type = GF_OPTION_TYPE_TIME, + .default_value = "180", + .description = "Auto commit period for the files." + }, }; - diff --git a/xlators/mgmt/glusterd/src/glusterd-volgen.c b/xlators/mgmt/glusterd/src/glusterd-volgen.c index 23890238933..e88116e02ba 100644 --- a/xlators/mgmt/glusterd/src/glusterd-volgen.c +++ b/xlators/mgmt/glusterd/src/glusterd-volgen.c @@ -2075,7 +2075,8 @@ brick_graph_add_ro (volgen_graph_t *graph, glusterd_volinfo_t *volinfo, goto out; if (dict_get_str_boolean (set_dict, "features.read-only", 0) && - dict_get_str_boolean (set_dict, "features.worm", 0)) { + (dict_get_str_boolean (set_dict, "features.worm", 0) || + dict_get_str_boolean (set_dict, "features.worm-file-level", 0))) { gf_msg (THIS->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "read-only and worm cannot be set together"); @@ -2107,7 +2108,8 @@ brick_graph_add_worm (volgen_graph_t *graph, glusterd_volinfo_t *volinfo, goto out; if (dict_get_str_boolean (set_dict, "features.read-only", 0) && - dict_get_str_boolean (set_dict, "features.worm", 0)) { + (dict_get_str_boolean (set_dict, "features.worm", 0) || + dict_get_str_boolean (set_dict, "features.worm-file-level", 0))) { gf_msg (THIS->name, GF_LOG_ERROR, 0, GD_MSG_INCOMPATIBLE_VALUE, "read-only and worm cannot be set together"); @@ -2402,8 +2404,6 @@ static volgen_brick_xlator_t server_graph_table[] = { {brick_graph_add_server, NULL}, {brick_graph_add_io_stats, NULL}, {brick_graph_add_cdc, NULL}, - {brick_graph_add_ro, NULL}, - {brick_graph_add_worm, NULL}, {brick_graph_add_quota, "quota"}, {brick_graph_add_index, "index"}, {brick_graph_add_barrier, NULL}, @@ -2412,6 +2412,8 @@ static volgen_brick_xlator_t server_graph_table[] = { {brick_graph_add_iot, "io-threads"}, {brick_graph_add_upcall, "upcall"}, {brick_graph_add_pump, NULL}, + {brick_graph_add_ro, NULL}, + {brick_graph_add_worm, NULL}, {brick_graph_add_locks, "locks"}, {brick_graph_add_acl, "acl"}, {brick_graph_add_bitrot_stub, "bitrot-stub"}, diff --git a/xlators/mgmt/glusterd/src/glusterd-volume-set.c b/xlators/mgmt/glusterd/src/glusterd-volume-set.c index 5bb9d9077b5..68dec22ecaf 100644 --- a/xlators/mgmt/glusterd/src/glusterd-volume-set.c +++ b/xlators/mgmt/glusterd/src/glusterd-volume-set.c @@ -863,6 +863,82 @@ out: return ret; } + +static int +validate_worm (glusterd_volinfo_t *volinfo, dict_t *dict, char *key, + char *value, char **op_errstr) +{ + xlator_t *this = NULL; + gf_boolean_t b = _gf_false; + int ret = -1; + + this = THIS; + GF_VALIDATE_OR_GOTO ("glusterd", this, out); + ret = gf_string2boolean (value, &b); + if (ret) { + gf_asprintf (op_errstr, "%s is not a valid boolean value. %s " + "expects a valid boolean value.", value, key); + gf_msg (this->name, GF_LOG_ERROR, 0, + GD_MSG_INVALID_ENTRY, "%s", *op_errstr); + } +out: + gf_msg_debug ("glusterd", 0, "Returning %d", ret); + + return ret; +} + + +static int +validate_worm_period (glusterd_volinfo_t *volinfo, dict_t *dict, char *key, + char *value, char **op_errstr) +{ + xlator_t *this = NULL; + uint64_t period = -1; + int ret = -1; + + this = THIS; + GF_VALIDATE_OR_GOTO ("glusterd", this, out); + ret = gf_string2uint64 (value, &period); + if (ret) { + gf_asprintf (op_errstr, "%s is not a valid uint64_t value." + " %s expects a valid uint64_t value.", value, key); + gf_msg (this->name, GF_LOG_ERROR, 0, + GD_MSG_INVALID_ENTRY, "%s", *op_errstr); + } +out: + gf_msg_debug ("glusterd", 0, "Returning %d", ret); + + return ret; +} + + +static int +validate_reten_mode (glusterd_volinfo_t *volinfo, dict_t *dict, char *key, + char *value, char **op_errstr) +{ + xlator_t *this = NULL; + int ret = -1; + + this = THIS; + GF_VALIDATE_OR_GOTO ("glusterd", this, out); + if ((strcmp (value, "relax") && + strcmp (value, "enterprise"))) { + gf_asprintf (op_errstr, "The value of retention mode should be " + "either relax or enterprise. But the value" + " of %s is %s", key, value); + gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_INVALID_ENTRY, + "%s", *op_errstr); + ret = -1; + goto out; + } + ret = 0; +out: + gf_msg_debug ("glusterd", 0, "Returning %d", ret); + + return ret; +} + + /* dispatch table for VOLUME SET * ----------------------------- * @@ -2314,13 +2390,40 @@ struct volopt_map_entry glusterd_volopt_map[] = { .op_version = 1, .flags = OPT_FLAG_CLIENT_OPT | OPT_FLAG_XLATOR_OPT }, - { .key = "features.worm", - .voltype = "features/worm", - .option = "worm", - .value = "off", - .op_version = 2, + { .key = "features.worm", + .voltype = "features/worm", + .option = "worm", + .value = "off", + .validate_fn = validate_worm, + .op_version = 2, + .flags = OPT_FLAG_CLIENT_OPT | OPT_FLAG_XLATOR_OPT + }, + { .key = "features.worm-file-level", + .voltype = "features/worm", + .option = "worm-file-level", + .value = "off", + .validate_fn = validate_worm, + .op_version = GD_OP_VERSION_3_8_0, .flags = OPT_FLAG_CLIENT_OPT | OPT_FLAG_XLATOR_OPT }, + { .key = "features.default-retention-period", + .voltype = "features/worm", + .option = "default-retention-period", + .validate_fn = validate_worm_period, + .op_version = GD_OP_VERSION_3_8_0, + }, + { .key = "features.retention-mode", + .voltype = "features/worm", + .option = "retention-mode", + .validate_fn = validate_reten_mode, + .op_version = GD_OP_VERSION_3_8_0, + }, + { .key = "features.auto-commit-period", + .voltype = "features/worm", + .option = "auto-commit-period", + .validate_fn = validate_worm_period, + .op_version = GD_OP_VERSION_3_8_0, + }, { .key = "storage.linux-aio", .voltype = "storage/posix", .op_version = 1 |