Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <limits.h> | ||
2 | #include <unistd.h> | ||
3 | #include "xreadwrite.h" | ||
4 | |||
5 | #if defined(__APPLE__) | ||
6 | // See: https://gitlab.com/craigbarnes/dte/-/issues/201 | ||
7 | #define MAX_RW_COUNT ((size_t)INT_MAX) | ||
8 | #elif defined(__linux__) && 0x7ffff000 <= SSIZE_MAX | ||
9 | // Unlike macOS, trying to read or write more than 0x7ffff000 bytes | ||
10 | // on Linux simply causes the syscall to return (at most) the upper | ||
11 | // limit, rather than indicating an error condition. Thus, using | ||
12 | // this value here is done simply because it's a more appropriate | ||
13 | // value than SSIZE_MAX, not because it's actually needed for | ||
14 | // correct functioning of xread_all(). | ||
15 | // See also: | ||
16 | // • https://man7.org/linux/man-pages/man2/read.2.html#NOTES | ||
17 | // • https://man7.org/linux/man-pages/man2/write.2.html#NOTES | ||
18 | // • https://stackoverflow.com/a/70370002 | ||
19 | #define MAX_RW_COUNT ((size_t)0x7ffff000) | ||
20 | #else | ||
21 | #define MAX_RW_COUNT ((size_t)SSIZE_MAX) | ||
22 | #endif | ||
23 | |||
24 | 26 | ssize_t xread(int fd, void *buf, size_t count) | |
25 | { | ||
26 | 26 | ssize_t r; | |
27 | 52 | do { | |
28 | 26 | r = read(fd, buf, count); | |
29 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
26 | } while (unlikely(r < 0 && errno == EINTR)); |
30 | 26 | return r; | |
31 | } | ||
32 | |||
33 | 4 | ssize_t xwrite(int fd, const void *buf, size_t count) | |
34 | { | ||
35 | 4 | ssize_t r; | |
36 | 8 | do { | |
37 | 4 | r = write(fd, buf, count); | |
38 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | } while (unlikely(r < 0 && errno == EINTR)); |
39 | 4 | return r; | |
40 | } | ||
41 | |||
42 | 114 | static size_t rwsize(size_t count) | |
43 | { | ||
44 | 114 | return MIN(count, MAX_RW_COUNT); | |
45 | } | ||
46 | |||
47 | 80 | ssize_t xread_all(int fd, void *buf, size_t count) | |
48 | { | ||
49 | 80 | char *b = buf; | |
50 | 80 | size_t pos = 0; | |
51 | 82 | do { | |
52 | 82 | ssize_t rc = read(fd, b + pos, rwsize(count - pos)); | |
53 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 82 times.
|
82 | if (unlikely(rc < 0)) { |
54 | ✗ | if (errno == EINTR) { | |
55 | ✗ | continue; | |
56 | } | ||
57 | return -1; | ||
58 | } | ||
59 |
2/2✓ Branch 0 taken 73 times.
✓ Branch 1 taken 9 times.
|
82 | if (rc == 0) { |
60 | // eof | ||
61 | break; | ||
62 | } | ||
63 | 73 | pos += rc; | |
64 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 71 times.
|
73 | } while (pos < count); |
65 | 80 | return pos; | |
66 | } | ||
67 | |||
68 | 32 | ssize_t xwrite_all(int fd, const void *buf, size_t count) | |
69 | { | ||
70 | 32 | const char *b = buf; | |
71 | 32 | const size_t count_save = count; | |
72 | 32 | do { | |
73 | 32 | ssize_t rc = write(fd, b, rwsize(count)); | |
74 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 31 times.
|
32 | if (unlikely(rc < 0)) { |
75 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (errno == EINTR) { |
76 | ✗ | continue; | |
77 | } | ||
78 | return -1; | ||
79 | } | ||
80 | 31 | b += rc; | |
81 | 31 | count -= rc; | |
82 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
|
31 | } while (count > 0); |
83 | 31 | return count_save; | |
84 | } | ||
85 | |||
86 | 209 | int xclose(int fd) | |
87 | { | ||
88 | 209 | int saved_errno = errno; | |
89 | 209 | int r = close(fd); | |
90 |
3/4✓ Branch 0 taken 194 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
|
209 | if (likely(r == 0 || (errno != EINTR && errno != EINPROGRESS))) { |
91 | 15 | goto out; | |
92 | } | ||
93 | |||
94 | // Treat EINPROGRESS the same as r == 0 | ||
95 | // (https://git.musl-libc.org/cgit/musl/commit/?id=82dc1e2e783815e00a90cd) | ||
96 | ✗ | if (errno == EINPROGRESS) { | |
97 | ✗ | r = 0; | |
98 | ✗ | goto out; | |
99 | } | ||
100 | |||
101 | // If the first close() call failed with EINTR, retry until it succeeds or | ||
102 | // fails with a different error | ||
103 | ✗ | do { | |
104 | ✗ | r = close(fd); | |
105 | ✗ | } while (r && errno == EINTR); | |
106 | |||
107 | // On some systems, when close() fails with EINTR, the descriptor | ||
108 | // still gets closed anyway and calling close() again then fails | ||
109 | // with EBADF. This is not really an "error" that can be handled | ||
110 | // meaningfully, so we just return as if successful. | ||
111 | // | ||
112 | // Note that this is only safe to do in a single-threaded context, | ||
113 | // where there's no risk of a concurrent open() call reusing the | ||
114 | // same file descriptor. | ||
115 | // | ||
116 | // • http://www.daemonology.net/blog/2011-12-17-POSIX-close-is-broken.html | ||
117 | // • https://ewontfix.com/4/ | ||
118 | // • https://sourceware.org/bugzilla/show_bug.cgi?id=14627 | ||
119 | // • https://www.austingroupbugs.net/view.php?id=529#c1200 | ||
120 | ✗ | if (r && errno == EBADF) { | |
121 | r = 0; | ||
122 | } | ||
123 | |||
124 | ✗ | out: | |
125 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
209 | if (r == 0) { |
126 | 194 | errno = saved_errno; | |
127 | } | ||
128 | 209 | return r; | |
129 | } | ||
130 |