Add level1 worm support

Add the first level of worm support.  "level1" adds protection for
regular files by setting an expiration time in the value of a known
scoutfs tagged extended attribute.  The set of protections are meant to
avoid the simplest accidental modification of files while still allowing
some file metadata modifications to support archiving.

This is the first case of extending the inode size and format version so
there is a reasonable amount of code in here that is related to dealing
with combinations of format versions and sizes between the persistent
device and code.

Signed-off-by: Bryant G. Duffy-Ly <bduffyly@versity.com>
[zab@versity.com: fixed bugs, dropped _bits and versions, docs, reworded]
Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Bryant G. Duffy-Ly
2022-05-13 10:16:02 -07:00
committed by Zach Brown
parent e7b22e19d2
commit 4c46a834f8
12 changed files with 343 additions and 33 deletions
+10
View File
@@ -529,6 +529,11 @@ static int scoutfs_get_block(struct inode *inode, sector_t iblock,
goto out;
}
if (create && !si->staging && scoutfs_inode_worm_denied(inode)) {
ret = -EACCES;
goto out;
}
/* convert unwritten to written, could be staging */
if (create && ext.map && (ext.flags & SEF_UNWRITTEN)) {
un.start = iblock;
@@ -1192,6 +1197,11 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off,
if (ret)
goto out;
if (scoutfs_inode_worm_denied(to)) {
ret = -EACCES;
goto out;
}
if ((from_off & SCOUTFS_BLOCK_SM_MASK) ||
(to_off & SCOUTFS_BLOCK_SM_MASK) ||
((byte_len & SCOUTFS_BLOCK_SM_MASK) &&
+11
View File
@@ -1029,6 +1029,11 @@ static int scoutfs_unlink(struct inode *dir, struct dentry *dentry)
goto unlock;
}
if (scoutfs_inode_worm_denied(inode)) {
ret = -EACCES;
goto unlock;
}
if (should_orphan(inode)) {
ret = scoutfs_lock_orphan(sb, SCOUTFS_LOCK_WRITE_ONLY, 0, scoutfs_ino(inode),
&orph_lock);
@@ -1697,6 +1702,12 @@ static int scoutfs_rename_common(struct inode *old_dir,
goto out_unlock;
}
if ((old_inode && scoutfs_inode_worm_denied(old_inode)) ||
(new_inode && scoutfs_inode_worm_denied(new_inode))) {
ret = -EACCES;
goto out_unlock;
}
if (should_orphan(new_inode)) {
ret = scoutfs_lock_orphan(sb, SCOUTFS_LOCK_WRITE_ONLY, 0, scoutfs_ino(new_inode),
&orph_lock);
+5
View File
@@ -107,6 +107,11 @@ retry:
if (ret)
goto out;
if (scoutfs_inode_worm_denied(inode)) {
ret = -EACCES;
goto out;
}
ret = scoutfs_complete_truncate(inode, inode_lock);
if (ret)
goto out;
+4
View File
@@ -856,8 +856,12 @@ struct scoutfs_inode {
struct scoutfs_timespec ctime;
struct scoutfs_timespec mtime;
struct scoutfs_timespec crtime;
struct scoutfs_timespec worm_level1_expire;
};
#define SCOUTFS_INODE_FMT_V1_BYTES offsetof(struct scoutfs_inode, worm_level1_expire)
#define SCOUTFS_INODE_FMT_V2_BYTES sizeof(struct scoutfs_inode)
#define SCOUTFS_INO_FLAG_TRUNCATE 0x1
#define SCOUTFS_ROOT_INO 1
+100 -10
View File
@@ -84,6 +84,7 @@ static void scoutfs_inode_ctor(void *obj)
{
struct scoutfs_inode_info *si = obj;
seqlock_init(&si->seqlock);
init_rwsem(&si->extent_sem);
mutex_init(&si->item_mutex);
seqcount_init(&si->seqcount);
@@ -213,6 +214,30 @@ static u64 get_item_minor(struct scoutfs_inode_info *si, u8 type)
return si->item_minors[ind];
}
void scoutfs_inode_get_worm(struct inode *inode, struct timespec *ts)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
unsigned int seq;
do {
seq = read_seqbegin(&si->seqlock);
*ts = si->worm_expire;
} while (read_seqretry(&si->seqlock, seq));
}
void scoutfs_inode_set_worm(struct inode *inode, u64 expire_sec, u32 expire_nsec)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
/* we don't deal with native timespec truncating our 64bit .sec */
BUILD_BUG_ON(sizeof(si->worm_expire.tv_sec) != sizeof(expire_sec));
write_seqlock(&si->seqlock);
si->worm_expire.tv_sec = expire_sec;
si->worm_expire.tv_nsec = expire_nsec;
write_sequnlock(&si->seqlock);
}
/*
* The caller has ensured that the fields in the incoming scoutfs inode
* reflect both the inode item and the inode index items. This happens
@@ -233,7 +258,7 @@ static void set_item_info(struct scoutfs_inode_info *si,
set_item_major(si, SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE, sinode->data_seq);
}
static void load_inode(struct inode *inode, struct scoutfs_inode *cinode)
static void load_inode(struct inode *inode, struct scoutfs_inode *cinode, int inode_bytes)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
@@ -262,6 +287,12 @@ static void load_inode(struct inode *inode, struct scoutfs_inode *cinode)
si->crtime.tv_sec = le64_to_cpu(cinode->crtime.sec);
si->crtime.tv_nsec = le32_to_cpu(cinode->crtime.nsec);
if (inode_bytes == SCOUTFS_INODE_FMT_V2_BYTES)
scoutfs_inode_set_worm(inode, le64_to_cpu(cinode->worm_level1_expire.sec),
le32_to_cpu(cinode->worm_level1_expire.nsec));
else
scoutfs_inode_set_worm(inode, 0, 0);
/*
* i_blocks is initialized from online and offline and is then
* maintained as blocks come and go.
@@ -272,6 +303,36 @@ static void load_inode(struct inode *inode, struct scoutfs_inode *cinode)
set_item_info(si, cinode);
}
/* Returns the max inode size given format version */
static int max_inode_fmt_ver_bytes(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
int ret = 0;
if (sbi->fmt_vers == 1)
ret = SCOUTFS_INODE_FMT_V1_BYTES;
else if (sbi->fmt_vers == 2)
ret = SCOUTFS_INODE_FMT_V2_BYTES;
return ret;
}
/* Returns if inode bytes is valid for our format version */
static bool valid_inode_fmt_ver_bytes(struct super_block *sb, int bytes)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
int ver;
if (bytes == SCOUTFS_INODE_FMT_V1_BYTES)
ver = 1;
else if (bytes == SCOUTFS_INODE_FMT_V2_BYTES)
ver = 2;
else
ver = 0;
return ver > 0 && ver <= sbi->fmt_vers;
}
void scoutfs_inode_init_key(struct scoutfs_key *key, u64 ino)
{
*key = (struct scoutfs_key) {
@@ -281,12 +342,6 @@ void scoutfs_inode_init_key(struct scoutfs_key *key, u64 ino)
};
}
/* Returns the max inode size given format version */
static int max_inode_fmt_ver_bytes(struct super_block *sb)
{
return sizeof(struct scoutfs_inode);
}
/*
* Read an inode item into the caller's buffer and return the size that
* we read. Returns errors if the inode size is unsupported or doesn't
@@ -295,11 +350,10 @@ static int max_inode_fmt_ver_bytes(struct super_block *sb)
static int lookup_inode_item(struct super_block *sb, struct scoutfs_key *key,
struct scoutfs_inode *sinode, struct scoutfs_lock *lock)
{
int inode_bytes = max_inode_fmt_ver_bytes(sb);
int ret;
ret = scoutfs_item_lookup_within(sb, key, sinode, sizeof(struct scoutfs_inode), lock);
if (ret >= 0 && ret != inode_bytes)
if (ret >= 0 && !valid_inode_fmt_ver_bytes(sb, ret))
return -EIO;
return ret;
@@ -479,6 +533,11 @@ retry:
if (ret)
goto out;
if (scoutfs_inode_worm_denied(inode)) {
ret = -EACCES;
goto out;
}
attr_size = (attr->ia_valid & ATTR_SIZE) ? attr->ia_size :
i_size_read(inode);
@@ -791,9 +850,10 @@ out:
return inode;
}
static void store_inode(struct scoutfs_inode *cinode, struct inode *inode)
static void store_inode(struct scoutfs_inode *cinode, struct inode *inode, int inode_bytes)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct timespec ts;
u64 online_blocks;
u64 offline_blocks;
@@ -827,6 +887,15 @@ static void store_inode(struct scoutfs_inode *cinode, struct inode *inode)
cinode->crtime.sec = cpu_to_le64(si->crtime.tv_sec);
cinode->crtime.nsec = cpu_to_le32(si->crtime.tv_nsec);
memset(cinode->crtime.__pad, 0, sizeof(cinode->crtime.__pad));
if (inode_bytes == SCOUTFS_INODE_FMT_V2_BYTES) {
scoutfs_inode_get_worm(inode, &ts);
cinode->worm_level1_expire.sec = cpu_to_le64(ts.tv_sec);
cinode->worm_level1_expire.nsec = cpu_to_le32(ts.tv_nsec);
memset(cinode->worm_level1_expire.__pad, 0,
sizeof(cinode->worm_level1_expire.__pad));
}
}
/*
@@ -1475,6 +1544,8 @@ int scoutfs_new_inode(struct super_block *sb, struct inode *dir, umode_t mode, d
si->drop_invalidated = false;
si->flags = 0;
scoutfs_inode_set_worm(inode, 0, 0);
scoutfs_inode_set_meta_seq(inode);
scoutfs_inode_set_data_seq(inode);
@@ -2101,6 +2172,25 @@ out:
return ret;
}
/*
* Return true if the inode is protected by worm and the current time is
* before the expiration time.
*/
bool scoutfs_inode_worm_denied(struct inode *inode)
{
struct timespec expire;
struct timespec cur;
scoutfs_inode_get_worm(inode, &expire);
if (expire.tv_sec != 0 || expire.tv_nsec != 0) {
cur = CURRENT_TIME;
if (timespec64_compare(&cur, &expire) < 0)
return true;
}
return false;
}
int scoutfs_inode_setup(struct super_block *sb)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
+8
View File
@@ -23,6 +23,10 @@ struct scoutfs_inode_info {
u64 offline_blocks;
u32 flags;
struct timespec crtime;
struct timespec worm_expire;
/* Prevent readers from racing with xattr_set */
seqlock_t seqlock;
/*
* Protects per-inode extent items, most particularly readers
@@ -141,4 +145,8 @@ void scoutfs_inode_orphan_stop(struct super_block *sb);
void scoutfs_inode_flush_iput(struct super_block *sb);
void scoutfs_inode_destroy(struct super_block *sb);
void scoutfs_inode_get_worm(struct inode *inode, struct timespec *ts);
void scoutfs_inode_set_worm(struct inode *inode, u64 expire_sec, u32 expire_nsec);
bool scoutfs_inode_worm_denied(struct inode *inode);
#endif
+6 -1
View File
@@ -659,6 +659,11 @@ static long scoutfs_ioc_setattr_more(struct file *file, unsigned long arg)
if (ret)
goto unlock;
if (scoutfs_inode_worm_denied(inode)) {
ret = -EACCES;
goto unlock;
}
/* can only change size/dv on untouched regular files */
if ((sm.i_size != 0 || sm.data_version != 0) &&
((!S_ISREG(inode->i_mode) ||
@@ -823,7 +828,7 @@ static long scoutfs_ioc_search_xattrs(struct file *file, unsigned long arg)
goto out;
}
if (scoutfs_xattr_parse_tags(name, sx.name_bytes, &tgs) < 0 ||
if (scoutfs_xattr_parse_tags(sb, name, sx.name_bytes, &tgs) < 0 ||
!tgs.srch) {
ret = -EINVAL;
goto out;
+139 -18
View File
@@ -79,10 +79,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)
static int unknown_prefix(const char *name)
static int unknown_prefix(const char *name, bool *is_user)
{
return strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) &&
if (!strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) {
if (is_user)
*is_user = true;
return false;
}
if (is_user)
*is_user = false;
return strncmp(name, XATTR_TRUSTED_PREFIX, XATTR_TRUSTED_PREFIX_LEN) &&
strncmp(name, XATTR_SYSTEM_PREFIX, XATTR_SYSTEM_PREFIX_LEN) &&
strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN)&&
strncmp(name, SCOUTFS_XATTR_PREFIX, SCOUTFS_XATTR_PREFIX_LEN);
@@ -92,11 +100,13 @@ static int unknown_prefix(const char *name)
#define HIDE_TAG "hide."
#define SRCH_TAG "srch."
#define TOTL_TAG "totl."
#define WORM_TAG "worm."
#define TAG_LEN (sizeof(HIDE_TAG) - 1)
int scoutfs_xattr_parse_tags(const char *name, unsigned int name_len,
struct scoutfs_xattr_prefix_tags *tgs)
int scoutfs_xattr_parse_tags(struct super_block *sb, const char *name,
unsigned int name_len, struct scoutfs_xattr_prefix_tags *tgs)
{
struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb);
bool found;
memset(tgs, 0, sizeof(struct scoutfs_xattr_prefix_tags));
@@ -117,6 +127,9 @@ int scoutfs_xattr_parse_tags(const char *name, unsigned int name_len,
} else if (!strncmp(name, TOTL_TAG, TAG_LEN)) {
if (++tgs->totl == 0)
return -EINVAL;
} else if (!strncmp(name, WORM_TAG, TAG_LEN)) {
if (++tgs->worm == 0 || sbi->fmt_vers < 2)
return -EINVAL;
} else {
/* only reason to use scoutfs. is tags */
if (!found)
@@ -468,7 +481,7 @@ ssize_t scoutfs_getxattr(struct dentry *dentry, const char *name, void *buffer,
size_t name_len;
int ret;
if (unknown_prefix(name))
if (unknown_prefix(name, NULL))
return -EOPNOTSUPP;
name_len = strlen(name);
@@ -524,6 +537,22 @@ void scoutfs_xattr_init_totl_key(struct scoutfs_key *key, u64 *name)
key->skxt_c = cpu_to_le64(name[2]);
}
/*
* Currently only support enabling level1 worm by setting a non-zero
* expiration.
*/
static int parse_worm_name(const char *name)
{
static const char worm_name[] = "level1_expire";
char *last_dot;
last_dot = strrchr(name, '.');
if (!last_dot)
return -EINVAL;
return strcmp(worm_name, last_dot + 1) == 0 ? 0 : -EINVAL;
}
/*
* Parse a u64 in any base after null terminating it while forbidding
* the leading + and trailing \n that kstrotull allows.
@@ -541,6 +570,66 @@ static int parse_totl_u64(const char *s, int len, u64 *res)
return kstrtoull(str, 0, res) != 0 ? -EINVAL : 0;
}
static int parse_worm_u32(const char *s, int len, u32 *res)
{
u64 tmp;
int ret;
ret = parse_totl_u64(s, len, &tmp);
if (ret == 0 && tmp > U32_MAX) {
tmp = 0;
ret = -EINVAL;
}
*res = tmp;
return ret;
}
static int parse_worm_timespec(struct timespec *ts, const char *name, int name_len)
{
char *delim;
u64 sec;
u32 nsec;
int sec_len;
int nsec_len;
int ret;
memset(ts, 0, sizeof(struct scoutfs_timespec));
if (name_len < 3)
return -EINVAL;
delim = strnchr(name, name_len, '.');
if (!delim)
return -EINVAL;
if (delim == name || delim == (name + name_len - 1))
return -EINVAL;
sec_len = delim - name;
nsec_len = name_len - (sec_len + 1);
/* Check to make sure only one '.' */
if (strnchr(delim + 1, nsec_len, '.'))
return -EINVAL;
ret = parse_totl_u64(name, sec_len, &sec);
if (ret < 0)
return ret;
ret = parse_worm_u32(delim + 1, nsec_len, &nsec);
if (ret < 0)
return ret;
if (sec > S64_MAX || nsec >= NSEC_PER_SEC || (sec == 0 && nsec == 0))
return -EINVAL;
ts->tv_sec = sec;
ts->tv_nsec = nsec;
return 0;
}
/*
* non-destructive relatively quick parse of the last 3 dotted u64s that
* make up the name of the xattr total. -EINVAL is returned if there
@@ -625,23 +714,25 @@ static int scoutfs_xattr_set(struct dentry *dentry, const char *name,
{
struct inode *inode = dentry->d_inode;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
const u64 ino = scoutfs_ino(inode);
struct scoutfs_xattr_totl_val tval = {0,};
struct scoutfs_lock *totl_lock = NULL;
struct super_block *sb = inode->i_sb;
struct scoutfs_xattr_prefix_tags tgs;
const u64 ino = scoutfs_ino(inode);
struct timespec worm_ts = {0,};
struct scoutfs_xattr *xat = NULL;
struct scoutfs_lock *lck = NULL;
struct scoutfs_lock *totl_lock = NULL;
size_t name_len = strlen(name);
struct scoutfs_key totl_key;
struct scoutfs_key key;
bool undo_srch = false;
bool undo_totl = false;
bool is_user = false;
LIST_HEAD(ind_locks);
u8 found_parts;
unsigned int xat_bytes_totl;
unsigned int xat_bytes;
unsigned int val_len;
u8 found_parts;
u64 ind_seq;
u64 total;
u64 hash = 0;
@@ -661,16 +752,20 @@ static int scoutfs_xattr_set(struct dentry *dentry, const char *name,
(flags & ~(XATTR_CREATE | XATTR_REPLACE)))
return -EINVAL;
if (unknown_prefix(name))
if (unknown_prefix(name, &is_user))
return -EOPNOTSUPP;
if (scoutfs_xattr_parse_tags(name, name_len, &tgs) != 0)
if (scoutfs_xattr_parse_tags(sb, name, name_len, &tgs) != 0)
return -EINVAL;
if ((tgs.hide | tgs.srch | tgs.totl) && !capable(CAP_SYS_ADMIN))
if ((tgs.hide | tgs.srch | tgs.totl | tgs.worm) && !capable(CAP_SYS_ADMIN))
return -EPERM;
if (tgs.totl && ((ret = parse_totl_key(&totl_key, name, name_len)) != 0))
if (tgs.worm && !tgs.hide)
return -EINVAL;
if ((tgs.totl && ((ret = parse_totl_key(&totl_key, name, name_len)) != 0)) ||
(tgs.worm && ((ret = parse_worm_name(name)) != 0)))
return ret;
/* allocate enough to always read an existing xattr's totl */
@@ -691,6 +786,11 @@ static int scoutfs_xattr_set(struct dentry *dentry, const char *name,
down_write(&si->xattr_rwsem);
if (!S_ISREG(inode->i_mode) && tgs.worm) {
ret = -EINVAL;
goto unlock;
}
/* find an existing xattr to delete, including possible totl value */
ret = get_next_xattr(inode, &key, xat, xat_bytes_totl, name, name_len, 0, 0, lck);
if (ret < 0 && ret != -ENOENT)
@@ -711,6 +811,12 @@ static int scoutfs_xattr_set(struct dentry *dentry, const char *name,
goto unlock;
}
/* current worm only protects user. xattrs and expiration xattr itself */
if (scoutfs_inode_worm_denied(inode) && (is_user || tgs.worm)) {
ret = -EACCES;
goto unlock;
}
/* s64 count delta if we create or delete */
if (tgs.totl)
tval.count = cpu_to_le64((u64)!!(value) - (u64)!!(ret != -ENOENT));
@@ -746,9 +852,22 @@ static int scoutfs_xattr_set(struct dentry *dentry, const char *name,
ret = parse_totl_u64(value, size, &total);
if (ret < 0)
goto unlock;
le64_add_cpu(&tval.total, total);
}
le64_add_cpu(&tval.total, total);
if (tgs.worm) {
/* can't set multiple times with different names */
scoutfs_inode_get_worm(inode, &worm_ts);
if (worm_ts.tv_sec || worm_ts.tv_nsec) {
ret = -EINVAL;
goto unlock;
}
ret = parse_worm_timespec(&worm_ts, value, size);
if (ret < 0)
goto unlock;
}
}
if (tgs.totl) {
@@ -800,6 +919,9 @@ retry:
if (ret < 0)
goto release;
if (tgs.worm)
scoutfs_inode_set_worm(inode, worm_ts.tv_sec, worm_ts.tv_nsec);
/* XXX do these want i_mutex or anything? */
inode_inc_iversion(inode);
inode->i_ctime = CURRENT_TIME;
@@ -889,7 +1011,7 @@ ssize_t scoutfs_list_xattrs(struct inode *inode, char *buffer,
break;
}
is_hidden = scoutfs_xattr_parse_tags(xat->name, xat->name_len,
is_hidden = scoutfs_xattr_parse_tags(sb, xat->name, xat->name_len,
&tgs) == 0 && tgs.hide;
if (show_hidden == is_hidden) {
@@ -985,8 +1107,7 @@ int scoutfs_xattr_drop(struct super_block *sb, u64 ino,
}
if (key.skx_part != 0 ||
scoutfs_xattr_parse_tags(xat->name, xat->name_len,
&tgs) != 0)
scoutfs_xattr_parse_tags(sb, xat->name, xat->name_len, &tgs) != 0)
memset(&tgs, 0, sizeof(tgs));
if (tgs.totl) {
+4 -3
View File
@@ -17,11 +17,12 @@ int scoutfs_xattr_drop(struct super_block *sb, u64 ino,
struct scoutfs_xattr_prefix_tags {
unsigned long hide:1,
srch:1,
totl:1;
totl:1,
worm:1;
};
int scoutfs_xattr_parse_tags(const char *name, unsigned int name_len,
struct scoutfs_xattr_prefix_tags *tgs);
int scoutfs_xattr_parse_tags(struct super_block *sb, const char *name,
unsigned int name_len, struct scoutfs_xattr_prefix_tags *tgs);
void scoutfs_xattr_init_totl_key(struct scoutfs_key *key, u64 *name);
int scoutfs_xattr_combine_totl(void *dst, int dst_len, void *src, int src_len);
+46
View File
@@ -212,6 +212,38 @@ name, total value, and a count of contributing attributes can be read
with the
.IB READ_XATTR_TOTALS
ioctl.
.TP
.B .worm.
Attributes with the .worm. flag are used to control WORM (write once,
read many) access restrictions, typically used to comply with operational
regulations. The only currently supported mechanism is controlled by a
single .worm. attribute whose name ends in ".level1_expire". Additional
levels with different enfrocement policies may be added and would be
controlled by different attributes.
.sp
The level1 policy is enabled by setting an attribute on a file that
contains the .worm. tag and whose name ends in ".level1_expire". The
attribute name must also include the .hide. tag. As with other scoutfs
tagged attributes, the name may include any other string between the
tags and the final required suffix. Only one level1 expiration
attribute may be set at a time.
.sp
The value of the attribute contains a string representing the kernel
time at which the policy enforcement will expire. The time is formated
as "seconds.nanoseconds" in GMT. The attribute must be set with the
CAP_SYS_ADMIN capability, perhaps via the root user. Setting an
expiration value of "0.0" will always fail. The policy can only be set
on regular files.
.sp
The file is protected once the expiration attribute is set and can not
be modified until the expiration time has passed. The file data, its
inode fields, directory entries that link to its inode, untrusted
"user." attributes, and non-hidden scoutfs attributes are all protected
and modification attempts will fail with with permission denied.
Trusted system-level attributes like "security." and hidden scoutfs
attributes may still be modified to support ongoing archiving
operations. The worm attribute itself can not be modified once it is
set and can only be removed once the expiration time has passed.
.RE
.SH FORMAT VERSION
@@ -292,6 +324,20 @@ The version that a mount is using is shown in the
file in the mount's sysfs directory, typically
.I /sys/fs/scoutfs/f.FSID.r.RID/
.RE
.sp
The defined format versions are:
.RS
.TP
.sp
.B 1
Initial format version.
.TP
.B 2
Added level1 WORM file protection for regular files. The
".level1_expire" worm tagged extended attribute was added and the inode
item size was increased to store the parsed expiration time from the
extended attribute.
.RE
.SH CORRUPTION DETECTION
A
+4 -1
View File
@@ -262,7 +262,10 @@ static int do_mkfs(struct mkfs_args *args)
inode.ctime.nsec = inode.atime.nsec;
inode.mtime.sec = inode.atime.sec;
inode.mtime.nsec = inode.atime.nsec;
btree_append_item(bt, &key, &inode, sizeof(inode));
if (args->fmt_vers == 1)
btree_append_item(bt, &key, &inode, SCOUTFS_INODE_FMT_V1_BYTES);
else
btree_append_item(bt, &key, &inode, SCOUTFS_INODE_FMT_V2_BYTES);
ret = write_block(meta_fd, SCOUTFS_BLOCK_MAGIC_BTREE, fsid, 1, blkno,
SCOUTFS_BLOCK_LG_SHIFT, &bt->hdr);
+6
View File
@@ -69,6 +69,12 @@ static void print_inode(struct scoutfs_key *key, void *val, int val_len)
le32_to_cpu(inode->ctime.nsec),
le64_to_cpu(inode->mtime.sec),
le32_to_cpu(inode->mtime.nsec));
if (val_len == SCOUTFS_INODE_FMT_V2_BYTES) {
printf(" worm_level1_expire %llu.%08u\n",
le64_to_cpu(inode->worm_level1_expire.sec),
le32_to_cpu(inode->worm_level1_expire.nsec));
}
}
static void print_orphan(struct scoutfs_key *key, void *val, int val_len)