GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/util/pipe.h
Date: 2025-07-13 02:35:07
Exec Total Coverage
Lines: 54 57 94.7%
Branches: 16 32 50.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #ifndef CVMFS_UTIL_PIPE_H_
6 #define CVMFS_UTIL_PIPE_H_
7
8 #include <sys/types.h>
9 #include <unistd.h>
10
11 #include <cerrno>
12 #include <cstddef>
13
14 #include "exception.h"
15 #include "gtest/gtest_prod.h"
16 #include "util/export.h"
17 #include "util/logging.h"
18 #include "util/single_copy.h"
19
20 #ifdef CVMFS_NAMESPACE_GUARD
21 namespace CVMFS_NAMESPACE_GUARD {
22 #endif
23
24 /**
25 * Describes the functionality of a pipe used as a template parameter to
26 * the Pipe class. This makes it clear in stack traces which pipe is blocking.
27 */
28 enum PipeType {
29 kPipeThreadTerminator = 0, // pipe only used to signal a thread to stop
30 kPipeWatchdog,
31 kPipeWatchdogSupervisor,
32 kPipeWatchdogPid,
33 kPipeDetachedChild,
34 kPipeTest,
35 kPipeDownloadJobs,
36 kPipeDownloadJobsResults
37 };
38
39 /**
40 * Common signals used by pipes
41 */
42 enum PipeSignals {
43 kPipeTerminateSignal = 1
44 };
45
46 template<PipeType pipeType>
47 class CVMFS_EXPORT Pipe : public SingleCopy {
48 FRIEND_TEST(T_Util, ManagedExecRunShell);
49 FRIEND_TEST(T_Util, ManagedExecExecuteBinaryDoubleFork);
50 FRIEND_TEST(T_Util, ManagedExecExecuteBinaryAsChild);
51
52 public:
53 /**
54 * A pipe is a simple asynchronous communication mechanism. It establishes
55 * a unidirectional communication link between two file descriptors. One of
56 * them is used to only write to the pipe, the other one only to read from it.
57 *
58 * This class is a simple wrapper around the handling of a "standard" pipe
59 * that uses system calls.
60 *
61 * @note PipeType as class template parameter should symbolize the
62 * functionality of the specific type, independent of what variable
63 * name it has
64 */
65 845 Pipe() {
66 int pipe_fd[2];
67
1/2
✓ Branch 1 taken 698 times.
✗ Branch 2 not taken.
845 MakePipe(pipe_fd);
68 845 fd_read_ = pipe_fd[0];
69 845 fd_write_ = pipe_fd[1];
70 845 }
71
72 /**
73 * Destructor closes all valid file descriptors of the pipe
74 */
75 714 ~Pipe() { Close(); }
76
77 /**
78 * Closes all open file descriptors of the pipe and marks them as invalid
79 */
80 975 void Close() {
81 975 CloseReadFd();
82 975 CloseWriteFd();
83 975 }
84
85 /**
86 * Closes file descriptor that reads from the pipe and marks it as invalid.
87 */
88 1607 void CloseReadFd() {
89
2/2
✓ Branch 0 taken 760 times.
✓ Branch 1 taken 648 times.
1607 if (fd_read_ >= 0) {
90 908 close(fd_read_);
91 908 fd_read_ = -1;
92 }
93 1607 }
94
95 /**
96 * Closes file descriptor that writes to the pipe and marks it as invalid.
97 */
98 1705 void CloseWriteFd() {
99
2/2
✓ Branch 0 taken 809 times.
✓ Branch 1 taken 648 times.
1705 if (fd_write_ >= 0) {
100 1006 close(fd_write_);
101 1006 fd_write_ = -1;
102 }
103 1705 }
104
105 /**
106 * Tries to write an object to the pipe
107 *
108 * @returns true if the entire object was written
109 * false otherwise
110 */
111 template<typename T>
112 bool TryWrite(const T &data) {
113 const int num_bytes = write(fd_write_, &data, sizeof(T));
114 return (num_bytes >= 0) && (static_cast<size_t>(num_bytes) == sizeof(T));
115 }
116
117 /**
118 * Writes an object to the pipe
119 *
120 * @returns true on success
121 * otherwise kills the program with an assert
122 *
123 */
124 template<typename T>
125 904 bool Write(const T &data) {
126 904 WritePipe(fd_write_, &data, sizeof(T));
127 904 return true;
128 }
129
130 /**
131 * Writes an object to the pipe
132 * If possible, it is recommended to use "bool Write(const T &data)"
133 *
134 * @returns true on success
135 * otherwise kills the program with an assert
136 *
137 */
138 147 bool Write(const void *buf, size_t nbyte) {
139 147 WritePipe(fd_write_, buf, nbyte);
140 147 return true;
141 }
142
143 /**
144 * (Re)tries to read from the pipe until it receives data or returned error
145 * is NOT a system interrupt
146 *
147 * @returns true if sizeof(data) bytes were received
148 * false otherwise
149 */
150 template<typename T>
151 442 bool TryRead(T *data) {
152 ssize_t num_bytes;
153 do {
154 442 num_bytes = read(fd_read_, data, sizeof(T));
155
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
442 } while ((num_bytes < 0) && (errno == EINTR));
156
3/4
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 173 times.
✓ Branch 3 taken 48 times.
442 return (num_bytes >= 0) && (static_cast<size_t>(num_bytes) == sizeof(T));
157 }
158
159 /**
160 * Reads an object from the pipe
161 *
162 * @returns true on success
163 * otherwise kills the program with an assert
164 */
165 template<typename T>
166 1442 bool Read(T *data) {
167 1442 ReadPipe(fd_read_, data, sizeof(T));
168 1442 return true;
169 }
170
171 /**
172 * Reads an object from the pipe
173 * If possible, it is recommend to use "bool Read(T *data)""
174 *
175 * @returns true on success
176 * otherwise kills the program with an assert
177 */
178 58 bool Read(void *buf, size_t nbyte) {
179 58 ReadPipe(fd_read_, buf, nbyte);
180 58 return true;
181 }
182
183 /**
184 * Returns the file descriptor that reads from the pipe
185 */
186 313 int GetReadFd() const { return fd_read_; }
187
188 /**
189 * Returns the file descriptor that writes to the pipe
190 */
191 324 int GetWriteFd() const { return fd_write_; }
192
193
194 private:
195 int fd_read_;
196 int fd_write_;
197
198 /**
199 * Only used in the unit tests to test pipes using stdin/stdout as read/write.
200 */
201 61 Pipe(const int fd_read, const int fd_write)
202 61 : fd_read_(fd_read), fd_write_(fd_write) { }
203
204 /**
205 * Creating a pipe should always succeed.
206 */
207 845 void MakePipe(int pipe_fd[2]) {
208 845 const int retval = pipe(pipe_fd);
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 698 times.
845 if (retval != 0) {
210 PANIC(kLogSyslogErr | kLogDebug, "MakePipe failed with errno %d", errno);
211 }
212 845 }
213
214
215 /**
216 * Writes to a pipe should always succeed.
217 */
218 649 void WritePipe(int fd, const void *buf, size_t nbyte) {
219 ssize_t num_bytes;
220 do {
221 649 num_bytes = write(fd, buf, nbyte);
222
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 599 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
649 } while ((num_bytes < 0) && (errno == EINTR));
223
2/4
✓ Branch 0 taken 599 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 599 times.
649 if (!((num_bytes >= 0) && (static_cast<size_t>(num_bytes) == nbyte))) {
224 PANIC(kLogSyslogErr | kLogDebug,
225 "WritePipe failed: expected write size %lu, "
226 "actually written %lu, errno %d, fd %d",
227 nbyte, num_bytes, errno, fd);
228 }
229 649 }
230
231
232 /**
233 * Reads from a pipe should always succeed.
234 */
235 828 void ReadPipe(int fd, void *buf, size_t nbyte) {
236 ssize_t num_bytes;
237 do {
238 828 num_bytes = read(fd, buf, nbyte);
239
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 779 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
828 } while ((num_bytes < 0) && (errno == EINTR));
240
2/4
✓ Branch 0 taken 779 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 779 times.
828 if (!((num_bytes >= 0) && (static_cast<size_t>(num_bytes) == nbyte))) {
241 PANIC(kLogSyslogErr | kLogDebug,
242 "ReadPipe failed: expected read size %lu, "
243 "actually read %lu, errno %d, fd %d",
244 nbyte, num_bytes, errno, fd);
245 }
246 828 }
247 };
248
249 #ifdef CVMFS_NAMESPACE_GUARD
250 } // namespace CVMFS_NAMESPACE_GUARD
251 #endif
252
253 #endif // CVMFS_UTIL_PIPE_H_
254