diff --git a/kmod/src/data.c b/kmod/src/data.c index aae6f601..27884086 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; @@ -1189,6 +1194,10 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off, if (ret) goto out; + if (scoutfs_inode_worm_denied(to)) { + return -EACCES; + } + 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 a4b6dc09..dd8cf027 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); @@ -1694,6 +1699,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..133da842 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -856,8 +856,15 @@ struct scoutfs_inode { struct scoutfs_timespec ctime; struct scoutfs_timespec mtime; struct scoutfs_timespec crtime; + __le64 worm_bits; + struct scoutfs_timespec worm_expiration; }; +#define SCOUTFS_WORM_V1_BIT (1 << 0) +#define SCOUTFS_INODE_FMT_V2_BYTES sizeof(struct scoutfs_inode) +#define SCOUTFS_INODE_FMT_V1_BYTES (SCOUTFS_INODE_FMT_V2_BYTES - \ + offsetof(struct scoutfs_inode, worm_bits)) + #define SCOUTFS_INO_FLAG_TRUNCATE 0x1 #define SCOUTFS_ROOT_INO 1 diff --git a/kmod/src/inode.c b/kmod/src/inode.c index a0363fa7..967d63fa 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -88,6 +88,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); @@ -217,6 +218,40 @@ static u64 get_item_minor(struct scoutfs_inode_info *si, u8 type) return si->item_minors[ind]; } +static u64 get_worm_inode_info(struct scoutfs_inode_info *si, + struct scoutfs_timespec **ts_ret) +{ + u64 worm_bits = 0; + unsigned int seq; + + do { + seq = read_seqbegin(&si->seqlock); + + *ts_ret = &si->worm_expiration; + worm_bits = si->worm_bits; + } while (read_seqretry(&si->seqlock, seq)); + + return worm_bits; +} + +void scoutfs_inode_set_worm(struct scoutfs_inode_info *si, __le64 worm_bits, + struct scoutfs_timespec *ts) +{ + write_seqlock(&si->seqlock); + + if (ts) { + si->worm_bits = le64_to_cpu(worm_bits); + si->worm_expiration.sec = ts->sec; + si->worm_expiration.nsec = ts->nsec; + } else { + si->worm_bits = 0; + si->worm_expiration.sec = 0; + si->worm_expiration.nsec = 0; + } + + 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 @@ -237,7 +272,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); @@ -266,6 +301,11 @@ 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(si, cinode->worm_bits, &cinode->worm_expiration); + else + scoutfs_inode_set_worm(si, 0, NULL); + /* * i_blocks is initialized from online and offline and is then * maintained as blocks come and go. @@ -276,6 +316,20 @@ 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; +} + void scoutfs_inode_init_key(struct scoutfs_key *key, u64 ino) { *key = (struct scoutfs_key) { @@ -285,12 +339,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); -} - static int lookup_inode_item(struct super_block *sb, struct scoutfs_key *key, struct scoutfs_lock *lock, struct scoutfs_inode_payload_wrapper *inode_payload) @@ -481,6 +529,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); @@ -771,11 +824,13 @@ 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 scoutfs_timespec *ts = NULL; u64 online_blocks; u64 offline_blocks; + u64 worm_bits = 0; scoutfs_inode_get_onoff(inode, &online_blocks, &offline_blocks); @@ -807,6 +862,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)); + + worm_bits = get_worm_inode_info(si, &ts); + + if (inode_bytes == SCOUTFS_INODE_FMT_V2_BYTES && ts) { + cinode->worm_bits = cpu_to_le64(worm_bits); + cinode->worm_expiration.sec = ts->sec; + cinode->worm_expiration.nsec = ts->nsec; + memset(cinode->worm_expiration.__pad, 0, sizeof(cinode->worm_expiration.__pad)); + } } /* @@ -1449,6 +1513,8 @@ struct inode *scoutfs_new_inode(struct super_block *sb, struct inode *dir, si->drop_invalidated = false; si->flags = 0; + scoutfs_inode_set_worm(si, 0, NULL); + scoutfs_inode_set_meta_seq(inode); scoutfs_inode_set_data_seq(inode); @@ -2008,6 +2074,28 @@ out: return ret; } +bool scoutfs_inode_worm_denied(struct inode *inode) +{ + struct scoutfs_inode_info *si = SCOUTFS_I(inode); + struct timespec cur_time = CURRENT_TIME; + struct scoutfs_timespec *worm = NULL; + struct timespec local_ts; + u64 worm_bits = 0; + + worm_bits = get_worm_inode_info(si, &worm); + if (!worm) + return true; + + local_ts.tv_sec = le64_to_cpu(worm->sec); + local_ts.tv_nsec = le32_to_cpu(worm->nsec); + + if ((worm_bits & SCOUTFS_WORM_V1_BIT) && + (timespec64_compare(&cur_time, &local_ts) < 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 83340bf9..74e38321 100644 --- a/kmod/src/inode.h +++ b/kmod/src/inode.h @@ -23,6 +23,11 @@ struct scoutfs_inode_info { u64 offline_blocks; u32 flags; struct timespec crtime; + u64 worm_bits; + struct scoutfs_timespec worm_expiration; + + /* Prevent readers from racing with xattr_set */ + seqlock_t seqlock; /* * Protects per-inode extent items, most particularly readers @@ -144,4 +149,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); +bool scoutfs_inode_worm_denied(struct inode *inode); +void scoutfs_inode_set_worm(struct scoutfs_inode_info *si, __le64 worm_bits, + struct scoutfs_timespec *ts); + #endif diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index d30a5021..a31d3002 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 3de6ee7d..12e8bf3a 100644 --- a/kmod/src/xattr.c +++ b/kmod/src/xattr.c @@ -98,11 +98,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)); @@ -123,6 +125,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) @@ -481,6 +486,23 @@ void scoutfs_xattr_init_totl_key(struct scoutfs_key *key, u64 *name) key->skxt_c = cpu_to_le64(name[2]); } +/* + * Parse for v1_expiration within the xattr + * the passed in character array must be NULL + * terminated and is. + */ +static int parse_worm_name(const char *name) +{ + static const char worm_name[] = "v1_expiration"; + char *last_chr; + + last_chr = strrchr(name, '.'); + if (!last_chr) + return -EINVAL; + + return strcmp(worm_name, last_chr + 1) == 0 ? 0 : -EINVAL; +} + /* * Parse a u64 in any base after null terminating it while forbidding * the leading + and trailing \n that kstrotull allows. @@ -498,6 +520,67 @@ 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 scoutfs_timespec *ts, const char *name, int name_len) +{ + const char *start = name; + 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 == start || 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) + return -EINVAL; + + ts->sec = cpu_to_le64(sec); + ts->nsec = cpu_to_le32(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 @@ -582,22 +665,24 @@ 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 scoutfs_timespec 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; LIST_HEAD(ind_locks); - u8 found_parts; - unsigned int bytes; unsigned int val_len; + unsigned int bytes; + u64 worm_bits = 0; + u8 found_parts; u64 ind_seq; u64 total; u64 hash = 0; @@ -620,15 +705,31 @@ static int scoutfs_xattr_set(struct dentry *dentry, const char *name, if (unknown_prefix(name)) 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.worm && !tgs.hide) + return -EINVAL; + if (tgs.totl && ((ret = parse_totl_key(&totl_key, name, name_len)) != 0)) return ret; + if (tgs.worm) { + ret = parse_worm_name(name); + if (ret != 0) { + return -EINVAL; + } + if (value) { + ret = parse_worm_timespec(&ts, value, size); + if (ret < 0) + return ret; + worm_bits = SCOUTFS_WORM_V1_BIT; + } + } + bytes = sizeof(struct scoutfs_xattr) + name_len + size; /* alloc enough to read old totl value */ xat = __vmalloc(bytes + SCOUTFS_XATTR_MAX_TOTL_U64, GFP_NOFS, PAGE_KERNEL); @@ -644,6 +745,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, sizeof(struct scoutfs_xattr) + name_len + SCOUTFS_XATTR_MAX_TOTL_U64, @@ -666,6 +772,11 @@ static int scoutfs_xattr_set(struct dentry *dentry, const char *name, goto unlock; } + if (scoutfs_inode_worm_denied(inode)) { + 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)); @@ -752,6 +863,9 @@ retry: if (ret < 0) goto release; + if (tgs.worm) + scoutfs_inode_set_worm(si, cpu_to_le64(worm_bits), &ts); + /* XXX do these want i_mutex or anything? */ inode_inc_iversion(inode); inode->i_ctime = CURRENT_TIME; @@ -842,7 +956,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) { @@ -938,8 +1052,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 a9303c9e..49b6f34f 100644 --- a/utils/man/scoutfs.5 +++ b/utils/man/scoutfs.5 @@ -197,6 +197,17 @@ 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 maintain the worm policy +set on a file. This is a time-based retention policy, which allows a user +to set a policy to store data for a specified amount of time. When a time +based retention policy is set, files can be created and read, but not +modified or deleted. The only supported version at the moment is +v1_expiration. The v1_expiration policy will also prevent the ROOT user +from modifying or deleting of the file. The value of the extended attribute +is sec.nsec timeval in wall clock GMT. The hidden tag must also be used, +so that the worm attribute is kept private to this scoutfs volume. .RE .SH FORMAT VERSION 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..a47e7a26 100644 --- a/utils/src/print.c +++ b/utils/src/print.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "sparse.h" #include "parse.h" @@ -69,6 +70,15 @@ 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_V1_BYTES) { + printf ("\n"); + } else { + printf(" worm_bits %llx worm_expiration %llu.%08u\n", + le64_to_cpu(inode->worm_bits), + le64_to_cpu(inode->worm_expiration.sec), + le32_to_cpu(inode->worm_expiration.nsec)); + } } static void print_orphan(struct scoutfs_key *key, void *val, int val_len)