| 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 |