mirror of
https://github.com/torvalds/linux.git
synced 2026-05-14 01:08:22 +02:00
Merge patch series "fs: generic file IO error reporting"
Darrick J. Wong <djwong@kernel.org> says: This patchset adds some generic helpers so that filesystems can report errors to fsnotify in a standard way. Then it adapts iomap to use the generic helpers so that any iomap-enabled filesystem can report I/O errors through this mechanism as well. Finally, it makes XFS report metadata errors through this mechanism in much the same way that ext4 does now. These are a prerequisite for the XFS self-healing series which will come at a later time. * patches from https://patch.msgid.link/176826402528.3490369.2415315475116356277.stgit@frogsfrogsfrogs: ext4: convert to new fserror helpers xfs: translate fsdax media errors into file "data lost" errors when convenient xfs: report fs metadata errors via fsnotify iomap: report file I/O errors to the VFS fs: report filesystem and file I/O errors to fsnotify uapi: promote EFSCORRUPTED and EUCLEAN to errno.h Link: https://patch.msgid.link/176826402528.3490369.2415315475116356277.stgit@frogsfrogsfrogs Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
commit
347b7042fb
|
|
@ -55,6 +55,7 @@
|
|||
#define ENOSR 82 /* Out of streams resources */
|
||||
#define ETIME 83 /* Timer expired */
|
||||
#define EBADMSG 84 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EPROTO 85 /* Protocol error */
|
||||
#define ENODATA 86 /* No data available */
|
||||
#define ENOSTR 87 /* Device not a stream */
|
||||
|
|
@ -96,6 +97,7 @@
|
|||
#define EREMCHG 115 /* Remote address changed */
|
||||
|
||||
#define EUCLEAN 117 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 118 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 119 /* No XENIX semaphores available */
|
||||
#define EISNAM 120 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
#define EDOTDOT 73 /* RFS specific error */
|
||||
#define EMULTIHOP 74 /* Multihop attempted */
|
||||
#define EBADMSG 77 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define ENAMETOOLONG 78 /* File name too long */
|
||||
#define EOVERFLOW 79 /* Value too large for defined data type */
|
||||
#define ENOTUNIQ 80 /* Name not unique on network */
|
||||
|
|
@ -88,6 +89,7 @@
|
|||
#define EISCONN 133 /* Transport endpoint is already connected */
|
||||
#define ENOTCONN 134 /* Transport endpoint is not connected */
|
||||
#define EUCLEAN 135 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 137 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 138 /* No XENIX semaphores available */
|
||||
#define EISNAM 139 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#define EDOTDOT 66 /* RFS specific error */
|
||||
#define EBADMSG 67 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EUSERS 68 /* Too many users */
|
||||
#define EDQUOT 69 /* Quota exceeded */
|
||||
#define ESTALE 70 /* Stale file handle */
|
||||
|
|
@ -62,6 +63,7 @@
|
|||
#define ERESTART 175 /* Interrupted system call should be restarted */
|
||||
#define ESTRPIPE 176 /* Streams pipe error */
|
||||
#define EUCLEAN 177 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 178 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 179 /* No XENIX semaphores available */
|
||||
#define EISNAM 180 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
#define ENOSR 74 /* Out of streams resources */
|
||||
#define ENOMSG 75 /* No message of desired type */
|
||||
#define EBADMSG 76 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EIDRM 77 /* Identifier removed */
|
||||
#define EDEADLK 78 /* Resource deadlock would occur */
|
||||
#define ENOLCK 79 /* No record locks available */
|
||||
|
|
@ -91,6 +92,7 @@
|
|||
#define ENOTUNIQ 115 /* Name not unique on network */
|
||||
#define ERESTART 116 /* Interrupted syscall should be restarted */
|
||||
#define EUCLEAN 117 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 118 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 119 /* No XENIX semaphores available */
|
||||
#define EISNAM 120 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ obj-y := open.o read_write.o file_table.o super.o \
|
|||
stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
|
||||
fs_dirent.o fs_context.o fs_parser.o fsopen.o init.o \
|
||||
kernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o \
|
||||
file_attr.o
|
||||
file_attr.o fserror.o
|
||||
|
||||
obj-$(CONFIG_BUFFER_HEAD) += buffer.o mpage.o
|
||||
obj-$(CONFIG_PROC_FS) += proc_namespace.o
|
||||
|
|
|
|||
|
|
@ -541,6 +541,4 @@ long erofs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
|
|||
long erofs_compat_ioctl(struct file *filp, unsigned int cmd,
|
||||
unsigned long arg);
|
||||
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
|
||||
#endif /* __EROFS_INTERNAL_H */
|
||||
|
|
|
|||
|
|
@ -357,7 +357,6 @@ struct ext2_inode {
|
|||
*/
|
||||
#define EXT2_VALID_FS 0x0001 /* Unmounted cleanly */
|
||||
#define EXT2_ERROR_FS 0x0002 /* Errors detected */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
|
||||
/*
|
||||
* Mount flags
|
||||
|
|
|
|||
|
|
@ -3938,7 +3938,4 @@ extern int ext4_block_write_begin(handle_t *handle, struct folio *folio,
|
|||
get_block_t *get_block);
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
|
||||
#endif /* _EXT4_H */
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <linux/fsmap.h>
|
||||
#include "fsmap.h"
|
||||
#include <trace/events/ext4.h>
|
||||
#include <linux/fserror.h>
|
||||
|
||||
typedef void ext4_update_sb_callback(struct ext4_sb_info *sbi,
|
||||
struct ext4_super_block *es,
|
||||
|
|
@ -844,6 +845,7 @@ int ext4_force_shutdown(struct super_block *sb, u32 flags)
|
|||
return -EINVAL;
|
||||
}
|
||||
clear_opt(sb, DISCARD);
|
||||
fserror_report_shutdown(sb, GFP_KERNEL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
#include <linux/fsnotify.h>
|
||||
#include <linux/fs_context.h>
|
||||
#include <linux/fs_parser.h>
|
||||
#include <linux/fserror.h>
|
||||
|
||||
#include "ext4.h"
|
||||
#include "ext4_extents.h" /* Needed for trace points definition */
|
||||
|
|
@ -824,7 +825,8 @@ void __ext4_error(struct super_block *sb, const char *function,
|
|||
sb->s_id, function, line, current->comm, &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
fsnotify_sb_error(sb, NULL, error ? error : EFSCORRUPTED);
|
||||
fserror_report_metadata(sb, error ? -abs(error) : -EFSCORRUPTED,
|
||||
GFP_ATOMIC);
|
||||
|
||||
ext4_handle_error(sb, force_ro, error, 0, block, function, line);
|
||||
}
|
||||
|
|
@ -856,7 +858,9 @@ void __ext4_error_inode(struct inode *inode, const char *function,
|
|||
current->comm, &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
fsnotify_sb_error(inode->i_sb, inode, error ? error : EFSCORRUPTED);
|
||||
fserror_report_file_metadata(inode,
|
||||
error ? -abs(error) : -EFSCORRUPTED,
|
||||
GFP_ATOMIC);
|
||||
|
||||
ext4_handle_error(inode->i_sb, false, error, inode->i_ino, block,
|
||||
function, line);
|
||||
|
|
@ -896,7 +900,7 @@ void __ext4_error_file(struct file *file, const char *function,
|
|||
current->comm, path, &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
fsnotify_sb_error(inode->i_sb, inode, EFSCORRUPTED);
|
||||
fserror_report_file_metadata(inode, -EFSCORRUPTED, GFP_ATOMIC);
|
||||
|
||||
ext4_handle_error(inode->i_sb, false, EFSCORRUPTED, inode->i_ino, block,
|
||||
function, line);
|
||||
|
|
@ -965,7 +969,8 @@ void __ext4_std_error(struct super_block *sb, const char *function,
|
|||
printk(KERN_CRIT "EXT4-fs error (device %s) in %s:%d: %s\n",
|
||||
sb->s_id, function, line, errstr);
|
||||
}
|
||||
fsnotify_sb_error(sb, NULL, errno ? errno : EFSCORRUPTED);
|
||||
fserror_report_metadata(sb, errno ? -abs(errno) : -EFSCORRUPTED,
|
||||
GFP_ATOMIC);
|
||||
|
||||
ext4_handle_error(sb, false, -errno, 0, 0, function, line);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5004,7 +5004,4 @@ static inline void f2fs_invalidate_internal_cache(struct f2fs_sb_info *sbi,
|
|||
f2fs_invalidate_compress_pages_range(sbi, blkaddr, len);
|
||||
}
|
||||
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
|
||||
#endif /* _LINUX_F2FS_H */
|
||||
|
|
|
|||
194
fs/fserror.c
Normal file
194
fs/fserror.c
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Copyright (c) 2025 Oracle. All Rights Reserved.
|
||||
* Author: Darrick J. Wong <djwong@kernel.org>
|
||||
*/
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fsnotify.h>
|
||||
#include <linux/mempool.h>
|
||||
#include <linux/fserror.h>
|
||||
|
||||
#define FSERROR_DEFAULT_EVENT_POOL_SIZE (32)
|
||||
|
||||
static struct mempool fserror_events_pool;
|
||||
|
||||
void fserror_mount(struct super_block *sb)
|
||||
{
|
||||
/*
|
||||
* The pending error counter is biased by 1 so that we don't wake_var
|
||||
* until we're actually trying to unmount.
|
||||
*/
|
||||
refcount_set(&sb->s_pending_errors, 1);
|
||||
}
|
||||
|
||||
void fserror_unmount(struct super_block *sb)
|
||||
{
|
||||
/*
|
||||
* If we don't drop the pending error count to zero, then wait for it
|
||||
* to drop below 1, which means that the pending errors cleared and
|
||||
* hopefully we didn't saturate with 1 billion+ concurrent events.
|
||||
*/
|
||||
if (!refcount_dec_and_test(&sb->s_pending_errors))
|
||||
wait_var_event(&sb->s_pending_errors,
|
||||
refcount_read(&sb->s_pending_errors) < 1);
|
||||
}
|
||||
|
||||
static inline void fserror_pending_dec(struct super_block *sb)
|
||||
{
|
||||
if (refcount_dec_and_test(&sb->s_pending_errors))
|
||||
wake_up_var(&sb->s_pending_errors);
|
||||
}
|
||||
|
||||
static inline void fserror_free_event(struct fserror_event *event)
|
||||
{
|
||||
fserror_pending_dec(event->sb);
|
||||
mempool_free(event, &fserror_events_pool);
|
||||
}
|
||||
|
||||
static void fserror_worker(struct work_struct *work)
|
||||
{
|
||||
struct fserror_event *event =
|
||||
container_of(work, struct fserror_event, work);
|
||||
struct super_block *sb = event->sb;
|
||||
|
||||
if (sb->s_flags & SB_ACTIVE) {
|
||||
struct fs_error_report report = {
|
||||
/* send positive error number to userspace */
|
||||
.error = -event->error,
|
||||
.inode = event->inode,
|
||||
.sb = event->sb,
|
||||
};
|
||||
|
||||
if (sb->s_op->report_error)
|
||||
sb->s_op->report_error(event);
|
||||
|
||||
fsnotify(FS_ERROR, &report, FSNOTIFY_EVENT_ERROR, NULL, NULL,
|
||||
NULL, 0);
|
||||
}
|
||||
|
||||
iput(event->inode);
|
||||
fserror_free_event(event);
|
||||
}
|
||||
|
||||
static inline struct fserror_event *fserror_alloc_event(struct super_block *sb,
|
||||
gfp_t gfp_flags)
|
||||
{
|
||||
struct fserror_event *event = NULL;
|
||||
|
||||
/*
|
||||
* If pending_errors already reached zero or is no longer active,
|
||||
* the superblock is being deactivated so there's no point in
|
||||
* continuing.
|
||||
*
|
||||
* The order of the check of s_pending_errors and SB_ACTIVE are
|
||||
* mandated by order of accesses in generic_shutdown_super and
|
||||
* fserror_unmount. Barriers are implicitly provided by the refcount
|
||||
* manipulations in this function and fserror_unmount.
|
||||
*/
|
||||
if (!refcount_inc_not_zero(&sb->s_pending_errors))
|
||||
return NULL;
|
||||
if (!(sb->s_flags & SB_ACTIVE))
|
||||
goto out_pending;
|
||||
|
||||
event = mempool_alloc(&fserror_events_pool, gfp_flags);
|
||||
if (!event)
|
||||
goto out_pending;
|
||||
|
||||
/* mempool_alloc doesn't support GFP_ZERO */
|
||||
memset(event, 0, sizeof(*event));
|
||||
event->sb = sb;
|
||||
INIT_WORK(&event->work, fserror_worker);
|
||||
|
||||
return event;
|
||||
|
||||
out_pending:
|
||||
fserror_pending_dec(sb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* fserror_report - report a filesystem error of some kind
|
||||
*
|
||||
* @sb: superblock of the filesystem
|
||||
* @inode: inode within that filesystem, if applicable
|
||||
* @type: type of error encountered
|
||||
* @pos: start of inode range affected, if applicable
|
||||
* @len: length of inode range affected, if applicable
|
||||
* @error: error number encountered, must be negative
|
||||
* @gfp: memory allocation flags for conveying the event to a worker,
|
||||
* since this function can be called from atomic contexts
|
||||
*
|
||||
* Report details of a filesystem error to the super_operations::report_error
|
||||
* callback if present; and to fsnotify for distribution to userspace. @sb,
|
||||
* @gfp, @type, and @error must all be specified. For file I/O errors, the
|
||||
* @inode, @pos, and @len fields must also be specified. For file metadata
|
||||
* errors, @inode must be specified. If @inode is not NULL, then @inode->i_sb
|
||||
* must point to @sb.
|
||||
*
|
||||
* Reporting work is deferred to a workqueue to ensure that ->report_error is
|
||||
* called from process context without any locks held. An active reference to
|
||||
* the inode is maintained until event handling is complete, and unmount will
|
||||
* wait for queued events to drain.
|
||||
*/
|
||||
void fserror_report(struct super_block *sb, struct inode *inode,
|
||||
enum fserror_type type, loff_t pos, u64 len, int error,
|
||||
gfp_t gfp)
|
||||
{
|
||||
struct fserror_event *event;
|
||||
|
||||
/* sb and inode must be from the same filesystem */
|
||||
WARN_ON_ONCE(inode && inode->i_sb != sb);
|
||||
|
||||
/* error number must be negative */
|
||||
WARN_ON_ONCE(error >= 0);
|
||||
|
||||
event = fserror_alloc_event(sb, gfp);
|
||||
if (!event)
|
||||
goto lost;
|
||||
|
||||
event->type = type;
|
||||
event->pos = pos;
|
||||
event->len = len;
|
||||
event->error = error;
|
||||
|
||||
/*
|
||||
* Can't iput from non-sleeping context, so grabbing another reference
|
||||
* to the inode must be the last thing before submitting the event.
|
||||
*/
|
||||
if (inode) {
|
||||
event->inode = igrab(inode);
|
||||
if (!event->inode)
|
||||
goto lost_event;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use schedule_work here even if we're already in process context so
|
||||
* that fsnotify and super_operations::report_error implementations are
|
||||
* guaranteed to run in process context without any locks held. Since
|
||||
* errors are supposed to be rare, the overhead shouldn't kill us any
|
||||
* more than the failing device will.
|
||||
*/
|
||||
schedule_work(&event->work);
|
||||
return;
|
||||
|
||||
lost_event:
|
||||
fserror_free_event(event);
|
||||
lost:
|
||||
if (inode)
|
||||
pr_err_ratelimited(
|
||||
"%s: lost file I/O error report for ino %lu type %u pos 0x%llx len 0x%llx error %d",
|
||||
sb->s_id, inode->i_ino, type, pos, len, error);
|
||||
else
|
||||
pr_err_ratelimited(
|
||||
"%s: lost filesystem error report for type %u error %d",
|
||||
sb->s_id, type, error);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fserror_report);
|
||||
|
||||
static int __init fserror_init(void)
|
||||
{
|
||||
return mempool_init_kmalloc_pool(&fserror_events_pool,
|
||||
FSERROR_DEFAULT_EVENT_POOL_SIZE,
|
||||
sizeof(struct fserror_event));
|
||||
}
|
||||
fs_initcall(fserror_init);
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
#include <linux/writeback.h>
|
||||
#include <linux/swap.h>
|
||||
#include <linux/migrate.h>
|
||||
#include <linux/fserror.h>
|
||||
#include "internal.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
|
@ -371,8 +372,11 @@ static int iomap_read_inline_data(const struct iomap_iter *iter,
|
|||
if (folio_test_uptodate(folio))
|
||||
return 0;
|
||||
|
||||
if (WARN_ON_ONCE(size > iomap->length))
|
||||
if (WARN_ON_ONCE(size > iomap->length)) {
|
||||
fserror_report_io(iter->inode, FSERR_BUFFERED_READ,
|
||||
iomap->offset, size, -EIO, GFP_NOFS);
|
||||
return -EIO;
|
||||
}
|
||||
if (offset > 0)
|
||||
ifs_alloc(iter->inode, folio, iter->flags);
|
||||
|
||||
|
|
@ -399,6 +403,11 @@ void iomap_finish_folio_read(struct folio *folio, size_t off, size_t len,
|
|||
spin_unlock_irqrestore(&ifs->state_lock, flags);
|
||||
}
|
||||
|
||||
if (error)
|
||||
fserror_report_io(folio->mapping->host, FSERR_BUFFERED_READ,
|
||||
folio_pos(folio) + off, len, error,
|
||||
GFP_ATOMIC);
|
||||
|
||||
if (finished)
|
||||
folio_end_read(folio, uptodate);
|
||||
}
|
||||
|
|
@ -540,6 +549,10 @@ static int iomap_read_folio_iter(struct iomap_iter *iter,
|
|||
if (!*bytes_submitted)
|
||||
iomap_read_init(folio);
|
||||
ret = ctx->ops->read_folio_range(iter, ctx, plen);
|
||||
if (ret < 0)
|
||||
fserror_report_io(iter->inode,
|
||||
FSERR_BUFFERED_READ, pos,
|
||||
plen, ret, GFP_NOFS);
|
||||
if (ret)
|
||||
return ret;
|
||||
*bytes_submitted += plen;
|
||||
|
|
@ -815,6 +828,10 @@ static int __iomap_write_begin(const struct iomap_iter *iter,
|
|||
else
|
||||
status = iomap_bio_read_folio_range_sync(iter,
|
||||
folio, block_start, plen);
|
||||
if (status < 0)
|
||||
fserror_report_io(iter->inode,
|
||||
FSERR_BUFFERED_READ, pos,
|
||||
len, status, GFP_NOFS);
|
||||
if (status)
|
||||
return status;
|
||||
}
|
||||
|
|
@ -1805,6 +1822,7 @@ int iomap_writeback_folio(struct iomap_writepage_ctx *wpc, struct folio *folio)
|
|||
u64 pos = folio_pos(folio);
|
||||
u64 end_pos = pos + folio_size(folio);
|
||||
u64 end_aligned = 0;
|
||||
loff_t orig_pos = pos;
|
||||
size_t bytes_submitted = 0;
|
||||
int error = 0;
|
||||
u32 rlen;
|
||||
|
|
@ -1848,6 +1866,9 @@ int iomap_writeback_folio(struct iomap_writepage_ctx *wpc, struct folio *folio)
|
|||
|
||||
if (bytes_submitted)
|
||||
wpc->nr_folios++;
|
||||
if (error && pos > orig_pos)
|
||||
fserror_report_io(inode, FSERR_BUFFERED_WRITE, orig_pos, 0,
|
||||
error, GFP_NOFS);
|
||||
|
||||
/*
|
||||
* We can have dirty bits set past end of file in page_mkwrite path
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <linux/pagemap.h>
|
||||
#include <linux/iomap.h>
|
||||
#include <linux/task_io_accounting_ops.h>
|
||||
#include <linux/fserror.h>
|
||||
#include "internal.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
|
@ -78,6 +79,13 @@ static void iomap_dio_submit_bio(const struct iomap_iter *iter,
|
|||
}
|
||||
}
|
||||
|
||||
static inline enum fserror_type iomap_dio_err_type(const struct iomap_dio *dio)
|
||||
{
|
||||
if (dio->flags & IOMAP_DIO_WRITE)
|
||||
return FSERR_DIRECTIO_WRITE;
|
||||
return FSERR_DIRECTIO_READ;
|
||||
}
|
||||
|
||||
ssize_t iomap_dio_complete(struct iomap_dio *dio)
|
||||
{
|
||||
const struct iomap_dio_ops *dops = dio->dops;
|
||||
|
|
@ -87,6 +95,10 @@ ssize_t iomap_dio_complete(struct iomap_dio *dio)
|
|||
|
||||
if (dops && dops->end_io)
|
||||
ret = dops->end_io(iocb, dio->size, ret, dio->flags);
|
||||
if (dio->error)
|
||||
fserror_report_io(file_inode(iocb->ki_filp),
|
||||
iomap_dio_err_type(dio), offset, dio->size,
|
||||
dio->error, GFP_NOFS);
|
||||
|
||||
if (likely(!ret)) {
|
||||
ret = dio->size;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <linux/list_sort.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/writeback.h>
|
||||
#include <linux/fserror.h>
|
||||
#include "internal.h"
|
||||
#include "trace.h"
|
||||
|
||||
|
|
@ -55,6 +56,11 @@ static u32 iomap_finish_ioend_buffered(struct iomap_ioend *ioend)
|
|||
|
||||
/* walk all folios in bio, ending page IO on them */
|
||||
bio_for_each_folio_all(fi, bio) {
|
||||
if (ioend->io_error)
|
||||
fserror_report_io(inode, FSERR_BUFFERED_WRITE,
|
||||
folio_pos(fi.folio) + fi.offset,
|
||||
fi.length, ioend->io_error,
|
||||
GFP_ATOMIC);
|
||||
iomap_finish_folio_write(inode, fi.folio, fi.length);
|
||||
folio_count++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,4 @@ static inline int minix_test_bit(int nr, const void *vaddr)
|
|||
__minix_error_inode((inode), __func__, __LINE__, \
|
||||
(fmt), ##__VA_ARGS__)
|
||||
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
|
||||
#endif /* FS_MINIX_H */
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#include <linux/lockdep.h>
|
||||
#include <linux/user_namespace.h>
|
||||
#include <linux/fs_context.h>
|
||||
#include <linux/fserror.h>
|
||||
#include <uapi/linux/mount.h>
|
||||
#include "internal.h"
|
||||
|
||||
|
|
@ -363,6 +364,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
|
|||
spin_lock_init(&s->s_inode_list_lock);
|
||||
INIT_LIST_HEAD(&s->s_inodes_wb);
|
||||
spin_lock_init(&s->s_inode_wblist_lock);
|
||||
fserror_mount(s);
|
||||
|
||||
s->s_count = 1;
|
||||
atomic_set(&s->s_active, 1);
|
||||
|
|
@ -622,6 +624,7 @@ void generic_shutdown_super(struct super_block *sb)
|
|||
sync_filesystem(sb);
|
||||
sb->s_flags &= ~SB_ACTIVE;
|
||||
|
||||
fserror_unmount(sb);
|
||||
cgroup_writeback_umount(sb);
|
||||
|
||||
/* Evict all inodes with zero refcount. */
|
||||
|
|
|
|||
|
|
@ -55,8 +55,6 @@
|
|||
#define MF_DUPLICATE_MD 0x01
|
||||
#define MF_MIRROR_FE_LOADED 0x02
|
||||
|
||||
#define EFSCORRUPTED EUCLEAN
|
||||
|
||||
struct udf_meta_data {
|
||||
__u32 s_meta_file_loc;
|
||||
__u32 s_mirror_file_loc;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
#include "xfs_rtrefcount_btree.h"
|
||||
#include "xfs_metafile.h"
|
||||
|
||||
#include <linux/fserror.h>
|
||||
|
||||
/*
|
||||
* Write new AG headers to disk. Non-transactional, but need to be
|
||||
* written and completed prior to the growfs transaction being logged.
|
||||
|
|
@ -540,6 +542,8 @@ xfs_do_force_shutdown(
|
|||
"Please unmount the filesystem and rectify the problem(s)");
|
||||
if (xfs_error_level >= XFS_ERRLEVEL_HIGH)
|
||||
xfs_stack_trace();
|
||||
|
||||
fserror_report_shutdown(mp->m_super, GFP_KERNEL);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
#include "xfs_quota_defs.h"
|
||||
#include "xfs_rtgroup.h"
|
||||
|
||||
#include <linux/fserror.h>
|
||||
|
||||
static void
|
||||
xfs_health_unmount_group(
|
||||
struct xfs_group *xg,
|
||||
|
|
@ -111,6 +113,8 @@ xfs_fs_mark_sick(
|
|||
spin_lock(&mp->m_sb_lock);
|
||||
mp->m_fs_sick |= mask;
|
||||
spin_unlock(&mp->m_sb_lock);
|
||||
|
||||
fserror_report_metadata(mp->m_super, -EFSCORRUPTED, GFP_NOFS);
|
||||
}
|
||||
|
||||
/* Mark per-fs metadata as having been checked and found unhealthy by fsck. */
|
||||
|
|
@ -126,6 +130,8 @@ xfs_fs_mark_corrupt(
|
|||
mp->m_fs_sick |= mask;
|
||||
mp->m_fs_checked |= mask;
|
||||
spin_unlock(&mp->m_sb_lock);
|
||||
|
||||
fserror_report_metadata(mp->m_super, -EFSCORRUPTED, GFP_NOFS);
|
||||
}
|
||||
|
||||
/* Mark a per-fs metadata healed. */
|
||||
|
|
@ -198,6 +204,8 @@ xfs_group_mark_sick(
|
|||
spin_lock(&xg->xg_state_lock);
|
||||
xg->xg_sick |= mask;
|
||||
spin_unlock(&xg->xg_state_lock);
|
||||
|
||||
fserror_report_metadata(xg->xg_mount->m_super, -EFSCORRUPTED, GFP_NOFS);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -215,6 +223,8 @@ xfs_group_mark_corrupt(
|
|||
xg->xg_sick |= mask;
|
||||
xg->xg_checked |= mask;
|
||||
spin_unlock(&xg->xg_state_lock);
|
||||
|
||||
fserror_report_metadata(xg->xg_mount->m_super, -EFSCORRUPTED, GFP_NOFS);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -287,6 +297,8 @@ xfs_inode_mark_sick(
|
|||
spin_lock(&VFS_I(ip)->i_lock);
|
||||
inode_state_clear(VFS_I(ip), I_DONTCACHE);
|
||||
spin_unlock(&VFS_I(ip)->i_lock);
|
||||
|
||||
fserror_report_file_metadata(VFS_I(ip), -EFSCORRUPTED, GFP_NOFS);
|
||||
}
|
||||
|
||||
/* Mark inode metadata as having been checked and found unhealthy by fsck. */
|
||||
|
|
@ -311,6 +323,8 @@ xfs_inode_mark_corrupt(
|
|||
spin_lock(&VFS_I(ip)->i_lock);
|
||||
inode_state_clear(VFS_I(ip), I_DONTCACHE);
|
||||
spin_unlock(&VFS_I(ip)->i_lock);
|
||||
|
||||
fserror_report_file_metadata(VFS_I(ip), -EFSCORRUPTED, GFP_NOFS);
|
||||
}
|
||||
|
||||
/* Mark parts of an inode healed. */
|
||||
|
|
|
|||
|
|
@ -121,8 +121,6 @@ typedef __u32 xfs_nlink_t;
|
|||
|
||||
#define ENOATTR ENODATA /* Attribute not found */
|
||||
#define EWRONGFS EINVAL /* Mount with wrong filesystem type */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
|
||||
#define __return_address __builtin_return_address(0)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/dax.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fserror.h>
|
||||
|
||||
struct xfs_failure_info {
|
||||
xfs_agblock_t startblock;
|
||||
|
|
@ -116,6 +117,9 @@ xfs_dax_failure_fn(
|
|||
invalidate_inode_pages2_range(mapping, pgoff,
|
||||
pgoff + pgcnt - 1);
|
||||
|
||||
fserror_report_data_lost(VFS_I(ip), (u64)pgoff << PAGE_SHIFT,
|
||||
(u64)pgcnt << PAGE_SHIFT, GFP_NOFS);
|
||||
|
||||
xfs_irele(ip);
|
||||
return error;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ struct user_namespace;
|
|||
struct workqueue_struct;
|
||||
struct writeback_control;
|
||||
struct xattr_handler;
|
||||
struct fserror_event;
|
||||
|
||||
extern struct super_block *blockdev_superblock;
|
||||
|
||||
|
|
@ -124,6 +125,9 @@ struct super_operations {
|
|||
*/
|
||||
int (*remove_bdev)(struct super_block *sb, struct block_device *bdev);
|
||||
void (*shutdown)(struct super_block *sb);
|
||||
|
||||
/* Report a filesystem error */
|
||||
void (*report_error)(const struct fserror_event *event);
|
||||
};
|
||||
|
||||
struct super_block {
|
||||
|
|
@ -268,6 +272,9 @@ struct super_block {
|
|||
spinlock_t s_inode_wblist_lock;
|
||||
struct list_head s_inodes_wb; /* writeback inodes */
|
||||
long s_min_writeback_pages;
|
||||
|
||||
/* number of fserrors that are being sent to fsnotify/filesystems */
|
||||
refcount_t s_pending_errors;
|
||||
} __randomize_layout;
|
||||
|
||||
/*
|
||||
|
|
|
|||
75
include/linux/fserror.h
Normal file
75
include/linux/fserror.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Copyright (c) 2025 Oracle. All Rights Reserved.
|
||||
* Author: Darrick J. Wong <djwong@kernel.org>
|
||||
*/
|
||||
#ifndef _LINUX_FSERROR_H__
|
||||
#define _LINUX_FSERROR_H__
|
||||
|
||||
void fserror_mount(struct super_block *sb);
|
||||
void fserror_unmount(struct super_block *sb);
|
||||
|
||||
enum fserror_type {
|
||||
/* pagecache I/O failed */
|
||||
FSERR_BUFFERED_READ,
|
||||
FSERR_BUFFERED_WRITE,
|
||||
|
||||
/* direct I/O failed */
|
||||
FSERR_DIRECTIO_READ,
|
||||
FSERR_DIRECTIO_WRITE,
|
||||
|
||||
/* out of band media error reported */
|
||||
FSERR_DATA_LOST,
|
||||
|
||||
/* filesystem metadata */
|
||||
FSERR_METADATA,
|
||||
};
|
||||
|
||||
struct fserror_event {
|
||||
struct work_struct work;
|
||||
struct super_block *sb;
|
||||
struct inode *inode;
|
||||
loff_t pos;
|
||||
u64 len;
|
||||
enum fserror_type type;
|
||||
|
||||
/* negative error number */
|
||||
int error;
|
||||
};
|
||||
|
||||
void fserror_report(struct super_block *sb, struct inode *inode,
|
||||
enum fserror_type type, loff_t pos, u64 len, int error,
|
||||
gfp_t gfp);
|
||||
|
||||
static inline void fserror_report_io(struct inode *inode,
|
||||
enum fserror_type type, loff_t pos,
|
||||
u64 len, int error, gfp_t gfp)
|
||||
{
|
||||
fserror_report(inode->i_sb, inode, type, pos, len, error, gfp);
|
||||
}
|
||||
|
||||
static inline void fserror_report_data_lost(struct inode *inode, loff_t pos,
|
||||
u64 len, gfp_t gfp)
|
||||
{
|
||||
fserror_report(inode->i_sb, inode, FSERR_DATA_LOST, pos, len, -EIO,
|
||||
gfp);
|
||||
}
|
||||
|
||||
static inline void fserror_report_file_metadata(struct inode *inode, int error,
|
||||
gfp_t gfp)
|
||||
{
|
||||
fserror_report(inode->i_sb, inode, FSERR_METADATA, 0, 0, error, gfp);
|
||||
}
|
||||
|
||||
static inline void fserror_report_metadata(struct super_block *sb, int error,
|
||||
gfp_t gfp)
|
||||
{
|
||||
fserror_report(sb, NULL, FSERR_METADATA, 0, 0, error, gfp);
|
||||
}
|
||||
|
||||
static inline void fserror_report_shutdown(struct super_block *sb, gfp_t gfp)
|
||||
{
|
||||
fserror_report(sb, NULL, FSERR_METADATA, 0, 0, -ESHUTDOWN, gfp);
|
||||
}
|
||||
|
||||
#endif /* _LINUX_FSERROR_H__ */
|
||||
|
|
@ -1815,7 +1815,4 @@ static inline int jbd2_handle_buffer_credits(handle_t *handle)
|
|||
|
||||
#endif /* __KERNEL__ */
|
||||
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
|
||||
#endif /* _LINUX_JBD2_H */
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#define EMULTIHOP 72 /* Multihop attempted */
|
||||
#define EDOTDOT 73 /* RFS specific error */
|
||||
#define EBADMSG 74 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EOVERFLOW 75 /* Value too large for defined data type */
|
||||
#define ENOTUNIQ 76 /* Name not unique on network */
|
||||
#define EBADFD 77 /* File descriptor in bad state */
|
||||
|
|
@ -98,6 +99,7 @@
|
|||
#define EINPROGRESS 115 /* Operation now in progress */
|
||||
#define ESTALE 116 /* Stale file handle */
|
||||
#define EUCLEAN 117 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 118 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 119 /* No XENIX semaphores available */
|
||||
#define EISNAM 120 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#define ENOSR 82 /* Out of streams resources */
|
||||
#define ETIME 83 /* Timer expired */
|
||||
#define EBADMSG 84 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EPROTO 85 /* Protocol error */
|
||||
#define ENODATA 86 /* No data available */
|
||||
#define ENOSTR 87 /* Device not a stream */
|
||||
|
|
@ -96,6 +97,7 @@
|
|||
#define EREMCHG 115 /* Remote address changed */
|
||||
|
||||
#define EUCLEAN 117 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 118 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 119 /* No XENIX semaphores available */
|
||||
#define EISNAM 120 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
#define EDOTDOT 73 /* RFS specific error */
|
||||
#define EMULTIHOP 74 /* Multihop attempted */
|
||||
#define EBADMSG 77 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define ENAMETOOLONG 78 /* File name too long */
|
||||
#define EOVERFLOW 79 /* Value too large for defined data type */
|
||||
#define ENOTUNIQ 80 /* Name not unique on network */
|
||||
|
|
@ -88,6 +89,7 @@
|
|||
#define EISCONN 133 /* Transport endpoint is already connected */
|
||||
#define ENOTCONN 134 /* Transport endpoint is not connected */
|
||||
#define EUCLEAN 135 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 137 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 138 /* No XENIX semaphores available */
|
||||
#define EISNAM 139 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
#define EDOTDOT 66 /* RFS specific error */
|
||||
#define EBADMSG 67 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EUSERS 68 /* Too many users */
|
||||
#define EDQUOT 69 /* Quota exceeded */
|
||||
#define ESTALE 70 /* Stale file handle */
|
||||
|
|
@ -62,6 +63,7 @@
|
|||
#define ERESTART 175 /* Interrupted system call should be restarted */
|
||||
#define ESTRPIPE 176 /* Streams pipe error */
|
||||
#define EUCLEAN 177 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 178 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 179 /* No XENIX semaphores available */
|
||||
#define EISNAM 180 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
#define ENOSR 74 /* Out of streams resources */
|
||||
#define ENOMSG 75 /* No message of desired type */
|
||||
#define EBADMSG 76 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EIDRM 77 /* Identifier removed */
|
||||
#define EDEADLK 78 /* Resource deadlock would occur */
|
||||
#define ENOLCK 79 /* No record locks available */
|
||||
|
|
@ -91,6 +92,7 @@
|
|||
#define ENOTUNIQ 115 /* Name not unique on network */
|
||||
#define ERESTART 116 /* Interrupted syscall should be restarted */
|
||||
#define EUCLEAN 117 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 118 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 119 /* No XENIX semaphores available */
|
||||
#define EISNAM 120 /* Is a named type file */
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#define EMULTIHOP 72 /* Multihop attempted */
|
||||
#define EDOTDOT 73 /* RFS specific error */
|
||||
#define EBADMSG 74 /* Not a data message */
|
||||
#define EFSBADCRC EBADMSG /* Bad CRC detected */
|
||||
#define EOVERFLOW 75 /* Value too large for defined data type */
|
||||
#define ENOTUNIQ 76 /* Name not unique on network */
|
||||
#define EBADFD 77 /* File descriptor in bad state */
|
||||
|
|
@ -98,6 +99,7 @@
|
|||
#define EINPROGRESS 115 /* Operation now in progress */
|
||||
#define ESTALE 116 /* Stale file handle */
|
||||
#define EUCLEAN 117 /* Structure needs cleaning */
|
||||
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
|
||||
#define ENOTNAM 118 /* Not a XENIX named type file */
|
||||
#define ENAVAIL 119 /* No XENIX semaphores available */
|
||||
#define EISNAM 120 /* Is a named type file */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user