#include <linux/kernel.h>
#include <linux/netfs.h>
#include "internal.h"
struct netfs_wb_waiter {
struct list_head link;
struct task_struct *waiter;
};
static int netfs_inode_dio_wait_interruptible(struct inode *inode)
{
if (inode_dio_finished(inode))
return 0;
inode_dio_wait_interruptible(inode);
return !inode_dio_finished(inode) ? -ERESTARTSYS : 0;
}
static int netfs_block_o_direct(struct netfs_inode *ictx)
{
if (!test_bit(NETFS_ICTX_ODIRECT, &ictx->flags))
return 0;
clear_bit(NETFS_ICTX_ODIRECT, &ictx->flags);
return netfs_inode_dio_wait_interruptible(&ictx->inode);
}
int netfs_start_io_read(struct inode *inode)
__acquires(inode->i_rwsem)
{
struct netfs_inode *ictx = netfs_inode(inode);
if (down_read_interruptible(&inode->i_rwsem) < 0)
return -ERESTARTSYS;
if (test_bit(NETFS_ICTX_ODIRECT, &ictx->flags) == 0)
return 0;
up_read(&inode->i_rwsem);
if (down_write_killable(&inode->i_rwsem) < 0)
return -ERESTARTSYS;
if (netfs_block_o_direct(ictx) < 0) {
up_write(&inode->i_rwsem);
return -ERESTARTSYS;
}
downgrade_write(&inode->i_rwsem);
return 0;
}
EXPORT_SYMBOL(netfs_start_io_read);
void netfs_end_io_read(struct inode *inode)
__releases(inode->i_rwsem)
{
up_read(&inode->i_rwsem);
}
EXPORT_SYMBOL(netfs_end_io_read);
int netfs_start_io_write(struct inode *inode)
__acquires(inode->i_rwsem)
{
struct netfs_inode *ictx = netfs_inode(inode);
if (down_write_killable(&inode->i_rwsem) < 0)
return -ERESTARTSYS;
if (netfs_block_o_direct(ictx) < 0) {
up_write(&inode->i_rwsem);
return -ERESTARTSYS;
}
downgrade_write(&inode->i_rwsem);
return 0;
}
EXPORT_SYMBOL(netfs_start_io_write);
void netfs_end_io_write(struct inode *inode)
__releases(inode->i_rwsem)
{
up_read(&inode->i_rwsem);
}
EXPORT_SYMBOL(netfs_end_io_write);
static int netfs_block_buffered(struct inode *inode)
{
struct netfs_inode *ictx = netfs_inode(inode);
int ret;
if (!test_bit(NETFS_ICTX_ODIRECT, &ictx->flags)) {
set_bit(NETFS_ICTX_ODIRECT, &ictx->flags);
if (inode->i_mapping->nrpages != 0) {
unmap_mapping_range(inode->i_mapping, 0, 0, 0);
ret = filemap_fdatawait(inode->i_mapping);
if (ret < 0) {
clear_bit(NETFS_ICTX_ODIRECT, &ictx->flags);
return ret;
}
}
}
return 0;
}
int netfs_start_io_direct(struct inode *inode)
__acquires(inode->i_rwsem)
{
struct netfs_inode *ictx = netfs_inode(inode);
int ret;
if (down_read_interruptible(&inode->i_rwsem) < 0)
return -ERESTARTSYS;
if (test_bit(NETFS_ICTX_ODIRECT, &ictx->flags) != 0)
return 0;
up_read(&inode->i_rwsem);
if (down_write_killable(&inode->i_rwsem) < 0)
return -ERESTARTSYS;
ret = netfs_block_buffered(inode);
if (ret < 0) {
up_write(&inode->i_rwsem);
return ret;
}
downgrade_write(&inode->i_rwsem);
return 0;
}
EXPORT_SYMBOL(netfs_start_io_direct);
void netfs_end_io_direct(struct inode *inode)
__releases(inode->i_rwsem)
{
up_read(&inode->i_rwsem);
}
EXPORT_SYMBOL(netfs_end_io_direct);
static bool netfs_wb_begin_wait(struct netfs_inode *ictx)
{
struct netfs_wb_waiter waiter = {};
struct task_struct *tsk = current;
bool got = false;
spin_lock(&ictx->lock);
if (test_and_set_bit_lock(NETFS_ICTX_WB_LOCK, &ictx->flags)) {
get_task_struct(tsk);
waiter.waiter = tsk;
list_add_tail(&waiter.link, &ictx->wb_queue);
} else {
got = true;
}
spin_unlock(&ictx->lock);
if (!got) {
for (;;) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (smp_load_acquire(&waiter.waiter) == NULL)
break;
schedule();
}
}
__set_current_state(TASK_RUNNING);
return true;
}
bool netfs_wb_begin(struct netfs_inode *ictx, bool nowait)
{
if (!test_and_set_bit_lock(NETFS_ICTX_WB_LOCK, &ictx->flags))
return true;
if (nowait) {
netfs_stat(&netfs_n_wb_lock_skip);
return false;
}
netfs_stat(&netfs_n_wb_lock_wait);
return netfs_wb_begin_wait(ictx);
}
EXPORT_SYMBOL(netfs_wb_begin);
void netfs_wb_end(struct netfs_inode *ictx)
{
struct netfs_wb_waiter *waiter;
struct task_struct *tsk;
WARN_ON_ONCE(!test_bit(NETFS_ICTX_WB_LOCK, &ictx->flags));
spin_lock(&ictx->lock);
waiter = list_first_entry_or_null(&ictx->wb_queue, struct netfs_wb_waiter, link);
if (waiter) {
list_del(&waiter->link);
tsk = waiter->waiter;
smp_store_release(&waiter->waiter, NULL);
wake_up_process(tsk);
put_task_struct(tsk);
} else {
clear_bit_unlock(NETFS_ICTX_WB_LOCK, &ictx->flags);
}
spin_unlock(&ictx->lock);
}
EXPORT_SYMBOL(netfs_wb_end);