GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/nfs4/callback.cc
Date: 2026-10-18 02:40:11
Exec Total Coverage
Lines: 142 173 82.1%
Branches: 109 232 47.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #include "nfs4/callback.h"
6
7 #include <arpa/inet.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <netinet/in.h>
11 #include <netinet/tcp.h>
12 #include <poll.h>
13 #include <sys/socket.h>
14 #include <unistd.h>
15
16 #include <cstdlib>
17 #include <cstring>
18 #include <ctime>
19 #include <vector>
20
21 #include "nfs4/logging.h"
22 #include "nfs4/nfs4_const.h"
23 #include "nfs4/xdr.h"
24
25 namespace nfs4 {
26
27 namespace {
28
29 const uint32_t kCallbackVersion = 1;
30 const uint32_t kCbProcNull = 0;
31 const uint32_t kCbProcCompound = 1;
32 const uint32_t kOpCbRecall = 4;
33 const uint32_t kLastFragment = 0x80000000u;
34 const uint32_t kMaxReply = 64 * 1024;
35
36 /** Waits until fd is readable or writable, at most timeout_ms */
37 65 bool WaitFd(int fd, short events, unsigned timeout_ms) {
38 struct pollfd pfd;
39 65 pfd.fd = fd;
40 65 pfd.events = events;
41 65 pfd.revents = 0;
42 int retval;
43 do {
44
1/2
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
65 retval = poll(&pfd, 1, static_cast<int>(timeout_ms));
45
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
65 } while (retval < 0 && errno == EINTR);
46
2/4
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 65 times.
✗ Branch 3 not taken.
65 return retval > 0 && !(pfd.revents & (POLLERR | POLLNVAL));
47 }
48
49 26 bool SendAll(int fd, const char *p, size_t len) {
50 26 int flags = 0;
51 #ifdef MSG_NOSIGNAL
52 26 flags = MSG_NOSIGNAL;
53 #endif
54
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 26 times.
52 while (len > 0) {
55 26 const ssize_t n = send(fd, p, len, flags);
56
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (n < 0) {
57 ✗ if (errno == EINTR)
58 ✗ continue;
59 ✗ return false;
60 }
61 26 p += n;
62 26 len -= n;
63 }
64 26 return true;
65 }
66
67 52 bool RecvAll(int fd, char *p, size_t len, unsigned timeout_ms) {
68
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 52 times.
104 while (len > 0) {
69
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
52 if (!WaitFd(fd, POLLIN, timeout_ms))
70 ✗ return false;
71 52 const ssize_t n = recv(fd, p, len, 0);
72
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
52 if (n == 0)
73 ✗ return false;
74
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
52 if (n < 0) {
75 ✗ if (errno == EINTR || errno == EAGAIN)
76 ✗ continue;
77 ✗ return false;
78 }
79 52 p += n;
80 52 len -= n;
81 }
82 52 return true;
83 }
84
85 } // anonymous namespace
86
87
88 13 CallbackClient::CallbackClient(uint32_t program, uint32_t ident,
89 const std::string &netid,
90 13 const std::string &address)
91 13 : program_(program)
92 13 , ident_(ident)
93 13 , port_(0)
94 13 , fd_(-1)
95 13 , next_xid_(static_cast<uint32_t>(time(NULL)) << 8) {
96
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
13 if (netid != "tcp")
97 ✗ return;
98 // Universal address: a.b.c.d.p1.p2
99 13 const size_t last = address.rfind('.');
100
2/4
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
13 if (last == std::string::npos || last == 0)
101 ✗ return;
102 13 const size_t second = address.rfind('.', last - 1);
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (second == std::string::npos)
104 ✗ return;
105
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 const int hi = atoi(address.substr(second + 1, last - second - 1).c_str());
106
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 const int lo = atoi(address.substr(last + 1).c_str());
107
4/8
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 13 times.
13 if (hi < 0 || hi > 255 || lo < 0 || lo > 255)
108 ✗ return;
109
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 host_ = address.substr(0, second);
110 struct in_addr probe;
111
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 13 times.
13 if (inet_pton(AF_INET, host_.c_str(), &probe) != 1)
112 ✗ return;
113 13 port_ = static_cast<uint16_t>(hi * 256 + lo);
114 }
115
116
117 13 CallbackClient::~CallbackClient() { Disconnect(); }
118
119
120 13 void CallbackClient::Disconnect() {
121
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (fd_ >= 0)
122 13 close(fd_);
123 13 fd_ = -1;
124 13 }
125
126
127 26 bool CallbackClient::Connect(unsigned timeout_ms) {
128
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 13 times.
26 if (fd_ >= 0)
129 13 return true;
130 13 fd_ = socket(AF_INET, SOCK_STREAM, 0);
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (fd_ < 0)
132 ✗ return false;
133 #ifdef SO_NOSIGPIPE
134 const int on = 1;
135 setsockopt(fd_, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
136 #endif
137 struct sockaddr_in addr;
138 13 memset(&addr, 0, sizeof(addr));
139 13 addr.sin_family = AF_INET;
140 13 addr.sin_port = htons(port_);
141 13 inet_pton(AF_INET, host_.c_str(), &addr.sin_addr);
142
143
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 const int flags = fcntl(fd_, F_GETFL);
144
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 fcntl(fd_, F_SETFL, flags | O_NONBLOCK);
145
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 int retval = connect(fd_, reinterpret_cast<struct sockaddr *>(&addr),
146 sizeof(addr));
147
5/10
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 13 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 13 times.
✗ Branch 10 not taken.
13 if (retval != 0 && errno == EINPROGRESS && WaitFd(fd_, POLLOUT, timeout_ms)) {
148 13 int error = 0;
149 13 socklen_t len = sizeof(error);
150 13 getsockopt(fd_, SOL_SOCKET, SO_ERROR, &error, &len);
151
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 retval = (error == 0) ? 0 : -1;
152 }
153
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (retval != 0) {
154 ✗ Log(kLogLevelDebug, "cannot connect to callback %s:%u", host_.c_str(),
155 ✗ port_);
156 ✗ Disconnect();
157 ✗ return false;
158 }
159 13 return true;
160 }
161
162
163 26 bool CallbackClient::Call(uint32_t procedure, const std::string &args,
164 std::string *result, unsigned timeout_ms) {
165
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 std::lock_guard<std::mutex> const guard(lock_);
166
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
26 if (!valid())
167 ✗ return false;
168
169 // A broken connection is reestablished once
170
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 for (unsigned attempt = 0; attempt < 2; ++attempt) {
171
2/4
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
26 if (!Connect(timeout_ms))
172 26 return false;
173 26 const uint32_t xid = next_xid_++;
174 26 XdrWriter w;
175
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 const size_t marker_offset = w.ReserveU32();
176
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(xid);
177
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(kRpcCall);
178
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(kRpcVersion);
179
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(program_);
180
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(kCallbackVersion);
181
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(procedure);
182 // AUTH_SYS as root: the Linux client rejects CB_COMPOUND with AUTH_NONE
183 26 XdrWriter cred;
184
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 cred.PutU32(static_cast<uint32_t>(time(NULL))); // stamp
185
2/4
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 26 times.
✗ Branch 6 not taken.
26 cred.PutString("cvmfs"); // machine name
186
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 cred.PutU32(0); // uid
187
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 cred.PutU32(0); // gid
188
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 cred.PutU32(0); // no auxiliary gids
189
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(kAuthSys);
190
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 w.PutOpaque(cred.buffer());
191
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(kAuthNone);
192
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 w.PutU32(0);
193
1/2
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
26 w.PutRaw(args.data(), args.size());
194 26 w.PatchU32(marker_offset,
195
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 kLastFragment | static_cast<uint32_t>(w.size() - 4));
196
2/4
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 26 times.
26 if (!SendAll(fd_, w.buffer().data(), w.size())) {
197 ✗ Disconnect();
198 ✗ continue;
199 }
200
201 // Read records until the reply to our xid arrives
202 while (true) {
203 uint32_t marker;
204
2/4
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
26 if (!RecvAll(fd_, reinterpret_cast<char *>(&marker), 4, timeout_ms)) {
205 ✗ Disconnect();
206 26 return false;
207 }
208 26 marker = ntohl(marker);
209 26 const uint32_t len = marker & ~kLastFragment;
210
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (len > kMaxReply) {
211 ✗ Disconnect();
212 ✗ return false;
213 }
214
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 std::string record(len, '\0');
215
5/10
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 26 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 26 times.
26 if (len > 0 && !RecvAll(fd_, &record[0], len, timeout_ms)) {
216 ✗ Disconnect();
217 ✗ return false;
218 }
219 26 XdrReader r(reinterpret_cast<const unsigned char *>(record.data()),
220 52 record.size());
221 uint32_t reply_xid, type, reply_stat, verf_flavor, accept;
222 26 std::string verf;
223
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 r.GetU32(&reply_xid);
224
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 r.GetU32(&type);
225
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 26 times.
26 if (!r.ok() || reply_xid != xid || type != kRpcReply)
226 ✗ continue;
227
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 r.GetU32(&reply_stat);
228
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 r.GetU32(&verf_flavor);
229
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 r.GetOpaque(&verf, 400);
230
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 r.GetU32(&accept);
231
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 26 times.
26 if (!r.ok() || reply_stat != kMsgAccepted || accept != kAcceptSuccess)
232 ✗ return false;
233
1/2
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
26 result->assign(reinterpret_cast<const char *>(r.cursor()),
234 r.remaining());
235 26 return true;
236
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
52 }
237
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
52 }
238 ✗ return false;
239 26 }
240
241
242 13 bool CallbackClient::Null(unsigned timeout_ms) {
243 13 std::string result;
244
2/4
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
26 return Call(kCbProcNull, "", &result, timeout_ms);
245 13 }
246
247
248 13 int CallbackClient::Recall(uint32_t stateid_seqid,
249 const std::string &stateid_other,
250 const std::string &fh, unsigned timeout_ms) {
251 13 XdrWriter args;
252
2/4
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
13 args.PutString(""); // tag
253
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 args.PutU32(0); // minor version
254
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 args.PutU32(ident_);
255
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 args.PutU32(1); // one op
256
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 args.PutU32(kOpCbRecall);
257
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 args.PutU32(stateid_seqid);
258
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 args.PutFixedOpaque(stateid_other.data(), kStateidOtherSize);
259
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 args.PutBool(false); // truncate
260
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
13 args.PutOpaque(fh);
261 13 std::string result;
262
2/4
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
13 if (!Call(kCbProcCompound, args.buffer(), &result, timeout_ms))
263 ✗ return -1;
264 13 XdrReader r(reinterpret_cast<const unsigned char *>(result.data()),
265 26 result.size());
266 uint32_t status;
267
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 13 times.
13 if (!r.GetU32(&status))
268 ✗ return -1;
269 13 return static_cast<int>(status);
270 13 }
271
272 } // namespace nfs4
273