Before 2011 there was no meaningful synchronization between read/readdir/write/seek. Only in commit ef3d0fd27e90 ("vfs: do (nearly) lockless generic_file_llseek") synchronization was added for SEEK_CUR by taking f_lock around vfs_setpos(). Then in 2014 full synchronization between read/readdir/write/seek was added in commit 9c225f2655e3 ("vfs: atomic f_pos accesses as per POSIX") by introducing f_pos_lock for regular files with FMODE_ATOMIC_POS and for directories. At that point taking f_lock became unnecessary for such files. So only acquire f_lock for SEEK_CUR if this isn't a file that would have acquired f_pos_lock if necessary. Signed-off-by: Christian Brauner <brauner@xxxxxxxxxx> --- fs/read_write.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/fs/read_write.c b/fs/read_write.c index a6133241dfb8..816189f9c56d 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -168,13 +168,23 @@ generic_file_llseek_size(struct file *file, loff_t offset, int whence, return offset; if (whence == SEEK_CUR) { + bool locked; + /* - * f_lock protects against read/modify/write race with - * other SEEK_CURs. Note that parallel writes and reads - * behave like SEEK_SET. + * If the file requires locking via f_pos_lock we know + * that mutual exclusion for SEEK_CUR on the same file + * is guaranteed. If the file isn't locked, we take + * f_lock to protect against f_pos races with other + * SEEK_CURs. */ - guard(spinlock)(&file->f_lock); - return vfs_setpos(file, file->f_pos + offset, maxsize); + locked = (file->f_mode & FMODE_ATOMIC_POS) || + file->f_op->iterate_shared; + if (!locked) + spin_lock(&file->f_lock); + offset = vfs_setpos(file, file->f_pos + offset, maxsize); + if (!locked) + spin_unlock(&file->f_lock); + return offset; } return vfs_setpos(file, offset, maxsize); -- 2.47.2