From: Kanchan Joshi Keep the attached stream id in the VFS inode rather than the filesystem's own, so that fcntl_set_rw_hint() can see it. It is a u16 in the 32-bit hole after i_state, so struct inode does not grow on 64-bit. Add inode_set_write_stream() to set or clear it. It refuses a stream while a write life time hint is set, under i_lock as fcntl_set_rw_hint() does, so both sides of the exclusion are enforced in the VFS. Suggested-by: Christoph Hellwig Suggested-by: Darrick J. Wong Signed-off-by: Kanchan Joshi Signed-off-by: Anuj Gupta --- fs/fcntl.c | 7 +++++++ fs/inode.c | 2 ++ fs/write_streams.c | 23 +++++++++++++++++++++++ include/linux/fs.h | 2 +- include/linux/write_streams.h | 6 ++++++ 5 files changed, 39 insertions(+), 1 deletion(-) diff --git a/fs/fcntl.c b/fs/fcntl.c index 9b71bc9d71d1..271ff8f61354 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -383,7 +383,14 @@ static long fcntl_set_rw_hint(struct file *file, unsigned long arg) if (!rw_hint_valid(hint)) return -EINVAL; + /* keep write stream and write life time hint mutually exclusive */ + spin_lock(&inode->i_lock); + if (hint != WRITE_LIFE_NOT_SET && READ_ONCE(inode->i_write_stream)) { + spin_unlock(&inode->i_lock); + return -EBUSY; + } WRITE_ONCE(inode->i_write_hint, hint); + spin_unlock(&inode->i_lock); /* * file->f_mapping->host may differ from inode. As an example, diff --git a/fs/inode.c b/fs/inode.c index f63535ee3629..4b07125f27cd 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -246,6 +247,7 @@ int inode_init_always_gfp(struct super_block *sb, struct inode *inode, gfp_t gfp atomic_set(&inode->i_writecount, 0); inode->i_size = 0; inode->i_write_hint = WRITE_LIFE_NOT_SET; + inode->i_write_stream = WRITE_STREAM_NOT_SET; inode->i_blocks = 0; inode->i_bytes = 0; inode->i_generation = 0; diff --git a/fs/write_streams.c b/fs/write_streams.c index 4c7ad3198bb9..d97cdafc7d1a 100644 --- a/fs/write_streams.c +++ b/fs/write_streams.c @@ -140,3 +140,26 @@ int write_stream_get_id(struct file *file, const struct write_stream_pool *pool) return ws->id; } EXPORT_SYMBOL_GPL(write_stream_get_id); + +/** + * inode_set_write_stream - attach or clear the write stream of an inode + * @inode: inode to update + * @id: stream id, or WRITE_STREAM_NOT_SET to clear + * + * A write stream and a write life time hint are mutually exclusive. + * + * Return: 0, or -EBUSY if @inode has a write life time hint. + */ +int inode_set_write_stream(struct inode *inode, u16 id) +{ + spin_lock(&inode->i_lock); + if (id != WRITE_STREAM_NOT_SET && + inode->i_write_hint != WRITE_LIFE_NOT_SET) { + spin_unlock(&inode->i_lock); + return -EBUSY; + } + WRITE_ONCE(inode->i_write_stream, id); + spin_unlock(&inode->i_lock); + return 0; +} +EXPORT_SYMBOL_GPL(inode_set_write_stream); diff --git a/include/linux/fs.h b/include/linux/fs.h index 99caef43ff3c..3ed20abf4a46 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -812,7 +812,7 @@ struct inode { /* Misc */ struct inode_state_flags i_state; - /* 32-bit hole */ + u16 i_write_stream; struct rw_semaphore i_rwsem; /* Timestamp for writeback list ordering and age checks (jiffies). */ diff --git a/include/linux/write_streams.h b/include/linux/write_streams.h index d27f42df5984..258358d9c31d 100644 --- a/include/linux/write_streams.h +++ b/include/linux/write_streams.h @@ -6,6 +6,10 @@ #include struct file; +struct inode; + +/* i_write_stream value of an inode that has no write stream */ +#define WRITE_STREAM_NOT_SET 0 /* * Write stream slot pool, embedded in whatever filesystem object scopes @@ -30,4 +34,6 @@ int write_stream_alloc_fd(struct write_stream_pool *pool, struct file *file); int write_stream_get_id(struct file *file, const struct write_stream_pool *pool); +int inode_set_write_stream(struct inode *inode, u16 id); + #endif /* _LINUX_WRITE_STREAMS_H */ -- 2.25.1