From patchwork Sat Sep 9 12:08:31 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Filipe Manana X-Patchwork-Id: 13378086 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 04165EEB58C for ; Sat, 9 Sep 2023 12:08:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1345514AbjIIMIx (ORCPT ); Sat, 9 Sep 2023 08:08:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37718 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231783AbjIIMIw (ORCPT ); Sat, 9 Sep 2023 08:08:52 -0400 Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 50348E47 for ; Sat, 9 Sep 2023 05:08:45 -0700 (PDT) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0B124C433C7; Sat, 9 Sep 2023 12:08:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1694261325; bh=23G2ZUIixUtUUZWWxMy5N3qNa3EmZFnPs4+pqwUc8pM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=e7Uhh2RSUAYMlWOhQFzttl2xIi2f/SXCAyECA3FwdIe0Kz7ZN8GURcRNgBiAAS/05 sI+QVbcmAoNEz6dSQ8wRMMLimIYtje08UkpwcaSYfi/wdeJMtiflPkoNO9eAqZk9aH q3TZfDJoEEA0KMWlNZm885LnIHsKhJI12OkDU6IvswFELILifjuOd6SqdNNRqDrnAT df4TWrCeA7wcPxlPJnMr/lNibfiplDMKjFudMgMGhNdpJPb+FnWkxlnlHLdbSATAFg GBlp+ipPCsUpnIIx6GAPkIaRJoheoxpMg/APFtcq7fzONpytk4dyRU2O3n52qNdpdF ruD8cPt2BKCMQ== From: fdmanana@kernel.org To: linux-btrfs@vger.kernel.org Cc: ian@ianjohnson.dev, Filipe Manana Subject: [PATCH 1/2] btrfs: set last dir index to the current last index when opening dir Date: Sat, 9 Sep 2023 13:08:31 +0100 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-btrfs@vger.kernel.org From: Filipe Manana When opening a directory for reading it, we set the last index where we stop iteration to the value in struct btrfs_inode::index_cnt. That value does not match the index of the most recently added directory entry but it's instead the index number that will be assigned the next directory entry. This means that if after the call to opendir(3) new directory entries are added, a readdir(3) call will return the first new directory entry. This is fine because POSIX says the following [1]: "If a file is removed from or added to the directory after the most recent call to opendir() or rewinddir(), whether a subsequent call to readdir() returns an entry for that file is unspecified." For example for the test script from commit 9b378f6ad48c ("btrfs: fix infinite directory reads"), where we have 2000 files in a directory, ext4 doesn't return any new directory entry after opendir(3), while xfs returns the first 13 new directory entries added after the opendir(3) call. If we move to a shorter example with an empty directory when opendir(3) is called, and 2 files added to the directory after the opendir(3) call, then readdir(3) on btrfs will return the first file, ext4 and xfs return the 2 files (but in a different order). A test program for this, reported by Ian Johnson, is the following: #include #include int main(void) { DIR *dir = opendir("test"); FILE *file; file = fopen("test/1", "w"); fwrite("1", 1, 1, file); fclose(file); file = fopen("test/2", "w"); fwrite("2", 1, 1, file); fclose(file); struct dirent *entry; while ((entry = readdir(dir))) { printf("%s\n", entry->d_name); } closedir(dir); return 0; } To make this less odd, change the behaviour to never return new entries that were added after the opendir(3) call. This is done by setting the last_index field of the struct btrfs_file_private attached to the directory's file handle with a value matching btrfs_inode::index_cnt minus 1, since that value always matches the index of the next new directory entry and not the index of the most recently added entry. [1] https://pubs.opengroup.org/onlinepubs/007904875/functions/readdir_r.html Link: https://lore.kernel.org/linux-btrfs/YR1P0S.NGASEG570GJ8@ianjohnson.dev/ Signed-off-by: Filipe Manana Reported-by: Ian Johnson Tested-by: Ian Johnson --- fs/btrfs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index ca0f4781b0e5..df035211bdf0 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -5782,7 +5782,8 @@ static int btrfs_get_dir_last_index(struct btrfs_inode *dir, u64 *index) } } - *index = dir->index_cnt; + /* index_cnt is the index number of next new entry, so decrement it. */ + *index = dir->index_cnt - 1; return 0; } From patchwork Sat Sep 9 12:08:32 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Filipe Manana X-Patchwork-Id: 13378085 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E2A9EEEB58B for ; Sat, 9 Sep 2023 12:08:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238454AbjIIMIx (ORCPT ); Sat, 9 Sep 2023 08:08:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37736 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1345505AbjIIMIx (ORCPT ); Sat, 9 Sep 2023 08:08:53 -0400 Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BA1E9E57 for ; Sat, 9 Sep 2023 05:08:46 -0700 (PDT) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 6C971C433CA; Sat, 9 Sep 2023 12:08:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1694261326; bh=hsZtPCJ+rX/mWq0JlGLmAleKgG1TdwFskNinE5jZx/A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=s8Q8AScpdN8BFNIgsQeorbhYxzlBqWGt4BJk86dmYD8VkDls2qQv6uewGyfXSekM2 GsH9TQB/6p2LE1z/RSTFU3h+oCbABrq1R4NoWU/r5EFyJlzExJV7cCQPYFJKnncLhK /eC0/Jry6xu65f6/jHSDt2TaGYaBUPk6cmjkCYLAehtdIPPlvC96yTHgXXMk0LKcnC Lz5PRark6MsO5wGAHRK5E3160A+D36Oq9toTac7H+EZeQVIekz0HrUL9pIMFf13VWi FiNIAiyubjjtkgkVTh9xX93EGw/Hw4lMriTXB2L7BXWSotcW1wG8VZysuniwNFylNs 5a+SwgsMHot+w== From: fdmanana@kernel.org To: linux-btrfs@vger.kernel.org Cc: ian@ianjohnson.dev, Filipe Manana Subject: [PATCH 2/2] btrfs: refresh dir last index during a rewinddir(3) call Date: Sat, 9 Sep 2023 13:08:32 +0100 Message-Id: X-Mailer: git-send-email 2.34.1 In-Reply-To: References: MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-btrfs@vger.kernel.org From: Filipe Manana When opening a directory we find what's the index of its last entry and then store it in the directory's file handle private data (struct btrfs_file_private::last_index), so that in the case new directory entries are added to a directory after an opendir(3) call we don't end up in an infinite loop (see commit 9b378f6ad48c ("btrfs: fix infinite directory reads")) when calling readdir(3). However once rewinddir(3) is called, POSIX states [1] that any new directory entries added after the previous opendir(3) call, must be returned by subsequent calls to readdir(3): "The rewinddir() function shall reset the position of the directory stream to which dirp refers to the beginning of the directory. It shall also cause the directory stream to refer to the current state of the corresponding directory, as a call to opendir() would have done." We currently don't refresh the last_index field of the struct btrfs_file_private associated to the directory, so after a rewinddir(3) we are not returning any new entries added after the opendir(3) call. Fix this by finding the current last index of the directory when llseek is called agains the directory. This can be reproduced by the following C program provided by Ian Johnson: #include #include int main(void) { DIR *dir = opendir("test"); FILE *file; file = fopen("test/1", "w"); fwrite("1", 1, 1, file); fclose(file); file = fopen("test/2", "w"); fwrite("2", 1, 1, file); fclose(file); rewinddir(dir); struct dirent *entry; while ((entry = readdir(dir))) { printf("%s\n", entry->d_name); } closedir(dir); return 0; } Reported-by: Ian Johnson Link: https://lore.kernel.org/linux-btrfs/YR1P0S.NGASEG570GJ8@ianjohnson.dev/ Fixes: 9b378f6ad48c ("btrfs: fix infinite directory reads") Signed-off-by: Filipe Manana Tested-by: Ian Johnson --- fs/btrfs/inode.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index df035211bdf0..006ca4cb4788 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -5820,6 +5820,19 @@ static int btrfs_opendir(struct inode *inode, struct file *file) return 0; } +static loff_t btrfs_dir_llseek(struct file *file, loff_t offset, int whence) +{ + struct btrfs_file_private *private = file->private_data; + int ret; + + ret = btrfs_get_dir_last_index(BTRFS_I(file_inode(file)), + &private->last_index); + if (ret) + return ret; + + return generic_file_llseek(file, offset, whence); +} + struct dir_entry { u64 ino; u64 offset; @@ -10893,7 +10906,7 @@ static const struct inode_operations btrfs_dir_inode_operations = { }; static const struct file_operations btrfs_dir_file_operations = { - .llseek = generic_file_llseek, + .llseek = btrfs_dir_llseek, .read = generic_read_dir, .iterate_shared = btrfs_real_readdir, .open = btrfs_opendir,