From: Darrick J. Wong <djwong@xxxxxxxxxx> Create hooks within XFS to deliver IO errors to callers. Signed-off-by: "Darrick J. Wong" <djwong@xxxxxxxxxx> --- fs/xfs/xfs_aops.c | 2 + fs/xfs/xfs_file.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/xfs/xfs_file.h | 36 +++++++++++ fs/xfs/xfs_mount.h | 3 + fs/xfs/xfs_super.c | 1 5 files changed, 208 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 4319d0488f2146..7892b794085251 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -21,6 +21,7 @@ #include "xfs_error.h" #include "xfs_zone_alloc.h" #include "xfs_rtgroup.h" +#include "xfs_file.h" struct xfs_writepage_ctx { struct iomap_writepage_ctx ctx; @@ -722,6 +723,7 @@ const struct address_space_operations xfs_address_space_operations = { .is_partially_uptodate = iomap_is_partially_uptodate, .error_remove_folio = generic_error_remove_folio, .swap_activate = xfs_iomap_swapfile_activate, + .ioerror = xfs_vm_ioerror, }; const struct address_space_operations xfs_dax_aops = { diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index ceb7936e5fd9a3..cbeb60582cb15f 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -230,6 +230,169 @@ xfs_ilock_iocb_for_write( return 0; } +#ifdef CONFIG_XFS_LIVE_HOOKS +DEFINE_STATIC_XFS_HOOK_SWITCH(xfs_file_ioerror_hooks_switch); + +void +xfs_file_ioerror_hook_disable(void) +{ + xfs_hooks_switch_off(&xfs_file_ioerror_hooks_switch); +} + +void +xfs_file_ioerror_hook_enable(void) +{ + xfs_hooks_switch_on(&xfs_file_ioerror_hooks_switch); +} + +struct xfs_file_ioerror { + struct work_struct work; + struct xfs_mount *mp; + xfs_ino_t ino; + loff_t pos; + u64 len; + u32 gen; + int error; + enum xfs_file_ioerror_type type; +}; + +/* Call downstream hooks for a file io error update. */ +STATIC void +xfs_file_report_ioerror( + struct work_struct *work) +{ + struct xfs_file_ioerror *ioerr; + + ioerr = container_of(work, struct xfs_file_ioerror, work); + + if (xfs_hooks_switched_on(&xfs_file_ioerror_hooks_switch)) { + struct xfs_file_ioerror_params p = { + .ino = ioerr->ino, + .gen = ioerr->gen, + .pos = ioerr->pos, + .len = ioerr->len, + }; + struct xfs_mount *mp = ioerr->mp; + + xfs_hooks_call(&mp->m_file_ioerror_hooks, ioerr->type, &p); + } + + kfree(ioerr); +} + +/* Queue a directio io error notification. */ +STATIC void +xfs_dio_ioerror( + struct inode *inode, + int direction, + loff_t pos, + u64 len, + int error) +{ + struct xfs_inode *ip = XFS_I(inode); + struct xfs_mount *mp = ip->i_mount; + struct xfs_file_ioerror *ioerr; + + if (xfs_hooks_switched_on(&xfs_file_ioerror_hooks_switch)) { + ioerr = kzalloc(sizeof(*ioerr), GFP_ATOMIC); + if (!ioerr) { + xfs_err(mp, + "lost ioerror report for ino 0x%llx %s pos 0x%llx len 0x%llx error %d", + ip->i_ino, + direction == WRITE ? "WRITE" : "READ", + pos, len, error); + return; + } + + INIT_WORK(&ioerr->work, xfs_file_report_ioerror); + ioerr->mp = mp; + ioerr->ino = ip->i_ino; + ioerr->gen = VFS_I(ip)->i_generation; + ioerr->pos = pos; + ioerr->len = len; + if (direction == WRITE) + ioerr->type = XFS_FILE_IOERROR_DIRECT_WRITE; + else + ioerr->type = XFS_FILE_IOERROR_DIRECT_READ; + ioerr->error = error; + queue_work(mp->m_unwritten_workqueue, &ioerr->work); + } +} + +/* Queue a buffered io error notification. */ +void +xfs_vm_ioerror( + struct address_space *mapping, + int direction, + loff_t pos, + u64 len, + int error) +{ + struct inode *inode = mapping->host; + struct xfs_inode *ip = XFS_I(inode); + struct xfs_mount *mp = ip->i_mount; + struct xfs_file_ioerror *ioerr; + + if (xfs_hooks_switched_on(&xfs_file_ioerror_hooks_switch)) { + ioerr = kzalloc(sizeof(*ioerr), GFP_ATOMIC); + if (!ioerr) { + xfs_err(mp, + "lost ioerror report for ino 0x%llx %s pos 0x%llx len 0x%llx error %d", + ip->i_ino, + direction == WRITE ? "WRITE" : "READ", + pos, len, error); + return; + } + + INIT_WORK(&ioerr->work, xfs_file_report_ioerror); + ioerr->mp = mp; + ioerr->ino = ip->i_ino; + ioerr->gen = VFS_I(ip)->i_generation; + ioerr->pos = pos; + ioerr->len = len; + if (direction == WRITE) + ioerr->type = XFS_FILE_IOERROR_BUFFERED_WRITE; + else + ioerr->type = XFS_FILE_IOERROR_BUFFERED_READ; + ioerr->error = error; + queue_work(mp->m_unwritten_workqueue, &ioerr->work); + } +} + +/* Call the specified function after a file io error. */ +int +xfs_file_ioerror_hook_add( + struct xfs_mount *mp, + struct xfs_file_ioerror_hook *hook) +{ + return xfs_hooks_add(&mp->m_file_ioerror_hooks, &hook->ioerror_hook); +} + +/* Stop calling the specified function after a file io error. */ +void +xfs_file_ioerror_hook_del( + struct xfs_mount *mp, + struct xfs_file_ioerror_hook *hook) +{ + xfs_hooks_del(&mp->m_file_ioerror_hooks, &hook->ioerror_hook); +} + +/* Configure file io error update hook functions. */ +void +xfs_file_ioerror_hook_setup( + struct xfs_file_ioerror_hook *hook, + notifier_fn_t mod_fn) +{ + xfs_hook_setup(&hook->ioerror_hook, mod_fn); +} +#else +# define xfs_dio_ioerror NULL +#endif /* CONFIG_XFS_LIVE_HOOKS */ + +static const struct iomap_dio_ops xfs_dio_read_ops = { + .ioerror = xfs_dio_ioerror, +}; + STATIC ssize_t xfs_file_dio_read( struct kiocb *iocb, @@ -248,7 +411,8 @@ xfs_file_dio_read( ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED); if (ret) return ret; - ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops, NULL, 0, NULL, 0); + ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops, &xfs_dio_read_ops, + 0, NULL, 0); xfs_iunlock(ip, XFS_IOLOCK_SHARED); return ret; @@ -769,6 +933,7 @@ xfs_dio_write_end_io( static const struct iomap_dio_ops xfs_dio_write_ops = { .end_io = xfs_dio_write_end_io, + .ioerror = xfs_dio_ioerror, }; static void diff --git a/fs/xfs/xfs_file.h b/fs/xfs/xfs_file.h index c9d50699baba85..38c546cd498a52 100644 --- a/fs/xfs/xfs_file.h +++ b/fs/xfs/xfs_file.h @@ -17,4 +17,40 @@ int xfs_file_unshare_at(struct xfs_inode *ip, loff_t pos); long xfs_ioc_map_freesp(struct file *file, struct xfs_map_freesp __user *argp); +enum xfs_file_ioerror_type { + XFS_FILE_IOERROR_BUFFERED_READ, + XFS_FILE_IOERROR_BUFFERED_WRITE, + XFS_FILE_IOERROR_DIRECT_READ, + XFS_FILE_IOERROR_DIRECT_WRITE, +}; + +struct xfs_file_ioerror_params { + xfs_ino_t ino; + loff_t pos; + u64 len; + u32 gen; + int error; +}; + +#ifdef CONFIG_XFS_LIVE_HOOKS +struct xfs_file_ioerror_hook { + struct xfs_hook ioerror_hook; +}; + +void xfs_file_ioerror_hook_disable(void); +void xfs_file_ioerror_hook_enable(void); + +int xfs_file_ioerror_hook_add(struct xfs_mount *mp, + struct xfs_file_ioerror_hook *hook); +void xfs_file_ioerror_hook_del(struct xfs_mount *mp, + struct xfs_file_ioerror_hook *hook); +void xfs_file_ioerror_hook_setup(struct xfs_file_ioerror_hook *hook, + notifier_fn_t mod_fn); + +void xfs_vm_ioerror(struct address_space *mapping, int direction, loff_t pos, + u64 len, int error); +#else +# define xfs_vm_ioerror NULL +#endif /* CONFIG_XFS_LIVE_HOOKS */ + #endif /* __XFS_FILE_H__ */ diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 3fcfdaaf199315..10b4ff3548601e 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -349,6 +349,9 @@ typedef struct xfs_mount { /* Hook to feed media error events to a daemon. */ struct xfs_hooks m_media_error_hooks; + + /* Hook to feed file io error events to a daemon. */ + struct xfs_hooks m_file_ioerror_hooks; } xfs_mount_t; #define M_IGEO(mp) (&(mp)->m_ino_geo) diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index a49082159faae8..df6afcf8840948 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -2185,6 +2185,7 @@ xfs_init_fs_context( xfs_hooks_init(&mp->m_shutdown_hooks); xfs_hooks_init(&mp->m_health_update_hooks); xfs_hooks_init(&mp->m_media_error_hooks); + xfs_hooks_init(&mp->m_file_ioerror_hooks); fc->s_fs_info = mp; fc->ops = &xfs_context_ops;