| 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 |