Previously, filtering more than 2GB through an external filter (see
test) failed on Mac OS X 10.8.4 (12E55) with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL if len >= 2GB.
I haven't found any information under which specific conditions this
occurs. My suspicion is that it happens when reading from a pipe, while
reading from a standard file should always be fine. I haven't tested
any other version of Mac OS X, though I'd expect that other versions are
affected as well.
The problem is fixed by always reading less than 2GB in xread().
xread() doesn't guarantee to read all the requested data at once, and
callers are expected to gracefully handle partial reads. Slicing large
reads into 2GB pieces should not hurt practical performance.
Signed-off-by: Steffen Prohaska <redacted>
---
t/t0021-conversion.sh | 9 +++++++++
wrapper.c | 8 ++++++++
2 files changed, 17 insertions(+)
@@ -139,6 +139,14 @@ ssize_t xread(int fd, void *buf, size_t len){ssize_tnr;while(1){+#ifdef __APPLE__+constsize_ttwoGB=(1l<<31);+/* len >= 2GB immediately fails on Mac OS X with EINVAL when+*readingfrompipe.*/+if(len>=twoGB){+len=twoGB-1;+}+#endifnr=read(fd,buf,len);if((nr<0)&&(errno==EAGAIN||errno==EINTR))continue;
From: John Keeping <hidden> Date: 2016-06-15 22:58:26
On Sat, Aug 17, 2013 at 02:40:05PM +0200, Steffen Prohaska wrote:
quoted hunk
Previously, filtering more than 2GB through an external filter (see
test) failed on Mac OS X 10.8.4 (12E55) with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL if len >= 2GB.
I haven't found any information under which specific conditions this
occurs. My suspicion is that it happens when reading from a pipe, while
reading from a standard file should always be fine. I haven't tested
any other version of Mac OS X, though I'd expect that other versions are
affected as well.
The problem is fixed by always reading less than 2GB in xread().
xread() doesn't guarantee to read all the requested data at once, and
callers are expected to gracefully handle partial reads. Slicing large
reads into 2GB pieces should not hurt practical performance.
Signed-off-by: Steffen Prohaska <redacted>
---
t/t0021-conversion.sh | 9 +++++++++
wrapper.c | 8 ++++++++
2 files changed, 17 insertions(+)
@@ -139,6 +139,14 @@ ssize_t xread(int fd, void *buf, size_t len){ssize_tnr;while(1){+#ifdef __APPLE__+constsize_ttwoGB=(1l<<31);+/* len >= 2GB immediately fails on Mac OS X with EINVAL when+*readingfrompipe.*/+if(len>=twoGB){+len=twoGB-1;+}
Please don't use unnecessary curly braces here (see
Documentation/CodingGuidelines).
Previously, filtering more than 2GB through an external filter (see
test) failed on Mac OS X 10.8.4 (12E55) with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL if len >= 2GB.
I haven't found any information under which specific conditions this
occurs. My suspicion is that it happens when reading from a pipe, while
reading from a standard file should always be fine. I haven't tested
any other version of Mac OS X, though I'd expect that other versions are
affected as well.
The problem is fixed by always reading less than 2GB in xread().
xread() doesn't guarantee to read all the requested data at once, and
callers are expected to gracefully handle partial reads. Slicing large
reads into 2GB pieces should not hurt practical performance.
Signed-off-by: Steffen Prohaska <redacted>
---
t/t0021-conversion.sh | 9 +++++++++
wrapper.c | 8 ++++++++
2 files changed, 17 insertions(+)
@@ -139,6 +139,14 @@ ssize_t xread(int fd, void *buf, size_t len){ssize_tnr;while(1){+#ifdef __APPLE__+constsize_ttwoGB=(1l<<31);+/* len >= 2GB immediately fails on Mac OS X with EINVAL when+*readingfrompipe.*/+if(len>=twoGB){+len=twoGB-1;+}+#endifnr=read(fd,buf,len);if((nr<0)&&(errno==EAGAIN||errno==EINTR))continue;
Thanks for the patch.
I think we can we can replace __APPLE__ define with a more generic one.
We had a similar patch for write() some time ago:
config.mak.uname
NEEDS_CLIPPED_WRITE = YesPlease
Makefile
ifdef NEEDS_CLIPPED_WRITE
BASIC_CFLAGS += -DNEEDS_CLIPPED_WRITE
COMPAT_OBJS += compat/clipped-write.o
endif
From: Johannes Sixt <hidden> Date: 2016-06-15 22:58:26
Am 17.08.2013 14:40, schrieb Steffen Prohaska:
quoted hunk
Previously, filtering more than 2GB through an external filter (see
test) failed on Mac OS X 10.8.4 (12E55) with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL if len >= 2GB.
I haven't found any information under which specific conditions this
occurs. My suspicion is that it happens when reading from a pipe, while
reading from a standard file should always be fine. I haven't tested
any other version of Mac OS X, though I'd expect that other versions are
affected as well.
The problem is fixed by always reading less than 2GB in xread().
xread() doesn't guarantee to read all the requested data at once, and
callers are expected to gracefully handle partial reads. Slicing large
reads into 2GB pieces should not hurt practical performance.
Signed-off-by: Steffen Prohaska <redacted>
---
t/t0021-conversion.sh | 9 +++++++++
wrapper.c | 8 ++++++++
2 files changed, 17 insertions(+)
We don't have /dev/zero on Windows. Even if we get a file slightly over
2GB, we can't handle it on Windows, and other 32bit architectures will
very likely also be handicapped.
Finally, this test (if it remains in some form) should probably be
protected by EXPENSIVE.
+ echo "/2GB filter=largefile" >.gitattributes &&
Drop the slash, please; it may confuse our bash on Windows (it doesn't
currently because echo is a builtin, but better safe than sorry).
+ git add 2GB 2>err &&
+ ! grep -q "error" err
Executive summary: drop everything starting at "2>err".
Long story: Can it happen that (1) git add succeeds, but still produces
something on stderr, and (2) we do not care what this something is as long
as it does not contain "error"? I don't think this combination of
conditions makes sense; it's sufficient to check that git add does not fail.
BTW, if you add
... &&
rm -f 2GB &&
git checkout -- 2GB
you would also test the smudge filter code path with a huge file, no?
BTW2, to create a file with slightly over 2GB, you can use
for i in $(test_seq 0 128); do printf "%16777216d" 1; done >2GB
@@ -139,6 +139,14 @@ ssize_t xread(int fd, void *buf, size_t len){ssize_tnr;while(1){+#ifdef __APPLE__+constsize_ttwoGB=(1l<<31);+/* len >= 2GB immediately fails on Mac OS X with EINVAL when+*readingfrompipe.*/+if(len>=twoGB){+len=twoGB-1;+}+#endifnr=read(fd,buf,len);if((nr<0)&&(errno==EAGAIN||errno==EINTR))continue;
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:58:26
Hi,
Steffen Prohaska wrote:
quoted hunk
--- a/wrapper.c+++ b/wrapper.c
@@ -139,6 +139,14 @@ ssize_t xread(int fd, void *buf, size_t len){ssize_tnr;while(1){+#ifdef __APPLE__+constsize_ttwoGB=(1l<<31);+/* len >= 2GB immediately fails on Mac OS X with EINVAL when+*readingfrompipe.*/+if(len>=twoGB){+len=twoGB-1;+}+#endifnr=read(fd,buf,len);
See 6c642a87 (compat: large write(2) fails on Mac OS X/XNU,
2013-05-10) for a cleaner way to do this.
Hope that helps,
Jonathan
From: Kyle J. McKay <hidden> Date: 2016-06-15 22:58:26
On Aug 17, 2013, at 05:40, Steffen Prohaska wrote:
Previously, filtering more than 2GB through an external filter (see
test) failed on Mac OS X 10.8.4 (12E55) with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason is that read() immediately returns with EINVAL if len >=
2GB.
I haven't found any information under which specific conditions this
occurs.
According to POSIX [1] for read:
If the value of nbyte is greater than {SSIZE_MAX}, the result is
implementation-defined.
The write function also has the same restriction [2].
Since OS X still supports running 32-bit executables, and SSIZE_MAX is
2GB - 1 when running 32-bit it would seem the same limit has been
imposed on 64-bit executables. In any case, we should avoid
"implementation-defined" behavior for portability unless we know the
OS we were compiled on has acceptable "implementation-defined"
behavior and otherwise never attempt to read or write more than
SSIZE_MAX bytes.
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:58:26
Kyle J. McKay wrote:
According to POSIX [1] for read:
If the value of nbyte is greater than {SSIZE_MAX}, the result is
implementation-defined.
Sure.
[...]
Since OS X still supports running 32-bit executables, and SSIZE_MAX is 2GB -
1 when running 32-bit it would seem the same limit has been imposed on
64-bit executables. In any case, we should avoid "implementation-defined"
behavior
Wait --- that's a big leap.
In a 64-bit executable, SSIZE_MAX is 2^63 - 1, so the behavior is not
implementation-defined. I'm not sure if Steffen's copy of git is
32-bit or 64-bit --- my guess would be 64-bit. So at first glance
this does look like an XNU-specific bug, not a standards thing.
What about the related case where someone does try to "git add"
a file with a clean filter producing more than SSIZE_MAX and less
than SIZE_MAX bytes?
strbuf_grow() does not directly protect against a strbuf growing to >
SSIZE_MAX bytes, but in practice on most machines realloc() does. So
in practice we could never read more than SSIZE_MAX characters in the
strbuf_read() codepath, but it might be worth a check for paranoia
anyway.
While we're here, it's easy to wonder: why is git reading into such a
large buffer anyway? Normally git uses the streaming codepath for
files larger than big_file_threshold (typically 512 MiB).
Unfortunately there are cases where it doesn't. For example:
- convert_to_git() has not been taught to stream, so paths
with a clean filter or requiring crlf conversion are read or
mapped into memory.
- deflate_to_pack() relies on seeking backward to retry when
a pack would grow too large, so "git hash-object --stdin"
cannot use that codepath.
- a "clean" filter can make a file bigger.
Perhaps git needs to learn to write to a temporary file
when asked to keep track of a blob that is larger than fits
reasonably in memory. Or maybe not.
So there is room for related work but the codepaths that read()
indefinitely large files do seem to be needed, at least in the short
term. Working around this Mac OS X-specific limitation at the read()
level like you've done still sounds like the right thing to do.
Thanks, both, for your work tracking this down. Hopefully the next
version of the patch will be in good shape and then it can be applied
quickly.
Thanks and hope that helps,
Jonathan
Previously, filtering 2GB or more through an external filter (see test)
failed on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason was that read() immediately returns with EINVAL if nbyte >=
2GB. According to POSIX [1], if the value of nbyte passed to read() is
greater than SSIZE_MAX, the result is implementation-defined. The write
function has the same restriction [2]. Since OS X still supports
running 32-bit executables, the 32-bit limit (SSIZE_MAX = INT_MAX
= 2GB - 1) seems to be also imposed on 64-bit executables under certain
conditions. For write, the problem has been addressed in a earlier
commit [6c642a].
The problem for read() is addressed in a similar way by introducing
a wrapper function in compat that always reads less than 2GB.
Unfortunately, '#undef read' is needed at a few places to avoid
expanding the compat macro in constructs like 'vtbl->read(...)'.
Note that 'git add' exits with 0 even if it prints filtering errors to
stderr. The test, therefore, checks stderr. 'git add' should probably
be changed (sometime in another commit) to exit with nonzero if
filtering fails. The test could then be changed to use test_must_fail.
Thanks to the following people for their suggestions:
Johannes Sixt [off-list ref]
John Keeping [off-list ref]
Jonathan Nieder [off-list ref]
Kyle J. McKay [off-list ref]
Torsten Bögershausen [off-list ref]
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <redacted>
---
Makefile | 8 ++++++++
builtin/var.c | 1 +
config.mak.uname | 1 +
git-compat-util.h | 5 +++++
streaming.c | 1 +
t/t0021-conversion.sh | 14 ++++++++++++++
6 files changed, 30 insertions(+)
@@ -69,6 +69,9 @@ all::# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt# doesn't support GNU extensions like --check and --statistics#+# Define NEEDS_CLIPPED_READ if your read(2) cannot read more than+# INT_MAX bytes at once (e.g. MacOS X).+## Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than# INT_MAX bytes at once (e.g. MacOS X).#
@@ -69,6 +69,9 @@ all::# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt# doesn't support GNU extensions like --check and --statistics#+# Define NEEDS_CLIPPED_READ if your read(2) cannot read more than+# INT_MAX bytes at once (e.g. MacOS X).+## Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than# INT_MAX bytes at once (e.g. MacOS X).#
Previously, filtering 2GB or more through an external filter (see test)
failed on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason was that read() immediately returns with EINVAL if nbyte >=
2GB. According to POSIX [1], if the value of nbyte passed to read() is
greater than SSIZE_MAX, the result is implementation-defined. The write
function has the same restriction [2]. Since OS X still supports
running 32-bit executables, the 32-bit limit (SSIZE_MAX = INT_MAX
= 2GB - 1) seems to be also imposed on 64-bit executables under certain
conditions. For write, the problem has been addressed in a earlier
commit [6c642a].
The problem for read() is addressed in a similar way by introducing
a wrapper function in compat that always reads less than 2GB.
Unfortunately, '#undef read' is needed at a few places to avoid
expanding the compat macro in constructs like 'vtbl->read(...)'.
Note that 'git add' exits with 0 even if it prints filtering errors to
stderr. The test, therefore, checks stderr. 'git add' should probably
be changed (sometime in another commit) to exit with nonzero if
filtering fails. The test could then be changed to use test_must_fail.
Thanks to the following people for their suggestions:
Johannes Sixt [off-list ref]
John Keeping [off-list ref]
Jonathan Nieder [off-list ref]
Kyle J. McKay [off-list ref]
Torsten Bögershausen [off-list ref]
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <redacted>
---
Makefile | 8 ++++++++
builtin/var.c | 1 +
compat/clipped-read.c | 13 +++++++++++++
config.mak.uname | 1 +
git-compat-util.h | 5 +++++
streaming.c | 1 +
t/t0021-conversion.sh | 14 ++++++++++++++
7 files changed, 43 insertions(+)
create mode 100644 compat/clipped-read.c
@@ -69,6 +69,9 @@ all::# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt# doesn't support GNU extensions like --check and --statistics#+# Define NEEDS_CLIPPED_READ if your read(2) cannot read more than+# INT_MAX bytes at once (e.g. MacOS X).+## Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than# INT_MAX bytes at once (e.g. MacOS X).#
From: Stefan Beller <hidden> Date: 2016-06-15 22:58:27
On 08/19/2013 10:20 AM, Johannes Sixt wrote:
Am 19.08.2013 08:38, schrieb Steffen Prohaska:
quoted
+test_expect_success EXPENSIVE 'filter large file' '
+ git config filter.largefile.smudge cat &&
+ git config filter.largefile.clean cat &&
+ for i in $(test_seq 1 2048); do printf "%1048576d" 1; done >2GB &&
Shouldn't you count to 2049 to get a file that is over 2GB?
Would it be possible to offload the looping from shell to a real
program? So for example
truncate -s 2049M <filename>
should do the job. That would create a file reading all bytes as zeros
being larger as 2G. If truncate is not available, what about dd?
Stefan
From: Johannes Sixt <hidden> Date: 2016-06-15 22:58:27
Am 19.08.2013 08:38, schrieb Steffen Prohaska:
Note that 'git add' exits with 0 even if it prints filtering errors to
stderr. The test, therefore, checks stderr. 'git add' should probably
be changed (sometime in another commit) to exit with nonzero if
filtering fails. The test could then be changed to use test_must_fail.
Thanks for this hint. I was not aware of this behavior.
Of course, we do *not* want to use test_must_fail because git add
generally must not fail for files with more than 2GB. (Architectures with
a 32bit size_t are a different matter, of course.)
-- Hannes
From: Johannes Sixt <hidden> Date: 2016-06-15 22:58:27
Am 19.08.2013 10:25, schrieb Stefan Beller:
On 08/19/2013 10:20 AM, Johannes Sixt wrote:
quoted
Am 19.08.2013 08:38, schrieb Steffen Prohaska:
quoted
+test_expect_success EXPENSIVE 'filter large file' '
+ git config filter.largefile.smudge cat &&
+ git config filter.largefile.clean cat &&
+ for i in $(test_seq 1 2048); do printf "%1048576d" 1; done >2GB &&
Shouldn't you count to 2049 to get a file that is over 2GB?
Would it be possible to offload the looping from shell to a real
program? So for example
truncate -s 2049M <filename>
should do the job. That would create a file reading all bytes as zeros
being larger as 2G. If truncate is not available, what about dd?
The point is exactly to avoid external dependencies. Our dd on Windows
doesn't do the right thing with seek=2GB (it makes the file twice as large
as expected).
-- Hannes
@@ -69,6 +69,9 @@ all::# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt# doesn't support GNU extensions like --check and --statistics#+# Define NEEDS_CLIPPED_READ if your read(2) cannot read more than+# INT_MAX bytes at once (e.g. MacOS X).+## Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than# INT_MAX bytes at once (e.g. MacOS X).
Is it likely that we would see a platform requiring only one or the
other CLIPPED? Would it make sense to combine these into a single
NEEDS_CLIPPED_IO?
This is techically right for this very version of the code,
but not really future proof, if someone uses read() further down in the code
(in a later version)
I think the problem comes from further up:
------------------
struct git_var {
const char *name;
const char *(*read)(int);
};
-----------------
could the read be replaced by readfn ?
===================
@@ -99,6 +99,7 @@ int close_istream(struct git_istream *st)returnr;}+#undef read
Same possible future problem as above.
When later someone uses read, the original (buggy) read() will be
used, and not the re-defined clipped_read() from git-compat-util.h
Previously, filtering 2GB or more through an external filter (see test)
failed on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason was that read() immediately returns with EINVAL if nbyte >=
2GB. According to POSIX [1], if the value of nbyte passed to read() is
greater than SSIZE_MAX, the result is implementation-defined. The write
function has the same restriction [2]. Since OS X still supports
running 32-bit executables, the 32-bit limit (SSIZE_MAX = INT_MAX
= 2GB - 1) seems to be also imposed on 64-bit executables under certain
conditions. For write, the problem has been addressed in a earlier
commit [6c642a].
The problem for read() is addressed in a similar way by introducing
a wrapper function in compat that always reads less than 2GB. It is
very likely that the read() and write() wrappers are always used
together. To avoid introducing another option, NEEDS_CLIPPED_WRITE is
changed to NEEDS_CLIPPED_IO and used to activate both wrappers.
To avoid expanding the read compat macro in constructs like
'vtbl->read(...)', 'read' is renamed to 'readfn' in two cases. The
solution seems more robust than using '#undef read'.
Note that 'git add' exits with 0 even if it prints filtering errors to
stderr. The test, therefore, checks stderr. 'git add' should probably
be changed (sometime in another commit) to exit with nonzero if
filtering fails. The test could then be changed to use test_must_fail.
Thanks to the following people for their suggestions:
Johannes Sixt [off-list ref]
John Keeping [off-list ref]
Jonathan Nieder [off-list ref]
Kyle J. McKay [off-list ref]
Torsten Bögershausen [off-list ref]
Eric Sunshine [off-list ref]
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <redacted>
---
Makefile | 10 +++++-----
builtin/var.c | 10 +++++-----
compat/{clipped-write.c => clipped-io.c} | 11 ++++++++++-
config.mak.uname | 2 +-
git-compat-util.h | 5 ++++-
streaming.c | 4 ++--
t/t0021-conversion.sh | 14 ++++++++++++++
7 files changed, 41 insertions(+), 15 deletions(-)
rename compat/{clipped-write.c => clipped-io.c} (53%)
@@ -69,8 +69,8 @@ all::# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt# doesn't support GNU extensions like --check and --statistics#-# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than-# INT_MAX bytes at once (e.g. MacOS X).+# Define NEEDS_CLIPPED_IO if your read(2) and/or write(2) cannot handle more+# than INT_MAX bytes at once (e.g. Mac OS X).## Define HAVE_PATHS_H if you have paths.h and want to use the default PATH# it specifies.
diff --git a/compat/clipped-write.c b/compat/clipped-io.csimilarity index 53%rename from compat/clipped-write.crename to compat/clipped-io.cindex b8f98ff..ec3232a 100644--- a/compat/clipped-write.c+++ b/compat/clipped-io.c
On Mon, Aug 19, 2013 at 8:41 AM, Steffen Prohaska [off-list ref] wrote:
The reason was that read() immediately returns with EINVAL if nbyte >=
2GB. According to POSIX [1], if the value of nbyte passed to read() is
greater than SSIZE_MAX, the result is implementation-defined.
Yeah, the OS X filesystem layer is an incredible piece of shit. Not
only doesn't it follow POSIX, it fails *badly*. Because OS X kernel
engineers apparently have the mental capacity of a retarded rodent on
crack.
Linux also refuses to actually read more than a maximum value in one
go (because quite frankly, doing more than 2GB at a time is just not
reasonable, especially in unkillable disk wait), but at least Linux
gives you the partial read, so that the usual "read until you're
happy" works (which you have to do anyway with sockets, pipes, NFS
intr mounts, etc etc). Returning EINVAL is a sign of a diseased mind.
I hate your patch for other reasons, though:
The problem for read() is addressed in a similar way by introducing
a wrapper function in compat that always reads less than 2GB.
Why do you do that? We already _have_ wrapper functions for read(),
namely xread(). Exactly because you basically have to, in order to
handle signals on interruptible filesystems (which aren't POSIX
either, but at least sanely so) or from other random sources. And to
handle the "you can't do reads that big" issue.
So why isn't the patch much more straightforward? Like the attached
totally untested one that just limits the read/write size to 8MB
(which is totally arbitrary, but small enough to not have any latency
issues even on slow disks, and big enough that any reasonable IO
subsystem will still get good throughput).
And by "totally untested" I mean that it actually passes the git test
suite, but since I didn't apply your patch nor do I have OS X
anywhere, I can't actually test that it fixes *your* problem. But it
should.
Linus
On Aug 19, 2013, at 6:04 PM, Linus Torvalds [off-list ref] wrote:
I hate your patch for other reasons, though:
quoted
The problem for read() is addressed in a similar way by introducing
a wrapper function in compat that always reads less than 2GB.
Why do you do that? We already _have_ wrapper functions for read(),
namely xread(). Exactly because you basically have to, in order to
handle signals on interruptible filesystems (which aren't POSIX
either, but at least sanely so) or from other random sources. And to
handle the "you can't do reads that big" issue.
So why isn't the patch much more straightforward?
The first version was more straightforward [1]. But reviewers suggested
that the compat wrappers would be the right way to do it and showed me
that it has been done like this before [2].
I haven't submitted anything in a while, so I tried to be a kind person
and followed the suggestions. I started to hate the patch a bit (maybe less
than you), but I wasn't brave enough to reject the suggestions. This is
why the patch became what it is.
I'm happy to rework it again towards your suggestion. I would also remove
the compat wrapper for write(). But I got a bit tired. I'd appreciate if
I received more indication whether a version without compat wrappers would
be accepted.
Steffen
[1] http://article.gmane.org/gmane.comp.version-control.git/232455
[2] 6c642a8 compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Previously, filtering 2GB or more through an external filter (see test)
failed on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason was that read() immediately returns with EINVAL if nbyte >=
2GB. According to POSIX [1], if the value of nbyte passed to read() is
greater than SSIZE_MAX, the result is implementation-defined. The write
function has the same restriction [2]. Since OS X still supports
running 32-bit executables, the 32-bit limit (SSIZE_MAX = INT_MAX
= 2GB - 1) seems to be also imposed on 64-bit executables under certain
conditions. For write, the problem has been addressed earlier [6c642a].
This commit addresses the problem for read() and write() by limiting
size of IO chunks unconditionally on all platforms in xread() and
xwrite(). Large chunks only cause problems, like triggering the OS
X bug or causing latencies when killing the process. Reasonably sized
smaller chunks have no negative impact on performance.
The compat wrapper clipped_write() introduced earlier [6c642a] is not
needed anymore. It will be reverted in a separate commit. The new test
catches read and write problems.
Note that 'git add' exits with 0 even if it prints filtering errors to
stderr. The test, therefore, checks stderr. 'git add' should probably
be changed (sometime in another commit) to exit with nonzero if
filtering fails. The test could then be changed to use test_must_fail.
Thanks to the following people for suggestions and testing:
Johannes Sixt [off-list ref]
John Keeping [off-list ref]
Jonathan Nieder [off-list ref]
Kyle J. McKay [off-list ref]
Linus Torvalds [off-list ref]
Torsten Bögershausen [off-list ref]
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] commit 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <redacted>
---
t/t0021-conversion.sh | 14 ++++++++++++++
wrapper.c | 12 ++++++++++++
2 files changed, 26 insertions(+)
@@ -69,9 +69,6 @@ all::# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt# doesn't support GNU extensions like --check and --statistics#-# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than-# INT_MAX bytes at once (e.g. MacOS X).-## Define HAVE_PATHS_H if you have paths.h and want to use the default PATH# it specifies.#
@@ -1,13 +0,0 @@-#include "../git-compat-util.h"-#undef write--/*- * Version of write that will write at most INT_MAX bytes.- * Workaround a xnu bug on Mac OS X- */-ssize_t clipped_write(int fildes, const void *buf, size_t nbyte)-{- if (nbyte > INT_MAX)- nbyte = INT_MAX;- return write(fildes, buf, nbyte);-}
This is the revised patch taking the considerations about IO chunk size into
account. The series deletes more than it adds and fixes a bug. Nice.
Steffen Prohaska (2):
xread, xwrite: Limit size of IO, fixing IO of 2GB and more on Mac OS X
Revert "compate/clipped-write.c: large write(2) fails on Mac OS X/XNU"
Makefile | 8 --------
compat/clipped-write.c | 13 -------------
config.mak.uname | 1 -
git-compat-util.h | 5 -----
t/t0021-conversion.sh | 14 ++++++++++++++
wrapper.c | 12 ++++++++++++
6 files changed, 26 insertions(+), 27 deletions(-)
delete mode 100644 compat/clipped-write.c
--
1.8.4.rc3.5.g4f480ff
Previously, filtering 2GB or more through an external filter (see test)
failed on Mac OS X 10.8.4 (12E55) for a 64-bit executable with:
error: read from external filter cat failed
error: cannot feed the input to external filter cat
error: cat died of signal 13
error: external filter cat failed 141
error: external filter cat failed
The reason was that read() immediately returns with EINVAL if nbyte >=
2GB. According to POSIX [1], if the value of nbyte passed to read() is
greater than SSIZE_MAX, the result is implementation-defined. The write
function has the same restriction [2]. Since OS X still supports
running 32-bit executables, the 32-bit limit (SSIZE_MAX = INT_MAX
= 2GB - 1) seems to be also imposed on 64-bit executables under certain
conditions. For write, the problem has been addressed earlier [6c642a].
This commit addresses the problem for read() and write() by limiting
size of IO chunks unconditionally on all platforms in xread() and
xwrite(). Large chunks only cause problems, like triggering the OS
X bug or causing latencies when killing the process. Reasonably sized
smaller chunks have no negative impact on performance.
The compat wrapper clipped_write() introduced earlier [6c642a] is not
needed anymore. It will be reverted in a separate commit. The new test
catches read and write problems.
Note that 'git add' exits with 0 even if it prints filtering errors to
stderr. The test, therefore, checks stderr. 'git add' should probably
be changed (sometime in another commit) to exit with nonzero if
filtering fails. The test could then be changed to use test_must_fail.
Thanks to the following people for suggestions and testing:
Johannes Sixt [off-list ref]
John Keeping [off-list ref]
Jonathan Nieder [off-list ref]
Kyle J. McKay [off-list ref]
Linus Torvalds [off-list ref]
Torsten Bögershausen [off-list ref]
[1] http://pubs.opengroup.org/onlinepubs/009695399/functions/read.html
[2] http://pubs.opengroup.org/onlinepubs/009695399/functions/write.html
[6c642a] commit 6c642a878688adf46b226903858b53e2d31ac5c3
compate/clipped-write.c: large write(2) fails on Mac OS X/XNU
Signed-off-by: Steffen Prohaska <redacted>
---
t/t0021-conversion.sh | 14 ++++++++++++++
wrapper.c | 12 ++++++++++++
2 files changed, 26 insertions(+)
@@ -69,9 +69,6 @@ all::# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt# doesn't support GNU extensions like --check and --statistics#-# Define NEEDS_CLIPPED_WRITE if your write(2) cannot write more than-# INT_MAX bytes at once (e.g. MacOS X).-## Define HAVE_PATHS_H if you have paths.h and want to use the default PATH# it specifies.#
@@ -1,13 +0,0 @@-#include "../git-compat-util.h"-#undef write--/*- * Version of write that will write at most INT_MAX bytes.- * Workaround a xnu bug on Mac OS X- */-ssize_t clipped_write(int fildes, const void *buf, size_t nbyte)-{- if (nbyte > INT_MAX)- nbyte = INT_MAX;- return write(fildes, buf, nbyte);-}
On 2013-08-20 08.43, Steffen Prohaska wrote:
[]
Thanks for V5. It was tested OK on my system here.
(And apologies for recommending a wrapper on top of a wrapper).
One question is left:
As xread() is tolerant against EAGAIN and especially EINTR,
could it make sense to replace read() with xread() everywhere?
(The risk for getting EINTR is smaller when we only read a small amount
of data, but it is more on the safe side)
And s/write/xwrite/
/Torsten