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 | 627 | static size_t rwsize(size_t count) | |
25 | { | ||
26 | 627 | return MIN(count, MAX_RW_COUNT); | |
27 | } | ||
28 | |||
29 | // NOLINTBEGIN(*-unsafe-functions) | ||
30 | |||
31 | 32 | ssize_t xread(int fd, void *buf, size_t count) | |
32 | { | ||
33 | 32 | ssize_t r; | |
34 | 64 | do { | |
35 | 32 | r = read(fd, buf, count); | |
36 |
1/4✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 32 times.
✗ Branch 2 (5→3) not taken.
✗ Branch 3 (5→6) not taken.
|
32 | } while (unlikely(r < 0 && errno == EINTR)); |
37 | 32 | return r; | |
38 | } | ||
39 | |||
40 | 5 | ssize_t xwrite(int fd, const void *buf, size_t count) | |
41 | { | ||
42 | 5 | ssize_t r; | |
43 | 10 | do { | |
44 | 5 | r = write(fd, buf, count); | |
45 |
1/4✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 5 times.
✗ Branch 2 (5→3) not taken.
✗ Branch 3 (5→6) not taken.
|
5 | } while (unlikely(r < 0 && errno == EINTR)); |
46 | 5 | return r; | |
47 | } | ||
48 | |||
49 | 174 | ssize_t xread_all(int fd, void *buf, size_t count) | |
50 | { | ||
51 | 174 | char *b = buf; | |
52 | 174 | size_t pos = 0; | |
53 | 176 | do { | |
54 | 176 | ssize_t rc = read(fd, b + pos, rwsize(count - pos)); | |
55 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→7) taken 176 times.
|
176 | if (unlikely(rc < 0)) { |
56 | ✗ | if (errno == EINTR) { | |
57 | ✗ | continue; | |
58 | } | ||
59 | return -1; | ||
60 | } | ||
61 |
2/2✓ Branch 0 (7→8) taken 163 times.
✓ Branch 1 (7→10) taken 13 times.
|
176 | if (rc == 0) { |
62 | // eof | ||
63 | break; | ||
64 | } | ||
65 | 163 | pos += rc; | |
66 |
2/2✓ Branch 0 (9→3) taken 2 times.
✓ Branch 1 (9→10) taken 161 times.
|
163 | } while (pos < count); |
67 | 174 | return pos; | |
68 | } | ||
69 | |||
70 | 451 | ssize_t xwrite_all(int fd, const void *buf, size_t count) | |
71 | { | ||
72 | 451 | const char *b = buf; | |
73 | 451 | const size_t count_save = count; | |
74 | 451 | do { | |
75 | 451 | ssize_t rc = write(fd, b, rwsize(count)); | |
76 |
2/2✓ Branch 0 (4→5) taken 1 times.
✓ Branch 1 (4→7) taken 450 times.
|
451 | if (unlikely(rc < 0)) { |
77 |
1/2✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→10) taken 1 times.
|
1 | if (errno == EINTR) { |
78 | ✗ | continue; | |
79 | } | ||
80 | return -1; | ||
81 | } | ||
82 | 450 | b += rc; | |
83 | 450 | count -= rc; | |
84 |
1/2✗ Branch 0 (8→3) not taken.
✓ Branch 1 (8→9) taken 450 times.
|
450 | } while (count > 0); |
85 | 450 | return count_save; | |
86 | } | ||
87 | |||
88 | // Like close(3), but with the following differences: | ||
89 | // • Retries the operation, if interrupted by a caught signal (EINTR) | ||
90 | // • Handles EINPROGRESS as if successful (i.e. by returning 0) | ||
91 | // • Always restores errno(3) to the previous value before returning | ||
92 | // • Returns an <errno.h> value, if a meaningful error occurred, or otherwise 0 | ||
93 | 338 | SystemErrno xclose(int fd) | |
94 | { | ||
95 | 338 | const SystemErrno saved_errno = errno; | |
96 | 338 | int r = close(fd); | |
97 |
3/4✓ Branch 0 (3→4) taken 24 times.
✓ Branch 1 (3→5) taken 314 times.
✓ Branch 2 (4→5) taken 24 times.
✗ Branch 3 (4→9) not taken.
|
338 | if (likely(r == 0 || errno != EINTR)) { |
98 | 338 | errno = saved_errno; | |
99 | // Treat EINPROGRESS the same as r == 0 | ||
100 | // (https://git.musl-libc.org/cgit/musl/commit/?id=82dc1e2e783815e00a90cd) | ||
101 |
3/4✓ Branch 0 (5→6) taken 24 times.
✓ Branch 1 (5→8) taken 314 times.
✓ Branch 2 (6→7) taken 24 times.
✗ Branch 3 (6→8) not taken.
|
338 | return (r && errno != EINPROGRESS) ? errno : 0; |
102 | } | ||
103 | |||
104 | // If the first close() call failed with EINTR, retry until it succeeds | ||
105 | // or fails with a different error | ||
106 | ✗ | do { | |
107 | ✗ | r = close(fd); | |
108 | ✗ | } while (r && errno == EINTR); | |
109 | |||
110 | // On some systems, when close() fails with EINTR, the descriptor | ||
111 | // still gets closed anyway and calling close() again then fails | ||
112 | // with EBADF. This is not really an "error" that can be handled | ||
113 | // meaningfully, so we just return as if successful. | ||
114 | // | ||
115 | // Note that this is only safe to do in a single-threaded context, | ||
116 | // where there's no risk of a concurrent open() call reusing the | ||
117 | // same file descriptor. | ||
118 | // | ||
119 | // • http://www.daemonology.net/blog/2011-12-17-POSIX-close-is-broken.html | ||
120 | // • https://ewontfix.com/4/ | ||
121 | // • https://sourceware.org/bugzilla/show_bug.cgi?id=14627 | ||
122 | // • https://austingroupbugs.net/view.php?id=529#c1200 | ||
123 | ✗ | errno = saved_errno; | |
124 | ✗ | return (r && errno != EBADF) ? errno : 0; | |
125 | } | ||
126 | |||
127 | // NOLINTEND(*-unsafe-functions) | ||
128 |