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 |