diff options
| author | Theodore Ts'o <tytso@mit.edu> | 2016-04-23 22:50:07 -0400 |
|---|---|---|
| committer | Theodore Ts'o <tytso@mit.edu> | 2016-04-23 22:50:07 -0400 |
| commit | 1f60fbe7274918adb8db2f616e321890730ab7e3 (patch) | |
| tree | 4e41c7a0fd5204cad9e651baa23207bf4dafe256 /fs | |
| parent | c3b46c73264b03000d1e18b22f5caf63332547c9 (diff) | |
ext4: allow readdir()'s of large empty directories to be interrupted
If a directory has a large number of empty blocks, iterating over all
of them can take a long time, leading to scheduler warnings and users
getting irritated when they can't kill a process in the middle of one
of these long-running readdir operations. Fix this by adding checks to
ext4_readdir() and ext4_htree_fill_tree().
This was reverted earlier due to a typo in the original commit where I
experimented with using signal_pending() instead of
fatal_signal_pending(). The test was in the wrong place if we were
going to return signal_pending() since we would end up returning
duplicant entries. See 9f2394c9be47 for a more detailed explanation.
Added fix as suggested by Linus to check for signal_pending() in
in the filldir() functions.
Reported-by: Benjamin LaHaise <bcrl@kvack.org>
Google-Bug-Id: 27880676
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Diffstat (limited to 'fs')
| -rw-r--r-- | fs/compat.c | 4 | ||||
| -rw-r--r-- | fs/ext4/dir.c | 5 | ||||
| -rw-r--r-- | fs/ext4/namei.c | 5 | ||||
| -rw-r--r-- | fs/readdir.c | 4 |
4 files changed, 18 insertions, 0 deletions
diff --git a/fs/compat.c b/fs/compat.c index a71936a3f4cb..f940cb20562f 100644 --- a/fs/compat.c +++ b/fs/compat.c | |||
| @@ -936,6 +936,8 @@ static int compat_filldir(struct dir_context *ctx, const char *name, int namlen, | |||
| 936 | } | 936 | } |
| 937 | dirent = buf->previous; | 937 | dirent = buf->previous; |
| 938 | if (dirent) { | 938 | if (dirent) { |
| 939 | if (signal_pending(current)) | ||
| 940 | return -EINTR; | ||
| 939 | if (__put_user(offset, &dirent->d_off)) | 941 | if (__put_user(offset, &dirent->d_off)) |
| 940 | goto efault; | 942 | goto efault; |
| 941 | } | 943 | } |
| @@ -1020,6 +1022,8 @@ static int compat_filldir64(struct dir_context *ctx, const char *name, | |||
| 1020 | dirent = buf->previous; | 1022 | dirent = buf->previous; |
| 1021 | 1023 | ||
| 1022 | if (dirent) { | 1024 | if (dirent) { |
| 1025 | if (signal_pending(current)) | ||
| 1026 | return -EINTR; | ||
| 1023 | if (__put_user_unaligned(offset, &dirent->d_off)) | 1027 | if (__put_user_unaligned(offset, &dirent->d_off)) |
| 1024 | goto efault; | 1028 | goto efault; |
| 1025 | } | 1029 | } |
diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c index 561d7308b393..4173bfe21114 100644 --- a/fs/ext4/dir.c +++ b/fs/ext4/dir.c | |||
| @@ -150,6 +150,11 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx) | |||
| 150 | while (ctx->pos < inode->i_size) { | 150 | while (ctx->pos < inode->i_size) { |
| 151 | struct ext4_map_blocks map; | 151 | struct ext4_map_blocks map; |
| 152 | 152 | ||
| 153 | if (fatal_signal_pending(current)) { | ||
| 154 | err = -ERESTARTSYS; | ||
| 155 | goto errout; | ||
| 156 | } | ||
| 157 | cond_resched(); | ||
| 153 | map.m_lblk = ctx->pos >> EXT4_BLOCK_SIZE_BITS(sb); | 158 | map.m_lblk = ctx->pos >> EXT4_BLOCK_SIZE_BITS(sb); |
| 154 | map.m_len = 1; | 159 | map.m_len = 1; |
| 155 | err = ext4_map_blocks(NULL, inode, &map, 0); | 160 | err = ext4_map_blocks(NULL, inode, &map, 0); |
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 48e4b8907826..c07422d254b6 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c | |||
| @@ -1107,6 +1107,11 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash, | |||
| 1107 | } | 1107 | } |
| 1108 | 1108 | ||
| 1109 | while (1) { | 1109 | while (1) { |
| 1110 | if (fatal_signal_pending(current)) { | ||
| 1111 | err = -ERESTARTSYS; | ||
| 1112 | goto errout; | ||
| 1113 | } | ||
| 1114 | cond_resched(); | ||
| 1110 | block = dx_get_block(frame->at); | 1115 | block = dx_get_block(frame->at); |
| 1111 | ret = htree_dirblock_to_tree(dir_file, dir, block, &hinfo, | 1116 | ret = htree_dirblock_to_tree(dir_file, dir, block, &hinfo, |
| 1112 | start_hash, start_minor_hash); | 1117 | start_hash, start_minor_hash); |
diff --git a/fs/readdir.c b/fs/readdir.c index e69ef3b79787..5f2d4bee5a73 100644 --- a/fs/readdir.c +++ b/fs/readdir.c | |||
| @@ -169,6 +169,8 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen, | |||
| 169 | } | 169 | } |
| 170 | dirent = buf->previous; | 170 | dirent = buf->previous; |
| 171 | if (dirent) { | 171 | if (dirent) { |
| 172 | if (signal_pending(current)) | ||
| 173 | return -EINTR; | ||
| 172 | if (__put_user(offset, &dirent->d_off)) | 174 | if (__put_user(offset, &dirent->d_off)) |
| 173 | goto efault; | 175 | goto efault; |
| 174 | } | 176 | } |
| @@ -248,6 +250,8 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen, | |||
| 248 | return -EINVAL; | 250 | return -EINVAL; |
| 249 | dirent = buf->previous; | 251 | dirent = buf->previous; |
| 250 | if (dirent) { | 252 | if (dirent) { |
| 253 | if (signal_pending(current)) | ||
| 254 | return -EINTR; | ||
| 251 | if (__put_user(offset, &dirent->d_off)) | 255 | if (__put_user(offset, &dirent->d_off)) |
| 252 | goto efault; | 256 | goto efault; |
| 253 | } | 257 | } |
