Skip to contentSkip to conversation

mm: Make sendfile(2) killable

PATCH In review Jan Kara 3 participants 4 messages Oct 12, 2015 – Oct 16, 2015

Subsystems (3)
lore
Share & tools
lore

Jan Kara

author
#0 lore raw

PATCH 1/1 mm: Make sendfile(2) killable

Currently a simple program below issues a sendfile(2) system call which takes about 62 days to complete in my test KVM instance.

        int fd;
        off_t off = 0;

        fd = open("file", O_RDWR | O_TRUNC | O_SYNC | O_CREAT, 0644);
        ftruncate(fd, 2);
        lseek(fd, 0, SEEK_END);
        sendfile(fd, fd, &off, 0xfffffff);

Now you should not ask kernel to do a stupid stuff like copying 256MB in 2-byte chunks and call fsync(2) after each chunk but if you do, sysadmin should have a way to stop you.

We actually do have a check for fatal_signal_pending() in generic_perform_write() which triggers in this path however because we always succeed in writing something before the check is done, we return value > 0 from generic_perform_write() and thus the information about signal gets lost.

Fix the problem by doing the signal check before writing anything. That way generic_perform_write() returns -EINTR, the error gets propagated up and the sendfile loop terminates early.

---
 mm/filemap.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)
mm/filemap.c +5 -4
index 1cc5467cf36c..327910c2400c 100644
@@ -2488,6 +2488,11 @@ again:
 			break;
 		}
 
+		if (fatal_signal_pending(current)) {
+			status = -EINTR;
+			break;
+		}
+
 		status = a_ops->write_begin(file, mapping, pos, bytes, flags,
 						&page, &fsdata);
 		if (unlikely(status < 0))
@@ -2525,10 +2530,6 @@ again:
 		written += copied;
 
 		balance_dirty_pages_ratelimited(mapping);
-		if (fatal_signal_pending(current)) {
-			status = -EINTR;
-			break;
-		}
 	} while (iov_iter_count(i));
 
 	return written ? written : status;
Signature · 7 lines
1.7.12.4

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
maintainer
#1 lore raw
Quoted 2 lines · On Mon, 12 Oct 2015 14:45:23 +0200 Jan Kara <[email protected]> wrote:

Currently a simple program below issues a sendfile(2) system call which takes about 62 days to complete in my test KVM instance.

Geeze some people are impatient.

Quoted 12 lines
        int fd;
        off_t off = 0;

        fd = open("file", O_RDWR | O_TRUNC | O_SYNC | O_CREAT, 0644);
        ftruncate(fd, 2);
        lseek(fd, 0, SEEK_END);
        sendfile(fd, fd, &off, 0xfffffff);

Now you should not ask kernel to do a stupid stuff like copying 256MB in 2-byte chunks and call fsync(2) after each chunk but if you do, sysadmin should have a way to stop you.

Quoted 5 lines

We actually do have a check for fatal_signal_pending() in generic_perform_write() which triggers in this path however because we always succeed in writing something before the check is done, we return value > 0 from generic_perform_write() and thus the information about signal gets lost.

ah.

Quoted 21 lines

Fix the problem by doing the signal check before writing anything. That way generic_perform_write() returns -EINTR, the error gets propagated up and the sendfile loop terminates early.

...

mm/filemap.c +5 -0
@@ -2488,6 +2488,11 @@ again:
 			break;
 		}
 
+		if (fatal_signal_pending(current)) {
+			status = -EINTR;
+			break;
+		}
+
 		status = a_ops->write_begin(file, mapping, pos, bytes, flags,
 						&page, &fsdata);
 		if (unlikely(status < 0))

mm/filemap.c again: · line 2535

Quoted 10 lines
mm/filemap.c +0 -4
@@ -2525,10 +2530,6 @@ again:
 		written += copied;
 
 		balance_dirty_pages_ratelimited(mapping);
