mirror of
https://github.com/versity/scoutfs.git
synced 2026-01-04 03:14:02 +00:00
Add inode retention bit
Add a bit to the private scoutfs inode flags which indicates that the inode is in retention mode. The bit is visible through the _attr_x interface. It can only be set on regular files and when set it prevents modification to all but non-user xattrs. It can be cleared by root. Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
@@ -29,6 +29,10 @@ static int validate_attr_x_input(struct super_block *sb, struct scoutfs_ioctl_in
|
||||
(iax->x_flags & SCOUTFS_IOC_IAX_F__UNKNOWN))
|
||||
return -EINVAL;
|
||||
|
||||
if ((iax->x_mask & SCOUTFS_IOC_IAX_RETENTION) &&
|
||||
(ret = scoutfs_fmt_vers_unsupported(sb, SCOUTFS_FORMAT_VERSION_FEAT_RETENTION)))
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -100,6 +104,13 @@ int scoutfs_get_attr_x(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *i
|
||||
size = fill_attr(size, iax, SCOUTFS_IOC_IAX_CRTIME, crtime_sec, si->crtime.tv_sec);
|
||||
size = fill_attr(size, iax, SCOUTFS_IOC_IAX_CRTIME, crtime_nsec, si->crtime.tv_nsec);
|
||||
size = fill_attr(size, iax, SCOUTFS_IOC_IAX_SIZE, size, i_size_read(inode));
|
||||
if (iax->x_mask & SCOUTFS_IOC_IAX__BITS) {
|
||||
bits = 0;
|
||||
if ((iax->x_mask & SCOUTFS_IOC_IAX_RETENTION) &&
|
||||
(scoutfs_inode_get_flags(inode) & SCOUTFS_INO_FLAG_RETENTION))
|
||||
bits |= SCOUTFS_IOC_IAX_B_RETENTION;
|
||||
size = fill_attr(size, iax, SCOUTFS_IOC_IAX__BITS, bits, bits);
|
||||
}
|
||||
|
||||
ret = size;
|
||||
unlock:
|
||||
@@ -131,6 +142,10 @@ static bool valid_attr_changes(struct inode *inode, struct scoutfs_ioctl_inode_a
|
||||
(!(iax->x_mask & SCOUTFS_IOC_IAX_SIZE) || (iax->size == 0)))
|
||||
return false;
|
||||
|
||||
/* the retention bit only applies to regular files */
|
||||
if ((iax->x_mask & SCOUTFS_IOC_IAX_RETENTION) && !S_ISREG(inode->i_mode))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -170,6 +185,12 @@ int scoutfs_set_attr_x(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *i
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
/* retention prevents modification unless also clearing retention */
|
||||
ret = scoutfs_inode_check_retention(inode);
|
||||
if (ret < 0 && !((iax->x_mask & SCOUTFS_IOC_IAX_RETENTION) &&
|
||||
!(iax->bits & SCOUTFS_IOC_IAX_B_RETENTION)))
|
||||
goto unlock;
|
||||
|
||||
/* setting only so we don't see 0 data seq with nonzero data_version */
|
||||
if ((iax->x_mask & SCOUTFS_IOC_IAX_DATA_VERSION) && (iax->data_version > 0))
|
||||
set_data_seq = true;
|
||||
@@ -204,6 +225,11 @@ int scoutfs_set_attr_x(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *i
|
||||
si->crtime.tv_sec = iax->crtime_sec;
|
||||
si->crtime.tv_nsec = iax->crtime_nsec;
|
||||
}
|
||||
if (iax->x_mask & SCOUTFS_IOC_IAX_RETENTION) {
|
||||
scoutfs_inode_set_flags(inode, ~SCOUTFS_INO_FLAG_RETENTION,
|
||||
(iax->bits & SCOUTFS_IOC_IAX_B_RETENTION) ?
|
||||
SCOUTFS_INO_FLAG_RETENTION : 0);
|
||||
}
|
||||
|
||||
scoutfs_update_inode_item(inode, lock, &ind_locks);
|
||||
ret = 0;
|
||||
|
||||
@@ -586,6 +586,12 @@ static int scoutfs_get_block(struct inode *inode, sector_t iblock,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (create && !si->staging) {
|
||||
ret = scoutfs_inode_check_retention(inode);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* convert unwritten to written, could be staging */
|
||||
if (create && ext.map && (ext.flags & SEF_UNWRITTEN)) {
|
||||
un.start = iblock;
|
||||
@@ -1254,6 +1260,9 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off,
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (!is_stage && (ret = scoutfs_inode_check_retention(to)))
|
||||
goto out;
|
||||
|
||||
if ((from_off & SCOUTFS_BLOCK_SM_MASK) ||
|
||||
(to_off & SCOUTFS_BLOCK_SM_MASK) ||
|
||||
((byte_len & SCOUTFS_BLOCK_SM_MASK) &&
|
||||
|
||||
@@ -926,6 +926,10 @@ static int scoutfs_unlink(struct inode *dir, struct dentry *dentry)
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = scoutfs_inode_check_retention(inode);
|
||||
if (ret < 0)
|
||||
goto unlock;
|
||||
|
||||
hash = dirent_name_hash(dentry->d_name.name, dentry->d_name.len);
|
||||
|
||||
ret = lookup_dirent(sb, scoutfs_ino(dir), dentry->d_name.name, dentry->d_name.len, hash,
|
||||
@@ -1632,6 +1636,10 @@ static int scoutfs_rename_common(struct inode *old_dir,
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if ((old_inode && (ret = scoutfs_inode_check_retention(old_inode))) ||
|
||||
(new_inode && (ret = scoutfs_inode_check_retention(new_inode))))
|
||||
goto out_unlock;
|
||||
|
||||
if (should_orphan(new_inode)) {
|
||||
ret = scoutfs_lock_orphan(sb, SCOUTFS_LOCK_WRITE_ONLY, 0, scoutfs_ino(new_inode),
|
||||
&orph_lock);
|
||||
|
||||
@@ -108,6 +108,10 @@ retry:
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_inode_check_retention(inode);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_complete_truncate(inode, scoutfs_inode_lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
@@ -216,6 +220,10 @@ retry:
|
||||
if (ret <= 0)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_inode_check_retention(inode);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_complete_truncate(inode, scoutfs_inode_lock);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#define SCOUTFS_FORMAT_VERSION_MAX 2
|
||||
#define SCOUTFS_FORMAT_VERSION_MAX_STR __stringify(SCOUTFS_FORMAT_VERSION_MAX)
|
||||
|
||||
#define SCOUTFS_FORMAT_VERSION_FEAT_RETENTION 2
|
||||
|
||||
/* statfs(2) f_type */
|
||||
#define SCOUTFS_SUPER_MAGIC 0x554f4353 /* "SCOU" */
|
||||
|
||||
@@ -882,7 +884,8 @@ static inline int scoutfs_inode_valid_vers_bytes(__u64 fmt_vers, int bytes)
|
||||
return bytes == sizeof(struct scoutfs_inode);
|
||||
}
|
||||
|
||||
#define SCOUTFS_INO_FLAG_TRUNCATE 0x1
|
||||
#define SCOUTFS_INO_FLAG_TRUNCATE 0x1
|
||||
#define SCOUTFS_INO_FLAG_RETENTION 0x2
|
||||
|
||||
#define SCOUTFS_ROOT_INO 1
|
||||
|
||||
|
||||
@@ -504,6 +504,10 @@ retry:
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = scoutfs_inode_check_retention(inode);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
attr_size = (attr->ia_valid & ATTR_SIZE) ? attr->ia_size :
|
||||
i_size_read(inode);
|
||||
|
||||
@@ -2190,6 +2194,17 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an error if the inode has the retention flag set and can not
|
||||
* be modified. This mimics the errno returned by the vfs whan an
|
||||
* inode's immutable flag is set. The flag won't be set on older format
|
||||
* versions so we don't check the mounted format version here.
|
||||
*/
|
||||
int scoutfs_inode_check_retention(struct inode *inode)
|
||||
{
|
||||
return (scoutfs_inode_get_flags(inode) & SCOUTFS_INO_FLAG_RETENTION) ? -EPERM : 0;
|
||||
}
|
||||
|
||||
int scoutfs_inode_setup(struct super_block *sb)
|
||||
{
|
||||
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
|
||||
|
||||
@@ -124,6 +124,8 @@ u32 scoutfs_inode_get_flags(struct inode *inode);
|
||||
void scoutfs_inode_set_flags(struct inode *inode, u32 and, u32 or);
|
||||
int scoutfs_complete_truncate(struct inode *inode, struct scoutfs_lock *lock);
|
||||
|
||||
int scoutfs_inode_check_retention(struct inode *inode);
|
||||
|
||||
int scoutfs_inode_refresh(struct inode *inode, struct scoutfs_lock *lock);
|
||||
#ifdef KC_LINUX_HAVE_RHEL_IOPS_WRAPPER
|
||||
int scoutfs_getattr(struct vfsmount *mnt, struct dentry *dentry,
|
||||
|
||||
@@ -686,6 +686,7 @@ struct scoutfs_ioctl_inode_attr_x {
|
||||
__u32 crtime_nsec;
|
||||
__u64 crtime_sec;
|
||||
__u64 size;
|
||||
__u64 bits;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -701,6 +702,13 @@ struct scoutfs_ioctl_inode_attr_x {
|
||||
#define SCOUTFS_IOC_IAX_F_SIZE_OFFLINE (1ULL << 0)
|
||||
#define SCOUTFS_IOC_IAX_F__UNKNOWN (U64_MAX << 1)
|
||||
|
||||
/*
|
||||
* Single-bit values stored in the @bits field. These indicate whether
|
||||
* the bit is set, or not. The main _IAX_ bits set in the mask indicate
|
||||
* whether this value bit is populated by _get or stored by _set.
|
||||
*/
|
||||
#define SCOUTFS_IOC_IAX_B_RETENTION (1ULL << 0)
|
||||
|
||||
/*
|
||||
* x_mask bits which indicate which attributes of the inode to populate
|
||||
* on return for _get or to set on the inode for _set. Each mask bit
|
||||
@@ -710,6 +718,14 @@ struct scoutfs_ioctl_inode_attr_x {
|
||||
* Each field can have different permissions or other attribute
|
||||
* requirements which can cause calls to fail. If _set_ fails then no
|
||||
* other attribute changes will have been made by the same call.
|
||||
*
|
||||
* @SCOUTFS_IOC_IAX_RETENTION: Mark a file for retention. When marked,
|
||||
* no modification can be made to the file other than changing extended
|
||||
* attributes outside the "user." prefix and clearing the retention
|
||||
* mark. This can only be set on regular files and requires root (the
|
||||
* CAP_SYS_ADMIN capability). Other attributes can be set with a
|
||||
* set_attr_x call on a retention inode as long as that call also
|
||||
* successfully clears the retention mark.
|
||||
*/
|
||||
#define SCOUTFS_IOC_IAX_META_SEQ (1ULL << 0)
|
||||
#define SCOUTFS_IOC_IAX_DATA_SEQ (1ULL << 1)
|
||||
@@ -719,7 +735,12 @@ struct scoutfs_ioctl_inode_attr_x {
|
||||
#define SCOUTFS_IOC_IAX_CTIME (1ULL << 5)
|
||||
#define SCOUTFS_IOC_IAX_CRTIME (1ULL << 6)
|
||||
#define SCOUTFS_IOC_IAX_SIZE (1ULL << 7)
|
||||
#define SCOUTFS_IOC_IAX__UNKNOWN (U64_MAX << 8)
|
||||
#define SCOUTFS_IOC_IAX_RETENTION (1ULL << 8)
|
||||
|
||||
/* single bit attributes that are packed in the bits field as _B_ */
|
||||
#define SCOUTFS_IOC_IAX__BITS (SCOUTFS_IOC_IAX_RETENTION)
|
||||
/* inverse of all the bits we understand */
|
||||
#define SCOUTFS_IOC_IAX__UNKNOWN (U64_MAX << 9)
|
||||
|
||||
#define SCOUTFS_IOC_GET_ATTR_X \
|
||||
_IOW(SCOUTFS_IOCTL_MAGIC, 18, struct scoutfs_ioctl_inode_attr_x)
|
||||
|
||||
@@ -81,6 +81,18 @@ static void init_xattr_key(struct scoutfs_key *key, u64 ino, u32 name_hash,
|
||||
#define SCOUTFS_XATTR_PREFIX "scoutfs."
|
||||
#define SCOUTFS_XATTR_PREFIX_LEN (sizeof(SCOUTFS_XATTR_PREFIX) - 1)
|
||||
|
||||
/*
|
||||
* We could have hidden the logic that needs this in a user-prefix
|
||||
* specific .set handler, but I wanted to make sure that we always
|
||||
* applied that logic from any call chains to _xattr_set. The
|
||||
* additional strcmp isn't so expensive given all the rest of the work
|
||||
* we're doing in here.
|
||||
*/
|
||||
static inline bool is_user(const char *name)
|
||||
{
|
||||
return !strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
|
||||
}
|
||||
|
||||
#define HIDE_TAG "hide."
|
||||
#define SRCH_TAG "srch."
|
||||
#define TOTL_TAG "totl."
|
||||
@@ -670,6 +682,11 @@ int scoutfs_xattr_set_locked(struct inode *inode, const char *name, size_t name_
|
||||
if (tgs->totl && ((ret = parse_totl_key(&totl_key, name, name_len)) != 0))
|
||||
return ret;
|
||||
|
||||
/* retention blocks user. xattr modification, all else allowed */
|
||||
ret = scoutfs_inode_check_retention(inode);
|
||||
if (ret < 0 && is_user(name))
|
||||
return ret;
|
||||
|
||||
/* allocate enough to always read an existing xattr's totl */
|
||||
xat_bytes_totl = first_item_bytes(name_len,
|
||||
max_t(size_t, size, SCOUTFS_XATTR_MAX_TOTL_U64));
|
||||
|
||||
@@ -295,6 +295,23 @@ with the
|
||||
ioctl.
|
||||
.RE
|
||||
|
||||
.SH FILE RETENTION MODE
|
||||
A file can be set to retention mode by setting the
|
||||
.IB RETENTION
|
||||
attribute with the
|
||||
.IB SET_ATTR_X
|
||||
ioctl. This flag can only be set on regular files and requires root
|
||||
permission (the
|
||||
.IB CAP_SYS_ADMIN
|
||||
capability).
|
||||
.sp
|
||||
Once in retention mode all modifications of the file will fail. The
|
||||
only exceptions are that system extended attributes (all those without
|
||||
the "user." prefix) may be modified. The retention bit may be cleared
|
||||
with sufficient priveledges to remove the retention restrictions on
|
||||
other modifications.
|
||||
.RE
|
||||
|
||||
.SH FORMAT VERSION
|
||||
The format version defines the layout and use of structures stored on
|
||||
devices and passed over the network. The version is incremented for
|
||||
@@ -382,6 +399,7 @@ The defined format versions are:
|
||||
Initial format version.
|
||||
.TP
|
||||
.B 2
|
||||
Added retention mode by setting the retention attribute.
|
||||
.RE
|
||||
|
||||
.SH CORRUPTION DETECTION
|
||||
|
||||
@@ -81,6 +81,7 @@ static int do_attr_x(struct attr_x_args *args)
|
||||
pr(iax, CTIME, "ctime", "%llu.%u", iax->ctime_sec, iax->ctime_nsec);
|
||||
pr(iax, CRTIME, "crtime", "%llu.%u", iax->crtime_sec, iax->crtime_nsec);
|
||||
pr(iax, SIZE, "size", "%llu", iax->size);
|
||||
prb(iax, RETENTION, "retention");
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
@@ -107,6 +108,7 @@ static int parse_opt(int key, char *arg, struct argp_state *state)
|
||||
struct attr_x_args *args = state->input;
|
||||
struct timespec ts;
|
||||
int ret;
|
||||
u64 x;
|
||||
|
||||
switch (key) {
|
||||
case 'm':
|
||||
@@ -179,6 +181,16 @@ static int parse_opt(int key, char *arg, struct argp_state *state)
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
args->iax.x_mask |= SCOUTFS_IOC_IAX_RETENTION;
|
||||
if (arg) {
|
||||
ret = parse_u64(arg, &x);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (x)
|
||||
args->iax.bits |= SCOUTFS_IOC_IAX_B_RETENTION;
|
||||
}
|
||||
break;
|
||||
case ARGP_KEY_ARG:
|
||||
if (!args->filename)
|
||||
args->filename = strdup_or_error(state, arg);
|
||||
@@ -209,6 +221,7 @@ static struct argp_option set_options[] = {
|
||||
{ "ctime", 'c', "SECS.NSECS", 0, "Inode change time (posix ctime)"},
|
||||
{ "crtime", 'r', "SECS.NSECS", 0, "ScoutFS creation time"},
|
||||
{ "size", 's', "SIZE", 0, "Inode i_size field"},
|
||||
{ "retention", 't', "0|1", 0, "Retention flag"},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user