#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)
{
refcount_set(&sb->s_pending_errors, 1);
}
void fserror_unmount(struct super_block *sb)
{
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 = {
.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 (!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;
memset(event, 0, sizeof(*event));
event->sb = sb;
INIT_WORK(&event->work, fserror_worker);
return event;
out_pending:
fserror_pending_dec(sb);
return NULL;
}
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;
WARN_ON_ONCE(inode && inode->i_sb != sb);
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;
if (inode) {
event->inode = igrab(inode);
if (!event->inode)
goto lost_event;
}
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);