GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/network/s3fanout.cc
Date: 2026-08-09 02:40:25
Exec Total Coverage
Lines: 643 912 70.5%
Branches: 469 1308 35.9%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * Runs a thread using libcurls asynchronous I/O mode to push data to S3
5 */
6
7 #include "s3fanout.h"
8
9 #include <pthread.h>
10
11 #include <algorithm>
12 #include <cassert>
13 #include <cerrno>
14 #include <cstring>
15 #include <utility>
16
17 #include "crypto/hash.h"
18 #include "util/exception.h"
19 #include "util/posix.h"
20 #include "util/string.h"
21 #include "util/platform.h"
22
23 using namespace std; // NOLINT
24
25 namespace s3fanout {
26
27 /**
28 * Escapes characters that are not allowed in XML text content.
29 * Only & and < need escaping; >, ', " are safe in text nodes.
30 */
31 10336 static string XmlEscape(const string &input) {
32 10336 string result;
33
1/2
✓ Branch 2 taken 10336 times.
✗ Branch 3 not taken.
10336 result.reserve(input.size());
34
2/2
✓ Branch 1 taken 235144 times.
✓ Branch 2 taken 10336 times.
245480 for (unsigned i = 0; i < input.size(); ++i) {
35
3/3
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 235110 times.
235144 switch (input[i]) {
36
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 case '&': result += "&amp;"; break;
37
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 case '<': result += "&lt;"; break;
38
1/2
✓ Branch 2 taken 235110 times.
✗ Branch 3 not taken.
235110 default: result += input[i]; break;
39 }
40 }
41 10336 return result;
42 }
43
44
45 /**
46 * The CanonicalizedResource of an S3 V2 signature. It must mirror MkUrl():
47 * the server rebuilds the resource from the request path, so any difference,
48 * down to a trailing slash, yields SignatureDoesNotMatch.
49 *
50 * With DNS buckets the bucket lives in the hostname and the path is
51 * "/" + object_key, so an empty key gives "/<bucket>/". In path style the
52 * path already carries the bucket and an empty key gives "/<bucket>".
53 */
54 20978 string MkV2CanonicalResource(const string &bucket, const string &object_key,
55 bool dns_buckets, bool multi_delete) {
56 20978 string resource = "/" + bucket;
57
6/6
✓ Branch 0 taken 20927 times.
✓ Branch 1 taken 51 times.
✓ Branch 3 taken 20774 times.
✓ Branch 4 taken 153 times.
✓ Branch 5 taken 20825 times.
✓ Branch 6 taken 153 times.
20978 if (dns_buckets || !object_key.empty())
58
2/4
✓ Branch 1 taken 20825 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20825 times.
✗ Branch 5 not taken.
20825 resource += "/" + object_key;
59 // V2 requires subresources to be signed; multi-delete posts to "?delete"
60
2/2
✓ Branch 0 taken 153 times.
✓ Branch 1 taken 20825 times.
20978 if (multi_delete)
61
1/2
✓ Branch 1 taken 153 times.
✗ Branch 2 not taken.
153 resource += "?delete";
62 20978 return resource;
63 }
64
65
66 /**
67 * Builds an S3 DeleteObjects XML request body for multi-object delete.
68 * Uses Quiet mode so the response only contains errors, not successes.
69 */
70 187 string ComposeDeleteMultiXml(const vector<string> &keys) {
71
1/2
✓ Branch 2 taken 187 times.
✗ Branch 3 not taken.
187 string xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Delete><Quiet>true</Quiet>";
72 // ~70 bytes per <Object><Key>...</Key></Object> entry
73
1/2
✓ Branch 3 taken 187 times.
✗ Branch 4 not taken.
187 xml.reserve(xml.size() + keys.size() * 70 + 10);
74
2/2
✓ Branch 1 taken 10336 times.
✓ Branch 2 taken 187 times.
10523 for (unsigned i = 0; i < keys.size(); ++i) {
75
4/8
✓ Branch 2 taken 10336 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10336 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 10336 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 10336 times.
✗ Branch 12 not taken.
10336 xml += "<Object><Key>" + XmlEscape(keys[i]) + "</Key></Object>";
76 }
77
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 xml += "</Delete>";
78 187 return xml;
79 }
80
81
82 /**
83 * Parses the S3 DeleteObjects error response XML.
84 * In Quiet mode, the response only contains <Error> elements for failed keys.
85 * Returns the number of errors found.
86 */
87 68 unsigned ParseDeleteMultiResponse(const string &response,
88 vector<string> *error_keys,
89 vector<string> *error_codes,
90 vector<string> *error_messages) {
91 68 unsigned num_errors = 0;
92 68 string::size_type pos = 0;
93
94 while (true) {
95 119 const string::size_type err_start = response.find("<Error>", pos);
96
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 68 times.
119 if (err_start == string::npos)
97 51 break;
98 68 const string::size_type err_end = response.find("</Error>", err_start);
99
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 51 times.
68 if (err_end == string::npos)
100 17 break;
101
102 const string error_block = response.substr(err_start,
103
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 err_end - err_start);
104 51 num_errors++;
105
106 // Extract <Key>...</Key>
107 51 const string::size_type key_start = error_block.find("<Key>");
108 51 const string::size_type key_end = error_block.find("</Key>");
109
2/4
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 if (key_start != string::npos && key_end != string::npos) {
110
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 error_keys->push_back(
111
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
102 error_block.substr(key_start + 5, key_end - key_start - 5));
112 } else {
113 error_keys->push_back("");
114 }
115
116 // Extract <Code>...</Code>
117 51 const string::size_type code_start = error_block.find("<Code>");
118 51 const string::size_type code_end = error_block.find("</Code>");
119
2/4
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 if (code_start != string::npos && code_end != string::npos) {
120
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 error_codes->push_back(
121
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
102 error_block.substr(code_start + 6, code_end - code_start - 6));
122 } else {
123 error_codes->push_back("");
124 }
125
126 // Extract <Message>...</Message>
127 51 const string::size_type msg_start = error_block.find("<Message>");
128 51 const string::size_type msg_end = error_block.find("</Message>");
129
2/4
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 if (msg_start != string::npos && msg_end != string::npos) {
130
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 error_messages->push_back(
131
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
102 error_block.substr(msg_start + 9, msg_end - msg_start - 9));
132 } else {
133 error_messages->push_back("");
134 }
135
136 51 pos = err_end + 8; // length of "</Error>"
137 51 }
138
139 68 return num_errors;
140 }
141
142 const char *S3FanoutManager::kCacheControlCas = "Cache-Control: max-age=259200";
143 const unsigned S3FanoutManager::kDefault429ThrottleMs = 250;
144 const unsigned S3FanoutManager::kMax429ThrottleMs = 10000;
145 const unsigned S3FanoutManager::kThrottleReportIntervalSec = 10;
146 const unsigned S3FanoutManager::kDefaultHTTPPort = 80;
147 const unsigned S3FanoutManager::kDefaultHTTPSPort = 443;
148
149 221 std::string S3FanoutManager::MkDotCvmfsCacheControlHeader(unsigned defaultMaxAge, int overrideMaxAge)
150 {
151 const char *var;
152 int max_age_sec;
153 char *at_null_terminator_if_number;
154 221 bool value_determined = false;
155
156
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 if (overrideMaxAge >= 0) {
157 max_age_sec = overrideMaxAge;
158 value_determined = true;
159 }
160
161
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 if (!value_determined) {
162 221 var = getenv("CVMFS_MAX_TTL_SECS");
163
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
221 if (var && var[0]) {
164 max_age_sec = strtoll(var, &at_null_terminator_if_number, 10);
165 if (*at_null_terminator_if_number == '\0'
166 && max_age_sec >= 0) {
167 value_determined = true;
168 }
169 }
170 }
171
172
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 if (!value_determined) {
173 221 var = getenv("CVMFS_MAX_TTL");
174
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
221 if (var && var[0]) {
175 max_age_sec = strtoll(var, &at_null_terminator_if_number, 10);
176 if (*at_null_terminator_if_number == '\0'
177 && max_age_sec >= 0) {
178 max_age_sec *= 60;
179 value_determined = true;
180 }
181 }
182 }
183
184
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 if (!value_determined) {
185 221 max_age_sec = defaultMaxAge;
186 }
187
188
2/4
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 221 times.
✗ Branch 5 not taken.
442 return "Cache-Control: max-age=" + std::to_string(max_age_sec);
189 }
190
191 /**
192 * Parses Retry-After and X-Retry-In headers attached to HTTP 429 responses
193 */
194 442 void S3FanoutManager::DetectThrottleIndicator(const std::string &header,
195 JobInfo *info) {
196 442 std::string value_str;
197
4/6
✓ Branch 2 taken 442 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 442 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 187 times.
✓ Branch 10 taken 255 times.
442 if (HasPrefix(header, "retry-after:", true))
198
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 value_str = header.substr(12);
199
4/6
✓ Branch 2 taken 442 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 442 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 85 times.
✓ Branch 10 taken 357 times.
442 if (HasPrefix(header, "x-retry-in:", true))
200
1/2
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
85 value_str = header.substr(11);
201
202
1/2
✓ Branch 1 taken 442 times.
✗ Branch 2 not taken.
442 value_str = Trim(value_str, true /* trim_newline */);
203
2/2
✓ Branch 1 taken 238 times.
✓ Branch 2 taken 204 times.
442 if (!value_str.empty()) {
204
1/2
✓ Branch 1 taken 238 times.
✗ Branch 2 not taken.
238 const unsigned value_numeric = String2Uint64(value_str);
205
2/4
✓ Branch 2 taken 238 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 238 times.
✗ Branch 6 not taken.
476 const unsigned value_ms = HasSuffix(value_str, "ms", true /* ignore_case */)
206
2/2
✓ Branch 0 taken 85 times.
✓ Branch 1 taken 153 times.
238 ? value_numeric
207 238 : (value_numeric * 1000);
208
2/2
✓ Branch 0 taken 221 times.
✓ Branch 1 taken 17 times.
238 if (value_ms > 0)
209 221 info->throttle_ms = std::min(value_ms, kMax429ThrottleMs);
210 }
211 442 }
212
213
214 /**
215 * Called by curl for every HTTP header. Not called for file:// transfers.
216 */
217 62900 static size_t CallbackCurlHeader(void *ptr, size_t size, size_t nmemb,
218 void *info_link) {
219 62900 const size_t num_bytes = size * nmemb;
220
1/2
✓ Branch 2 taken 62900 times.
✗ Branch 3 not taken.
62900 const string header_line(static_cast<const char *>(ptr), num_bytes);
221 62900 JobInfo *info = static_cast<JobInfo *>(info_link);
222
223 // Check for http status code errors
224
4/6
✓ Branch 2 taken 62900 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 62900 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 20944 times.
✓ Branch 10 taken 41956 times.
62900 if (HasPrefix(header_line, "HTTP/1.", false)) {
225
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20944 times.
20944 if (header_line.length() < 10)
226 return 0;
227
228 unsigned i;
229
5/6
✓ Branch 1 taken 41888 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20944 times.
✓ Branch 5 taken 20944 times.
✓ Branch 6 taken 20944 times.
✓ Branch 7 taken 20944 times.
41888 for (i = 8; (i < header_line.length()) && (header_line[i] == ' '); ++i) {
230 }
231
232
2/2
✓ Branch 1 taken 10506 times.
✓ Branch 2 taken 10438 times.
20944 if (header_line[i] == '2') {
233 10506 return num_bytes;
234 } else {
235
1/2
✓ Branch 2 taken 10438 times.
✗ Branch 3 not taken.
10438 LogCvmfs(kLogS3Fanout, kLogDebug, "http status error code [info %p]: %s",
236 info, header_line.c_str());
237
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10438 times.
10438 if (header_line.length() < i + 3) {
238 LogCvmfs(kLogS3Fanout, kLogStderr, "S3: invalid HTTP response '%s'",
239 header_line.c_str());
240 info->error_code = kFailOther;
241 return 0;
242 }
243
2/4
✓ Branch 3 taken 10438 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 10438 times.
✗ Branch 7 not taken.
10438 info->http_error = String2Int64(string(&header_line[i], 3));
244
245
2/7
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10370 times.
✗ Branch 6 not taken.
10438 switch (info->http_error) {
246 68 case 429:
247 68 info->error_code = kFailRetry;
248 68 info->throttle_ms = S3FanoutManager::kDefault429ThrottleMs;
249 68 info->throttle_timestamp = platform_monotonic_time();
250 68 return num_bytes;
251 case 507: // Insufficient Storage
252 info->error_code = kFailInsufficientStorage;
253 break;
254 case 503:
255 case 502: // Can happen if the S3 gateway-backend connection breaks
256 case 500: // sometimes see this as a transient error from S3
257 info->error_code = kFailServiceUnavailable;
258 break;
259 case 501:
260 case 400:
261 info->error_code = kFailBadRequest;
262 break;
263 case 403:
264 info->error_code = kFailForbidden;
265 break;
266 10370 case 404:
267 10370 info->error_code = kFailNotFound;
268 10370 return num_bytes;
269 default:
270 info->error_code = kFailOther;
271 }
272 return 0;
273 }
274 }
275
276
2/2
✓ Branch 0 taken 204 times.
✓ Branch 1 taken 41752 times.
41956 if (info->error_code == kFailRetry) {
277
1/2
✓ Branch 1 taken 204 times.
✗ Branch 2 not taken.
204 S3FanoutManager::DetectThrottleIndicator(header_line, info);
278 }
279
280 41956 return num_bytes;
281 62900 }
282
283
284 /**
285 * Called by curl for every new chunk to upload.
286 */
287 266492 static size_t CallbackCurlData(void *ptr, size_t size, size_t nmemb,
288 void *info_link) {
289 266492 const size_t num_bytes = size * nmemb;
290 266492 JobInfo *info = static_cast<JobInfo *>(info_link);
291
292 266492 LogCvmfs(kLogS3Fanout, kLogDebug, "Data callback with %zu bytes", num_bytes);
293
294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 266492 times.
266492 if (num_bytes == 0)
295 return 0;
296
297 266492 const uint64_t read_bytes = info->origin->Read(ptr, num_bytes);
298
299 266492 LogCvmfs(kLogS3Fanout, kLogDebug, "source buffer pushed out %lu bytes",
300 read_bytes);
301
302 266492 return read_bytes;
303 }
304
305
306 /**
307 * Captures the HTTP response body. For kReqDeleteMulti, the response contains
308 * XML error information. For other requests, the body is ignored.
309 */
310 static size_t CallbackCurlBody(char *ptr, size_t size, size_t nmemb,
311 void *userdata) {
312 const size_t num_bytes = size * nmemb;
313 JobInfo *info = static_cast<JobInfo *>(userdata);
314 if (info != NULL && info->request == JobInfo::kReqDeleteMulti) {
315 info->response_body.append(ptr, num_bytes);
316 }
317 return num_bytes;
318 }
319
320
321 /**
322 * Called when new curl sockets arrive or existing curl sockets depart.
323 */
324 46427 int S3FanoutManager::CallbackCurlSocket(CURL *easy, curl_socket_t s, int action,
325 void *userp, void *socketp) {
326 46427 S3FanoutManager *s3fanout_mgr = static_cast<S3FanoutManager *>(userp);
327 46427 LogCvmfs(kLogS3Fanout, kLogDebug,
328 "CallbackCurlSocket called with easy "
329 "handle %p, socket %d, action %d, up %p, "
330 "sp %p, fds_inuse %d, jobs %d",
331 easy, s, action, userp, socketp, s3fanout_mgr->watch_fds_inuse_,
332 46427 s3fanout_mgr->available_jobs_->Get());
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46427 times.
46427 if (action == CURL_POLL_NONE)
334 return 0;
335
336 // Find s in watch_fds_
337 // First 2 fds are job and terminate pipes (not curl related)
338 unsigned index;
339
2/2
✓ Branch 0 taken 73321 times.
✓ Branch 1 taken 20944 times.
94265 for (index = 2; index < s3fanout_mgr->watch_fds_inuse_; ++index) {
340
2/2
✓ Branch 0 taken 25483 times.
✓ Branch 1 taken 47838 times.
73321 if (s3fanout_mgr->watch_fds_[index].fd == s)
341 25483 break;
342 }
343 // Or create newly
344
2/2
✓ Branch 0 taken 20944 times.
✓ Branch 1 taken 25483 times.
46427 if (index == s3fanout_mgr->watch_fds_inuse_) {
345 // Extend array if necessary
346
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 20893 times.
20944 if (s3fanout_mgr->watch_fds_inuse_ == s3fanout_mgr->watch_fds_size_) {
347 51 s3fanout_mgr->watch_fds_size_ *= 2;
348 51 s3fanout_mgr->watch_fds_ = static_cast<struct pollfd *>(
349 51 srealloc(s3fanout_mgr->watch_fds_,
350 51 s3fanout_mgr->watch_fds_size_ * sizeof(struct pollfd)));
351 }
352 20944 s3fanout_mgr->watch_fds_[s3fanout_mgr->watch_fds_inuse_].fd = s;
353 20944 s3fanout_mgr->watch_fds_[s3fanout_mgr->watch_fds_inuse_].events = 0;
354 20944 s3fanout_mgr->watch_fds_[s3fanout_mgr->watch_fds_inuse_].revents = 0;
355 20944 s3fanout_mgr->watch_fds_inuse_++;
356 }
357
358
4/5
✓ Branch 0 taken 20944 times.
✓ Branch 1 taken 51 times.
✓ Branch 2 taken 4488 times.
✓ Branch 3 taken 20944 times.
✗ Branch 4 not taken.
46427 switch (action) {
359 20944 case CURL_POLL_IN:
360 20944 s3fanout_mgr->watch_fds_[index].events = POLLIN | POLLPRI;
361 20944 break;
362 51 case CURL_POLL_OUT:
363 51 s3fanout_mgr->watch_fds_[index].events = POLLOUT | POLLWRBAND;
364 51 break;
365 4488 case CURL_POLL_INOUT:
366 4488 s3fanout_mgr->watch_fds_[index].events = POLLIN | POLLPRI | POLLOUT
367 | POLLWRBAND;
368 4488 break;
369 20944 case CURL_POLL_REMOVE:
370
2/2
✓ Branch 0 taken 3349 times.
✓ Branch 1 taken 17595 times.
20944 if (index < s3fanout_mgr->watch_fds_inuse_ - 1)
371 s3fanout_mgr
372 3349 ->watch_fds_[index] = s3fanout_mgr->watch_fds_
373 3349 [s3fanout_mgr->watch_fds_inuse_ - 1];
374 20944 s3fanout_mgr->watch_fds_inuse_--;
375 // Shrink array if necessary
376
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20944 times.
20944 if ((s3fanout_mgr->watch_fds_inuse_ > s3fanout_mgr->watch_fds_max_)
377 && (s3fanout_mgr->watch_fds_inuse_
378 < s3fanout_mgr->watch_fds_size_ / 2)) {
379 s3fanout_mgr->watch_fds_size_ /= 2;
380 s3fanout_mgr->watch_fds_ = static_cast<struct pollfd *>(
381 srealloc(s3fanout_mgr->watch_fds_,
382 s3fanout_mgr->watch_fds_size_ * sizeof(struct pollfd)));
383 }
384 20944 break;
385 default:
386 PANIC(NULL);
387 }
388
389 46427 return 0;
390 }
391
392
393 /**
394 * Worker thread event loop.
395 */
396 221 void *S3FanoutManager::MainUpload(void *data) {
397
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 LogCvmfs(kLogS3Fanout, kLogDebug, "Upload I/O thread started");
398 221 S3FanoutManager *s3fanout_mgr = static_cast<S3FanoutManager *>(data);
399
400 221 s3fanout_mgr->InitPipeWatchFds();
401
402 // Don't schedule more jobs into the multi handle than the maximum number of
403 // parallel connections. This should prevent starvation and thus a timeout
404 // of the authorization header (CVM-1339).
405 221 unsigned jobs_in_flight = 0;
406
407 while (true) {
408 // Check events with 100ms timeout
409 283560 const int timeout_ms = 100;
410
1/2
✓ Branch 1 taken 283560 times.
✗ Branch 2 not taken.
283560 int retval = poll(s3fanout_mgr->watch_fds_, s3fanout_mgr->watch_fds_inuse_,
411 timeout_ms);
412
2/2
✓ Branch 0 taken 1666 times.
✓ Branch 1 taken 281894 times.
283560 if (retval == 0) {
413 // Handle timeout
414 1666 int still_running = 0;
415
1/2
✓ Branch 1 taken 1666 times.
✗ Branch 2 not taken.
1666 retval = curl_multi_socket_action(s3fanout_mgr->curl_multi_,
416 CURL_SOCKET_TIMEOUT, 0, &still_running);
417
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1666 times.
1666 if (retval != CURLM_OK) {
418 LogCvmfs(kLogS3Fanout, kLogStderr, "Error, timeout due to: %d", retval);
419 assert(retval == CURLM_OK);
420 }
421
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 281894 times.
281894 } else if (retval < 0) {
422 assert(errno == EINTR);
423 continue;
424 }
425
426 // Terminate I/O thread
427
2/2
✓ Branch 0 taken 221 times.
✓ Branch 1 taken 283339 times.
283560 if (s3fanout_mgr->watch_fds_[0].revents)
428 221 break;
429
430 // New job incoming
431
2/2
✓ Branch 0 taken 10540 times.
✓ Branch 1 taken 272799 times.
283339 if (s3fanout_mgr->watch_fds_[1].revents) {
432 10540 s3fanout_mgr->watch_fds_[1].revents = 0;
433 JobInfo *info;
434
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 ReadPipe(s3fanout_mgr->pipe_jobs_[0], &info, sizeof(info));
435
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 CURL *handle = s3fanout_mgr->AcquireCurlHandle();
436
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10540 times.
10540 if (handle == NULL) {
437 PANIC(kLogStderr, "Failed to acquire CURL handle.");
438 }
439
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 const s3fanout::Failures init_failure = s3fanout_mgr->InitializeRequest(
440 info, handle);
441
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10540 times.
10540 if (init_failure != s3fanout::kFailOk) {
442 PANIC(kLogStderr,
443 "Failed to initialize CURL handle (error: %d - %s | errno: %d)",
444 init_failure, Code2Ascii(init_failure), errno);
445 }
446
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 s3fanout_mgr->SetUrlOptions(info);
447
448
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 curl_multi_add_handle(s3fanout_mgr->curl_multi_, handle);
449
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 s3fanout_mgr->active_requests_->insert(info);
450 10540 jobs_in_flight++;
451 10540 int still_running = 0, retval = 0;
452
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 retval = curl_multi_socket_action(s3fanout_mgr->curl_multi_,
453 CURL_SOCKET_TIMEOUT, 0, &still_running);
454
455
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 LogCvmfs(kLogS3Fanout, kLogDebug, "curl_multi_socket_action: %d - %d",
456 retval, still_running);
457 }
458
459
460 // Activity on curl sockets
461 // Within this loop the curl_multi_socket_action() may cause socket(s)
462 // to be removed from watch_fds_. If a socket is removed it is replaced
463 // by the socket at the end of the array and the inuse count is decreased.
464 // Therefore loop over the array in reverse order.
465 // First 2 fds are job and terminate pipes (not curl related)
466
2/2
✓ Branch 0 taken 627215 times.
✓ Branch 1 taken 283339 times.
910554 for (int32_t i = s3fanout_mgr->watch_fds_inuse_ - 1; i >= 2; --i) {
467
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 627215 times.
627215 if (static_cast<uint32_t>(i) >= s3fanout_mgr->watch_fds_inuse_) {
468 continue;
469 }
470
2/2
✓ Branch 0 taken 290309 times.
✓ Branch 1 taken 336906 times.
627215 if (s3fanout_mgr->watch_fds_[i].revents) {
471 290309 int ev_bitmask = 0;
472
2/2
✓ Branch 0 taken 31365 times.
✓ Branch 1 taken 258944 times.
290309 if (s3fanout_mgr->watch_fds_[i].revents & (POLLIN | POLLPRI))
473 31365 ev_bitmask |= CURL_CSELECT_IN;
474
2/2
✓ Branch 0 taken 258944 times.
✓ Branch 1 taken 31365 times.
290309 if (s3fanout_mgr->watch_fds_[i].revents & (POLLOUT | POLLWRBAND))
475 258944 ev_bitmask |= CURL_CSELECT_OUT;
476 290309 if (s3fanout_mgr->watch_fds_[i].revents
477
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 290309 times.
290309 & (POLLERR | POLLHUP | POLLNVAL))
478 ev_bitmask |= CURL_CSELECT_ERR;
479 290309 s3fanout_mgr->watch_fds_[i].revents = 0;
480
481 290309 int still_running = 0;
482 290309 retval = curl_multi_socket_action(s3fanout_mgr->curl_multi_,
483
1/2
✓ Branch 1 taken 290309 times.
✗ Branch 2 not taken.
290309 s3fanout_mgr->watch_fds_[i].fd,
484 ev_bitmask,
485 &still_running);
486 }
487 }
488
489 // Check if transfers are completed
490 CURLMsg *curl_msg;
491 int msgs_in_queue;
492
3/4
✓ Branch 1 taken 304283 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 20944 times.
✓ Branch 4 taken 283339 times.
304283 while ((curl_msg = curl_multi_info_read(s3fanout_mgr->curl_multi_,
493 &msgs_in_queue))) {
494
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20944 times.
20944 assert(curl_msg->msg == CURLMSG_DONE);
495
496 20944 s3fanout_mgr->statistics_->num_requests++;
497 JobInfo *info;
498 20944 CURL *easy_handle = curl_msg->easy_handle;
499 20944 const int curl_error = curl_msg->data.result;
500
1/2
✓ Branch 1 taken 20944 times.
✗ Branch 2 not taken.
20944 curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &info);
501
502
1/2
✓ Branch 1 taken 20944 times.
✗ Branch 2 not taken.
20944 curl_multi_remove_handle(s3fanout_mgr->curl_multi_, easy_handle);
503
3/4
✓ Branch 1 taken 20944 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10404 times.
✓ Branch 4 taken 10540 times.
20944 if (s3fanout_mgr->VerifyAndFinalize(curl_error, info)) {
504
1/2
✓ Branch 1 taken 10404 times.
✗ Branch 2 not taken.
10404 curl_multi_add_handle(s3fanout_mgr->curl_multi_, easy_handle);
505 10404 int still_running = 0;
506
1/2
✓ Branch 1 taken 10404 times.
✗ Branch 2 not taken.
10404 curl_multi_socket_action(s3fanout_mgr->curl_multi_, CURL_SOCKET_TIMEOUT,
507 0, &still_running);
508 } else {
509 // Return easy handle into pool and write result back
510 10540 jobs_in_flight--;
511
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 s3fanout_mgr->active_requests_->erase(info);
512
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 s3fanout_mgr->ReleaseCurlHandle(info, easy_handle);
513
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 s3fanout_mgr->available_jobs_->Decrement();
514
515 // Add to list of completed jobs
516
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 s3fanout_mgr->PushCompletedJob(info);
517 }
518 }
519 283339 }
520
521 221 set<CURL *>::iterator i = s3fanout_mgr->pool_handles_inuse_->begin();
522 221 const set<CURL *>::const_iterator i_end = s3fanout_mgr->pool_handles_inuse_
523 221 ->end();
524
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 221 times.
221 for (; i != i_end; ++i) {
525 curl_multi_remove_handle(s3fanout_mgr->curl_multi_, *i);
526 curl_easy_cleanup(*i);
527 }
528 221 s3fanout_mgr->pool_handles_inuse_->clear();
529 221 free(s3fanout_mgr->watch_fds_);
530
531
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 LogCvmfs(kLogS3Fanout, kLogDebug, "Upload I/O thread terminated");
532 221 return NULL;
533 }
534
535
536 /**
537 * Gets an idle CURL handle from the pool. Creates a new one and adds it to
538 * the pool if necessary.
539 */
540 10540 CURL *S3FanoutManager::AcquireCurlHandle() const {
541 CURL *handle;
542
543 10540 const MutexLockGuard guard(curl_handle_lock_);
544
545
2/2
✓ Branch 1 taken 935 times.
✓ Branch 2 taken 9605 times.
10540 if (pool_handles_idle_->empty()) {
546 CURLcode retval;
547
548 // Create a new handle
549
1/2
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
935 handle = curl_easy_init();
550
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 935 times.
935 assert(handle != NULL);
551
552 // Other settings
553
1/2
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
935 retval = curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1);
554
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 935 times.
935 assert(retval == CURLE_OK);
555
1/2
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
935 retval = curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION,
556 CallbackCurlHeader);
557
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 935 times.
935 assert(retval == CURLE_OK);
558
1/2
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
935 retval = curl_easy_setopt(handle, CURLOPT_READFUNCTION, CallbackCurlData);
559
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 935 times.
935 assert(retval == CURLE_OK);
560
1/2
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
935 retval = curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, CallbackCurlBody);
561
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 935 times.
935 assert(retval == CURLE_OK);
562 // WRITEDATA is set per-request in InitializeRequest
563 } else {
564 9605 handle = *(pool_handles_idle_->begin());
565
1/2
✓ Branch 2 taken 9605 times.
✗ Branch 3 not taken.
9605 pool_handles_idle_->erase(pool_handles_idle_->begin());
566 }
567
568
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 pool_handles_inuse_->insert(handle);
569
570 10540 return handle;
571 10540 }
572
573
574 10540 void S3FanoutManager::ReleaseCurlHandle(JobInfo *info, CURL *handle) const {
575
1/2
✓ Branch 0 taken 10540 times.
✗ Branch 1 not taken.
10540 if (info->http_headers) {
576
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 curl_slist_free_all(info->http_headers);
577 10540 info->http_headers = NULL;
578 }
579
580 10540 const MutexLockGuard guard(curl_handle_lock_);
581
582
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 const set<CURL *>::iterator elem = pool_handles_inuse_->find(handle);
583
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 10540 times.
10540 assert(elem != pool_handles_inuse_->end());
584
585
2/2
✓ Branch 1 taken 493 times.
✓ Branch 2 taken 10047 times.
10540 if (pool_handles_idle_->size() > config_.pool_max_handles) {
586
1/2
✓ Branch 1 taken 493 times.
✗ Branch 2 not taken.
493 const CURLcode retval = curl_easy_setopt(handle, CURLOPT_SHARE, NULL);
587
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 493 times.
493 assert(retval == CURLE_OK);
588
1/2
✓ Branch 1 taken 493 times.
✗ Branch 2 not taken.
493 curl_easy_cleanup(handle);
589 const std::map<CURL *, S3FanOutDnsEntry *>::size_type
590
1/2
✓ Branch 1 taken 493 times.
✗ Branch 2 not taken.
493 retitems = curl_sharehandles_->erase(handle);
591
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 493 times.
493 assert(retitems == 1);
592 } else {
593
1/2
✓ Branch 1 taken 10047 times.
✗ Branch 2 not taken.
10047 pool_handles_idle_->insert(handle);
594 }
595
596
1/2
✓ Branch 1 taken 10540 times.
✗ Branch 2 not taken.
10540 pool_handles_inuse_->erase(elem);
597 10540 }
598
599 221 void S3FanoutManager::InitPipeWatchFds() {
600
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(watch_fds_inuse_ == 0);
601
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(watch_fds_size_ >= 2);
602 221 watch_fds_[0].fd = pipe_terminate_[0];
603 221 watch_fds_[0].events = POLLIN | POLLPRI;
604 221 watch_fds_[0].revents = 0;
605 221 ++watch_fds_inuse_;
606 221 watch_fds_[1].fd = pipe_jobs_[0];
607 221 watch_fds_[1].events = POLLIN | POLLPRI;
608 221 watch_fds_[1].revents = 0;
609 221 ++watch_fds_inuse_;
610 221 }
611
612 /**
613 * The Amazon AWS 2 authorization header according to
614 * http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#ConstructingTheAuthenticationHeader
615 */
616 20876 bool S3FanoutManager::MkV2Authz(const JobInfo &info,
617 vector<string> *headers) const {
618 20876 string payload_hash;
619
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 const bool retval = MkPayloadHash(info, &payload_hash);
620
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 if (!retval)
621 return false;
622
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 const string content_type = GetContentType(info);
623
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 const string request = GetRequestString(info);
624
625
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 const string timestamp = RfcTimestamp();
626
5/10
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 20876 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 20876 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 20876 times.
✗ Branch 14 not taken.
41752 string to_sign = request + "\n" + payload_hash + "\n" + content_type + "\n"
627
2/4
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
41752 + timestamp + "\n";
628
2/4
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 20876 times.
✗ Branch 4 not taken.
20876 if (config_.x_amz_acl != "")
629
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
41752 to_sign += "x-amz-acl:" + config_.x_amz_acl
630
2/4
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
20876 + "\n"; // CanonicalizedAmzHeaders
631 20876 to_sign += MkV2CanonicalResource(config_.bucket, info.object_key,
632 20876 config_.dns_buckets,
633
2/4
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
20876 info.request == JobInfo::kReqDeleteMulti);
634
1/2
✓ Branch 3 taken 20876 times.
✗ Branch 4 not taken.
20876 LogCvmfs(kLogS3Fanout, kLogDebug, "%s string to sign: %s", request.c_str(),
635 to_sign.c_str());
636
637
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 shash::Any hmac;
638 20876 hmac.algorithm = shash::kSha1;
639
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 shash::Hmac(config_.secret_key,
640 20876 reinterpret_cast<const unsigned char *>(to_sign.data()),
641 20876 to_sign.length(), &hmac);
642
643
3/6
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 20876 times.
✗ Branch 8 not taken.
62628 headers->push_back("Authorization: AWS " + config_.access_key + ":"
644
3/6
✓ Branch 2 taken 20876 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20876 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 20876 times.
✗ Branch 9 not taken.
104380 + Base64(string(reinterpret_cast<char *>(hmac.digest),
645 20876 hmac.GetDigestSize())));
646
2/4
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
20876 headers->push_back("Date: " + timestamp);
647
2/4
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
20876 headers->push_back("X-Amz-Acl: " + config_.x_amz_acl);
648
2/2
✓ Branch 1 taken 10455 times.
✓ Branch 2 taken 10421 times.
20876 if (!payload_hash.empty())
649
2/4
✓ Branch 1 taken 10455 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10455 times.
✗ Branch 5 not taken.
10455 headers->push_back("Content-MD5: " + payload_hash);
650
2/2
✓ Branch 1 taken 10455 times.
✓ Branch 2 taken 10421 times.
20876 if (!content_type.empty())
651
2/4
✓ Branch 1 taken 10455 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10455 times.
✗ Branch 5 not taken.
10455 headers->push_back("Content-Type: " + content_type);
652 20876 return true;
653 20876 }
654
655
656 string S3FanoutManager::GetUriEncode(const string &val,
657 bool encode_slash) const {
658 string result;
659 const unsigned len = val.length();
660 result.reserve(len);
661 for (unsigned i = 0; i < len; ++i) {
662 const char c = val[i];
663 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
664 || (c >= '0' && c <= '9') || c == '_' || c == '-' || c == '~'
665 || c == '.') {
666 result.push_back(c);
667 } else if (c == '/') {
668 if (encode_slash) {
669 result += "%2F";
670 } else {
671 result.push_back(c);
672 }
673 } else {
674 result.push_back('%');
675 result.push_back((c / 16) + ((c / 16 <= 9) ? '0' : 'A' - 10));
676 result.push_back((c % 16) + ((c % 16 <= 9) ? '0' : 'A' - 10));
677 }
678 }
679 return result;
680 }
681
682
683 string S3FanoutManager::GetAwsV4SigningKey(const string &date) const {
684 if (last_signing_key_.first == date)
685 return last_signing_key_.second;
686
687 const string date_key = shash::Hmac256("AWS4" + config_.secret_key, date,
688 true);
689 const string date_region_key = shash::Hmac256(date_key, config_.region, true);
690 const string date_region_service_key = shash::Hmac256(date_region_key, "s3",
691 true);
692 string signing_key = shash::Hmac256(date_region_service_key, "aws4_request",
693 true);
694 last_signing_key_.first = date;
695 last_signing_key_.second = signing_key;
696 return signing_key;
697 }
698
699
700 /**
701 * The Amazon AWS4 authorization header according to
702 * http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html
703 */
704 bool S3FanoutManager::MkV4Authz(const JobInfo &info,
705 vector<string> *headers) const {
706 string payload_hash;
707 const bool retval = MkPayloadHash(info, &payload_hash);
708 if (!retval)
709 return false;
710 const string content_type = GetContentType(info);
711 const string timestamp = IsoTimestamp();
712 const string date = timestamp.substr(0, 8);
713 vector<string> tokens = SplitString(complete_hostname_, ':');
714 assert(tokens.size() <= 2);
715 string canonical_hostname = tokens[0];
716
717 // if we could split the hostname in two and if the port is *NOT* a default
718 // one
719 if (tokens.size() == 2
720 && !((String2Uint64(tokens[1]) == kDefaultHTTPPort)
721 || (String2Uint64(tokens[1]) == kDefaultHTTPSPort)))
722 canonical_hostname += ":" + tokens[1];
723
724 string signed_headers;
725 string canonical_headers;
726 if (!content_type.empty()) {
727 signed_headers += "content-type;";
728 headers->push_back("Content-Type: " + content_type);
729 canonical_headers += "content-type:" + content_type + "\n";
730 }
731 if (config_.x_amz_acl != "") {
732 signed_headers += "host;x-amz-acl;x-amz-content-sha256;x-amz-date";
733 } else {
734 signed_headers += "host;x-amz-content-sha256;x-amz-date";
735 }
736 canonical_headers += "host:" + canonical_hostname + "\n";
737 if (config_.x_amz_acl != "") {
738 canonical_headers += "x-amz-acl:" + config_.x_amz_acl + "\n";
739 }
740 canonical_headers += "x-amz-content-sha256:" + payload_hash + "\n"
741 + "x-amz-date:" + timestamp + "\n";
742
743 const string scope = date + "/" + config_.region + "/s3/aws4_request";
744 string uri;
745 if (config_.dns_buckets) {
746 uri = string("/") + info.object_key;
747 } else if (info.object_key.empty()) {
748 uri = string("/") + config_.bucket;
749 } else {
750 uri = string("/") + config_.bucket + "/" + info.object_key;
751 }
752
753 // V4 canonical query string: empty for most requests, "delete=" for
754 // multi-object delete (the S3 ?delete parameter has no value)
755 const string canonical_query = (info.request == JobInfo::kReqDeleteMulti)
756 ? "delete="
757 : "";
758
759 const string canonical_request = GetRequestString(info) + "\n"
760 + GetUriEncode(uri, false) + "\n"
761 + canonical_query + "\n"
762 + canonical_headers + "\n" + signed_headers
763 + "\n" + payload_hash;
764
765 const string hash_request = shash::Sha256String(canonical_request.c_str());
766
767 const string string_to_sign = "AWS4-HMAC-SHA256\n" + timestamp + "\n" + scope
768 + "\n" + hash_request;
769
770 const string signing_key = GetAwsV4SigningKey(date);
771 const string signature = shash::Hmac256(signing_key, string_to_sign);
772
773 headers->push_back("X-Amz-Acl: " + config_.x_amz_acl);
774 headers->push_back("X-Amz-Content-Sha256: " + payload_hash);
775 headers->push_back("X-Amz-Date: " + timestamp);
776 headers->push_back("Authorization: AWS4-HMAC-SHA256 "
777 "Credential="
778 + config_.access_key + "/" + scope
779 + ","
780 "SignedHeaders="
781 + signed_headers
782 + ","
783 "Signature="
784 + signature);
785
786 // S3 requires Content-MD5 for multi-object delete
787 if (info.request == JobInfo::kReqDeleteMulti) {
788 shash::Any md5_hash(shash::kMd5);
789 unsigned char *data;
790 const unsigned int nbytes = info.origin->Data(
791 reinterpret_cast<void **>(&data), info.origin->GetSize(), 0);
792 assert(nbytes == info.origin->GetSize());
793 shash::HashMem(data, nbytes, &md5_hash);
794 headers->push_back(
795 "Content-MD5: "
796 + Base64(string(reinterpret_cast<char *>(md5_hash.digest),
797 md5_hash.GetDigestSize())));
798 }
799 return true;
800 }
801
802 /**
803 * The Azure Blob authorization header according to
804 * https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
805 */
806 bool S3FanoutManager::MkAzureAuthz(const JobInfo &info,
807 vector<string> *headers) const {
808 const string timestamp = RfcTimestamp();
809 const string canonical_headers = "x-ms-blob-type:BlockBlob\nx-ms-date:"
810 + timestamp + "\nx-ms-version:2011-08-18";
811 const string canonical_resource = "/" + config_.access_key + "/"
812 + config_.bucket + "/" + info.object_key;
813
814 string string_to_sign;
815 if ((info.request == JobInfo::kReqHeadOnly)
816 || (info.request == JobInfo::kReqHeadPut)
817 || (info.request == JobInfo::kReqDelete)) {
818 string_to_sign = GetRequestString(info) + string("\n\n\n")
819 + "\n\n\n\n\n\n\n\n\n" + canonical_headers + "\n"
820 + canonical_resource;
821 } else {
822 string_to_sign = GetRequestString(info) + string("\n\n\n")
823 + string(StringifyInt(info.origin->GetSize()))
824 + "\n\n\n\n\n\n\n\n\n" + canonical_headers + "\n"
825 + canonical_resource;
826 }
827
828 string signing_key;
829 const int retval = Debase64(config_.secret_key, &signing_key);
830 if (!retval)
831 return false;
832
833 const string signature = shash::Hmac256(signing_key, string_to_sign, true);
834
835 headers->push_back("x-ms-date: " + timestamp);
836 headers->push_back("x-ms-version: 2011-08-18");
837 headers->push_back("Authorization: SharedKey " + config_.access_key + ":"
838 + Base64(signature));
839 headers->push_back("x-ms-blob-type: BlockBlob");
840 return true;
841 }
842
843 20876 void S3FanoutManager::InitializeDnsSettingsCurl(CURL *handle,
844 CURLSH *sharehandle,
845 curl_slist *clist) const {
846 20876 CURLcode retval = curl_easy_setopt(handle, CURLOPT_SHARE, sharehandle);
847
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
848 20876 retval = curl_easy_setopt(handle, CURLOPT_RESOLVE, clist);
849
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
850 20876 }
851
852
853 20876 int S3FanoutManager::InitializeDnsSettings(CURL *handle,
854 std::string host_with_port) const {
855 // Use existing handle
856 const std::map<CURL *, S3FanOutDnsEntry *>::const_iterator
857
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 it = curl_sharehandles_->find(handle);
858
2/2
✓ Branch 3 taken 19941 times.
✓ Branch 4 taken 935 times.
20876 if (it != curl_sharehandles_->end()) {
859
1/2
✓ Branch 1 taken 19941 times.
✗ Branch 2 not taken.
19941 InitializeDnsSettingsCurl(handle, it->second->sharehandle,
860 19941 it->second->clist);
861 19941 return 0;
862 }
863
864 // Add protocol information for extraction of fields for DNS
865
2/4
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 935 times.
✗ Branch 4 not taken.
935 if (!IsHttpUrl(host_with_port))
866
2/4
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 935 times.
✗ Branch 5 not taken.
935 host_with_port = config_.protocol + "://" + host_with_port;
867
1/2
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
935 const std::string remote_host = dns::ExtractHost(host_with_port);
868
1/2
✓ Branch 1 taken 935 times.
✗ Branch 2 not taken.
935 const std::string remote_port = dns::ExtractPort(host_with_port);
869
870 // If we have the name already resolved, use the least used IP
871 935 S3FanOutDnsEntry *useme = NULL;
872 935 unsigned int usemin = UINT_MAX;
873 935 std::set<S3FanOutDnsEntry *>::iterator its3 = sharehandles_->begin();
874
2/2
✓ Branch 3 taken 748 times.
✓ Branch 4 taken 935 times.
1683 for (; its3 != sharehandles_->end(); ++its3) {
875
1/2
✓ Branch 2 taken 748 times.
✗ Branch 3 not taken.
748 if ((*its3)->dns_name == remote_host) {
876
1/2
✓ Branch 1 taken 748 times.
✗ Branch 2 not taken.
748 if (usemin >= (*its3)->counter) {
877 748 usemin = (*its3)->counter;
878 748 useme = (*its3);
879 }
880 }
881 }
882
2/2
✓ Branch 0 taken 748 times.
✓ Branch 1 taken 187 times.
935 if (useme != NULL) {
883
1/2
✓ Branch 1 taken 748 times.
✗ Branch 2 not taken.
748 curl_sharehandles_->insert(
884 748 std::pair<CURL *, S3FanOutDnsEntry *>(handle, useme));
885 748 useme->counter++;
886
1/2
✓ Branch 1 taken 748 times.
✗ Branch 2 not taken.
748 InitializeDnsSettingsCurl(handle, useme->sharehandle, useme->clist);
887 748 return 0;
888 }
889
890 // We need to resolve the hostname
891 // TODO(ssheikki): support ipv6 also... if (opt_ipv4_only_)
892
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 const dns::Host host = resolver_->Resolve(remote_host);
893
1/2
✓ Branch 2 taken 187 times.
✗ Branch 3 not taken.
187 set<string> const ipv4_addresses = host.ipv4_addresses();
894 187 std::set<string>::iterator its = ipv4_addresses.begin();
895 187 S3FanOutDnsEntry *dnse = NULL;
896
2/2
✓ Branch 3 taken 187 times.
✓ Branch 4 taken 187 times.
374 for (; its != ipv4_addresses.end(); ++its) {
897
2/4
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 187 times.
✗ Branch 5 not taken.
187 dnse = new S3FanOutDnsEntry();
898 187 dnse->counter = 0;
899
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 dnse->dns_name = remote_host;
900
4/12
✗ Branch 1 not taken.
✓ Branch 2 taken 187 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 8 taken 187 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 187 times.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 187 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
187 dnse->port = remote_port.size() == 0 ? "80" : remote_port;
901
1/2
✓ Branch 2 taken 187 times.
✗ Branch 3 not taken.
187 dnse->ip = *its;
902 187 dnse->clist = NULL;
903
1/2
✓ Branch 2 taken 187 times.
✗ Branch 3 not taken.
187 dnse->clist = curl_slist_append(
904 dnse->clist,
905
4/8
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 187 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 187 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 187 times.
✗ Branch 11 not taken.
374 (dnse->dns_name + ":" + dnse->port + ":" + dnse->ip).c_str());
906
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 dnse->sharehandle = curl_share_init();
907
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 187 times.
187 assert(dnse->sharehandle != NULL);
908
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 const CURLSHcode share_retval = curl_share_setopt(
909 dnse->sharehandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
910
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 187 times.
187 assert(share_retval == CURLSHE_OK);
911
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 sharehandles_->insert(dnse);
912 }
913
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 187 times.
187 if (dnse == NULL) {
914 LogCvmfs(kLogS3Fanout, kLogStderr | kLogSyslogErr,
915 "Error: DNS resolve failed for address '%s'.",
916 remote_host.c_str());
917 assert(dnse != NULL);
918 return -1;
919 }
920
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 curl_sharehandles_->insert(
921 187 std::pair<CURL *, S3FanOutDnsEntry *>(handle, dnse));
922 187 dnse->counter++;
923
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 InitializeDnsSettingsCurl(handle, dnse->sharehandle, dnse->clist);
924
925 187 return 0;
926 935 }
927
928
929 20876 bool S3FanoutManager::MkPayloadHash(const JobInfo &info,
930 string *hex_hash) const {
931
2/2
✓ Branch 0 taken 20791 times.
✓ Branch 1 taken 85 times.
20876 if (info.request == JobInfo::kReqHeadOnly
932
2/2
✓ Branch 0 taken 10455 times.
✓ Branch 1 taken 10336 times.
20791 || info.request == JobInfo::kReqHeadPut
933
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10455 times.
10455 || info.request == JobInfo::kReqDelete) {
934
1/4
✓ Branch 0 taken 10421 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
10421 switch (config_.authz_method) {
935 10421 case kAuthzAwsV2:
936 10421 hex_hash->clear();
937 10421 break;
938 case kAuthzAwsV4:
939 // Sha256 over empty string
940 *hex_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b78"
941 "52b855";
942 break;
943 case kAuthzAzure:
944 // no payload hash required for Azure signature
945 hex_hash->clear();
946 break;
947 default:
948 PANIC(NULL);
949 }
950 10421 return true;
951 }
952
953 // kReqDeleteMulti is a POST with a body (XML payload), same hashing as PUT
954 // falls through intentionally.
955
956 // PUT or POST with payload
957
1/2
✓ Branch 1 taken 10455 times.
✗ Branch 2 not taken.
10455 shash::Any payload_hash(shash::kMd5);
958
959 unsigned char *data;
960 10455 const unsigned int nbytes = info.origin->Data(
961
2/4
✓ Branch 2 taken 10455 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10455 times.
✗ Branch 6 not taken.
10455 reinterpret_cast<void **>(&data), info.origin->GetSize(), 0);
962
2/4
✓ Branch 2 taken 10455 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10455 times.
10455 assert(nbytes == info.origin->GetSize());
963
964
1/4
✓ Branch 0 taken 10455 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
10455 switch (config_.authz_method) {
965 10455 case kAuthzAwsV2:
966
1/2
✓ Branch 1 taken 10455 times.
✗ Branch 2 not taken.
10455 shash::HashMem(data, nbytes, &payload_hash);
967
2/4
✓ Branch 2 taken 10455 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10455 times.
✗ Branch 6 not taken.
31365 *hex_hash = Base64(string(reinterpret_cast<char *>(payload_hash.digest),
968 20910 payload_hash.GetDigestSize()));
969 10455 return true;
970 case kAuthzAwsV4:
971 *hex_hash = shash::Sha256Mem(data, nbytes);
972 return true;
973 case kAuthzAzure:
974 // no payload hash required for Azure signature
975 hex_hash->clear();
976 return true;
977 default:
978 PANIC(NULL);
979 }
980 }
981
982 20876 string S3FanoutManager::GetRequestString(const JobInfo &info) const {
983
3/5
✓ Branch 0 taken 10421 times.
✓ Branch 1 taken 10336 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 119 times.
✗ Branch 4 not taken.
20876 switch (info.request) {
984 10421 case JobInfo::kReqHeadOnly:
985 case JobInfo::kReqHeadPut:
986
1/2
✓ Branch 2 taken 10421 times.
✗ Branch 3 not taken.
10421 return "HEAD";
987 10336 case JobInfo::kReqPutCas:
988 case JobInfo::kReqPutDotCvmfs:
989 case JobInfo::kReqPutHtml:
990 case JobInfo::kReqPutBucket:
991
1/2
✓ Branch 2 taken 10336 times.
✗ Branch 3 not taken.
10336 return "PUT";
992 case JobInfo::kReqDelete:
993 return "DELETE";
994 119 case JobInfo::kReqDeleteMulti:
995
1/2
✓ Branch 2 taken 119 times.
✗ Branch 3 not taken.
119 return "POST";
996 default:
997 PANIC(NULL);
998 }
999 }
1000
1001
1002 20876 string S3FanoutManager::GetContentType(const JobInfo &info) const {
1003
3/6
✓ Branch 0 taken 10421 times.
✓ Branch 1 taken 10336 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 119 times.
✗ Branch 5 not taken.
20876 switch (info.request) {
1004 10421 case JobInfo::kReqHeadOnly:
1005 case JobInfo::kReqHeadPut:
1006 case JobInfo::kReqDelete:
1007
1/2
✓ Branch 2 taken 10421 times.
✗ Branch 3 not taken.
10421 return "";
1008 10336 case JobInfo::kReqPutCas:
1009
1/2
✓ Branch 2 taken 10336 times.
✗ Branch 3 not taken.
10336 return "application/octet-stream";
1010 case JobInfo::kReqPutDotCvmfs:
1011 return "application/x-cvmfs";
1012 case JobInfo::kReqPutHtml:
1013 return "text/html";
1014 119 case JobInfo::kReqPutBucket:
1015 case JobInfo::kReqDeleteMulti:
1016
1/2
✓ Branch 2 taken 119 times.
✗ Branch 3 not taken.
119 return "text/xml";
1017 default:
1018 PANIC(NULL);
1019 }
1020 }
1021
1022
1023 /**
1024 * Request parameters set the URL and other options such as timeout and
1025 * proxy.
1026 */
1027 20876 Failures S3FanoutManager::InitializeRequest(JobInfo *info, CURL *handle) const {
1028 // Initialize internal download state
1029 20876 info->curl_handle = handle;
1030 20876 info->error_code = kFailOk;
1031 20876 info->http_error = 0;
1032 20876 info->num_retries = 0;
1033 20876 info->backoff_ms = 0;
1034 20876 info->throttle_ms = 0;
1035 20876 info->throttle_timestamp = 0;
1036 20876 info->http_headers = NULL;
1037 // info->payload_size is needed in S3Uploader::MainCollectResults,
1038 // where info->origin is already destroyed.
1039
1/2
✓ Branch 2 taken 20876 times.
✗ Branch 3 not taken.
20876 info->payload_size = info->origin->GetSize();
1040
1041
2/4
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 20876 times.
✗ Branch 5 not taken.
20876 InitializeDnsSettings(handle, complete_hostname_);
1042
1043 CURLcode retval;
1044
2/2
✓ Branch 0 taken 20791 times.
✓ Branch 1 taken 85 times.
20876 if (info->request == JobInfo::kReqHeadOnly
1045
2/2
✓ Branch 0 taken 10455 times.
✓ Branch 1 taken 10336 times.
20791 || info->request == JobInfo::kReqHeadPut
1046
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10455 times.
10455 || info->request == JobInfo::kReqDelete) {
1047
1/2
✓ Branch 1 taken 10421 times.
✗ Branch 2 not taken.
10421 retval = curl_easy_setopt(handle, CURLOPT_UPLOAD, 0);
1048
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10421 times.
10421 assert(retval == CURLE_OK);
1049
1/2
✓ Branch 1 taken 10421 times.
✗ Branch 2 not taken.
10421 retval = curl_easy_setopt(handle, CURLOPT_NOBODY, 1);
1050
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10421 times.
10421 assert(retval == CURLE_OK);
1051
1052
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10421 times.
10421 if (info->request == JobInfo::kReqDelete) {
1053 retval = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST,
1054 GetRequestString(*info).c_str());
1055 assert(retval == CURLE_OK);
1056 } else {
1057
1/2
✓ Branch 1 taken 10421 times.
✗ Branch 2 not taken.
10421 retval = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
1058
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10421 times.
10421 assert(retval == CURLE_OK);
1059 }
1060
2/2
✓ Branch 0 taken 119 times.
✓ Branch 1 taken 10336 times.
10455 } else if (info->request == JobInfo::kReqDeleteMulti) {
1061 // POST request with XML body read from origin buffer
1062
1/2
✓ Branch 1 taken 119 times.
✗ Branch 2 not taken.
119 retval = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1);
1063
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 assert(retval == CURLE_OK);
1064
1/2
✓ Branch 1 taken 119 times.
✗ Branch 2 not taken.
119 retval = curl_easy_setopt(handle, CURLOPT_NOBODY, 0);
1065
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 assert(retval == CURLE_OK);
1066
1/2
✓ Branch 1 taken 119 times.
✗ Branch 2 not taken.
119 retval = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST");
1067
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 assert(retval == CURLE_OK);
1068
2/4
✓ Branch 2 taken 119 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 119 times.
✗ Branch 6 not taken.
119 retval = curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE,
1069 static_cast<curl_off_t>(info->origin->GetSize()));
1070
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 assert(retval == CURLE_OK);
1071 119 info->response_body.clear();
1072 } else {
1073
1/2
✓ Branch 1 taken 10336 times.
✗ Branch 2 not taken.
10336 retval = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
1074
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10336 times.
10336 assert(retval == CURLE_OK);
1075
1/2
✓ Branch 1 taken 10336 times.
✗ Branch 2 not taken.
10336 retval = curl_easy_setopt(handle, CURLOPT_UPLOAD, 1);
1076
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10336 times.
10336 assert(retval == CURLE_OK);
1077
1/2
✓ Branch 1 taken 10336 times.
✗ Branch 2 not taken.
10336 retval = curl_easy_setopt(handle, CURLOPT_NOBODY, 0);
1078
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10336 times.
10336 assert(retval == CURLE_OK);
1079
2/4
✓ Branch 2 taken 10336 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10336 times.
✗ Branch 6 not taken.
10336 retval = curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE,
1080 static_cast<curl_off_t>(info->origin->GetSize()));
1081
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10336 times.
10336 assert(retval == CURLE_OK);
1082
1083
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10336 times.
10336 if (info->request == JobInfo::kReqPutDotCvmfs) {
1084 info->http_headers = curl_slist_append(
1085 info->http_headers, dot_cvmfs_cache_control_header.c_str());
1086
1/2
✓ Branch 0 taken 10336 times.
✗ Branch 1 not taken.
10336 } else if (info->request == JobInfo::kReqPutCas) {
1087
1/2
✓ Branch 1 taken 10336 times.
✗ Branch 2 not taken.
10336 info->http_headers = curl_slist_append(info->http_headers,
1088 kCacheControlCas);
1089 }
1090 }
1091
1092 bool retval_b;
1093
1094 // Authorization
1095 20876 vector<string> authz_headers;
1096
1/4
✓ Branch 0 taken 20876 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
20876 switch (config_.authz_method) {
1097 20876 case kAuthzAwsV2:
1098
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval_b = MkV2Authz(*info, &authz_headers);
1099 20876 break;
1100 case kAuthzAwsV4:
1101 retval_b = MkV4Authz(*info, &authz_headers);
1102 break;
1103 case kAuthzAzure:
1104 retval_b = MkAzureAuthz(*info, &authz_headers);
1105 break;
1106 default:
1107 PANIC(NULL);
1108 }
1109
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 if (!retval_b)
1110 return kFailLocalIO;
1111
2/2
✓ Branch 1 taken 83538 times.
✓ Branch 2 taken 20876 times.
104414 for (unsigned i = 0; i < authz_headers.size(); ++i) {
1112
1/2
✓ Branch 2 taken 83538 times.
✗ Branch 3 not taken.
83538 info->http_headers = curl_slist_append(info->http_headers,
1113 83538 authz_headers[i].c_str());
1114 }
1115
1116 // Common headers
1117
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 info->http_headers = curl_slist_append(info->http_headers,
1118 "Connection: Keep-Alive");
1119
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 info->http_headers = curl_slist_append(info->http_headers, "Pragma:");
1120 // No 100-continue
1121
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 info->http_headers = curl_slist_append(info->http_headers, "Expect:");
1122 // Strip unnecessary header
1123
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 info->http_headers = curl_slist_append(info->http_headers, "Accept:");
1124
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 info->http_headers = curl_slist_append(info->http_headers,
1125 20876 user_agent_->c_str());
1126
1127 // Set curl parameters
1128
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(handle, CURLOPT_PRIVATE, static_cast<void *>(info));
1129
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1130
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(handle, CURLOPT_HEADERDATA,
1131 static_cast<void *>(info));
1132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1133
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(handle, CURLOPT_READDATA,
1134 static_cast<void *>(info));
1135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1136
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(handle, CURLOPT_WRITEDATA,
1137 static_cast<void *>(info));
1138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1139
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(handle, CURLOPT_HTTPHEADER, info->http_headers);
1140
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 if (opt_ipv4_only_) {
1142 retval = curl_easy_setopt(handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
1143 assert(retval == CURLE_OK);
1144 }
1145 // Follow HTTP redirects
1146
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
1147
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1148
1149
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, info->errorbuffer);
1150
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1151
1152
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 20876 times.
20876 if (config_.protocol == "https") {
1153 retval = curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1L);
1154 assert(retval == CURLE_OK);
1155 retval = curl_easy_setopt(handle, CURLOPT_PROXY_SSL_VERIFYPEER, 1L);
1156 assert(retval == CURLE_OK);
1157 const bool add_cert = ssl_certificate_store_.ApplySslCertificatePath(
1158 handle);
1159 assert(add_cert);
1160 }
1161
1162 20876 return kFailOk;
1163 20876 }
1164
1165
1166 /**
1167 * Sets the URL specific options such as host to use and timeout.
1168 */
1169 20876 void S3FanoutManager::SetUrlOptions(JobInfo *info) const {
1170 20876 CURL *curl_handle = info->curl_handle;
1171 CURLcode retval;
1172
1173
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT,
1174 config_.opt_timeout_sec);
1175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1176
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_LIMIT,
1177 kLowSpeedLimit);
1178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1179
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 retval = curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_TIME,
1180 config_.opt_timeout_sec);
1181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1182
1183
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 if (is_curl_debug_) {
1184 retval = curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1);
1185 assert(retval == CURLE_OK);
1186 }
1187
1188
1/2
✓ Branch 1 taken 20876 times.
✗ Branch 2 not taken.
20876 string url = MkUrl(info->object_key);
1189
2/2
✓ Branch 0 taken 119 times.
✓ Branch 1 taken 20757 times.
20876 if (info->request == JobInfo::kReqDeleteMulti)
1190
1/2
✓ Branch 1 taken 119 times.
✗ Branch 2 not taken.
119 url += "?delete";
1191
1/2
✓ Branch 2 taken 20876 times.
✗ Branch 3 not taken.
20876 retval = curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
1192
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1193
1194
1/2
✓ Branch 2 taken 20876 times.
✗ Branch 3 not taken.
20876 retval = curl_easy_setopt(curl_handle, CURLOPT_PROXY, config_.proxy.c_str());
1195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20876 times.
20876 assert(retval == CURLE_OK);
1196 20876 }
1197
1198
1199 /**
1200 * Adds transfer time and uploaded bytes to the global counters.
1201 */
1202 20944 void S3FanoutManager::UpdateStatistics(CURL *handle) {
1203 double val;
1204
1205
2/4
✓ Branch 1 taken 20944 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 20944 times.
✗ Branch 4 not taken.
20944 if (curl_easy_getinfo(handle, CURLINFO_SIZE_UPLOAD, &val) == CURLE_OK)
1206 20944 statistics_->transferred_bytes += val;
1207 20944 }
1208
1209
1210 /**
1211 * Retry if possible and if not already done too often.
1212 */
1213 102 bool S3FanoutManager::CanRetry(const JobInfo *info) {
1214 102 return (info->error_code == kFailHostConnection
1215
1/2
✓ Branch 0 taken 102 times.
✗ Branch 1 not taken.
102 || info->error_code == kFailHostResolve
1216
1/2
✓ Branch 0 taken 102 times.
✗ Branch 1 not taken.
102 || info->error_code == kFailServiceUnavailable
1217
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 34 times.
102 || info->error_code == kFailRetry)
1218
2/4
✓ Branch 0 taken 102 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
204 && (info->num_retries < config_.opt_max_retries);
1219 }
1220
1221
1222 /**
1223 * Backoff for retry to introduce a jitter into a upload sequence.
1224 *
1225 * \return true if backoff has been performed, false otherwise
1226 */
1227 68 void S3FanoutManager::Backoff(JobInfo *info) {
1228
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (info->error_code != kFailRetry)
1229 info->num_retries++;
1230 68 statistics_->num_retries++;
1231
1232
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 if (info->throttle_ms > 0) {
1233 68 LogCvmfs(kLogS3Fanout, kLogDebug, "throttling for %d ms",
1234 info->throttle_ms);
1235 68 const uint64_t now = platform_monotonic_time();
1236
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 if ((info->throttle_timestamp + (info->throttle_ms / 1000)) >= now) {
1237
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 51 times.
68 if ((now - timestamp_last_throttle_report_)
1238 > kThrottleReportIntervalSec) {
1239 17 LogCvmfs(kLogS3Fanout, kLogStdout,
1240 "Warning: S3 backend throttling %ums "
1241 "(total backoff time so far %lums)",
1242 17 info->throttle_ms, statistics_->ms_throttled);
1243 17 timestamp_last_throttle_report_ = now;
1244 }
1245 68 statistics_->ms_throttled += info->throttle_ms;
1246 68 SafeSleepMs(info->throttle_ms);
1247 }
1248 } else {
1249 if (info->backoff_ms == 0) {
1250 // Must be != 0
1251 info->backoff_ms = prng_.Next(config_.opt_backoff_init_ms + 1);
1252 } else {
1253 info->backoff_ms *= 2;
1254 }
1255 if (info->backoff_ms > config_.opt_backoff_max_ms)
1256 info->backoff_ms = config_.opt_backoff_max_ms;
1257
1258 LogCvmfs(kLogS3Fanout, kLogDebug, "backing off for %d ms",
1259 info->backoff_ms);
1260 SafeSleepMs(info->backoff_ms);
1261 }
1262 68 }
1263
1264
1265 /**
1266 * Checks the result of a curl request and implements the failure logic
1267 * and takes care of cleanup.
1268 *
1269 * @return true if request should be repeated, false otherwise
1270 */
1271 20944 bool S3FanoutManager::VerifyAndFinalize(const int curl_error, JobInfo *info) {
1272 20944 LogCvmfs(kLogS3Fanout, kLogDebug,
1273 "Verify uploaded/tested object %s "
1274 "(curl error %d, info error %d, info request %d)",
1275 20944 info->object_key.c_str(), curl_error, info->error_code,
1276 20944 info->request);
1277 20944 UpdateStatistics(info->curl_handle);
1278
1279 // Verification and error classification
1280
1/6
✓ Branch 0 taken 20944 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
20944 switch (curl_error) {
1281 20944 case CURLE_OK:
1282
2/2
✓ Branch 0 taken 20876 times.
✓ Branch 1 taken 68 times.
20944 if ((info->error_code != kFailRetry)
1283
2/2
✓ Branch 0 taken 10506 times.
✓ Branch 1 taken 10370 times.
20876 && (info->error_code != kFailNotFound)) {
1284 10506 info->error_code = kFailOk;
1285 }
1286 20944 break;
1287 case CURLE_UNSUPPORTED_PROTOCOL:
1288 case CURLE_URL_MALFORMAT:
1289 info->error_code = kFailBadRequest;
1290 break;
1291 case CURLE_COULDNT_RESOLVE_HOST:
1292 info->error_code = kFailHostResolve;
1293 break;
1294 case CURLE_COULDNT_CONNECT:
1295 case CURLE_OPERATION_TIMEDOUT:
1296 case CURLE_SEND_ERROR:
1297 case CURLE_RECV_ERROR:
1298 info->error_code = kFailHostConnection;
1299 break;
1300 case CURLE_ABORTED_BY_CALLBACK:
1301 case CURLE_WRITE_ERROR:
1302 // Error set by callback
1303 break;
1304 default:
1305 LogCvmfs(kLogS3Fanout, kLogStderr | kLogSyslogErr,
1306 "unexpected curl error (%d) while trying to upload %s: %s",
1307 curl_error, info->object_key.c_str(), info->errorbuffer);
1308 info->error_code = kFailOther;
1309 break;
1310 }
1311
1312 // Transform HEAD to PUT request
1313
2/2
✓ Branch 0 taken 10370 times.
✓ Branch 1 taken 10574 times.
20944 if ((info->error_code == kFailNotFound)
1314
2/2
✓ Branch 0 taken 10336 times.
✓ Branch 1 taken 34 times.
10370 && (info->request == JobInfo::kReqHeadPut)) {
1315 10336 LogCvmfs(kLogS3Fanout, kLogDebug, "not found: %s, uploading",
1316 info->object_key.c_str());
1317 10336 info->request = JobInfo::kReqPutCas;
1318 10336 curl_slist_free_all(info->http_headers);
1319 10336 info->http_headers = NULL;
1320 10336 const s3fanout::Failures init_failure = InitializeRequest(
1321 info, info->curl_handle);
1322
1323
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10336 times.
10336 if (init_failure != s3fanout::kFailOk) {
1324 PANIC(kLogStderr,
1325 "Failed to initialize CURL handle "
1326 "(error: %d - %s | errno: %d)",
1327 init_failure, Code2Ascii(init_failure), errno);
1328 }
1329 10336 SetUrlOptions(info);
1330 // Reset origin
1331 10336 info->origin->Rewind();
1332 10336 return true; // Again, Put
1333 }
1334
1335 // Determination if failed request should be repeated
1336 10608 bool try_again = false;
1337
2/2
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 10506 times.
10608 if (info->error_code != kFailOk) {
1338 102 try_again = CanRetry(info);
1339 }
1340
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 10540 times.
10608 if (try_again) {
1341
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 if (info->request == JobInfo::kReqPutCas
1342
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 || info->request == JobInfo::kReqPutDotCvmfs
1343
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 || info->request == JobInfo::kReqPutHtml
1344
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 || info->request == JobInfo::kReqDeleteMulti) {
1345 LogCvmfs(kLogS3Fanout, kLogDebug, "Trying again to upload %s",
1346 info->object_key.c_str());
1347 // Reset origin
1348 info->origin->Rewind();
1349 if (info->request == JobInfo::kReqDeleteMulti)
1350 info->response_body.clear();
1351 }
1352 68 Backoff(info);
1353 68 info->error_code = kFailOk;
1354 68 info->http_error = 0;
1355 68 info->throttle_ms = 0;
1356 68 info->backoff_ms = 0;
1357 68 info->throttle_timestamp = 0;
1358 68 return true; // try again
1359 }
1360
1361 // Cleanup opened resources
1362 10540 info->origin.Destroy();
1363
1364
3/4
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 10506 times.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
10540 if ((info->error_code != kFailOk) && (info->http_error != 0)
1365
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 && (info->http_error != 404)) {
1366 LogCvmfs(kLogS3Fanout, kLogStderr, "S3: HTTP failure %d", info->http_error);
1367 }
1368 10540 return false; // stop transfer
1369 }
1370
1371
2/4
✓ Branch 3 taken 221 times.
✗ Branch 4 not taken.
✓ Branch 9 taken 221 times.
✗ Branch 10 not taken.
221 S3FanoutManager::S3FanoutManager(const S3Config &config) : config_(config) {
1372 221 atomic_init32(&multi_threaded_);
1373
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 MakePipe(pipe_terminate_);
1374
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 MakePipe(pipe_jobs_);
1375
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 MakePipe(pipe_completed_);
1376
1377 int retval;
1378 221 jobs_todo_lock_ = reinterpret_cast<pthread_mutex_t *>(
1379 221 smalloc(sizeof(pthread_mutex_t)));
1380 221 retval = pthread_mutex_init(jobs_todo_lock_, NULL);
1381
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(retval == 0);
1382 221 curl_handle_lock_ = reinterpret_cast<pthread_mutex_t *>(
1383 221 smalloc(sizeof(pthread_mutex_t)));
1384 221 retval = pthread_mutex_init(curl_handle_lock_, NULL);
1385
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(retval == 0);
1386
1387
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 active_requests_ = new set<JobInfo *>;
1388
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 pool_handles_idle_ = new set<CURL *>;
1389
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 pool_handles_inuse_ = new set<CURL *>;
1390
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 curl_sharehandles_ = new map<CURL *, S3FanOutDnsEntry *>;
1391
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 sharehandles_ = new set<S3FanOutDnsEntry *>;
1392 221 watch_fds_max_ = 4 * config_.pool_max_handles;
1393 221 max_available_jobs_ = 4 * config_.pool_max_handles;
1394
2/4
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 221 times.
✗ Branch 5 not taken.
221 available_jobs_ = new Semaphore(max_available_jobs_);
1395
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(NULL != available_jobs_);
1396
1397
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 statistics_ = new Statistics();
1398
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 user_agent_ = new string();
1399
2/4
✓ Branch 2 taken 221 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 221 times.
✗ Branch 6 not taken.
221 *user_agent_ = "User-Agent: cvmfs " + string(CVMFS_VERSION);
1400
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 complete_hostname_ = MkCompleteHostname();
1401
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 dot_cvmfs_cache_control_header = MkDotCvmfsCacheControlHeader();
1402
1403
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 const CURLcode cretval = curl_global_init(CURL_GLOBAL_ALL);
1404
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(cretval == CURLE_OK);
1405
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 curl_multi_ = curl_multi_init();
1406
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(curl_multi_ != NULL);
1407 CURLMcode mretval;
1408
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 mretval = curl_multi_setopt(curl_multi_, CURLMOPT_SOCKETFUNCTION,
1409 CallbackCurlSocket);
1410
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(mretval == CURLM_OK);
1411
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 mretval = curl_multi_setopt(curl_multi_, CURLMOPT_SOCKETDATA,
1412 static_cast<void *>(this));
1413
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(mretval == CURLM_OK);
1414
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 mretval = curl_multi_setopt(curl_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS,
1415 config_.pool_max_handles);
1416
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(mretval == CURLM_OK);
1417
1418 221 prng_.InitLocaltime();
1419
1420 221 thread_upload_ = 0;
1421 221 timestamp_last_throttle_report_ = 0;
1422 221 is_curl_debug_ = (getenv("_CVMFS_CURL_DEBUG") != NULL);
1423
1424 // Parsing environment variables
1425 221 if ((getenv("CVMFS_IPV4_ONLY") != NULL)
1426
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 221 times.
221 && (strlen(getenv("CVMFS_IPV4_ONLY")) > 0)) {
1427 opt_ipv4_only_ = true;
1428 } else {
1429 221 opt_ipv4_only_ = false;
1430 }
1431
1432
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 resolver_ = dns::CaresResolver::Create(opt_ipv4_only_, 2, 2000);
1433
1434 221 watch_fds_ = static_cast<struct pollfd *>(smalloc(4 * sizeof(struct pollfd)));
1435 221 watch_fds_size_ = 4;
1436 221 watch_fds_inuse_ = 0;
1437
1438
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 ssl_certificate_store_.UseSystemCertificatePath();
1439 221 }
1440
1441 442 S3FanoutManager::~S3FanoutManager() {
1442 221 pthread_mutex_destroy(jobs_todo_lock_);
1443 221 free(jobs_todo_lock_);
1444 221 pthread_mutex_destroy(curl_handle_lock_);
1445 221 free(curl_handle_lock_);
1446
1447
1/2
✓ Branch 1 taken 221 times.
✗ Branch 2 not taken.
221 if (atomic_xadd32(&multi_threaded_, 0) == 1) {
1448 // Shutdown I/O thread
1449 221 char buf = 'T';
1450 221 WritePipe(pipe_terminate_[1], &buf, 1);
1451 221 pthread_join(thread_upload_, NULL);
1452 }
1453 221 ClosePipe(pipe_terminate_);
1454 221 ClosePipe(pipe_jobs_);
1455 221 ClosePipe(pipe_completed_);
1456
1457 221 set<CURL *>::iterator i = pool_handles_idle_->begin();
1458 221 const set<CURL *>::const_iterator iEnd = pool_handles_idle_->end();
1459
2/2
✓ Branch 2 taken 442 times.
✓ Branch 3 taken 221 times.
663 for (; i != iEnd; ++i) {
1460 442 curl_easy_cleanup(*i);
1461 }
1462
1463 221 set<S3FanOutDnsEntry *>::iterator is = sharehandles_->begin();
1464 221 const set<S3FanOutDnsEntry *>::const_iterator isEnd = sharehandles_->end();
1465
2/2
✓ Branch 2 taken 187 times.
✓ Branch 3 taken 221 times.
408 for (; is != isEnd; ++is) {
1466 187 curl_share_cleanup((*is)->sharehandle);
1467 187 curl_slist_free_all((*is)->clist);
1468
1/2
✓ Branch 1 taken 187 times.
✗ Branch 2 not taken.
187 delete *is;
1469 }
1470 221 pool_handles_idle_->clear();
1471 221 curl_sharehandles_->clear();
1472 221 sharehandles_->clear();
1473
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete active_requests_;
1474
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete pool_handles_idle_;
1475
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete pool_handles_inuse_;
1476
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete curl_sharehandles_;
1477
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete sharehandles_;
1478
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete user_agent_;
1479 221 curl_multi_cleanup(curl_multi_);
1480
1481
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete statistics_;
1482
1483
1/2
✓ Branch 0 taken 221 times.
✗ Branch 1 not taken.
221 delete available_jobs_;
1484
1485 221 curl_global_cleanup();
1486 221 }
1487
1488 /**
1489 * Spawns the I/O worker thread. No way back except ~S3FanoutManager.
1490 */
1491 221 void S3FanoutManager::Spawn() {
1492 221 LogCvmfs(kLogS3Fanout, kLogDebug, "S3FanoutManager spawned");
1493
1494 221 const int retval = pthread_create(&thread_upload_, NULL, MainUpload,
1495 static_cast<void *>(this));
1496
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 221 times.
221 assert(retval == 0);
1497
1498 221 atomic_inc32(&multi_threaded_);
1499 221 }
1500
1501 51 const Statistics &S3FanoutManager::GetStatistics() { return *statistics_; }
1502
1503 /**
1504 * Push new job to be uploaded to the S3 cloud storage.
1505 */
1506 10540 void S3FanoutManager::PushNewJob(JobInfo *info) {
1507 10540 available_jobs_->Increment();
1508 10540 WritePipe(pipe_jobs_[1], &info, sizeof(info));
1509 10540 }
1510
1511 /**
1512 * Push completed job to list of completed jobs
1513 */
1514 10761 void S3FanoutManager::PushCompletedJob(JobInfo *info) {
1515 10761 WritePipe(pipe_completed_[1], &info, sizeof(info));
1516 10761 }
1517
1518 /**
1519 * Pop completed job
1520 */
1521 10761 JobInfo *S3FanoutManager::PopCompletedJob() {
1522 JobInfo *info;
1523
1/2
✓ Branch 1 taken 10761 times.
✗ Branch 2 not taken.
10761 ReadPipe(pipe_completed_[0], &info, sizeof(info));
1524 10761 return info;
1525 }
1526
1527 //------------------------------------------------------------------------------
1528
1529
1530 string Statistics::Print() const {
1531 return "Transferred Bytes: " + StringifyInt(uint64_t(transferred_bytes))
1532 + "\n" + "Transfer duration: " + StringifyInt(uint64_t(transfer_time))
1533 + " s\n" + "Number of requests: " + StringifyInt(num_requests) + "\n"
1534 + "Number of retries: " + StringifyInt(num_retries) + "\n";
1535 }
1536
1537 } // namespace s3fanout
1538