diff --git a/kmod/src/data.c b/kmod/src/data.c index f83089e9..b129aa42 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -1923,6 +1923,8 @@ int scoutfs_data_wait(struct inode *inode, struct scoutfs_data_wait *dw) spin_lock(&rt->lock); rb_erase(&dw->node, &rt->root); RB_CLEAR_NODE(&dw->node); + if (!ret && dw->err) + ret = dw->err; spin_unlock(&rt->lock); return ret; @@ -1936,6 +1938,36 @@ void scoutfs_data_wait_changed(struct inode *inode) wake_up(&wq->waitq); } +long scoutfs_data_wait_err(struct inode *inode, u64 sblock, u64 eblock, + u64 op, long err) +{ + struct super_block *sb = inode->i_sb; + const u64 ino = scoutfs_ino(inode); + DECLARE_DATA_WAIT_ROOT(sb, rt); + struct scoutfs_data_wait *dw; + long nr = 0; + + if (!err) + return 0; + + spin_lock(&rt->lock); + + for (dw = next_data_wait(&rt->root, ino, sblock); + dw; dw = dw_next(dw)) { + if (dw->ino != ino || dw->iblock > eblock) + break; + if ((dw->op & op) && !dw->err) { + dw->err = err; + nr++; + } + } + + spin_unlock(&rt->lock); + if (nr) + scoutfs_data_wait_changed(inode); + return nr; +} + int scoutfs_data_waiting(struct super_block *sb, u64 ino, u64 iblock, struct scoutfs_ioctl_data_waiting_entry *dwe, unsigned int nr) diff --git a/kmod/src/data.h b/kmod/src/data.h index 9e0a9c87..b4ee7344 100644 --- a/kmod/src/data.h +++ b/kmod/src/data.h @@ -28,12 +28,14 @@ struct scoutfs_data_wait { u64 chg; u64 ino; u64 iblock; + long err; u8 op; }; #define DECLARE_DATA_WAIT(nm) \ struct scoutfs_data_wait nm = { \ .node.__rb_parent_color = (unsigned long)(&nm.node), \ + .err = 0, \ } struct scoutfs_traced_extent { @@ -68,6 +70,8 @@ bool scoutfs_data_wait_found(struct scoutfs_data_wait *ow); int scoutfs_data_wait(struct inode *inode, struct scoutfs_data_wait *ow); void scoutfs_data_wait_changed(struct inode *inode); +long scoutfs_data_wait_err(struct inode *inode, u64 sblock, u64 eblock, u64 op, + long err); int scoutfs_data_waiting(struct super_block *sb, u64 ino, u64 iblock, struct scoutfs_ioctl_data_waiting_entry *dwe, unsigned int nr); diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 24317e73..a321a1db 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -348,6 +348,65 @@ out: return ret; } +static long scoutfs_ioc_data_wait_err(struct file *file, unsigned long arg) +{ + struct super_block *sb = file_inode(file)->i_sb; + struct scoutfs_ioctl_data_wait_err args; + struct scoutfs_lock *lock = NULL; + struct inode *inode = NULL; + u64 sblock; + u64 eblock; + long ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (copy_from_user(&args, (void __user *)arg, sizeof(args))) + return -EFAULT; + if (args.count == 0) + return 0; + if ((args.op & SCOUTFS_IOC_DWO_UNKNOWN) || !IS_ERR_VALUE(args.err)) + return -EINVAL; + if ((args.op & SCOUTFS_IOC_DWO_UNKNOWN) || !IS_ERR_VALUE(args.err)) + return -EINVAL; + + trace_scoutfs_ioc_data_wait_err(sb, &args); + + sblock = args.offset >> SCOUTFS_BLOCK_SHIFT; + eblock = (args.offset + args.count - 1) >> SCOUTFS_BLOCK_SHIFT; + + if (sblock > eblock) + return -EINVAL; + + inode = scoutfs_ilookup(sb, args.ino); + if (!inode) { + ret = -ESTALE; + goto out; + } + + mutex_lock(&inode->i_mutex); + + ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, + SCOUTFS_LKF_REFRESH_INODE, inode, &lock); + if (ret) + goto unlock; + + if (!S_ISREG(inode->i_mode)) { + ret = -EINVAL; + } else if (scoutfs_inode_data_version(inode) != args.data_version) { + ret = -ESTALE; + } else { + ret = scoutfs_data_wait_err(inode, sblock, eblock, args.op, + args.err); + } + + scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ); +unlock: + mutex_unlock(&inode->i_mutex); + iput(inode); +out: + return ret; +} + /* * Write the archived contents of the file back if the data_version * still matches. @@ -832,6 +891,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_find_xattrs(file, arg); case SCOUTFS_IOC_STATFS_MORE: return scoutfs_ioc_statfs_more(file, arg); + case SCOUTFS_IOC_DATA_WAIT_ERR: + return scoutfs_ioc_data_wait_err(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index df0c1b54..4b635f88 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -346,5 +346,22 @@ struct scoutfs_ioctl_statfs_more { #define SCOUTFS_IOC_STATFS_MORE _IOR(SCOUTFS_IOCTL_MAGIC, 10, \ struct scoutfs_ioctl_statfs_more) +/* + * Cause matching waiters to return an error. + * + * Find current waiters that match the inode, op, and block range to wake + * up and return an error. + */ +struct scoutfs_ioctl_data_wait_err { + __u64 ino; + __u64 data_version; + __u64 offset; + __u64 count; + __u64 op; + __s64 err; +}; + +#define SCOUTFS_IOC_DATA_WAIT_ERR _IOR(SCOUTFS_IOCTL_MAGIC, 11, \ + struct scoutfs_ioctl_data_wait_err) #endif diff --git a/kmod/src/scoutfs_trace.h b/kmod/src/scoutfs_trace.h index 66867318..b87a5443 100644 --- a/kmod/src/scoutfs_trace.h +++ b/kmod/src/scoutfs_trace.h @@ -556,6 +556,37 @@ TRACE_EVENT(scoutfs_ioc_stage, __entry->offset, __entry->count) ); +TRACE_EVENT(scoutfs_ioc_data_wait_err, + TP_PROTO(struct super_block *sb, + struct scoutfs_ioctl_data_wait_err *args), + + TP_ARGS(sb, args), + + TP_STRUCT__entry( + SCSB_TRACE_FIELDS + __field(__u64, ino) + __field(__u64, vers) + __field(__u64, offset) + __field(__u64, count) + __field(__u64, op) + __field(__s64, err) + ), + + TP_fast_assign( + SCSB_TRACE_ASSIGN(sb); + __entry->ino = args->ino; + __entry->vers = args->data_version; + __entry->offset = args->offset; + __entry->count = args->count; + __entry->op = args->op; + __entry->err = args->err; + ), + + TP_printk(SCSBF" ino %llu vers %llu offset %llu count %llu op %llx err %lld", + SCSB_TRACE_ARGS, __entry->ino, __entry->vers, + __entry->offset, __entry->count, __entry->op, __entry->err) +); + DEFINE_EVENT(scoutfs_ino_ret_class, scoutfs_ioc_stage_ret, TP_PROTO(struct super_block *sb, u64 ino, int ret), TP_ARGS(sb, ino, ret)