dte test coverage


Directory: ./
File: src/util/xreadwrite.c
Date: 2025-09-07 23:01:39
Exec Total Coverage
Lines: 41 49 83.7%
Functions: 6 6 100.0%
Branches: 17 38 44.7%

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