Directory: | cvmfs/ |
---|---|
File: | cvmfs/util/pipe.h |
Date: | 2025-02-02 02:34:22 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 58 | 61 | 95.1% |
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 | 40 | Pipe() { | |
64 | int pipe_fd[2]; | ||
65 |
1/2✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
|
40 | MakePipe(pipe_fd); |
66 | 40 | fd_read_ = pipe_fd[0]; | |
67 | 40 | fd_write_ = pipe_fd[1]; | |
68 | 40 | } | |
69 | |||
70 | /** | ||
71 | * Destructor closes all valid file descriptors of the pipe | ||
72 | */ | ||
73 | 43 | ~Pipe() { | |
74 | 43 | Close(); | |
75 | 43 | } | |
76 | |||
77 | /** | ||
78 | * Closes all open file descriptors of the pipe and marks them as invalid | ||
79 | */ | ||
80 | 50 | void Close() { | |
81 | 50 | CloseReadFd(); | |
82 | 50 | CloseWriteFd(); | |
83 | 50 | } | |
84 | |||
85 | /** | ||
86 | * Closes file descriptor that reads from the pipe and marks it as invalid. | ||
87 | */ | ||
88 | 181 | void CloseReadFd() { | |
89 |
2/2✓ Branch 0 taken 88 times.
✓ Branch 1 taken 39 times.
|
181 | if (fd_read_ >= 0) { |
90 | 139 | close(fd_read_); | |
91 | 139 | fd_read_ = -1; | |
92 | } | ||
93 | 181 | } | |
94 | |||
95 | /** | ||
96 | * Closes file descriptor that writes to the pipe and marks it as invalid. | ||
97 | */ | ||
98 | 277 | void CloseWriteFd() { | |
99 |
2/2✓ Branch 0 taken 136 times.
✓ Branch 1 taken 39 times.
|
277 | if (fd_write_ >= 0) { |
100 | 235 | close(fd_write_); | |
101 | 235 | fd_write_ = -1; | |
102 | } | ||
103 | 277 | } | |
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 | 230 | bool Write(const T &data) { | |
126 | 230 | WritePipe(fd_write_, &data, sizeof(T)); | |
127 | 230 | 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 | 5 | bool Write(const void *buf, size_t nbyte) { | |
139 | 5 | WritePipe(fd_write_, buf, nbyte); | |
140 | 5 | 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 | 104 | bool TryRead(T *data) { | |
152 | ssize_t num_bytes; | ||
153 | do { | ||
154 | 104 | num_bytes = read(fd_read_, data, sizeof(T)); | |
155 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
104 | } while ((num_bytes < 0) && (errno == EINTR)); |
156 |
3/4✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 47 times.
|
104 | 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 | 118 | bool Read(T *data) { | |
167 | 118 | ReadPipe(fd_read_, data, sizeof(T)); | |
168 | 118 | 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 | 2 | bool Read(void *buf, size_t nbyte) { | |
179 | 2 | ReadPipe(fd_read_, buf, nbyte); | |
180 | 2 | return true; | |
181 | } | ||
182 | |||
183 | /** | ||
184 | * Returns the file descriptor that reads from the pipe | ||
185 | */ | ||
186 | 101 | int GetReadFd() const { | |
187 | 101 | return fd_read_; | |
188 | } | ||
189 | |||
190 | /** | ||
191 | * Returns the file descriptor that writes to the pipe | ||
192 | */ | ||
193 | 113 | int GetWriteFd() const { | |
194 | 113 | return fd_write_; | |
195 | } | ||
196 | |||
197 | |||
198 | private: | ||
199 | int fd_read_; | ||
200 | int fd_write_; | ||
201 | |||
202 | /** | ||
203 | * Only used in the unit tests to test pipes using stdin/stdout as read/write. | ||
204 | */ | ||
205 | 6 | Pipe(const int fd_read, const int fd_write) : fd_read_(fd_read), | |
206 | 3 | fd_write_(fd_write) {} | |
207 | |||
208 | /** | ||
209 | * Creating a pipe should always succeed. | ||
210 | */ | ||
211 | 40 | void MakePipe(int pipe_fd[2]) { | |
212 | 40 | int retval = pipe(pipe_fd); | |
213 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
|
40 | if (retval != 0) { |
214 | ✗ | PANIC(kLogSyslogErr | kLogDebug, "MakePipe failed with errno %d", errno); | |
215 | } | ||
216 | 40 | } | |
217 | |||
218 | |||
219 | /** | ||
220 | * Writes to a pipe should always succeed. | ||
221 | */ | ||
222 | 169 | void WritePipe(int fd, const void *buf, size_t nbyte) { | |
223 | ssize_t num_bytes; | ||
224 | do { | ||
225 | 169 | num_bytes = write(fd, buf, nbyte); | |
226 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 120 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
169 | } while ((num_bytes < 0) && (errno == EINTR)); |
227 |
2/4✓ Branch 0 taken 120 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
|
169 | if (!((num_bytes >= 0) && (static_cast<size_t>(num_bytes) == nbyte))) { |
228 | ✗ | PANIC(kLogSyslogErr | kLogDebug, | |
229 | "WritePipe failed: expected write size %lu, " | ||
230 | "actually written %lu, errno %d, fd %d", | ||
231 | nbyte, num_bytes, errno, fd); | ||
232 | } | ||
233 | 169 | } | |
234 | |||
235 | |||
236 | /** | ||
237 | * Reads from a pipe should always succeed. | ||
238 | */ | ||
239 | 62 | void ReadPipe(int fd, void *buf, size_t nbyte) { | |
240 | ssize_t num_bytes; | ||
241 | do { | ||
242 | 62 | num_bytes = read(fd, buf, nbyte); | |
243 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 61 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
62 | } while ((num_bytes < 0) && (errno == EINTR)); |
244 |
2/4✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 61 times.
|
62 | if (!((num_bytes >= 0) && (static_cast<size_t>(num_bytes) == nbyte))) { |
245 | ✗ | PANIC(kLogSyslogErr | kLogDebug, | |
246 | "ReadPipe failed: expected read size %lu, " | ||
247 | "actually read %lu, errno %d, fd %d", | ||
248 | nbyte, num_bytes, errno, fd); | ||
249 | } | ||
250 | 62 | } | |
251 | }; | ||
252 | |||
253 | #ifdef CVMFS_NAMESPACE_GUARD | ||
254 | } // namespace CVMFS_NAMESPACE_GUARD | ||
255 | #endif | ||
256 | |||
257 | #endif // CVMFS_UTIL_PIPE_H_ | ||
258 |