diff --git a/kmod/src/data.c b/kmod/src/data.c index 2c0822db..af3d3f79 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -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) && diff --git a/kmod/src/dir.c b/kmod/src/dir.c index 00734909..807356b7 100644 --- a/kmod/src/dir.c +++ b/kmod/src/dir.c @@ -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); diff --git a/kmod/src/file.c b/kmod/src/file.c index 586d77fd..495a4de6 100644 --- a/kmod/src/file.c +++ b/kmod/src/file.c @@ -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; diff --git a/kmod/src/format.h b/kmod/src/format.h index 1482eeea..a20e457f 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -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 diff --git a/kmod/src/inode.c b/kmod/src/inode.c index 83225ced..5a503410 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -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); diff --git a/kmod/src/inode.h b/kmod/src/inode.h index 88058117..c171b664 100644 --- a/kmod/src/inode.h +++ b/kmod/src/inode.h @@ -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 diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 1ae9da1f..b96b538f 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -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; diff --git a/kmod/src/xattr.c b/kmod/src/xattr.c index a87b69c2..73b5fe29 100644 --- a/kmod/src/xattr.c +++ b/kmod/src/xattr.c @@ -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) { diff --git a/kmod/src/xattr.h b/kmod/src/xattr.h index cbc6c599..22affcc6 100644 --- a/kmod/src/xattr.h +++ b/kmod/src/xattr.h @@ -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); diff --git a/utils/man/scoutfs.5 b/utils/man/scoutfs.5 index f6cbe193..09583bc1 100644 --- a/utils/man/scoutfs.5 +++ b/utils/man/scoutfs.5 @@ -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 diff --git a/utils/src/mkfs.c b/utils/src/mkfs.c index 49295b0b..822b8215 100644 --- a/utils/src/mkfs.c +++ b/utils/src/mkfs.c @@ -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); diff --git a/utils/src/print.c b/utils/src/print.c index 7328b6f8..ab71a861 100644 --- a/utils/src/print.c +++ b/utils/src/print.c @@ -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)