-		if (fatal_signal_pending(current)) {
-			status = -EINTR;
-			break;
-		}
 	} while (iov_iter_count(i));
 
 	return written ? written : status;

This won't work, will it? If user hits ^C after we've written a few pages, `written' is non-zero and the same thing happens?

maintainer
#2 lore raw
Quoted 57 lines · Andrew Morton
Quoted 2 lines · On Mon, 12 Oct 2015 14:45:23 +0200 Jan Kara <[email protected]> wrote:

Currently a simple program below issues a sendfile(2) system call which takes about 62 days to complete in my test KVM instance.

Geeze some people are impatient.

Quoted 12 lines
        int fd;
        off_t off = 0;

        fd = open("file", O_RDWR | O_TRUNC | O_SYNC | O_CREAT, 0644);
        ftruncate(fd, 2);
        lseek(fd, 0, SEEK_END);
        sendfile(fd, fd, &off, 0xfffffff);

Now you should not ask kernel to do a stupid stuff like copying 256MB in 2-byte chunks and call fsync(2) after each chunk but if you do, sysadmin should have a way to stop you.

Quoted 5 lines

We actually do have a check for fatal_signal_pending() in generic_perform_write() which triggers in this path however because we always succeed in writing something before the check is done, we return value > 0 from generic_perform_write() and thus the information about signal gets lost.

ah.

Quoted 30 lines

Fix the problem by doing the signal check before writing anything. That way generic_perform_write() returns -EINTR, the error gets propagated up and the sendfile loop terminates early.

...

mm/filemap.c +5 -4
@@ -2488,6 +2488,11 @@ again:
 			break;
 		}
 
+		if (fatal_signal_pending(current)) {
+			status = -EINTR;
+			break;
+		}
+
 		status = a_ops->write_begin(file, mapping, pos, bytes, flags,
 						&page, &fsdata);
 		if (unlikely(status < 0))
@@ -2525,10 +2530,6 @@ again:
 		written += copied;
 
 		balance_dirty_pages_ratelimited(mapping);
-		if (fatal_signal_pending(current)) {
-			status = -EINTR;
-			break;
-		}
 	} while (iov_iter_count(i));
 
Quoted 4 lines

mm/filemap.c again: · line 2535

Quoted 1 line
mm/filemap.c +0 -0
 	return written ? written : status;

This won't work, will it? If user hits ^C after we've written a few pages, `written' is non-zero and the same thing happens?

It does work - I've tested it :). Sure, the generic_perform_write() call that is running when the signal is delivered will return with value > 0. But the interesting thing is what happens after that: Either we return to userspace (and then we are fine) or generic_perform_write() gets called again because there's more to write and *that* call will return -EINTR which ends up terminating the whole sendfile syscall.

Actually there is one general lesson to be learned here: When you check for fatal signal and bail out, it's better to do it before doing any work. That way things keep working even if the function is called in a loop.

Honza

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
maintainer
#3 lore raw
Quoted 12 lines · On Fri, 16 Oct 2015 08:40:27 +0200 Jan Kara <[email protected]> wrote:
Quoted 11 lines

mm/filemap.c again: · line 2535

Quoted 8 lines
mm/filemap.c +0 -4
 		balance_dirty_pages_ratelimited(mapping);
-		if (fatal_signal_pending(current)) {
-			status = -EINTR;
-			break;
-		}
 	} while (iov_iter_count(i));
 
 	return written ? written : status;

This won't work, will it? If user hits ^C after we've written a few pages, `written' is non-zero and the same thing happens?

Quoted 6 lines

It does work - I've tested it :). Sure, the generic_perform_write() call that is running when the signal is delivered will return with value > 0. But the interesting thing is what happens after that: Either we return to userspace (and then we are fine) or generic_perform_write() gets called again because there's more to write and *that* call will return -EINTR which ends up terminating the whole sendfile syscall.

OK. I guess that's better behaviour than overwriting a non-zero `written' when signalled.

I'm going to tag this one for -stable. It's a bit of a DoS.

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>