GCC Code Coverage Report


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