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 |