GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/network/download.cc
Date: 2025-03-09 02:34:28
Exec Total Coverage
Lines: 893 1740 51.3%
Branches: 701 2245 31.2%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * The download module provides an interface for fetching files via HTTP
5 * and file. It is internally using libcurl and the asynchronous DNS resolver
6 * c-ares. The JobInfo struct describes a single file/url to download and
7 * keeps the state during the several phases of downloading.
8 *
9 * The module starts in single-threaded mode and can be switched to multi-
10 * threaded mode by Spawn(). In multi-threaded mode, the Fetch() function still
11 * blocks but there is a separate I/O thread using asynchronous I/O, which
12 * maintains all concurrent connections simultaneously. As there might be more
13 * than 1024 file descriptors for the CernVM-FS process, the I/O thread uses
14 * poll and the libcurl multi socket interface.
15 *
16 * While downloading, files can be decompressed and the secure hash can be
17 * calculated on the fly.
18 *
19 * The module also implements failure handling. If corrupted data has been
20 * downloaded, the transfer is restarted using HTTP "no-cache" pragma.
21 * A "host chain" can be configured. When a host fails, there is automatic
22 * fail-over to the next host in the chain until all hosts are probed.
23 * Similarly a chain of proxy sets can be configured. Inside a proxy set,
24 * proxies are selected randomly (load-balancing set).
25 */
26
27 // TODO(jblomer): MS for time summing
28 // NOLINTNEXTLINE
29 #define __STDC_FORMAT_MACROS
30
31
32 #include "download.h"
33
34 #include <alloca.h>
35 #include <errno.h>
36 #include <inttypes.h>
37 #include <poll.h>
38 #include <pthread.h>
39 #include <signal.h>
40 #include <stdint.h>
41 #include <sys/time.h>
42 #include <unistd.h>
43
44 #include <algorithm>
45 #include <cassert>
46 #include <cstdio>
47 #include <cstdlib>
48 #include <cstring>
49 #include <map>
50 #include <set>
51 #include <utility>
52
53 #include "compression/compression.h"
54 #include "crypto/hash.h"
55 #include "duplex_curl.h"
56 #include "interrupt.h"
57 #include "sanitizer.h"
58 #include "ssl.h"
59 #include "util/algorithm.h"
60 #include "util/atomic.h"
61 #include "util/concurrency.h"
62 #include "util/exception.h"
63 #include "util/logging.h"
64 #include "util/posix.h"
65 #include "util/prng.h"
66 #include "util/smalloc.h"
67 #include "util/string.h"
68
69 using namespace std; // NOLINT
70
71 namespace download {
72
73 /**
74 * Returns the status if an interrupt happened for a given repository.
75 *
76 * Used only in case CVMFS_FAILOVER_INDEFINITELY (failover_indefinitely_) is set
77 * where failed downloads are retried indefinitely, unless an interrupt occurred
78 *
79 * @note If you use this functionality you need to change the source code of
80 * e.g. cvmfs_config reload to create a sentinel file. See comment below.
81 *
82 * @return true if an interrupt occurred
83 * false otherwise
84 */
85 bool Interrupted(const std::string &fqrn, JobInfo *info) {
86 if (info->allow_failure()) {
87 return true;
88 }
89
90 if (!fqrn.empty()) {
91 // it is up to the user the create this sentinel file ("pause_file") if
92 // CVMFS_FAILOVER_INDEFINITELY is used. It must be created during
93 // "cvmfs_config reload" and "cvmfs_config reload $fqrn"
94 std::string pause_file = std::string("/var/run/cvmfs/interrupt.") + fqrn;
95
96 LogCvmfs(kLogDownload, kLogDebug,
97 "(id %" PRId64 ") Interrupted(): checking for existence of %s",
98 info->id(), pause_file.c_str());
99 if (FileExists(pause_file)) {
100 LogCvmfs(kLogDownload, kLogDebug,
101 "(id %" PRId64 ") Interrupt marker found - "
102 "Interrupting current download, this will EIO outstanding IO.",
103 info->id());
104 if (0 != unlink(pause_file.c_str())) {
105 LogCvmfs(kLogDownload, kLogDebug,
106 "(id %" PRId64 ") Couldn't delete interrupt marker: errno=%d",
107 info->id(), errno);
108 }
109 return true;
110 }
111 }
112 return false;
113 }
114
115 203 static Failures PrepareDownloadDestination(JobInfo *info) {
116
3/6
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 203 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 203 times.
203 if (info->sink() != NULL && !info->sink()->IsValid()) {
117 cvmfs::PathSink* psink = dynamic_cast<cvmfs::PathSink*>(info->sink());
118 if (psink != NULL) {
119 LogCvmfs(kLogDownload, kLogDebug,
120 "(id %" PRId64 ") Failed to open path %s: %s (errno=%d).",
121 info->id(), psink->path().c_str(), strerror(errno), errno);
122 return kFailLocalIO;
123 } else {
124 LogCvmfs(kLogDownload, kLogDebug, "(id %" PRId64 ") "
125 "Failed to create a valid sink: \n %s",
126 info->id(), info->sink()->Describe().c_str());
127 return kFailOther;
128 }
129 }
130
131 203 return kFailOk;
132 }
133
134
135 /**
136 * Called by curl for every HTTP header. Not called for file:// transfers.
137 */
138 602 static size_t CallbackCurlHeader(void *ptr, size_t size, size_t nmemb,
139 void *info_link)
140 {
141 602 const size_t num_bytes = size*nmemb;
142
1/2
✓ Branch 2 taken 602 times.
✗ Branch 3 not taken.
602 const string header_line(static_cast<const char *>(ptr), num_bytes);
143 602 JobInfo *info = static_cast<JobInfo *>(info_link);
144
145 // LogCvmfs(kLogDownload, kLogDebug, "REMOVE-ME: Header callback with %s",
146 // header_line.c_str());
147
148 // Check http status codes
149
4/6
✓ Branch 2 taken 602 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 602 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 12 times.
✓ Branch 10 taken 590 times.
602 if (HasPrefix(header_line, "HTTP/1.", false)) {
150
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12 if (header_line.length() < 10) {
151 return 0;
152 }
153
154 unsigned i;
155
5/6
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 12 times.
24 for (i = 8; (i < header_line.length()) && (header_line[i] == ' '); ++i) {}
156
157 // Code is initialized to -1
158
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 if (header_line.length() > i+2) {
159 12 info->SetHttpCode(DownloadManager::ParseHttpCode(&header_line[i]));
160 }
161
162
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 3 times.
12 if ((info->http_code() / 100) == 2) {
163 9 return num_bytes;
164
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
3 } else if ((info->http_code() == 301) ||
165 (info->http_code() == 302) ||
166
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
3 (info->http_code() == 303) ||
167 (info->http_code() == 307))
168 {
169
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (!info->follow_redirects()) {
170 LogCvmfs(kLogDownload, kLogDebug,
171 "(id %" PRId64 ") redirect support not enabled: %s",
172 info->id(), header_line.c_str());
173 info->SetErrorCode(kFailHostHttp);
174 return 0;
175 }
176
1/2
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 LogCvmfs(kLogDownload, kLogDebug, "(id %" PRId64 ") http redirect: %s",
177 info->id(), header_line.c_str());
178 // libcurl will handle this because of CURLOPT_FOLLOWLOCATION
179 3 return num_bytes;
180 } else {
181 LogCvmfs(kLogDownload, kLogDebug,
182 "(id %" PRId64 ") http status error code: %s [%d]",
183 info->id(), header_line.c_str(), info->http_code());
184 if (((info->http_code() / 100) == 5) ||
185 (info->http_code() == 400) || (info->http_code() == 404))
186 {
187 // 5XX returned by host
188 // 400: error from the GeoAPI module
189 // 404: the stratum 1 does not have the newest files
190 info->SetErrorCode(kFailHostHttp);
191 } else if (info->http_code() == 429) {
192 // 429: rate throttling (we ignore the backoff hint for the time being)
193 info->SetErrorCode(kFailHostConnection);
194 } else {
195 info->SetErrorCode((info->proxy() == "DIRECT") ? kFailHostHttp :
196 kFailProxyHttp);
197 }
198 return 0;
199 }
200 }
201
202 // If needed: allocate space in sink
203
4/6
✓ Branch 1 taken 590 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 590 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 254 times.
✓ Branch 8 taken 336 times.
844 if (info->sink() != NULL && info->sink()->RequiresReserve() &&
204
10/17
✓ Branch 2 taken 254 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 254 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 81 times.
✓ Branch 8 taken 173 times.
✓ Branch 9 taken 254 times.
✓ Branch 10 taken 336 times.
✓ Branch 12 taken 254 times.
✓ Branch 13 taken 336 times.
✓ Branch 15 taken 81 times.
✓ Branch 16 taken 509 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
844 HasPrefix(header_line, "CONTENT-LENGTH:", true))
205 {
206 81 char *tmp = reinterpret_cast<char *>(alloca(num_bytes+1));
207 81 uint64_t length = 0;
208 81 sscanf(header_line.c_str(), "%s %" PRIu64, tmp, &length);
209
1/2
✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
81 if (length > 0) {
210
2/4
✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 81 times.
81 if (!info->sink()->Reserve(length)) {
211 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogErr, "(id %" PRId64 ") "
212 "resource %s too large to store in memory (%" PRIu64 ")",
213 info->id(), info->url()->c_str(), length);
214 info->SetErrorCode(kFailTooBig);
215 return 0;
216 }
217 } else {
218 // Empty resource
219 info->sink()->Reserve(0);
220 }
221
4/6
✓ Branch 2 taken 509 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 509 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 3 times.
✓ Branch 10 taken 506 times.
509 } else if (HasPrefix(header_line, "LOCATION:", true)) {
222 // This comes along with redirects
223
1/2
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 LogCvmfs(kLogDownload, kLogDebug, "(id %" PRId64 ") %s",
224 info->id(), header_line.c_str());
225
3/6
✓ Branch 2 taken 506 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 506 times.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 506 times.
506 } else if (HasPrefix(header_line, "LINK:", true)) {
226 // This is metalink info
227 LogCvmfs(kLogDownload, kLogDebug, "(id %" PRId64 ") %s",
228 info->id(), header_line.c_str());
229 std::string link = info->link();
230 if (link.size() != 0) {
231 // multiple LINK headers are allowed
232 link = link + ", " + header_line.substr(5);
233 } else {
234 link = header_line.substr(5);
235 }
236 info->SetLink(link);
237
3/6
✓ Branch 3 taken 506 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 506 times.
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 506 times.
506 } else if (HasPrefix(header_line, "X-SQUID-ERROR:", true)) {
238 // Reinterpret host error as proxy error
239 if (info->error_code() == kFailHostHttp) {
240 info->SetErrorCode(kFailProxyHttp);
241 }
242
3/6
✓ Branch 2 taken 506 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 506 times.
✗ Branch 6 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 506 times.
506 } else if (HasPrefix(header_line, "PROXY-STATUS:", true)) {
243 // Reinterpret host error as proxy error if applicable
244 if ((info->error_code() == kFailHostHttp) &&
245 (header_line.find("error=") != string::npos)) {
246 info->SetErrorCode(kFailProxyHttp);
247 }
248 }
249
250 590 return num_bytes;
251 602 }
252
253
254 /**
255 * Called by curl for every received data chunk.
256 */
257 229 static size_t CallbackCurlData(void *ptr, size_t size, size_t nmemb,
258 void *info_link)
259 {
260 229 const size_t num_bytes = size*nmemb;
261 229 JobInfo *info = static_cast<JobInfo *>(info_link);
262
263 // TODO(heretherebedragons) remove if no error comes up
264 // as this means only jobinfo data request (and not header only)
265 // come here
266
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 229 times.
229 assert(info->sink() != NULL);
267
268 // LogCvmfs(kLogDownload, kLogDebug, "Data callback, %d bytes", num_bytes);
269
270
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 229 times.
229 if (num_bytes == 0)
271 return 0;
272
273
2/2
✓ Branch 1 taken 125 times.
✓ Branch 2 taken 104 times.
229 if (info->expected_hash()) {
274 125 shash::Update(reinterpret_cast<unsigned char *>(ptr),
275 num_bytes, info->hash_context());
276 }
277
278
2/2
✓ Branch 1 taken 124 times.
✓ Branch 2 taken 105 times.
229 if (info->compressed()) {
279 zlib::StreamStates retval =
280 124 zlib::DecompressZStream2Sink(ptr, static_cast<int64_t>(num_bytes),
281 info->GetZstreamPtr(), info->sink());
282
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 124 times.
124 if (retval == zlib::kStreamDataError) {
283 LogCvmfs(kLogDownload, kLogSyslogErr,
284 "(id %" PRId64 ") failed to decompress %s",
285 info->id(), info->url()->c_str());
286 info->SetErrorCode(kFailBadData);
287 return 0;
288
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 124 times.
124 } else if (retval == zlib::kStreamIOError) {
289 LogCvmfs(kLogDownload, kLogSyslogErr,
290 "(id %" PRId64 ") decompressing %s, local IO error",
291 info->id(), info->url()->c_str());
292 info->SetErrorCode(kFailLocalIO);
293 return 0;
294 }
295 } else {
296 105 int64_t written = info->sink()->Write(ptr, num_bytes);
297
2/4
✓ Branch 0 taken 105 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 105 times.
105 if (written < 0 || static_cast<uint64_t>(written) != num_bytes) {
298 LogCvmfs(kLogDownload, kLogDebug, "(id %" PRId64 ") "
299 "Failed to perform write of %zu bytes to sink %s with errno %ld",
300 info->id(), num_bytes, info->sink()->Describe().c_str(), written);
301 }
302 }
303
304 229 return num_bytes;
305 }
306
307 #ifdef DEBUGMSG
308 113 static int CallbackCurlDebug(
309 CURL * handle,
310 curl_infotype type,
311 char *data,
312 size_t size,
313 void * /* clientp */)
314 {
315 JobInfo *info;
316
1/2
✓ Branch 1 taken 113 times.
✗ Branch 2 not taken.
113 curl_easy_getinfo(handle, CURLINFO_PRIVATE, &info);
317
318
3/6
✓ Branch 2 taken 113 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 113 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 113 times.
✗ Branch 9 not taken.
226 std::string prefix = "(id " + StringifyInt(info->id()) + ") ";
319
4/8
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 47 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 36 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
113 switch (type) {
320 18 case CURLINFO_TEXT:
321
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 prefix += "{info} ";
322 18 break;
323 47 case CURLINFO_HEADER_IN:
324
1/2
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
47 prefix += "{header/recv} ";
325 47 break;
326 12 case CURLINFO_HEADER_OUT:
327
1/2
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
12 prefix += "{header/sent} ";
328 12 break;
329 36 case CURLINFO_DATA_IN:
330
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 32 times.
36 if (size < 50) {
331
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 prefix += "{data/recv} ";
332 4 break;
333 } else {
334
1/2
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
32 LogCvmfs(kLogCurl, kLogDebug, "%s{data/recv} <snip>", prefix.c_str());
335 32 return 0;
336 }
337 case CURLINFO_DATA_OUT:
338 if (size < 50) {
339 prefix += "{data/sent} ";
340 break;
341 } else {
342 LogCvmfs(kLogCurl, kLogDebug, "%s{data/sent} <snip>", prefix.c_str());
343 return 0;
344 }
345 case CURLINFO_SSL_DATA_IN:
346 if (size < 50) {
347 prefix += "{ssldata/recv} ";
348 break;
349 } else {
350 LogCvmfs(kLogCurl, kLogDebug, "%s{ssldata/recv} <snip>",
351 prefix.c_str());
352 return 0;
353 }
354 case CURLINFO_SSL_DATA_OUT:
355 if (size < 50) {
356 prefix += "{ssldata/sent} ";
357 break;
358 } else {
359 LogCvmfs(kLogCurl, kLogDebug, "%s{ssldata/sent} <snip>",
360 prefix.c_str());
361 return 0;
362 }
363 default:
364 // just log the message
365 break;
366 }
367
368 81 bool valid_char = true;
369
1/2
✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
81 std::string msg(data, size);
370
2/2
✓ Branch 1 taken 4067 times.
✓ Branch 2 taken 81 times.
4148 for (size_t i = 0; i < msg.length(); ++i) {
371
2/4
✓ Branch 1 taken 4067 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4067 times.
4067 if (msg[i] == '\0') {
372 msg[i] = '~';
373 }
374
375 // verify that char is a valid printable char
376
3/6
✓ Branch 1 taken 4067 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3803 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3803 times.
7870 if ((msg[i] < ' ' || msg[i] > '~')
377
6/8
✓ Branch 0 taken 3803 times.
✓ Branch 1 taken 264 times.
✓ Branch 3 taken 264 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 123 times.
✓ Branch 6 taken 141 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4067 times.
7993 && (msg[i] != 10 /*line feed*/
378
2/4
✓ Branch 1 taken 123 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 123 times.
123 && msg[i] != 13 /*carriage return*/)) {
379 valid_char = false;
380 }
381 }
382
383
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 81 times.
81 if (!valid_char) {
384 msg = "<Non-plaintext sequence>";
385 }
386
387
1/2
✓ Branch 3 taken 81 times.
✗ Branch 4 not taken.
81 LogCvmfs(kLogCurl, kLogDebug, "%s%s",
388
1/2
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
162 prefix.c_str(), Trim(msg, true /* trim_newline */).c_str());
389 81 return 0;
390 113 }
391 #endif
392
393 //------------------------------------------------------------------------------
394
395
396 const int DownloadManager::kProbeUnprobed = -1;
397 const int DownloadManager::kProbeDown = -2;
398 const int DownloadManager::kProbeGeo = -3;
399
400 16970 bool DownloadManager::EscapeUrlChar(unsigned char input, char output[3]) {
401
6/6
✓ Branch 0 taken 15163 times.
✓ Branch 1 taken 1807 times.
✓ Branch 2 taken 11744 times.
✓ Branch 3 taken 3419 times.
✓ Branch 4 taken 11525 times.
✓ Branch 5 taken 2026 times.
16970 if (((input >= '0') && (input <= '9')) ||
402
4/4
✓ Branch 0 taken 10758 times.
✓ Branch 1 taken 767 times.
✓ Branch 2 taken 10149 times.
✓ Branch 3 taken 2635 times.
13551 ((input >= 'A') && (input <= 'Z')) ||
403
4/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 10140 times.
✓ Branch 2 taken 1167 times.
✓ Branch 3 taken 1477 times.
12784 ((input >= 'a') && (input <= 'z')) ||
404
5/6
✓ Branch 0 taken 948 times.
✓ Branch 1 taken 219 times.
✓ Branch 2 taken 622 times.
✓ Branch 3 taken 326 times.
✓ Branch 4 taken 622 times.
✗ Branch 5 not taken.
1167 (input == '/') || (input == ':') || (input == '.') ||
405
1/2
✓ Branch 0 taken 622 times.
✗ Branch 1 not taken.
622 (input == '@') ||
406
4/4
✓ Branch 0 taken 618 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 609 times.
622 (input == '+') || (input == '-') ||
407
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 (input == '_') || (input == '~') ||
408
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
9 (input == '[') || (input == ']') || (input == ','))
409 {
410 16961 output[0] = static_cast<char>(input);
411 16961 return false;
412 }
413
414 9 output[0] = '%';
415 9 output[1] = static_cast<char>(
416
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9 (input / 16) + ((input / 16 <= 9) ? '0' : 'A'-10));
417 9 output[2] = static_cast<char>(
418
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 (input % 16) + ((input % 16 <= 9) ? '0' : 'A'-10));
419 9 return true;
420 }
421
422
423 /**
424 * Escape special chars from the URL, except for ':' and '/',
425 * which should keep their meaning.
426 */
427 207 string DownloadManager::EscapeUrl(const int64_t jobinfo_id, const string &url) {
428 207 string escaped;
429
1/2
✓ Branch 2 taken 207 times.
✗ Branch 3 not taken.
207 escaped.reserve(url.length());
430
431 char escaped_char[3];
432
2/2
✓ Branch 1 taken 16970 times.
✓ Branch 2 taken 207 times.
17177 for (unsigned i = 0, s = url.length(); i < s; ++i) {
433
2/2
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 16961 times.
16970 if (EscapeUrlChar(url[i], escaped_char)) {
434
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 escaped.append(escaped_char, 3);
435 } else {
436
1/2
✓ Branch 1 taken 16961 times.
✗ Branch 2 not taken.
16961 escaped.push_back(escaped_char[0]);
437 }
438 }
439
1/2
✓ Branch 3 taken 207 times.
✗ Branch 4 not taken.
207 LogCvmfs(kLogDownload, kLogDebug, "(id %" PRId64 ") escaped %s to %s",
440 jobinfo_id, url.c_str(), escaped.c_str());
441
442 414 return escaped;
443 }
444
445 /**
446 * escaped array needs to be sufficiently large. Its size is calculated by
447 * passing NULL to EscapeHeader.
448 */
449 unsigned DownloadManager::EscapeHeader(const string &header,
450 char *escaped_buf,
451 size_t buf_size)
452 {
453 unsigned esc_pos = 0;
454 char escaped_char[3];
455 for (unsigned i = 0, s = header.size(); i < s; ++i) {
456 if (EscapeUrlChar(header[i], escaped_char)) {
457 for (unsigned j = 0; j < 3; ++j) {
458 if (escaped_buf) {
459 if (esc_pos >= buf_size)
460 return esc_pos;
461 escaped_buf[esc_pos] = escaped_char[j];
462 }
463 esc_pos++;
464 }
465 } else {
466 if (escaped_buf) {
467 if (esc_pos >= buf_size)
468 return esc_pos;
469 escaped_buf[esc_pos] = escaped_char[0];
470 }
471 esc_pos++;
472 }
473 }
474
475 return esc_pos;
476 }
477
478 /**
479 * -1 of digits is not a valid Http return code
480 */
481 17 int DownloadManager::ParseHttpCode(const char digits[3]) {
482 17 int result = 0;
483 17 int factor = 100;
484
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 16 times.
67 for (int i = 0; i < 3; ++i) {
485
3/4
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 50 times.
51 if ((digits[i] < '0') || (digits[i] > '9'))
486 1 return -1;
487 50 result += (digits[i] - '0') * factor;
488 50 factor /= 10;
489 }
490 16 return result;
491 }
492
493
494 /**
495 * Called when new curl sockets arrive or existing curl sockets depart.
496 */
497 int DownloadManager::CallbackCurlSocket(CURL * /* easy */,
498 curl_socket_t s,
499 int action,
500 void *userp,
501 void * /* socketp */)
502 {
503 // LogCvmfs(kLogDownload, kLogDebug, "CallbackCurlSocket called with easy "
504 // "handle %p, socket %d, action %d", easy, s, action);
505 DownloadManager *download_mgr = static_cast<DownloadManager *>(userp);
506 if (action == CURL_POLL_NONE)
507 return 0;
508
509 // Find s in watch_fds_
510 unsigned index;
511
512 // TODO(heretherebedragons) why start at index = 0 and not 2?
513 // fd[0] and fd[1] are fixed?
514 for (index = 0; index < download_mgr->watch_fds_inuse_; ++index) {
515 if (download_mgr->watch_fds_[index].fd == s)
516 break;
517 }
518 // Or create newly
519 if (index == download_mgr->watch_fds_inuse_) {
520 // Extend array if necessary
521 if (download_mgr->watch_fds_inuse_ == download_mgr->watch_fds_size_)
522 {
523 assert(download_mgr->watch_fds_size_ > 0);
524 download_mgr->watch_fds_size_ *= 2;
525 download_mgr->watch_fds_ = static_cast<struct pollfd *>(
526 srealloc(download_mgr->watch_fds_,
527 download_mgr->watch_fds_size_ * sizeof(struct pollfd)));
528 }
529 download_mgr->watch_fds_[download_mgr->watch_fds_inuse_].fd = s;
530 download_mgr->watch_fds_[download_mgr->watch_fds_inuse_].events = 0;
531 download_mgr->watch_fds_[download_mgr->watch_fds_inuse_].revents = 0;
532 download_mgr->watch_fds_inuse_++;
533 }
534
535 switch (action) {
536 case CURL_POLL_IN:
537 download_mgr->watch_fds_[index].events = POLLIN | POLLPRI;
538 break;
539 case CURL_POLL_OUT:
540 download_mgr->watch_fds_[index].events = POLLOUT | POLLWRBAND;
541 break;
542 case CURL_POLL_INOUT:
543 download_mgr->watch_fds_[index].events =
544 POLLIN | POLLPRI | POLLOUT | POLLWRBAND;
545 break;
546 case CURL_POLL_REMOVE:
547 if (index < download_mgr->watch_fds_inuse_-1) {
548 download_mgr->watch_fds_[index] =
549 download_mgr->watch_fds_[download_mgr->watch_fds_inuse_-1];
550 }
551 download_mgr->watch_fds_inuse_--;
552 // Shrink array if necessary
553 if ((download_mgr->watch_fds_inuse_ > download_mgr->watch_fds_max_) &&
554 (download_mgr->watch_fds_inuse_ < download_mgr->watch_fds_size_/2))
555 {
556 download_mgr->watch_fds_size_ /= 2;
557 // LogCvmfs(kLogDownload, kLogDebug, "shrinking watch_fds_ (%d)",
558 // watch_fds_size_);
559 download_mgr->watch_fds_ = static_cast<struct pollfd *>(
560 srealloc(download_mgr->watch_fds_,
561 download_mgr->watch_fds_size_*sizeof(struct pollfd)));
562 // LogCvmfs(kLogDownload, kLogDebug, "shrinking watch_fds_ done",
563 // watch_fds_size_);
564 }
565 break;
566 default:
567 break;
568 }
569
570 return 0;
571 }
572
573
574 /**
575 * Worker thread event loop. Waits on new JobInfo structs on a pipe.
576 */
577 void *DownloadManager::MainDownload(void *data) {
578 DownloadManager *download_mgr = static_cast<DownloadManager *>(data);
579 LogCvmfs(kLogDownload, kLogDebug,
580 "download I/O thread of DownloadManager '%s' started",
581 download_mgr->name_.c_str());
582
583 const int kIdxPipeTerminate = 0;
584 const int kIdxPipeJobs = 1;
585
586 download_mgr->watch_fds_ =
587 static_cast<struct pollfd *>(smalloc(2 * sizeof(struct pollfd)));
588 download_mgr->watch_fds_size_ = 2;
589 download_mgr->watch_fds_[kIdxPipeTerminate].fd =
590 download_mgr->pipe_terminate_->GetReadFd();
591 download_mgr->watch_fds_[kIdxPipeTerminate].events = POLLIN | POLLPRI;
592 download_mgr->watch_fds_[kIdxPipeTerminate].revents = 0;
593 download_mgr->watch_fds_[kIdxPipeJobs].fd =
594 download_mgr->pipe_jobs_->GetReadFd();
595 download_mgr->watch_fds_[kIdxPipeJobs].events = POLLIN | POLLPRI;
596 download_mgr->watch_fds_[kIdxPipeJobs].revents = 0;
597 download_mgr->watch_fds_inuse_ = 2;
598
599 int still_running = 0;
600 struct timeval timeval_start, timeval_stop;
601 gettimeofday(&timeval_start, NULL);
602 while (true) {
603 int timeout;
604 if (still_running) {
605 /* NOTE: The following might degrade the performance for many small files
606 * use case. TODO(jblomer): look into it.
607 // Specify a timeout for polling in ms; this allows us to return
608 // to libcurl once a second so it can look for internal operations
609 // which timed out. libcurl has a more elaborate mechanism
610 // (CURLMOPT_TIMERFUNCTION) that would inform us of the next potential
611 // timeout. TODO(bbockelm) we should switch to that in the future.
612 timeout = 100;
613 */
614 timeout = 1;
615 } else {
616 timeout = -1;
617 gettimeofday(&timeval_stop, NULL);
618 int64_t delta = static_cast<int64_t>(
619 1000 * DiffTimeSeconds(timeval_start, timeval_stop));
620 perf::Xadd(download_mgr->counters_->sz_transfer_time, delta);
621 }
622 int retval = poll(download_mgr->watch_fds_, download_mgr->watch_fds_inuse_,
623 timeout);
624 if (retval < 0) {
625 continue;
626 }
627
628 // Handle timeout
629 if (retval == 0) {
630 curl_multi_socket_action(download_mgr->curl_multi_,
631 CURL_SOCKET_TIMEOUT,
632 0,
633 &still_running);
634 }
635
636 // Terminate I/O thread
637 if (download_mgr->watch_fds_[kIdxPipeTerminate].revents)
638 break;
639
640 // New job arrives
641 if (download_mgr->watch_fds_[kIdxPipeJobs].revents) {
642 download_mgr->watch_fds_[kIdxPipeJobs].revents = 0;
643 JobInfo *info;
644 download_mgr->pipe_jobs_->Read<JobInfo*>(&info);
645 if (!still_running) {
646 gettimeofday(&timeval_start, NULL);
647 }
648 CURL *handle = download_mgr->AcquireCurlHandle();
649 download_mgr->InitializeRequest(info, handle);
650 download_mgr->SetUrlOptions(info);
651 curl_multi_add_handle(download_mgr->curl_multi_, handle);
652 curl_multi_socket_action(download_mgr->curl_multi_,
653 CURL_SOCKET_TIMEOUT,
654 0,
655 &still_running);
656 }
657
658 // Activity on curl sockets
659 // Within this loop the curl_multi_socket_action() may cause socket(s)
660 // to be removed from watch_fds_. If a socket is removed it is replaced
661 // by the socket at the end of the array and the inuse count is decreased.
662 // Therefore loop over the array in reverse order.
663 for (int64_t i = download_mgr->watch_fds_inuse_-1; i >= 2; --i) {
664 if (i >= download_mgr->watch_fds_inuse_) {
665 continue;
666 }
667 if (download_mgr->watch_fds_[i].revents) {
668 int ev_bitmask = 0;
669 if (download_mgr->watch_fds_[i].revents & (POLLIN | POLLPRI))
670 ev_bitmask |= CURL_CSELECT_IN;
671 if (download_mgr->watch_fds_[i].revents & (POLLOUT | POLLWRBAND))
672 ev_bitmask |= CURL_CSELECT_OUT;
673 if (download_mgr->watch_fds_[i].revents &
674 (POLLERR | POLLHUP | POLLNVAL))
675 {
676 ev_bitmask |= CURL_CSELECT_ERR;
677 }
678 download_mgr->watch_fds_[i].revents = 0;
679
680 curl_multi_socket_action(download_mgr->curl_multi_,
681 download_mgr->watch_fds_[i].fd,
682 ev_bitmask,
683 &still_running);
684 }
685 }
686
687 // Check if transfers are completed
688 CURLMsg *curl_msg;
689 int msgs_in_queue;
690 while ((curl_msg = curl_multi_info_read(download_mgr->curl_multi_,
691 &msgs_in_queue)))
692 {
693 if (curl_msg->msg == CURLMSG_DONE) {
694 perf::Inc(download_mgr->counters_->n_requests);
695 JobInfo *info;
696 CURL *easy_handle = curl_msg->easy_handle;
697 int curl_error = curl_msg->data.result;
698 curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &info);
699
700 int64_t redir_count;
701 curl_easy_getinfo(easy_handle, CURLINFO_REDIRECT_COUNT, &redir_count);
702 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
703 "Number of CURL redirects %" PRId64 ,
704 download_mgr->name_.c_str(), info->id(),
705 redir_count);
706
707 curl_multi_remove_handle(download_mgr->curl_multi_, easy_handle);
708 if (download_mgr->VerifyAndFinalize(curl_error, info)) {
709 curl_multi_add_handle(download_mgr->curl_multi_, easy_handle);
710 curl_multi_socket_action(download_mgr->curl_multi_,
711 CURL_SOCKET_TIMEOUT,
712 0,
713 &still_running);
714 } else {
715 // Return easy handle into pool and write result back
716 download_mgr->ReleaseCurlHandle(easy_handle);
717
718 DataTubeElement *ele = new DataTubeElement(kActionStop);
719 info->GetDataTubePtr()->EnqueueBack(ele);
720 info->GetPipeJobResultPtr()->
721 Write<download::Failures>(info->error_code());
722 }
723 }
724 }
725 }
726
727 for (set<CURL *>::iterator i = download_mgr->pool_handles_inuse_->begin(),
728 iEnd = download_mgr->pool_handles_inuse_->end(); i != iEnd; ++i)
729 {
730 curl_multi_remove_handle(download_mgr->curl_multi_, *i);
731 curl_easy_cleanup(*i);
732 }
733 download_mgr->pool_handles_inuse_->clear();
734 free(download_mgr->watch_fds_);
735
736 LogCvmfs(kLogDownload, kLogDebug,
737 "download I/O thread of DownloadManager '%s' terminated",
738 download_mgr->name_.c_str());
739 return NULL;
740 }
741
742
743 //------------------------------------------------------------------------------
744
745
746 160 HeaderLists::~HeaderLists() {
747
2/2
✓ Branch 1 taken 161 times.
✓ Branch 2 taken 160 times.
321 for (unsigned i = 0; i < blocks_.size(); ++i) {
748
1/2
✓ Branch 1 taken 161 times.
✗ Branch 2 not taken.
161 delete[] blocks_[i];
749 }
750 160 blocks_.clear();
751 160 }
752
753
754 880 curl_slist *HeaderLists::GetList(const char *header) {
755 880 return Get(header);
756 }
757
758
759 203 curl_slist *HeaderLists::DuplicateList(curl_slist *slist) {
760
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
203 assert(slist);
761 203 curl_slist *copy = GetList(slist->data);
762 203 copy->next = slist->next;
763 203 curl_slist *prev = copy;
764 203 slist = slist->next;
765
2/2
✓ Branch 0 taken 406 times.
✓ Branch 1 taken 203 times.
609 while (slist) {
766 406 curl_slist *new_link = Get(slist->data);
767 406 new_link->next = slist->next;
768 406 prev->next = new_link;
769 406 prev = new_link;
770 406 slist = slist->next;
771 }
772 203 return copy;
773 }
774
775
776 334 void HeaderLists::AppendHeader(curl_slist *slist, const char *header) {
777
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 334 times.
334 assert(slist);
778 334 curl_slist *new_link = Get(header);
779 334 new_link->next = NULL;
780
781
2/2
✓ Branch 0 taken 194 times.
✓ Branch 1 taken 334 times.
528 while (slist->next)
782 194 slist = slist->next;
783 334 slist->next = new_link;
784 334 }
785
786
787 /**
788 * Ensures that a certain header string is _not_ part of slist on return.
789 * Note that if the first header element matches, the returned slist points
790 * to a different value.
791 */
792 5 void HeaderLists::CutHeader(const char *header, curl_slist **slist) {
793
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 assert(slist);
794 curl_slist head;
795 5 head.next = *slist;
796 5 curl_slist *prev = &head;
797 5 curl_slist *rover = *slist;
798
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 5 times.
14 while (rover) {
799
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 if (strcmp(rover->data, header) == 0) {
800 4 prev->next = rover->next;
801 4 Put(rover);
802 4 rover = prev;
803 }
804 9 prev = rover;
805 9 rover = rover->next;
806 }
807 5 *slist = head.next;
808 5 }
809
810
811 463 void HeaderLists::PutList(curl_slist *slist) {
812
2/2
✓ Branch 0 taken 885 times.
✓ Branch 1 taken 463 times.
1348 while (slist) {
813 885 curl_slist *next = slist->next;
814 885 Put(slist);
815 885 slist = next;
816 }
817 463 }
818
819
820 4 string HeaderLists::Print(curl_slist *slist) {
821 4 string verbose;
822
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 while (slist) {
823
3/6
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
8 verbose += string(slist->data) + "\n";
824 8 slist = slist->next;
825 }
826 4 return verbose;
827 }
828
829
830 1620 curl_slist *HeaderLists::Get(const char *header) {
831
2/2
✓ Branch 1 taken 1459 times.
✓ Branch 2 taken 162 times.
1621 for (unsigned i = 0; i < blocks_.size(); ++i) {
832
2/2
✓ Branch 0 taken 70249 times.
✓ Branch 1 taken 1 times.
70250 for (unsigned j = 0; j < kBlockSize; ++j) {
833
2/2
✓ Branch 2 taken 1458 times.
✓ Branch 3 taken 68791 times.
70249 if (!IsUsed(&(blocks_[i][j]))) {
834 1458 blocks_[i][j].data = const_cast<char *>(header);
835 1458 return &(blocks_[i][j]);
836 }
837 }
838 }
839
840 // All used, new block
841 162 AddBlock();
842 162 blocks_[blocks_.size()-1][0].data = const_cast<char *>(header);
843 162 return &(blocks_[blocks_.size()-1][0]);
844 }
845
846
847 42361 void HeaderLists::Put(curl_slist *slist) {
848 42361 slist->data = NULL;
849 42361 slist->next = NULL;
850 42361 }
851
852
853 162 void HeaderLists::AddBlock() {
854
1/2
✓ Branch 1 taken 162 times.
✗ Branch 2 not taken.
162 curl_slist *new_block = new curl_slist[kBlockSize];
855
2/2
✓ Branch 0 taken 41472 times.
✓ Branch 1 taken 162 times.
41634 for (unsigned i = 0; i < kBlockSize; ++i) {
856 41472 Put(&new_block[i]);
857 }
858
1/2
✓ Branch 1 taken 162 times.
✗ Branch 2 not taken.
162 blocks_.push_back(new_block);
859 162 }
860
861
862 //------------------------------------------------------------------------------
863
864
865 string DownloadManager::ProxyInfo::Print() {
866 if (url == "DIRECT")
867 return url;
868
869 string result = url;
870 int remaining =
871 static_cast<int>(host.deadline()) - static_cast<int>(time(NULL));
872 string expinfo = (remaining >= 0) ? "+" : "";
873 if (abs(remaining) >= 3600) {
874 expinfo += StringifyInt(remaining/3600) + "h";
875 } else if (abs(remaining) >= 60) {
876 expinfo += StringifyInt(remaining/60) + "m";
877 } else {
878 expinfo += StringifyInt(remaining) + "s";
879 }
880 if (host.status() == dns::kFailOk) {
881 result += " (" + host.name() + ", " + expinfo + ")";
882 } else {
883 result += " (:unresolved:, " + expinfo + ")";
884 }
885 return result;
886 }
887
888
889 /**
890 * Gets an idle CURL handle from the pool. Creates a new one and adds it to
891 * the pool if necessary.
892 */
893 203 CURL *DownloadManager::AcquireCurlHandle() {
894 CURL *handle;
895
896
2/2
✓ Branch 1 taken 78 times.
✓ Branch 2 taken 125 times.
203 if (pool_handles_idle_->empty()) {
897 // Create a new handle
898
1/2
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
78 handle = curl_easy_init();
899
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
78 assert(handle != NULL);
900
901
1/2
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
78 curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1);
902 // curl_easy_setopt(curl_default, CURLOPT_FAILONERROR, 1);
903
1/2
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
78 curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, CallbackCurlHeader);
904
1/2
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
78 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, CallbackCurlData);
905 } else {
906 125 handle = *(pool_handles_idle_->begin());
907
1/2
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
125 pool_handles_idle_->erase(pool_handles_idle_->begin());
908 }
909
910
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 pool_handles_inuse_->insert(handle);
911
912 203 return handle;
913 }
914
915
916 203 void DownloadManager::ReleaseCurlHandle(CURL *handle) {
917
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 set<CURL *>::iterator elem = pool_handles_inuse_->find(handle);
918
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 203 times.
203 assert(elem != pool_handles_inuse_->end());
919
920
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
203 if (pool_handles_idle_->size() > pool_max_handles_) {
921 curl_easy_cleanup(*elem);
922 } else {
923
1/2
✓ Branch 2 taken 203 times.
✗ Branch 3 not taken.
203 pool_handles_idle_->insert(*elem);
924 }
925
926
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 pool_handles_inuse_->erase(elem);
927 203 }
928
929
930 /**
931 * HTTP request options: set the URL and other options such as timeout and
932 * proxy.
933 */
934 203 void DownloadManager::InitializeRequest(JobInfo *info, CURL *handle) {
935 // Initialize internal download state
936 203 info->SetCurlHandle(handle);
937 203 info->SetErrorCode(kFailOk);
938 203 info->SetHttpCode(-1);
939 203 info->SetFollowRedirects(follow_redirects_);
940 203 info->SetNumUsedProxies(1);
941 203 info->SetNumUsedMetalinks(1);
942 203 info->SetNumUsedHosts(1);
943 203 info->SetNumRetries(0);
944 203 info->SetBackoffMs(0);
945 203 info->SetHeaders(header_lists_->DuplicateList(default_headers_));
946
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
203 if (info->info_header()) {
947 header_lists_->AppendHeader(info->headers(), info->info_header());
948 }
949
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
203 if (enable_http_tracing_) {
950 for (unsigned int i = 0; i < http_tracing_headers_.size(); i++) {
951 header_lists_->AppendHeader(info->headers(),
952 (http_tracing_headers_)[i].c_str());
953 }
954
955 header_lists_->AppendHeader(info->headers(), info->tracing_header_pid());
956 header_lists_->AppendHeader(info->headers(), info->tracing_header_gid());
957 header_lists_->AppendHeader(info->headers(), info->tracing_header_uid());
958
959 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
960 "CURL Header for URL: %s is:\n %s",
961 name_.c_str(), info->id(), info->url()->c_str(),
962 header_lists_->Print(info->headers()).c_str());
963 }
964
965
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 200 times.
203 if (info->force_nocache()) {
966 3 SetNocache(info);
967 } else {
968 200 info->SetNocache(false);
969 }
970
2/2
✓ Branch 1 taken 129 times.
✓ Branch 2 taken 74 times.
203 if (info->compressed()) {
971 129 zlib::DecompressInit(info->GetZstreamPtr());
972 }
973
2/2
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 73 times.
203 if (info->expected_hash()) {
974
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 130 times.
130 assert(info->hash_context().buffer != NULL);
975 130 shash::Init(info->hash_context());
976 }
977
978
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 203 times.
203 if ((info->range_offset() != -1) && (info->range_size())) {
979 char byte_range_array[100];
980 const int64_t range_lower = static_cast<int64_t>(info->range_offset());
981 const int64_t range_upper = static_cast<int64_t>(
982 info->range_offset() + info->range_size() - 1);
983 if (snprintf(byte_range_array, sizeof(byte_range_array),
984 "%" PRId64 "-%" PRId64,
985 range_lower, range_upper) == 100)
986 {
987 PANIC(NULL); // Should be impossible given limits on offset size.
988 }
989 curl_easy_setopt(handle, CURLOPT_RANGE, byte_range_array);
990 } else {
991 203 curl_easy_setopt(handle, CURLOPT_RANGE, NULL);
992 }
993
994 // Set curl parameters
995 203 curl_easy_setopt(handle, CURLOPT_PRIVATE, static_cast<void *>(info));
996 203 curl_easy_setopt(handle, CURLOPT_WRITEHEADER,
997 static_cast<void *>(info));
998 203 curl_easy_setopt(handle, CURLOPT_WRITEDATA, static_cast<void *>(info));
999 203 curl_easy_setopt(handle, CURLOPT_HTTPHEADER, info->headers());
1000
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
203 if (info->head_request()) {
1001 curl_easy_setopt(handle, CURLOPT_NOBODY, 1);
1002 } else {
1003 203 curl_easy_setopt(handle, CURLOPT_HTTPGET, 1);
1004 }
1005
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
203 if (opt_ipv4_only_) {
1006 curl_easy_setopt(handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
1007 }
1008
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 149 times.
203 if (follow_redirects_) {
1009 54 curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
1010 54 curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 4);
1011 }
1012 #ifdef DEBUGMSG
1013 203 curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
1014 203 curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, CallbackCurlDebug);
1015 #endif
1016 203 }
1017
1018 412 void DownloadManager::CheckHostInfoReset(
1019 const std::string &typ,
1020 HostInfo &info,
1021 JobInfo *jobinfo,
1022 time_t &now)
1023 {
1024
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 412 times.
412 if (info.timestamp_backup > 0) {
1025 if (now == 0)
1026 now = time(NULL);
1027 if (static_cast<int64_t>(now) >
1028 static_cast<int64_t>(info.timestamp_backup + info.reset_after))
1029 {
1030 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
1031 "(manager %s - id %" PRId64 ") "
1032 "switching %s from %s to %s (reset %s)", name_.c_str(),
1033 jobinfo->id(), typ.c_str(), (*info.chain)[info.current].c_str(),
1034 (*info.chain)[0].c_str(), typ.c_str());
1035 info.current = 0;
1036 info.timestamp_backup = 0;
1037 }
1038 }
1039 412 }
1040
1041
1042 /**
1043 * Sets the URL specific options such as host to use and timeout. It might also
1044 * set an error code, in which case the further processing should react on.
1045 */
1046 206 void DownloadManager::SetUrlOptions(JobInfo *info) {
1047 206 CURL *curl_handle = info->curl_handle();
1048 206 string url_prefix;
1049 206 time_t now = 0;
1050
1051 206 MutexLockGuard m(lock_options_);
1052
1053 // sharding policy
1054
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 206 times.
206 if (sharding_policy_.UseCount() > 0) {
1055 if (info->proxy() != "") {
1056 // proxy already set, so this is a failover event
1057 perf::Inc(counters_->n_proxy_failover);
1058 }
1059 info->SetProxy(sharding_policy_->GetNextProxy(info->url(), info->proxy(),
1060 info->range_offset() == -1 ? 0 : info->range_offset()));
1061
1062 curl_easy_setopt(info->curl_handle(), CURLOPT_PROXY, info->proxy().c_str());
1063 } else { // no sharding policy
1064 // Check if proxy group needs to be reset from backup to primary
1065
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 206 times.
206 if (opt_timestamp_backup_proxies_ > 0) {
1066 now = time(NULL);
1067 if (static_cast<int64_t>(now) >
1068 static_cast<int64_t>(opt_timestamp_backup_proxies_ +
1069 opt_proxy_groups_reset_after_))
1070 {
1071 opt_proxy_groups_current_ = 0;
1072 opt_timestamp_backup_proxies_ = 0;
1073 RebalanceProxiesUnlocked("Reset proxy group from backup to primary");
1074 }
1075 }
1076 // Check if load-balanced proxies within the group need to be reset
1077
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 206 times.
206 if (opt_timestamp_failover_proxies_ > 0) {
1078 if (now == 0)
1079 now = time(NULL);
1080 if (static_cast<int64_t>(now) >
1081 static_cast<int64_t>(opt_timestamp_failover_proxies_ +
1082 opt_proxy_groups_reset_after_))
1083 {
1084 RebalanceProxiesUnlocked(
1085 "Reset load-balanced proxies within the active group");
1086 }
1087 }
1088
1089
1/2
✓ Branch 2 taken 206 times.
✗ Branch 3 not taken.
206 ProxyInfo *proxy = ChooseProxyUnlocked(info->expected_hash());
1090
6/6
✓ Branch 0 taken 85 times.
✓ Branch 1 taken 121 times.
✓ Branch 3 taken 81 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 202 times.
✓ Branch 6 taken 4 times.
206 if (!proxy || (proxy->url == "DIRECT")) {
1091
2/4
✓ Branch 2 taken 202 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 202 times.
✗ Branch 6 not taken.
202 info->SetProxy("DIRECT");
1092
1/2
✓ Branch 2 taken 202 times.
✗ Branch 3 not taken.
202 curl_easy_setopt(info->curl_handle(), CURLOPT_PROXY, "");
1093 } else {
1094 // Note: inside ValidateProxyIpsUnlocked() we may change the proxy data
1095 // structure, so we must not pass proxy->... (== current_proxy())
1096 // parameters directly
1097
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 std::string purl = proxy->url;
1098
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 dns::Host phost = proxy->host;
1099
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 const bool changed = ValidateProxyIpsUnlocked(purl, phost);
1100 // Current proxy may have changed
1101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (changed) {
1102 proxy = ChooseProxyUnlocked(info->expected_hash());
1103 }
1104
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 info->SetProxy(proxy->url);
1105
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 if (proxy->host.status() == dns::kFailOk) {
1106
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 curl_easy_setopt(info->curl_handle(), CURLOPT_PROXY,
1107 info->proxy().c_str());
1108 } else {
1109 // We know it can't work, don't even try to download
1110 curl_easy_setopt(info->curl_handle(), CURLOPT_PROXY, "0.0.0.0");
1111 }
1112 4 }
1113 } // end !sharding
1114
1115 // Check if metalink and host chains need to be reset
1116
2/4
✓ Branch 2 taken 206 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 206 times.
✗ Branch 6 not taken.
206 CheckHostInfoReset("metalink", opt_metalink_, info, now);
1117
2/4
✓ Branch 2 taken 206 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 206 times.
✗ Branch 6 not taken.
206 CheckHostInfoReset("host", opt_metalink_, info, now);
1118
1119
1/2
✓ Branch 1 taken 206 times.
✗ Branch 2 not taken.
206 curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_LIMIT, opt_low_speed_limit_);
1120
3/6
✓ Branch 1 taken 206 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 202 times.
206 if (info->proxy() != "DIRECT") {
1121
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, opt_timeout_proxy_);
1122
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_TIME, opt_timeout_proxy_);
1123 } else {
1124
1/2
✓ Branch 1 taken 202 times.
✗ Branch 2 not taken.
202 curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, opt_timeout_direct_);
1125
1/2
✓ Branch 1 taken 202 times.
✗ Branch 2 not taken.
202 curl_easy_setopt(curl_handle, CURLOPT_LOW_SPEED_TIME, opt_timeout_direct_);
1126 }
1127
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 206 times.
206 if (!opt_dns_server_.empty())
1128 curl_easy_setopt(curl_handle, CURLOPT_DNS_SERVERS, opt_dns_server_.c_str());
1129
1130
2/2
✓ Branch 1 taken 103 times.
✓ Branch 2 taken 103 times.
206 if (info->probe_hosts()) {
1131
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 103 times.
103 if (CheckMetalinkChain(now)) {
1132 url_prefix = (*opt_metalink_.chain)[opt_metalink_.current];
1133 info->SetCurrentMetalinkChainIndex(opt_metalink_.current);
1134 LogCvmfs(kLogDownload, kLogDebug, "(manager %s - id %" PRId64 ") "
1135 "reading from metalink %d",
1136 name_.c_str(), info->id(), opt_metalink_.current);
1137
1/2
✓ Branch 0 taken 103 times.
✗ Branch 1 not taken.
103 } else if (opt_host_.chain) {
1138
1/2
✓ Branch 2 taken 103 times.
✗ Branch 3 not taken.
103 url_prefix = (*opt_host_.chain)[opt_host_.current];
1139 103 info->SetCurrentHostChainIndex(opt_host_.current);
1140
1/2
✓ Branch 3 taken 103 times.
✗ Branch 4 not taken.
103 LogCvmfs(kLogDownload, kLogDebug, "(manager %s - id %" PRId64 ") "
1141 "reading from host %d",
1142 name_.c_str(), info->id(), opt_host_.current);
1143 }
1144 }
1145
1146
1/2
✓ Branch 2 taken 206 times.
✗ Branch 3 not taken.
206 string url = url_prefix + *(info->url());
1147
1148
1/2
✓ Branch 1 taken 206 times.
✗ Branch 2 not taken.
206 curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1L);
1149
2/6
✓ Branch 1 taken 206 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 206 times.
206 if (url.substr(0, 5) == "https") {
1150 bool rvb = ssl_certificate_store_.ApplySslCertificatePath(curl_handle);
1151 if (!rvb) {
1152 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
1153 "(manager %s - id %" PRId64 ") "
1154 "Failed to set SSL certificate path %s", name_.c_str(),
1155 info->id(), ssl_certificate_store_.GetCaPath().c_str());
1156 }
1157 if (info->pid() != -1) {
1158 if (credentials_attachment_ == NULL) {
1159 LogCvmfs(kLogDownload, kLogDebug, "(manager %s - id %" PRId64 ") "
1160 "uses secure downloads but no credentials attachment set",
1161 name_.c_str(), info->id());
1162 } else {
1163 bool retval = credentials_attachment_->ConfigureCurlHandle(
1164 curl_handle, info->pid(), info->GetCredDataPtr());
1165 if (!retval) {
1166 LogCvmfs(kLogDownload, kLogDebug, "(manager %s - id %" PRId64 ") "
1167 "failed attaching credentials",
1168 name_.c_str(), info->id());
1169 }
1170 }
1171 }
1172 // The download manager disables signal handling in the curl library;
1173 // as OpenSSL's implementation of TLS will generate a sigpipe in some
1174 // error paths, we must explicitly disable SIGPIPE here.
1175 // TODO(jblomer): it should be enough to do this once
1176 signal(SIGPIPE, SIG_IGN);
1177 }
1178
1179
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 206 times.
206 if (url.find("@proxy@") != string::npos) {
1180 // This is used in Geo-API requests (only), to replace a portion of the
1181 // URL with the current proxy name for the sake of caching the result.
1182 // Replace the @proxy@ either with a passed in "forced" template (which
1183 // is set from $CVMFS_PROXY_TEMPLATE) if there is one, or a "direct"
1184 // template (which is the uuid) if there's no proxy, or the name of the
1185 // proxy.
1186 string replacement;
1187 if (proxy_template_forced_ != "") {
1188 replacement = proxy_template_forced_;
1189 } else if (info->proxy() == "DIRECT") {
1190 replacement = proxy_template_direct_;
1191 } else {
1192 if (opt_proxy_groups_current_ >= opt_proxy_groups_fallback_) {
1193 // It doesn't make sense to use the fallback proxies in Geo-API requests
1194 // since the fallback proxies are supposed to get sorted, too.
1195 info->SetProxy("DIRECT");
1196 curl_easy_setopt(info->curl_handle(), CURLOPT_PROXY, "");
1197 replacement = proxy_template_direct_;
1198 } else {
1199 replacement = ChooseProxyUnlocked(info->expected_hash())->host.name();
1200 }
1201 }
1202 replacement = (replacement == "") ? proxy_template_direct_ : replacement;
1203 LogCvmfs(kLogDownload, kLogDebug, "(manager %s - id %" PRId64 ") "
1204 "replacing @proxy@ by %s",
1205 name_.c_str(), info->id(), replacement.c_str());
1206 url = ReplaceAll(url, "@proxy@", replacement);
1207 }
1208
1209 // TODO(heretherebedragons) before removing
1210 // static_cast<cvmfs::MemSink*>(info->sink)->size() == 0
1211 // and just always call info->sink->Reserve()
1212 // we should do a speed check
1213
4/6
✓ Branch 2 taken 206 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 89 times.
✓ Branch 5 taken 117 times.
✓ Branch 6 taken 89 times.
✗ Branch 7 not taken.
295 if ((info->sink() != NULL) && info->sink()->RequiresReserve() &&
1214
1/4
✓ Branch 1 taken 206 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
501 (static_cast<cvmfs::MemSink*>(info->sink())->size() == 0) &&
1215
10/17
✓ Branch 2 taken 89 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 89 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 76 times.
✓ Branch 8 taken 13 times.
✓ Branch 9 taken 89 times.
✓ Branch 10 taken 117 times.
✓ Branch 12 taken 89 times.
✓ Branch 13 taken 117 times.
✓ Branch 15 taken 76 times.
✓ Branch 16 taken 130 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
295 HasPrefix(url, "file://", false)) {
1216 platform_stat64 stat_buf;
1217 76 int retval = platform_stat(url.c_str(), &stat_buf);
1218
1/2
✓ Branch 0 taken 76 times.
✗ Branch 1 not taken.
76 if (retval != 0) {
1219 // this is an error: file does not exist or out of memory
1220 // error is caught in other code section.
1221
1/2
✓ Branch 2 taken 76 times.
✗ Branch 3 not taken.
76 info->sink()->Reserve(64ul * 1024ul);
1222 } else {
1223 info->sink()->Reserve(stat_buf.st_size);
1224 }
1225 }
1226
1227
2/4
✓ Branch 2 taken 206 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 206 times.
✗ Branch 7 not taken.
206 curl_easy_setopt(curl_handle, CURLOPT_URL,
1228 EscapeUrl(info->id(), url).c_str());
1229 206 }
1230
1231
1232 /**
1233 * Checks if the name resolving information is still up to date. The host
1234 * object should be one from the current load-balance group. If the information
1235 * changed, gather new set of resolved IPs and, if different, exchange them in
1236 * the load-balance group on the fly. In the latter case, also rebalance the
1237 * proxies. The options mutex needs to be open.
1238 *
1239 * Returns true if proxies may have changed.
1240 */
1241 4 bool DownloadManager::ValidateProxyIpsUnlocked(
1242 const string &url,
1243 const dns::Host &host)
1244 {
1245
2/4
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 if (!host.IsExpired())
1246 4 return false;
1247 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s') validate DNS entry for %s",
1248 name_.c_str(), host.name().c_str());
1249
1250 unsigned group_idx = opt_proxy_groups_current_;
1251 dns::Host new_host = resolver_->Resolve(host.name());
1252
1253 bool update_only = true; // No changes to the list of IP addresses.
1254 if (new_host.status() != dns::kFailOk) {
1255 // Try again later in case resolving fails.
1256 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
1257 "(manager '%s') failed to resolve IP addresses for %s (%d - %s)",
1258 name_.c_str(), host.name().c_str(), new_host.status(),
1259 dns::Code2Ascii(new_host.status()));
1260 new_host = dns::Host::ExtendDeadline(host, resolver_->min_ttl());
1261 } else if (!host.IsEquivalent(new_host)) {
1262 update_only = false;
1263 }
1264
1265 if (update_only) {
1266 for (unsigned i = 0; i < (*opt_proxy_groups_)[group_idx].size(); ++i) {
1267 if ((*opt_proxy_groups_)[group_idx][i].host.id() == host.id())
1268 (*opt_proxy_groups_)[group_idx][i].host = new_host;
1269 }
1270 return false;
1271 }
1272
1273 assert(new_host.status() == dns::kFailOk);
1274
1275 // Remove old host objects, insert new objects, and rebalance.
1276 LogCvmfs(kLogDownload, kLogDebug | kLogSyslog,
1277 "(manager '%s') DNS entries for proxy %s changed, adjusting",
1278 name_.c_str(), host.name().c_str());
1279 vector<ProxyInfo> *group = current_proxy_group();
1280 opt_num_proxies_ -= group->size();
1281 for (unsigned i = 0; i < group->size(); ) {
1282 if ((*group)[i].host.id() == host.id()) {
1283 group->erase(group->begin() + i);
1284 } else {
1285 i++;
1286 }
1287 }
1288 vector<ProxyInfo> new_infos;
1289 set<string> best_addresses = new_host.ViewBestAddresses(opt_ip_preference_);
1290 set<string>::const_iterator iter_ips = best_addresses.begin();
1291 for (; iter_ips != best_addresses.end(); ++iter_ips) {
1292 string url_ip = dns::RewriteUrl(url, *iter_ips);
1293 new_infos.push_back(ProxyInfo(new_host, url_ip));
1294 }
1295 group->insert(group->end(), new_infos.begin(), new_infos.end());
1296 opt_num_proxies_ += new_infos.size();
1297
1298 std::string msg = "DNS entries for proxy " + host.name() + " changed";
1299
1300 RebalanceProxiesUnlocked(msg);
1301 return true;
1302 }
1303
1304
1305 /**
1306 * Adds transfer time and downloaded bytes to the global counters.
1307 */
1308 214 void DownloadManager::UpdateStatistics(CURL *handle) {
1309 double val;
1310 int retval;
1311 214 int64_t sum = 0;
1312
1313
1/2
✓ Branch 1 taken 214 times.
✗ Branch 2 not taken.
214 retval = curl_easy_getinfo(handle, CURLINFO_SIZE_DOWNLOAD, &val);
1314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 214 times.
214 assert(retval == CURLE_OK);
1315 214 sum += static_cast<int64_t>(val);
1316 /*retval = curl_easy_getinfo(handle, CURLINFO_HEADER_SIZE, &val);
1317 assert(retval == CURLE_OK);
1318 sum += static_cast<int64_t>(val);*/
1319 214 perf::Xadd(counters_->sz_transferred_bytes, sum);
1320 214 }
1321
1322
1323 /**
1324 * Retry if possible if not on no-cache and if not already done too often.
1325 */
1326 214 bool DownloadManager::CanRetry(const JobInfo *info) {
1327 214 MutexLockGuard m(lock_options_);
1328 214 unsigned max_retries = opt_max_retries_;
1329
1330
5/6
✓ Branch 1 taken 208 times.
✓ Branch 2 taken 6 times.
✓ Branch 4 taken 76 times.
✓ Branch 5 taken 132 times.
✓ Branch 6 taken 76 times.
✗ Branch 7 not taken.
366 return !(info->nocache()) && (info->num_retries() < max_retries) &&
1331
2/2
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 71 times.
152 (IsProxyTransferError(info->error_code()) ||
1332 76 IsHostTransferError(info->error_code()));
1333 214 }
1334
1335 /**
1336 * Backoff for retry to introduce a jitter into a cluster of requesting
1337 * cvmfs nodes.
1338 * Retry only when HTTP caching is on.
1339 *
1340 * \return true if backoff has been performed, false otherwise
1341 */
1342 5 void DownloadManager::Backoff(JobInfo *info) {
1343 5 unsigned backoff_init_ms = 0;
1344 5 unsigned backoff_max_ms = 0;
1345 {
1346 5 MutexLockGuard m(lock_options_);
1347 5 backoff_init_ms = opt_backoff_init_ms_;
1348 5 backoff_max_ms = opt_backoff_max_ms_;
1349 5 }
1350
1351 5 info->SetNumRetries(info->num_retries() + 1);
1352 5 perf::Inc(counters_->n_retries);
1353
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
5 if (info->backoff_ms() == 0) {
1354 2 info->SetBackoffMs(prng_.Next(backoff_init_ms + 1)); // Must be != 0
1355 } else {
1356 3 info->SetBackoffMs(info->backoff_ms() * 2);
1357 }
1358
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 4 times.
5 if (info->backoff_ms() > backoff_max_ms) {
1359 1 info->SetBackoffMs(backoff_max_ms);
1360 }
1361
1362 5 LogCvmfs(kLogDownload, kLogDebug,
1363 "(manager '%s' - id %" PRId64 ") backing off for %d ms",
1364 name_.c_str(), info->id(), info->backoff_ms());
1365 5 SafeSleepMs(info->backoff_ms());
1366 5 }
1367
1368 6 void DownloadManager::SetNocache(JobInfo *info) {
1369
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if (info->nocache())
1370 return;
1371 6 header_lists_->AppendHeader(info->headers(), "Pragma: no-cache");
1372 6 header_lists_->AppendHeader(info->headers(), "Cache-Control: no-cache");
1373 6 curl_easy_setopt(info->curl_handle(), CURLOPT_HTTPHEADER, info->headers());
1374 6 info->SetNocache(true);
1375 }
1376
1377
1378 /**
1379 * Reverse operation of SetNocache. Makes sure that "no-cache" header
1380 * disappears from the list of headers to let proxies work normally.
1381 */
1382 11 void DownloadManager::SetRegularCache(JobInfo *info) {
1383
1/2
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
11 if (info->nocache() == false)
1384 11 return;
1385 header_lists_->CutHeader("Pragma: no-cache", info->GetHeadersPtr());
1386 header_lists_->CutHeader("Cache-Control: no-cache", info->GetHeadersPtr());
1387 curl_easy_setopt(info->curl_handle(), CURLOPT_HTTPHEADER, info->headers());
1388 info->SetNocache(false);
1389 }
1390
1391
1392 /**
1393 * Frees the storage associated with the authz attachment from the job
1394 */
1395 206 void DownloadManager::ReleaseCredential(JobInfo *info) {
1396
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 206 times.
206 if (info->cred_data()) {
1397 assert(credentials_attachment_ != NULL); // Someone must have set it
1398 credentials_attachment_->ReleaseCurlHandle(info->curl_handle(),
1399 info->cred_data());
1400 info->SetCredData(NULL);
1401 }
1402 206 }
1403
1404
1405 /* Sort links based on the "pri=" parameter */
1406 static bool sortlinks(const std::string &s1, const std::string &s2) {
1407 const size_t pos1 = s1.find("; pri=");
1408 const size_t pos2 = s2.find("; pri=");
1409 int pri1, pri2;
1410 if ((pos1 != std::string::npos) &&
1411 (pos2 != std::string::npos) &&
1412 (sscanf(s1.substr(pos1+6).c_str(), "%d", &pri1) == 1) &&
1413 (sscanf(s2.substr(pos2+6).c_str(), "%d", &pri2) == 1)) {
1414 return pri1 < pri2;
1415 }
1416 return false;
1417 }
1418
1419 /**
1420 * Parses Link header and uses it to set a new host chain.
1421 * See rfc6249.
1422 */
1423 void DownloadManager::ProcessLink(JobInfo *info) {
1424
1425 std::vector<std::string> links = SplitString(info->link(), ',');
1426 if (info->link().find("; pri=") != std::string::npos)
1427 std::sort(links.begin(), links.end(), sortlinks);
1428
1429 std::vector<std::string> host_list;
1430
1431 std::vector<std::string>::const_iterator il = links.begin();
1432 for (; il != links.end(); ++il) {
1433 const std::string &link = *il;
1434 if ((link.find("; rel=duplicate") == std::string::npos) &&
1435 (link.find("; rel=\"duplicate\"") == std::string::npos)) {
1436 LogCvmfs(kLogDownload, kLogDebug,
1437 "skipping link '%s' because it does not contain rel=duplicate",
1438 link.c_str());
1439 continue;
1440 }
1441 // ignore depth= field since there's nothing useful we can do with it
1442
1443 size_t start = link.find('<');
1444 if (start == std::string::npos) {
1445 LogCvmfs(kLogDownload, kLogDebug,
1446 "skipping link '%s' because it does not have a left angle bracket",
1447 link.c_str());
1448 continue;
1449 }
1450
1451 start++;
1452 if ((link.substr(start, 7) != "http://") &&
1453 (link.substr(start, 8) != "https://")) {
1454 LogCvmfs(kLogDownload, kLogDebug,
1455 "skipping link '%s' of unrecognized url protocol", link.c_str());
1456 continue;
1457 }
1458
1459 size_t end = link.find('/', start+8);
1460 if (end == std::string::npos)
1461 end = link.find('>');
1462 if (end == std::string::npos) {
1463 LogCvmfs(kLogDownload, kLogDebug,
1464 "skipping link '%s' because no slash in url and no right angle bracket",
1465 link.c_str());
1466 continue;
1467 }
1468 const std::string host = link.substr(start, end-start);
1469 LogCvmfs(kLogDownload, kLogDebug, "adding linked host '%s'", host.c_str());
1470 host_list.push_back(host);
1471 }
1472
1473 if (host_list.size() > 0) {
1474 SetHostChain(host_list);
1475 opt_metalink_timestamp_link_ = time(NULL);
1476 }
1477 }
1478
1479
1480 /**
1481 * Checks the result of a curl download and implements the failure logic, such
1482 * as changing the proxy server. Takes care of cleanup.
1483 *
1484 * \return true if another download should be performed, false otherwise
1485 */
1486 214 bool DownloadManager::VerifyAndFinalize(const int curl_error, JobInfo *info) {
1487
1/2
✓ Branch 6 taken 214 times.
✗ Branch 7 not taken.
214 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
1488 "Verify downloaded url %s, proxy %s (curl error %d)",
1489 name_.c_str(), info->id(), info->url()->c_str(),
1490
1/2
✓ Branch 1 taken 214 times.
✗ Branch 2 not taken.
428 info->proxy().c_str(), curl_error);
1491
1/2
✓ Branch 2 taken 214 times.
✗ Branch 3 not taken.
214 UpdateStatistics(info->curl_handle());
1492
1493 bool was_metalink;
1494 214 std::string typ;
1495
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 214 times.
214 if (info->current_metalink_chain_index() >= 0) {
1496 was_metalink = true;
1497 typ = "metalink";
1498 if (info->link() != "") {
1499 // process Link header whether or not the redirected URL got an error
1500 ProcessLink(info);
1501 }
1502 } else {
1503 214 was_metalink = false;
1504
1/2
✓ Branch 1 taken 214 times.
✗ Branch 2 not taken.
214 typ = "host";
1505 }
1506
1507
1508 // Verification and error classification
1509
4/14
✓ Branch 0 taken 194 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 15 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
214 switch (curl_error) {
1510 194 case CURLE_OK:
1511 // Verify content hash
1512
2/2
✓ Branch 1 taken 125 times.
✓ Branch 2 taken 69 times.
194 if (info->expected_hash()) {
1513
1/2
✓ Branch 1 taken 125 times.
✗ Branch 2 not taken.
125 shash::Any match_hash;
1514
1/2
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
125 shash::Final(info->hash_context(), &match_hash);
1515
3/4
✓ Branch 2 taken 125 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 119 times.
125 if (match_hash != *(info->expected_hash())) {
1516
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (ignore_signature_failures_) {
1517 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogErr,
1518 "(manager '%s' - id %" PRId64 ") "
1519 "ignoring failed hash verification of %s (expected %s, got %s)",
1520 name_.c_str(), info->id(), info->url()->c_str(),
1521 info->expected_hash()->ToString().c_str(),
1522 match_hash.ToString().c_str());
1523 } else {
1524
1/2
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
12 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
1525 "hash verification of %s failed (expected %s, got %s)",
1526 name_.c_str(), info->id(), info->url()->c_str(),
1527
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
12 info->expected_hash()->ToString().c_str(),
1528
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
12 match_hash.ToString().c_str());
1529 6 info->SetErrorCode(kFailBadData);
1530 6 break;
1531 }
1532 }
1533 }
1534
1535 188 info->SetErrorCode(kFailOk);
1536 188 break;
1537 case CURLE_UNSUPPORTED_PROTOCOL:
1538 info->SetErrorCode(kFailUnsupportedProtocol);
1539 break;
1540 2 case CURLE_URL_MALFORMAT:
1541 2 info->SetErrorCode(kFailBadUrl);
1542 2 break;
1543 case CURLE_COULDNT_RESOLVE_PROXY:
1544 info->SetErrorCode(kFailProxyResolve);
1545 break;
1546 case CURLE_COULDNT_RESOLVE_HOST:
1547 info->SetErrorCode(kFailHostResolve);
1548 break;
1549 3 case CURLE_OPERATION_TIMEDOUT:
1550
2/5
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 info->SetErrorCode((info->proxy() == "DIRECT") ?
1551 kFailHostTooSlow : kFailProxyTooSlow);
1552 3 break;
1553 case CURLE_PARTIAL_FILE:
1554 case CURLE_GOT_NOTHING:
1555 case CURLE_RECV_ERROR:
1556 info->SetErrorCode((info->proxy() == "DIRECT") ?
1557 kFailHostShortTransfer : kFailProxyShortTransfer);
1558 break;
1559 15 case CURLE_FILE_COULDNT_READ_FILE:
1560 case CURLE_COULDNT_CONNECT:
1561
3/6
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 14 times.
15 if (info->proxy() != "DIRECT") {
1562 // This is a guess. Fail-over can still change to switching host
1563 1 info->SetErrorCode(kFailProxyConnection);
1564 } else {
1565 14 info->SetErrorCode(kFailHostConnection);
1566 }
1567 15 break;
1568 case CURLE_TOO_MANY_REDIRECTS:
1569 info->SetErrorCode(kFailHostConnection);
1570 break;
1571 case CURLE_SSL_CACERT_BADFILE:
1572 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogErr,
1573 "(manager '%s' -id %" PRId64 ") "
1574 "Failed to load certificate bundle. "
1575 "X509_CERT_BUNDLE might point to the wrong location.",
1576 name_.c_str(), info->id());
1577 info->SetErrorCode(kFailHostConnection);
1578 break;
1579 // As of curl 7.62.0, CURLE_SSL_CACERT is the same as
1580 // CURLE_PEER_FAILED_VERIFICATION
1581 case CURLE_PEER_FAILED_VERIFICATION:
1582 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogErr,
1583 "(manager '%s' - id %" PRId64 ") "
1584 "invalid SSL certificate of remote host. "
1585 "X509_CERT_DIR and/or X509_CERT_BUNDLE might point to the wrong "
1586 "location.", name_.c_str(), info->id());
1587 info->SetErrorCode(kFailHostConnection);
1588 break;
1589 case CURLE_ABORTED_BY_CALLBACK:
1590 case CURLE_WRITE_ERROR:
1591 // Error set by callback
1592 break;
1593 case CURLE_SEND_ERROR:
1594 // The curl error CURLE_SEND_ERROR can be seen when a cache is misbehaving
1595 // and closing connections before the http request send is completed.
1596 // Handle this error, treating it as a short transfer error.
1597 info->SetErrorCode((info->proxy() == "DIRECT") ?
1598 kFailHostShortTransfer : kFailProxyShortTransfer);
1599 break;
1600 default:
1601 LogCvmfs(kLogDownload, kLogSyslogErr, "(manager '%s' - id %" PRId64 ") "
1602 "unexpected curl error (%d) while trying to fetch %s",
1603 name_.c_str(), info->id(), curl_error, info->url()->c_str());
1604 info->SetErrorCode(kFailOther);
1605 break;
1606 }
1607
1608 std::vector<std::string> *host_chain;
1609 unsigned char num_used_hosts;
1610
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 214 times.
214 if (was_metalink) {
1611 host_chain = opt_metalink_.chain;
1612 num_used_hosts = info->num_used_metalinks();
1613 } else {
1614 214 host_chain = opt_host_.chain;
1615 214 num_used_hosts = info->num_used_hosts();
1616 }
1617
1618 // Determination if download should be repeated
1619 214 bool try_again = false;
1620
1/2
✓ Branch 1 taken 214 times.
✗ Branch 2 not taken.
214 bool same_url_retry = CanRetry(info);
1621
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 188 times.
214 if (info->error_code() != kFailOk) {
1622 26 MutexLockGuard m(lock_options_);
1623
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 20 times.
26 if (info->error_code() == kFailBadData) {
1624
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
6 if (!info->nocache()) {
1625 3 try_again = true;
1626 } else {
1627 // Make it a host failure
1628
1/2
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
1629 "(manager '%s' - id %" PRId64 ") "
1630 "data corruption with no-cache header, try another %s",
1631 name_.c_str(), info->id(), typ.c_str());
1632
1633 3 info->SetErrorCode(kFailHostHttp);
1634 }
1635 }
1636
5/6
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 18 times.
68 if ( same_url_retry || (
1637
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 12 times.
42 ( (info->error_code() == kFailHostResolve) ||
1638
2/2
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 6 times.
30 IsHostTransferError(info->error_code()) ||
1639
2/2
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 6 times.
24 (info->error_code() == kFailHostHttp)) &&
1640
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
24 info->probe_hosts() &&
1641
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
9 host_chain && (num_used_hosts < host_chain->size()))
1642 )
1643 {
1644 8 try_again = true;
1645 }
1646
5/6
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 20 times.
68 if ( same_url_retry || (
1647
2/2
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 1 times.
42 ( (info->error_code() == kFailProxyResolve) ||
1648
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
41 IsProxyTransferError(info->error_code()) ||
1649 20 (info->error_code() == kFailProxyHttp)) )
1650 )
1651 {
1652
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
6 if (sharding_policy_.UseCount() > 0) { // sharding policy
1653 try_again = true;
1654 same_url_retry = false;
1655 } else { // no sharding
1656 6 try_again = true;
1657 // If all proxies failed, do a next round with the next host
1658
4/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
6 if (!same_url_retry && (info->num_used_proxies() >= opt_num_proxies_)) {
1659 // Check if this can be made a host fail-over
1660 if (info->probe_hosts() &&
1661 host_chain && (num_used_hosts < host_chain->size()))
1662 {
1663 // reset proxy group if not already performed by other handle
1664 if (opt_proxy_groups_) {
1665 if ((opt_proxy_groups_current_ > 0) ||
1666 (opt_proxy_groups_current_burned_ > 0))
1667 {
1668 opt_proxy_groups_current_ = 0;
1669 opt_timestamp_backup_proxies_ = 0;
1670 const std::string msg =
1671 "reset proxies for " + typ + " failover";
1672 RebalanceProxiesUnlocked(msg);
1673 }
1674 }
1675
1676 // Make it a host failure
1677 LogCvmfs(kLogDownload, kLogDebug,
1678 "(manager '%s' - id %" PRId64 ") make it a %s failure",
1679 name_.c_str(), info->id(), typ.c_str());
1680 info->SetNumUsedProxies(1);
1681 info->SetErrorCode(kFailHostAfterProxy);
1682 } else {
1683 if (failover_indefinitely_) {
1684 // Instead of giving up, reset the num_used_proxies counter,
1685 // switch proxy and try again
1686 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
1687 "(manager '%s' - id %" PRId64 ") "
1688 "VerifyAndFinalize() would fail the download here. "
1689 "Instead switch proxy and retry download. "
1690 "typ=%s "
1691 "info->probe_hosts=%d host_chain=%p num_used_hosts=%d "
1692 "host_chain->size()=%lu same_url_retry=%d "
1693 "info->num_used_proxies=%d opt_num_proxies_=%d",
1694 name_.c_str(), info->id(), typ.c_str(),
1695 static_cast<int>(info->probe_hosts()),
1696 host_chain, num_used_hosts,
1697 host_chain ?
1698 host_chain->size() : -1, static_cast<int>(same_url_retry),
1699 info->num_used_proxies(), opt_num_proxies_);
1700 info->SetNumUsedProxies(1);
1701 RebalanceProxiesUnlocked(
1702 "download failed - failover indefinitely");
1703 try_again = !Interrupted(fqrn_, info);
1704 } else {
1705 try_again = false;
1706 }
1707 }
1708 } // Make a proxy failure a host failure
1709 } // Proxy failure assumed
1710 } // end !sharding
1711 26 }
1712
1713
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 202 times.
214 if (try_again) {
1714
1/2
✓ Branch 3 taken 12 times.
✗ Branch 4 not taken.
12 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
1715 "Trying again on same curl handle, same url: %d, "
1716 "error code %d no-cache %d",
1717 name_.c_str(), info->id(), same_url_retry,
1718 12 info->error_code(), info->nocache());
1719 // Reset internal state and destination
1720
4/8
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
12 if (info->sink() != NULL && info->sink()->Reset() != 0) {
1721 info->SetErrorCode(kFailLocalIO);
1722 goto verify_and_finalize_stop;
1723 }
1724
6/8
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 11 times.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 11 times.
12 if (info->interrupt_cue() && info->interrupt_cue()->IsCanceled()) {
1725 1 info->SetErrorCode(kFailCanceled);
1726 1 goto verify_and_finalize_stop;
1727 }
1728
1729
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 5 times.
11 if (info->expected_hash()) {
1730
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 shash::Init(info->hash_context());
1731 }
1732
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 5 times.
11 if (info->compressed()) {
1733
1/2
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 zlib::DecompressInit(info->GetZstreamPtr());
1734 }
1735
1736
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
11 if (sharding_policy_.UseCount() > 0) { // sharding policy
1737 ReleaseCredential(info);
1738 SetUrlOptions(info);
1739 } else { // no sharding policy
1740
1/2
✓ Branch 1 taken 11 times.
✗ Branch 2 not taken.
11 SetRegularCache(info);
1741
1742 // Failure handling
1743 11 bool switch_proxy = false;
1744 11 bool switch_host = false;
1745
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
11 switch (info->error_code()) {
1746 3 case kFailBadData:
1747
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 SetNocache(info);
1748 3 break;
1749 case kFailProxyResolve:
1750 case kFailProxyHttp:
1751 switch_proxy = true;
1752 break;
1753 case kFailHostResolve:
1754 case kFailHostHttp:
1755 case kFailHostAfterProxy:
1756 switch_host = true;
1757 break;
1758 8 default:
1759
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 7 times.
8 if (IsProxyTransferError(info->error_code())) {
1760
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (same_url_retry) {
1761 Backoff(info);
1762 } else {
1763 1 switch_proxy = true;
1764 }
1765
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 } else if (IsHostTransferError(info->error_code())) {
1766
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 if (same_url_retry) {
1767
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 Backoff(info);
1768 } else {
1769 2 switch_host = true;
1770 }
1771 } else {
1772 // No other errors expected when retrying
1773 PANIC(NULL);
1774 }
1775 }
1776
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
11 if (switch_proxy) {
1777
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 ReleaseCredential(info);
1778
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 SwitchProxy(info);
1779 1 info->SetNumUsedProxies(info->num_used_proxies() + 1);
1780
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 SetUrlOptions(info);
1781 }
1782
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9 times.
11 if (switch_host) {
1783
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 ReleaseCredential(info);
1784
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (was_metalink) {
1785 SwitchMetalink(info);
1786 info->SetNumUsedMetalinks(num_used_hosts + 1);
1787 } else {
1788
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 SwitchHost(info);
1789 2 info->SetNumUsedHosts(num_used_hosts + 1);
1790 }
1791
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 SetUrlOptions(info);
1792 }
1793 } // end !sharding
1794
1795
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
11 if (failover_indefinitely_) {
1796 // try again, breaking if there's a cvmfs reload happening and we are in a
1797 // proxy failover. This will EIO the call application.
1798 return !Interrupted(fqrn_, info);
1799 }
1800 11 return true; // try again
1801 }
1802
1803 202 verify_and_finalize_stop:
1804 // Finalize, flush destination file
1805
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 ReleaseCredential(info);
1806
4/8
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 203 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 203 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 203 times.
203 if (info->sink() != NULL && info->sink()->Flush() != 0) {
1807 info->SetErrorCode(kFailLocalIO);
1808 }
1809
1810
2/2
✓ Branch 1 taken 129 times.
✓ Branch 2 taken 74 times.
203 if (info->compressed())
1811
1/2
✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
129 zlib::DecompressFini(info->GetZstreamPtr());
1812
1813
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 if (info->headers()) {
1814
1/2
✓ Branch 2 taken 203 times.
✗ Branch 3 not taken.
203 header_lists_->PutList(info->headers());
1815 203 info->SetHeaders(NULL);
1816 }
1817
1818 203 return false; // stop transfer and return to Fetch()
1819 214 }
1820
1821 158 DownloadManager::~DownloadManager() {
1822 // cleaned up fini
1823
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
158 if (sharding_policy_.UseCount() > 0) {
1824 sharding_policy_.Reset();
1825 }
1826
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
158 if (health_check_.UseCount() > 0) {
1827 if (health_check_.Unique()) {
1828 LogCvmfs(kLogDownload, kLogDebug,
1829 "(manager '%s') Stopping healthcheck thread", name_.c_str());
1830 health_check_->StopHealthcheck();
1831 }
1832 health_check_.Reset();
1833 }
1834
1835
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
158 if (atomic_xadd32(&multi_threaded_, 0) == 1) {
1836 // Shutdown I/O thread
1837 pipe_terminate_->Write(kPipeTerminateSignal);
1838 pthread_join(thread_download_, NULL);
1839 // All handles are removed from the multi stack
1840 pipe_terminate_.Destroy();
1841 pipe_jobs_.Destroy();
1842 }
1843
1844 316 for (set<CURL *>::iterator i = pool_handles_idle_->begin(),
1845
2/2
✓ Branch 3 taken 78 times.
✓ Branch 4 taken 158 times.
394 iEnd = pool_handles_idle_->end(); i != iEnd; ++i)
1846 {
1847 78 curl_easy_cleanup(*i);
1848 }
1849
1850
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 delete pool_handles_idle_;
1851
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 delete pool_handles_inuse_;
1852 158 curl_multi_cleanup(curl_multi_);
1853
1854
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 delete header_lists_;
1855
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 if (user_agent_)
1856 158 free(user_agent_);
1857
1858
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 delete counters_;
1859
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 80 times.
158 delete opt_host_.chain;
1860
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 80 times.
158 delete opt_host_chain_rtt_;
1861
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 91 times.
158 delete opt_proxy_groups_;
1862
1863 158 curl_global_cleanup();
1864
1/2
✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
158 delete resolver_;
1865
1866 // old destructor
1867 158 pthread_mutex_destroy(lock_options_);
1868 158 pthread_mutex_destroy(lock_synchronous_mode_);
1869 158 free(lock_options_);
1870 158 free(lock_synchronous_mode_);
1871 158 }
1872
1873 158 void DownloadManager::InitHeaders() {
1874 // User-Agent
1875
1/2
✓ Branch 2 taken 158 times.
✗ Branch 3 not taken.
158 string cernvm_id = "User-Agent: cvmfs ";
1876 #ifdef CVMFS_LIBCVMFS
1877
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 cernvm_id += "libcvmfs ";
1878 #else
1879 cernvm_id += "Fuse ";
1880 #endif
1881
2/4
✓ Branch 2 taken 158 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 158 times.
✗ Branch 6 not taken.
158 cernvm_id += string(CVMFS_VERSION);
1882
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
158 if (getenv("CERNVM_UUID") != NULL) {
1883 cernvm_id += " " +
1884 sanitizer::InputSanitizer("az AZ 09 -").Filter(getenv("CERNVM_UUID"));
1885 }
1886 158 user_agent_ = strdup(cernvm_id.c_str());
1887
1888
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 header_lists_ = new HeaderLists();
1889
1890
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 default_headers_ = header_lists_->GetList("Connection: Keep-Alive");
1891
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 header_lists_->AppendHeader(default_headers_, "Pragma:");
1892
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 header_lists_->AppendHeader(default_headers_, user_agent_);
1893 158 }
1894
1895 158 DownloadManager::DownloadManager(const unsigned max_pool_handles,
1896 const perf::StatisticsTemplate &statistics,
1897 158 const std::string &name) :
1898 158 prng_(Prng()),
1899 158 pool_handles_idle_(new set<CURL *>),
1900 158 pool_handles_inuse_(new set<CURL *>),
1901 158 pool_max_handles_(max_pool_handles),
1902 158 pipe_terminate_(NULL),
1903
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 pipe_jobs_(NULL),
1904 158 watch_fds_(NULL),
1905 158 watch_fds_size_(0),
1906 158 watch_fds_inuse_(0),
1907 158 watch_fds_max_(4 * max_pool_handles),
1908 158 opt_timeout_proxy_(5),
1909 158 opt_timeout_direct_(10),
1910 158 opt_low_speed_limit_(1024),
1911 158 opt_max_retries_(0),
1912 158 opt_backoff_init_ms_(0),
1913 158 opt_backoff_max_ms_(0),
1914 158 enable_info_header_(false),
1915 158 opt_ipv4_only_(false),
1916 158 follow_redirects_(false),
1917 158 ignore_signature_failures_(false),
1918 158 enable_http_tracing_(false),
1919 158 opt_metalink_(NULL, 0, 0, 0),
1920 158 opt_metalink_timestamp_link_(0),
1921 158 opt_host_(NULL, 0, 0, 0),
1922 158 opt_host_chain_rtt_(NULL),
1923 158 opt_proxy_groups_(NULL),
1924 158 opt_proxy_groups_current_(0),
1925 158 opt_proxy_groups_current_burned_(0),
1926 158 opt_proxy_groups_fallback_(0),
1927 158 opt_num_proxies_(0),
1928 158 opt_proxy_shard_(false),
1929 158 failover_indefinitely_(false),
1930
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 name_(name),
1931 158 opt_ip_preference_(dns::kIpPreferSystem),
1932 158 opt_timestamp_backup_proxies_(0),
1933 158 opt_timestamp_failover_proxies_(0),
1934 158 opt_proxy_groups_reset_after_(0),
1935 158 credentials_attachment_(NULL),
1936
4/8
✓ Branch 12 taken 158 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 158 times.
✗ Branch 16 not taken.
✓ Branch 18 taken 158 times.
✗ Branch 19 not taken.
✓ Branch 22 taken 158 times.
✗ Branch 23 not taken.
474 counters_(new Counters(statistics))
1937 {
1938 158 atomic_init32(&multi_threaded_);
1939
1940 158 lock_options_ =
1941 158 reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
1942 158 int retval = pthread_mutex_init(lock_options_, NULL);
1943
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
158 assert(retval == 0);
1944 158 lock_synchronous_mode_ =
1945 158 reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
1946 158 retval = pthread_mutex_init(lock_synchronous_mode_, NULL);
1947
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
158 assert(retval == 0);
1948
1949
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 retval = curl_global_init(CURL_GLOBAL_ALL);
1950
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
158 assert(retval == CURLE_OK);
1951
1952
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 InitHeaders();
1953
1954
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 curl_multi_ = curl_multi_init();
1955
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
158 assert(curl_multi_ != NULL);
1956
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 curl_multi_setopt(curl_multi_, CURLMOPT_SOCKETFUNCTION, CallbackCurlSocket);
1957
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 curl_multi_setopt(curl_multi_, CURLMOPT_SOCKETDATA,
1958 static_cast<void *>(this));
1959
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 curl_multi_setopt(curl_multi_, CURLMOPT_MAXCONNECTS, watch_fds_max_);
1960
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 curl_multi_setopt(curl_multi_, CURLMOPT_MAX_TOTAL_CONNECTIONS,
1961 pool_max_handles_);
1962
1963 158 prng_.InitLocaltime();
1964
1965 // Name resolving
1966
2/4
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 158 times.
158 if ((getenv("CVMFS_IPV4_ONLY") != NULL) &&
1967 (strlen(getenv("CVMFS_IPV4_ONLY")) > 0))
1968 {
1969 opt_ipv4_only_ = true;
1970 }
1971
1/2
✓ Branch 1 taken 158 times.
✗ Branch 2 not taken.
158 resolver_ = dns::NormalResolver::Create(opt_ipv4_only_,
1972 kDnsDefaultRetries, kDnsDefaultTimeoutMs);
1973
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
158 assert(resolver_);
1974 158 }
1975
1976 /**
1977 * Spawns the I/O worker thread and switches the module in multi-threaded mode.
1978 * No way back except Fini(); Init();
1979 */
1980 void DownloadManager::Spawn() {
1981 pipe_terminate_ = new Pipe<kPipeThreadTerminator>();
1982 pipe_jobs_ = new Pipe<kPipeDownloadJobs>();
1983
1984 int retval = pthread_create(&thread_download_, NULL, MainDownload,
1985 static_cast<void *>(this));
1986 assert(retval == 0);
1987
1988 atomic_inc32(&multi_threaded_);
1989
1990 if (health_check_.UseCount() > 0) {
1991 LogCvmfs(kLogDownload, kLogDebug,
1992 "(manager '%s') Starting healthcheck thread", name_.c_str());
1993 health_check_->StartHealthcheck();
1994 }
1995 }
1996
1997
1998 /**
1999 * Downloads data from an insecure outside channel (currently HTTP or file).
2000 */
2001 203 Failures DownloadManager::Fetch(JobInfo *info) {
2002
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
203 assert(info != NULL);
2003
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
203 assert(info->url() != NULL);
2004
2005 Failures result;
2006
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 result = PrepareDownloadDestination(info);
2007
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
203 if (result != kFailOk)
2008 return result;
2009
2010
2/2
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 73 times.
203 if (info->expected_hash()) {
2011 130 const shash::Algorithms algorithm = info->expected_hash()->algorithm;
2012 130 info->GetHashContextPtr()->algorithm = algorithm;
2013
1/2
✓ Branch 1 taken 130 times.
✗ Branch 2 not taken.
130 info->GetHashContextPtr()->size = shash::GetContextSize(algorithm);
2014 130 info->GetHashContextPtr()->buffer = alloca(info->hash_context().size);
2015 }
2016
2017 // In case JobInfo object is being reused
2018
2/4
✓ Branch 2 taken 203 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 203 times.
✗ Branch 6 not taken.
203 info->SetLink("");
2019
2020 // Prepare cvmfs-info: header, allocate string on the stack
2021 203 info->SetInfoHeader(NULL);
2022
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 203 times.
203 if (enable_info_header_ && info->extra_info()) {
2023 const char *header_name = "cvmfs-info: ";
2024 const size_t header_name_len = strlen(header_name);
2025 const unsigned header_size = 1 + header_name_len +
2026 EscapeHeader(*(info->extra_info()), NULL, 0);
2027 info->SetInfoHeader(static_cast<char *>(alloca(header_size)));
2028 memcpy(info->info_header(), header_name, header_name_len);
2029 EscapeHeader(*(info->extra_info()), info->info_header() + header_name_len,
2030 header_size - header_name_len);
2031 info->info_header()[header_size-1] = '\0';
2032 }
2033
2034
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 203 times.
203 if (enable_http_tracing_) {
2035 const std::string str_pid = "X-CVMFS-PID: " + StringifyInt(info->pid());
2036 const std::string str_gid = "X-CVMFS-GID: " + StringifyUint(info->gid());
2037 const std::string str_uid = "X-CVMFS-UID: " + StringifyUint(info->uid());
2038
2039 // will be auto freed at the end of this function Fetch(JobInfo *info)
2040 info->SetTracingHeaderPid(static_cast<char *>(alloca(str_pid.size() + 1)));
2041 info->SetTracingHeaderGid(static_cast<char *>(alloca(str_gid.size() + 1)));
2042 info->SetTracingHeaderUid(static_cast<char *>(alloca(str_uid.size() + 1)));
2043
2044 memcpy(info->tracing_header_pid(), str_pid.c_str(), str_pid.size() + 1);
2045 memcpy(info->tracing_header_gid(), str_gid.c_str(), str_gid.size() + 1);
2046 memcpy(info->tracing_header_uid(), str_uid.c_str(), str_uid.size() + 1);
2047 }
2048
2049
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 203 times.
203 if (atomic_xadd32(&multi_threaded_, 0) == 1) {
2050 if (!info->IsValidPipeJobResults()) {
2051 info->CreatePipeJobResults();
2052 }
2053 if (!info->IsValidDataTube()) {
2054 info->CreateDataTube();
2055 }
2056
2057 // LogCvmfs(kLogDownload, kLogDebug, "send job to thread, pipe %d %d",
2058 // info->wait_at[0], info->wait_at[1]);
2059 pipe_jobs_->Write<JobInfo*>(info);
2060
2061 do {
2062 DataTubeElement* ele = info->GetDataTubePtr()->PopFront();
2063
2064 if (ele->action == kActionStop) {
2065 delete ele;
2066 break;
2067 }
2068 // TODO(heretherebedragons) add compression
2069 } while (true);
2070
2071 info->GetPipeJobResultPtr()->Read<download::Failures>(&result);
2072 // LogCvmfs(kLogDownload, kLogDebug, "got result %d", result);
2073 } else {
2074 203 MutexLockGuard l(lock_synchronous_mode_);
2075
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 CURL *handle = AcquireCurlHandle();
2076
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 InitializeRequest(info, handle);
2077
1/2
✓ Branch 1 taken 203 times.
✗ Branch 2 not taken.
203 SetUrlOptions(info);
2078 // curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
2079 int retval;
2080 do {
2081
1/2
✓ Branch 1 taken 214 times.
✗ Branch 2 not taken.
214 retval = curl_easy_perform(handle);
2082 214 perf::Inc(counters_->n_requests);
2083 double elapsed;
2084
2/4
✓ Branch 1 taken 214 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 214 times.
✗ Branch 4 not taken.
214 if (curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME, &elapsed) == CURLE_OK)
2085 {
2086 214 perf::Xadd(counters_->sz_transfer_time,
2087 214 static_cast<int64_t>(elapsed * 1000));
2088 }
2089
3/4
✓ Branch 1 taken 214 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 203 times.
214 } while (VerifyAndFinalize(retval, info));
2090 203 result = info->error_code();
2091
1/2
✓ Branch 2 taken 203 times.
✗ Branch 3 not taken.
203 ReleaseCurlHandle(info->curl_handle());
2092 203 }
2093
2094
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 188 times.
203 if (result != kFailOk) {
2095
1/2
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
2096 "download failed (error %d - %s)",
2097 name_.c_str(),
2098 info->id(), result, Code2Ascii(result));
2099
2100
1/2
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
15 if (info->sink() != NULL) {
2101
1/2
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
15 info->sink()->Purge();
2102 }
2103 }
2104
2105 203 return result;
2106 }
2107
2108
2109 /**
2110 * Used by the client to connect the authz session manager to the download
2111 * manager.
2112 */
2113 39 void DownloadManager::SetCredentialsAttachment(CredentialsAttachment *ca) {
2114 39 MutexLockGuard m(lock_options_);
2115 39 credentials_attachment_ = ca;
2116 39 }
2117
2118 /**
2119 * Gets the DNS sever.
2120 */
2121 std::string DownloadManager::GetDnsServer() const {
2122 return opt_dns_server_;
2123 }
2124
2125 /**
2126 * Sets a DNS server. Only for testing as it cannot be reverted to the system
2127 * default.
2128 */
2129 void DownloadManager::SetDnsServer(const string &address) {
2130 if (!address.empty()) {
2131 MutexLockGuard m(lock_options_);
2132 opt_dns_server_ = address;
2133 assert(!opt_dns_server_.empty());
2134
2135 vector<string> servers;
2136 servers.push_back(address);
2137 bool retval = resolver_->SetResolvers(servers);
2138 assert(retval);
2139 }
2140 LogCvmfs(kLogDownload, kLogSyslog, "(manager '%s') set nameserver to %s",
2141 name_.c_str(), address.c_str());
2142 }
2143
2144
2145 /**
2146 * Sets the DNS query timeout parameters.
2147 */
2148 73 void DownloadManager::SetDnsParameters(
2149 const unsigned retries,
2150 const unsigned timeout_ms)
2151 {
2152 73 MutexLockGuard m(lock_options_);
2153
3/6
✓ Branch 1 taken 73 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 73 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 73 times.
✗ Branch 6 not taken.
146 if ((resolver_->retries() == retries) &&
2154 73 (resolver_->timeout_ms() == timeout_ms))
2155 {
2156 73 return;
2157 }
2158 delete resolver_;
2159 resolver_ = NULL;
2160 resolver_ =
2161 dns::NormalResolver::Create(opt_ipv4_only_, retries, timeout_ms);
2162 assert(resolver_);
2163
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 73 times.
73 }
2164
2165
2166 73 void DownloadManager::SetDnsTtlLimits(
2167 const unsigned min_seconds,
2168 const unsigned max_seconds)
2169 {
2170 73 MutexLockGuard m(lock_options_);
2171 73 resolver_->set_min_ttl(min_seconds);
2172 73 resolver_->set_max_ttl(max_seconds);
2173 73 }
2174
2175
2176 void DownloadManager::SetIpPreference(dns::IpPreference preference) {
2177 MutexLockGuard m(lock_options_);
2178 opt_ip_preference_ = preference;
2179 }
2180
2181
2182 /**
2183 * Sets two timeout values for proxied and for direct connections, respectively.
2184 * The timeout counts for all sorts of connection phases,
2185 * DNS, HTTP connect, etc.
2186 */
2187 95 void DownloadManager::SetTimeout(const unsigned seconds_proxy,
2188 const unsigned seconds_direct)
2189 {
2190 95 MutexLockGuard m(lock_options_);
2191 95 opt_timeout_proxy_ = seconds_proxy;
2192 95 opt_timeout_direct_ = seconds_direct;
2193 95 }
2194
2195
2196 /**
2197 * Sets contains the average transfer speed in bytes per second that the
2198 * transfer should be below during CURLOPT_LOW_SPEED_TIME seconds for libcurl to
2199 * consider it to be too slow and abort. Only effective for new connections.
2200 */
2201 void DownloadManager::SetLowSpeedLimit(const unsigned low_speed_limit) {
2202 MutexLockGuard m(lock_options_);
2203 opt_low_speed_limit_ = low_speed_limit;
2204 }
2205
2206
2207 /**
2208 * Receives the currently active timeout values.
2209 */
2210 32 void DownloadManager::GetTimeout(unsigned *seconds_proxy,
2211 unsigned *seconds_direct)
2212 {
2213 32 MutexLockGuard m(lock_options_);
2214 32 *seconds_proxy = opt_timeout_proxy_;
2215 32 *seconds_direct = opt_timeout_direct_;
2216 32 }
2217
2218
2219 /**
2220 * Parses a list of ';'-separated hosts for the metalink chain. The empty
2221 * string removes the metalink list.
2222 */
2223 void DownloadManager::SetMetalinkChain(const string &metalink_list) {
2224 SetMetalinkChain(SplitString(metalink_list, ';'));
2225 }
2226
2227
2228 void DownloadManager::SetMetalinkChain(
2229 const std::vector<std::string> &metalink_list) {
2230 const MutexLockGuard m(lock_options_);
2231 opt_metalink_.timestamp_backup = 0;
2232 delete opt_metalink_.chain;
2233 opt_metalink_.current = 0;
2234
2235 if (metalink_list.empty()) {
2236 opt_metalink_.chain = NULL;
2237 return;
2238 }
2239
2240 opt_metalink_.chain = new vector<string>(metalink_list);
2241 }
2242
2243
2244 /**
2245 * Retrieves the currently set chain of metalink hosts and the currently
2246 * used metalink host.
2247 */
2248 void DownloadManager::GetMetalinkInfo(vector<string> *metalink_chain,
2249 unsigned *current_metalink)
2250 {
2251 const MutexLockGuard m(lock_options_);
2252 if (opt_metalink_.chain) {
2253 if (current_metalink) {*current_metalink = opt_metalink_.current;}
2254 if (metalink_chain) {*metalink_chain = *opt_metalink_.chain;}
2255 }
2256 }
2257
2258
2259 /**
2260 * Parses a list of ';'-separated hosts for the host chain. The empty string
2261 * removes the host list.
2262 */
2263 79 void DownloadManager::SetHostChain(const string &host_list) {
2264
1/2
✓ Branch 2 taken 79 times.
✗ Branch 3 not taken.
79 SetHostChain(SplitString(host_list, ';'));
2265 79 }
2266
2267
2268 81 void DownloadManager::SetHostChain(const std::vector<std::string> &host_list) {
2269 81 MutexLockGuard m(lock_options_);
2270 81 opt_host_.timestamp_backup = 0;
2271
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 46 times.
81 delete opt_host_.chain;
2272
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 46 times.
81 delete opt_host_chain_rtt_;
2273 81 opt_host_.current = 0;
2274
2275
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 81 times.
81 if (host_list.empty()) {
2276 opt_host_.chain = NULL;
2277 opt_host_chain_rtt_ = NULL;
2278 return;
2279 }
2280
2281
2/4
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 81 times.
✗ Branch 5 not taken.
81 opt_host_.chain = new vector<string>(host_list);
2282 162 opt_host_chain_rtt_ =
2283
2/4
✓ Branch 2 taken 81 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 81 times.
✗ Branch 6 not taken.
162 new vector<int>(opt_host_.chain->size(), kProbeUnprobed);
2284 // LogCvmfs(kLogDownload, kLogSyslog, "using host %s",
2285 // (*opt_host_.chain)[0].c_str());
2286
1/2
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
81 }
2287
2288
2289 /**
2290 * Retrieves the currently set chain of hosts, their round trip times, and the
2291 * currently used host.
2292 */
2293 14 void DownloadManager::GetHostInfo(vector<string> *host_chain, vector<int> *rtt,
2294 unsigned *current_host)
2295 {
2296 14 MutexLockGuard m(lock_options_);
2297
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (opt_host_.chain) {
2298
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (current_host) {*current_host = opt_host_.current;}
2299
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 14 times.
✗ Branch 4 not taken.
14 if (host_chain) {*host_chain = *opt_host_.chain;}
2300
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
14 if (rtt) {*rtt = *opt_host_chain_rtt_;}
2301 }
2302 14 }
2303
2304
2305 /**
2306 * Jumps to the next proxy in the ring of forward proxy servers.
2307 * Selects one randomly from a load-balancing group.
2308 *
2309 * Allow for the fact that the proxy may have already been failed by
2310 * another transfer, or that the proxy may no longer be part of the
2311 * current load-balancing group.
2312 */
2313 1 void DownloadManager::SwitchProxy(JobInfo *info) {
2314 1 MutexLockGuard m(lock_options_);
2315
2316
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!opt_proxy_groups_) {
2317 return;
2318 }
2319
2320 // Fail any matching proxies within the current load-balancing group
2321 1 vector<ProxyInfo> *group = current_proxy_group();
2322 1 const unsigned group_size = group->size();
2323 1 unsigned failed = 0;
2324
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (unsigned i = 0; i < group_size - opt_proxy_groups_current_burned_; ++i) {
2325
5/14
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
1 if (info && (info->proxy() == (*group)[i].url)) {
2326 // Move to list of failed proxies
2327 1 opt_proxy_groups_current_burned_++;
2328
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 swap((*group)[i],
2329 1 (*group)[group_size - opt_proxy_groups_current_burned_]);
2330 1 perf::Inc(counters_->n_proxy_failover);
2331 1 failed++;
2332 }
2333 }
2334
2335 // Do nothing more unless at least one proxy was marked as failed
2336
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!failed)
2337 return;
2338
2339 // If all proxies from the current load-balancing group are burned, switch to
2340 // another group
2341
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (opt_proxy_groups_current_burned_ == group->size()) {
2342 1 opt_proxy_groups_current_burned_ = 0;
2343
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (opt_proxy_groups_->size() > 1) {
2344 2 opt_proxy_groups_current_ = (opt_proxy_groups_current_ + 1) %
2345 1 opt_proxy_groups_->size();
2346 // Remember the timestamp of switching to backup proxies
2347
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (opt_proxy_groups_reset_after_ > 0) {
2348 if (opt_proxy_groups_current_ > 0) {
2349 if (opt_timestamp_backup_proxies_ == 0)
2350 opt_timestamp_backup_proxies_ = time(NULL);
2351 // LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
2352 // "switched to (another) backup proxy group");
2353 } else {
2354 opt_timestamp_backup_proxies_ = 0;
2355 // LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
2356 // "switched back to primary proxy group");
2357 }
2358 opt_timestamp_failover_proxies_ = 0;
2359 }
2360 }
2361 } else {
2362 // Record failover time
2363 if (opt_proxy_groups_reset_after_ > 0) {
2364 if (opt_timestamp_failover_proxies_ == 0)
2365 opt_timestamp_failover_proxies_ = time(NULL);
2366 }
2367 }
2368
2369
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
1 UpdateProxiesUnlocked("failed proxy");
2370
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
2371 "%lu proxies remain in group", name_.c_str(), info->id(),
2372 1 current_proxy_group()->size() - opt_proxy_groups_current_burned_);
2373
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 }
2374
2375
2376 /**
2377 * Switches to the next host in the chain. If jobinfo is set, switch only if
2378 * the current host is identical to the one used by jobinfo, otherwise another
2379 * transfer has already done the switch.
2380 */
2381 3 void DownloadManager::SwitchHostInfo(const std::string &typ,
2382 HostInfo &info,
2383 JobInfo *jobinfo) {
2384 3 MutexLockGuard m(lock_options_);
2385
2386
5/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2 times.
3 if (!info.chain || (info.chain->size() == 1)) {
2387 1 return;
2388 }
2389
2390
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (jobinfo) {
2391 int lastused;
2392
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (typ == "host") {
2393 2 lastused = jobinfo->current_host_chain_index();
2394 } else {
2395 lastused = jobinfo->current_metalink_chain_index();
2396 }
2397
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (lastused != info.current) {
2398 LogCvmfs(kLogDownload, kLogDebug,
2399 "(manager '%s' - id %" PRId64 ")"
2400 "don't switch %s, "
2401 "last used %s: %s, current %s: %s",
2402 name_.c_str(), jobinfo->id(), typ.c_str(),
2403 typ.c_str(), (*info.chain)[lastused].c_str(),
2404 typ.c_str(), (*info.chain)[info.current].c_str());
2405 return;
2406 }
2407 }
2408
2409
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 string reason = "manually triggered";
2410
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 string info_id = "(manager " + name_;
2411
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (jobinfo) {
2412
1/2
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
2 reason = download::Code2Ascii(jobinfo->error_code());
2413
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
2 info_id = " - id " + StringifyInt(jobinfo->id());
2414 }
2415
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 info_id += ")";
2416
2417
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 const std::string old_host = (*info.chain)[info.current];
2418 2 info.current = (info.current + 1) % static_cast<int>(info.chain->size());
2419
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 if (typ == "host") {
2420 2 perf::Inc(counters_->n_host_failover);
2421 } else {
2422 perf::Inc(counters_->n_metalink_failover);
2423 }
2424
1/3
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
4 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
2425 "%s switching %s from %s to %s (%s)", info_id.c_str(), typ.c_str(),
2426 2 old_host.c_str(), (*info.chain)[info.current].c_str(),
2427 reason.c_str());
2428
2429 // Remember the timestamp of switching to backup host
2430
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (info.reset_after > 0) {
2431 if (info.current != 0) {
2432 if (info.timestamp_backup == 0)
2433 info.timestamp_backup = time(NULL);
2434 } else {
2435 info.timestamp_backup = 0;
2436 }
2437 }
2438
2/2
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
3 }
2439
2440 3 void DownloadManager::SwitchHost(JobInfo *info) {
2441
2/4
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
3 SwitchHostInfo("host", opt_host_, info);
2442 3 }
2443
2444 1 void DownloadManager::SwitchHost() {
2445 1 SwitchHost(NULL);
2446 1 }
2447
2448
2449 void DownloadManager::SwitchMetalink(JobInfo *info) {
2450 SwitchHostInfo("metalink", opt_metalink_, info);
2451 }
2452
2453
2454 void DownloadManager::SwitchMetalink() {
2455 SwitchMetalink(NULL);
2456 }
2457
2458 103 bool DownloadManager::CheckMetalinkChain(time_t now) {
2459
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 103 times.
103 return (opt_metalink_.chain &&
2460 ((opt_metalink_timestamp_link_ == 0) ||
2461 (static_cast<int64_t>((now == 0) ? time(NULL) : now) >
2462 static_cast<int64_t>(opt_metalink_timestamp_link_ +
2463
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
103 opt_metalink_.reset_after))));
2464 }
2465
2466
2467 /**
2468 * Orders the hostlist according to RTT of downloading .cvmfschecksum.
2469 * Sets the current host to the best-responsive host.
2470 * If you change the host list in between by SetHostChain(), it will be
2471 * overwritten by this function.
2472 */
2473 void DownloadManager::ProbeHosts() {
2474 vector<string> host_chain;
2475 vector<int> host_rtt;
2476 unsigned current_host;
2477
2478 GetHostInfo(&host_chain, &host_rtt, &current_host);
2479
2480 // Stopwatch, two times to fill caches first
2481 unsigned i, retries;
2482 string url;
2483
2484 cvmfs::MemSink memsink;
2485 JobInfo info(&url, false, false, NULL, &memsink);
2486 for (retries = 0; retries < 2; ++retries) {
2487 for (i = 0; i < host_chain.size(); ++i) {
2488 url = host_chain[i] + "/.cvmfspublished";
2489
2490 struct timeval tv_start, tv_end;
2491 gettimeofday(&tv_start, NULL);
2492 Failures result = Fetch(&info);
2493 gettimeofday(&tv_end, NULL);
2494 memsink.Reset();
2495 if (result == kFailOk) {
2496 host_rtt[i] = static_cast<int>(
2497 DiffTimeSeconds(tv_start, tv_end) * 1000);
2498 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
2499 "probing host %s had %dms rtt",
2500 name_.c_str(), info.id(),
2501 url.c_str(), host_rtt[i]);
2502 } else {
2503 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s' - id %" PRId64 ") "
2504 "error while probing host %s: %d %s",
2505 name_.c_str(), info.id(),
2506 url.c_str(), result, Code2Ascii(result));
2507 host_rtt[i] = INT_MAX;
2508 }
2509 }
2510 }
2511
2512 SortTeam(&host_rtt, &host_chain);
2513 for (i = 0; i < host_chain.size(); ++i) {
2514 if (host_rtt[i] == INT_MAX) host_rtt[i] = kProbeDown;
2515 }
2516
2517 MutexLockGuard m(lock_options_);
2518 delete opt_host_.chain;
2519 delete opt_host_chain_rtt_;
2520 opt_host_.chain = new vector<string>(host_chain);
2521 opt_host_chain_rtt_ = new vector<int>(host_rtt);
2522 opt_host_.current = 0;
2523 }
2524
2525 bool DownloadManager::GeoSortServers(std::vector<std::string> *servers,
2526 std::vector<uint64_t> *output_order) {
2527 if (!servers) {return false;}
2528 if (servers->size() == 1) {
2529 if (output_order) {
2530 output_order->clear();
2531 output_order->push_back(0);
2532 }
2533 return true;
2534 }
2535
2536 std::vector<std::string> host_chain;
2537 GetHostInfo(&host_chain, NULL, NULL);
2538
2539 std::vector<std::string> server_dns_names;
2540 server_dns_names.reserve(servers->size());
2541 for (unsigned i = 0; i < servers->size(); ++i) {
2542 std::string host = dns::ExtractHost((*servers)[i]);
2543 server_dns_names.push_back(host.empty() ? (*servers)[i] : host);
2544 }
2545 std::string host_list = JoinStrings(server_dns_names, ",");
2546
2547 vector<string> host_chain_shuffled;
2548 {
2549 // Protect against concurrent access to prng_
2550 MutexLockGuard m(lock_options_);
2551 // Determine random hosts for the Geo-API query
2552 host_chain_shuffled = Shuffle(host_chain, &prng_);
2553 }
2554 // Request ordered list via Geo-API
2555 bool success = false;
2556 unsigned max_attempts = std::min(host_chain_shuffled.size(), size_t(3));
2557 vector<uint64_t> geo_order(servers->size());
2558 for (unsigned i = 0; i < max_attempts; ++i) {
2559 string url = host_chain_shuffled[i] + "/api/v1.0/geo/@proxy@/" + host_list;
2560 LogCvmfs(kLogDownload, kLogDebug,
2561 "(manager '%s') requesting ordered server list from %s",
2562 name_.c_str(), url.c_str());
2563 cvmfs::MemSink memsink;
2564 JobInfo info(&url, false, false, NULL, &memsink);
2565 Failures result = Fetch(&info);
2566 if (result == kFailOk) {
2567 string order(reinterpret_cast<char*>(memsink.data()), memsink.pos());
2568 memsink.Reset();
2569 bool retval = ValidateGeoReply(order, servers->size(), &geo_order);
2570 if (!retval) {
2571 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
2572 "(manager '%s') retrieved invalid GeoAPI reply from %s [%s]",
2573 name_.c_str(), url.c_str(), order.c_str());
2574 } else {
2575 LogCvmfs(kLogDownload, kLogDebug | kLogSyslog, "(manager '%s') "
2576 "geographic order of servers retrieved from %s",
2577 name_.c_str(),
2578 dns::ExtractHost(host_chain_shuffled[i]).c_str());
2579 // remove new line at end of "order"
2580 LogCvmfs(kLogDownload, kLogDebug, "order is %s",
2581 Trim(order, true /* trim_newline */).c_str());
2582 success = true;
2583 break;
2584 }
2585 } else {
2586 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
2587 "(manager '%s') GeoAPI request for %s failed with error %d [%s]",
2588 name_.c_str(), url.c_str(), result, Code2Ascii(result));
2589 }
2590 }
2591 if (!success) {
2592 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn, "(manager '%s') "
2593 "failed to retrieve geographic order from stratum 1 servers",
2594 name_.c_str());
2595 return false;
2596 }
2597
2598 if (output_order) {
2599 output_order->swap(geo_order);
2600 } else {
2601 std::vector<std::string> sorted_servers;
2602 sorted_servers.reserve(geo_order.size());
2603 for (unsigned i = 0; i < geo_order.size(); ++i) {
2604 uint64_t orderval = geo_order[i];
2605 sorted_servers.push_back((*servers)[orderval]);
2606 }
2607 servers->swap(sorted_servers);
2608 }
2609 return true;
2610 }
2611
2612
2613 /**
2614 * Uses the Geo-API of Stratum 1s to let any of them order the list of servers
2615 * and fallback proxies (if any).
2616 * Tries at most three random Stratum 1s before giving up.
2617 * If you change the host list in between by SetHostChain() or the fallback
2618 * proxy list by SetProxyChain(), they will be overwritten by this function.
2619 */
2620 bool DownloadManager::ProbeGeo() {
2621 vector<string> host_chain;
2622 vector<int> host_rtt;
2623 unsigned current_host;
2624 vector< vector<ProxyInfo> > proxy_chain;
2625 unsigned fallback_group;
2626
2627 GetHostInfo(&host_chain, &host_rtt, &current_host);
2628 GetProxyInfo(&proxy_chain, NULL, &fallback_group);
2629 if ((host_chain.size() < 2) && ((proxy_chain.size() - fallback_group) < 2))
2630 return true;
2631
2632 vector<string> host_names;
2633 for (unsigned i = 0; i < host_chain.size(); ++i)
2634 host_names.push_back(dns::ExtractHost(host_chain[i]));
2635 SortTeam(&host_names, &host_chain);
2636 unsigned last_geo_host = host_names.size();
2637
2638 if ((fallback_group == 0) && (last_geo_host > 1)) {
2639 // There are no non-fallback proxies, which means that the client
2640 // will always use the fallback proxies. Add a keyword separator
2641 // between the hosts and fallback proxies so the geosorting service
2642 // will know to sort the hosts based on the distance from the
2643 // closest fallback proxy rather than the distance from the client.
2644 host_names.push_back("+PXYSEP+");
2645 }
2646
2647 // Add fallback proxy names to the end of the host list
2648 unsigned first_geo_fallback = host_names.size();
2649 for (unsigned i = fallback_group; i < proxy_chain.size(); ++i) {
2650 // We only take the first fallback proxy name from every group under the
2651 // assumption that load-balanced servers are at the same location
2652 host_names.push_back(proxy_chain[i][0].host.name());
2653 }
2654
2655 std::vector<uint64_t> geo_order;
2656 bool success = GeoSortServers(&host_names, &geo_order);
2657 if (!success) {
2658 // GeoSortServers already logged a failure message.
2659 return false;
2660 }
2661
2662 // Re-install host chain and proxy chain
2663 MutexLockGuard m(lock_options_);
2664 delete opt_host_.chain;
2665 opt_num_proxies_ = 0;
2666 opt_host_.chain = new vector<string>(host_chain.size());
2667
2668 // It's possible that opt_proxy_groups_fallback_ might have changed while
2669 // the lock wasn't held
2670 vector<vector<ProxyInfo> > *proxy_groups = new vector<vector<ProxyInfo> >(
2671 opt_proxy_groups_fallback_ + proxy_chain.size() - fallback_group);
2672 // First copy the non-fallback part of the current proxy chain
2673 for (unsigned i = 0; i < opt_proxy_groups_fallback_; ++i) {
2674 (*proxy_groups)[i] = (*opt_proxy_groups_)[i];
2675 opt_num_proxies_ += (*opt_proxy_groups_)[i].size();
2676 }
2677
2678 // Copy the host chain and fallback proxies by geo order. Array indices
2679 // in geo_order that are smaller than last_geo_host refer to a stratum 1,
2680 // and those indices greater than or equal to first_geo_fallback refer to
2681 // a fallback proxy.
2682 unsigned hosti = 0;
2683 unsigned proxyi = opt_proxy_groups_fallback_;
2684 for (unsigned i = 0; i < geo_order.size(); ++i) {
2685 uint64_t orderval = geo_order[i];
2686 if (orderval < static_cast<uint64_t>(last_geo_host)) {
2687 // LogCvmfs(kLogCvmfs, kLogSyslog, "this is orderval %u at host index
2688 // %u", orderval, hosti);
2689 (*opt_host_.chain)[hosti++] = host_chain[orderval];
2690 } else if (orderval >= static_cast<uint64_t>(first_geo_fallback)) {
2691 // LogCvmfs(kLogCvmfs, kLogSyslog,
2692 // "this is orderval %u at proxy index %u, using proxy_chain index %u",
2693 // orderval, proxyi, fallback_group + orderval - first_geo_fallback);
2694 (*proxy_groups)[proxyi] =
2695 proxy_chain[fallback_group + orderval - first_geo_fallback];
2696 opt_num_proxies_ += (*proxy_groups)[proxyi].size();
2697 proxyi++;
2698 }
2699 }
2700
2701 opt_proxy_map_.clear();
2702 delete opt_proxy_groups_;
2703 opt_proxy_groups_ = proxy_groups;
2704 // In pathological cases, opt_proxy_groups_current_ can be larger now when
2705 // proxies changed in-between.
2706 if (opt_proxy_groups_current_ > opt_proxy_groups_->size()) {
2707 if (opt_proxy_groups_->size() == 0) {
2708 opt_proxy_groups_current_ = 0;
2709 } else {
2710 opt_proxy_groups_current_ = opt_proxy_groups_->size() - 1;
2711 }
2712 opt_proxy_groups_current_burned_ = 0;
2713 }
2714
2715 UpdateProxiesUnlocked("geosort");
2716
2717 delete opt_host_chain_rtt_;
2718 opt_host_chain_rtt_ = new vector<int>(host_chain.size(), kProbeGeo);
2719 opt_host_.current = 0;
2720
2721 return true;
2722 }
2723
2724
2725 /**
2726 * Validates a string of the form "1,4,2,3" representing in which order the
2727 * the expected_size number of hosts should be put for optimal geographic
2728 * proximity. Returns false if the reply_order string is invalid, otherwise
2729 * fills in the reply_vals array with zero-based order indexes (e.g.
2730 * [0,3,1,2]) and returns true.
2731 */
2732 14 bool DownloadManager::ValidateGeoReply(
2733 const string &reply_order,
2734 const unsigned expected_size,
2735 vector<uint64_t> *reply_vals)
2736 {
2737
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 13 times.
14 if (reply_order.empty())
2738 1 return false;
2739
2/4
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 13 times.
✗ Branch 6 not taken.
26 sanitizer::InputSanitizer sanitizer("09 , \n");
2740
3/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 12 times.
13 if (!sanitizer.IsValid(reply_order))
2741 1 return false;
2742
2/4
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
24 sanitizer::InputSanitizer strip_newline("09 ,");
2743 vector<string> reply_strings =
2744
2/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
12 SplitString(strip_newline.Filter(reply_order), ',');
2745 12 vector<uint64_t> tmp_vals;
2746
2/2
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 10 times.
33 for (unsigned i = 0; i < reply_strings.size(); ++i) {
2747
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 21 times.
23 if (reply_strings[i].empty())
2748 2 return false;
2749
2/4
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
21 tmp_vals.push_back(String2Uint64(reply_strings[i]));
2750 }
2751
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
10 if (tmp_vals.size() != expected_size)
2752 5 return false;
2753
2754 // Check if tmp_vals contains the number 1..n
2755
1/2
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
5 set<uint64_t> coverage(tmp_vals.begin(), tmp_vals.end());
2756
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (coverage.size() != tmp_vals.size())
2757 return false;
2758
5/6
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 4 times.
5 if ((*coverage.begin() != 1) || (*coverage.rbegin() != coverage.size()))
2759 1 return false;
2760
2761
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 4 times.
13 for (unsigned i = 0; i < expected_size; ++i) {
2762 9 (*reply_vals)[i] = tmp_vals[i] - 1;
2763 }
2764 4 return true;
2765 13 }
2766
2767
2768 /**
2769 * Removes DIRECT from a list of ';' and '|' separated proxies.
2770 * \return true if DIRECT was present, false otherwise
2771 */
2772 77 bool DownloadManager::StripDirect(
2773 const string &proxy_list,
2774 string *cleaned_list)
2775 {
2776
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 77 times.
77 assert(cleaned_list);
2777
2/2
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 9 times.
77 if (proxy_list == "") {
2778
1/2
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
68 *cleaned_list = "";
2779 68 return false;
2780 }
2781 9 bool result = false;
2782
2783
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 vector<string> proxy_groups = SplitString(proxy_list, ';');
2784 9 vector<string> cleaned_groups;
2785
2/2
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 9 times.
31 for (unsigned i = 0; i < proxy_groups.size(); ++i) {
2786
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 vector<string> group = SplitString(proxy_groups[i], '|');
2787 22 vector<string> cleaned;
2788
2/2
✓ Branch 1 taken 37 times.
✓ Branch 2 taken 22 times.
59 for (unsigned j = 0; j < group.size(); ++j) {
2789
6/6
✓ Branch 2 taken 27 times.
✓ Branch 3 taken 10 times.
✓ Branch 6 taken 13 times.
✓ Branch 7 taken 14 times.
✓ Branch 8 taken 23 times.
✓ Branch 9 taken 14 times.
37 if ((group[j] == "DIRECT") || (group[j] == "")) {
2790 23 result = true;
2791 } else {
2792
1/2
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 cleaned.push_back(group[j]);
2793 }
2794 }
2795
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 15 times.
22 if (!cleaned.empty())
2796
3/6
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
7 cleaned_groups.push_back(JoinStrings(cleaned, "|"));
2797 22 }
2798
2799
2/4
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
9 *cleaned_list = JoinStrings(cleaned_groups, ";");
2800 9 return result;
2801 9 }
2802
2803
2804 /**
2805 * Parses a list of ';'- and '|'-separated proxy servers and fallback proxy
2806 * servers for the proxy groups.
2807 * The empty string for both removes the proxy chain.
2808 * The set_mode parameter can be used to set either proxies (leaving fallback
2809 * proxies unchanged) or fallback proxies (leaving regular proxies unchanged)
2810 * or both.
2811 */
2812 67 void DownloadManager::SetProxyChain(
2813 const string &proxy_list,
2814 const string &fallback_proxy_list,
2815 const ProxySetModes set_mode)
2816 {
2817 67 MutexLockGuard m(lock_options_);
2818
2819 67 opt_timestamp_backup_proxies_ = 0;
2820 67 opt_timestamp_failover_proxies_ = 0;
2821
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 string set_proxy_list = opt_proxy_list_;
2822
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 string set_proxy_fallback_list = opt_proxy_fallback_list_;
2823 bool contains_direct;
2824
3/4
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 3 times.
67 if ((set_mode == kSetProxyFallback) || (set_mode == kSetProxyBoth)) {
2825
1/2
✓ Branch 1 taken 64 times.
✗ Branch 2 not taken.
64 opt_proxy_fallback_list_ = fallback_proxy_list;
2826 }
2827
3/4
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
67 if ((set_mode == kSetProxyRegular) || (set_mode == kSetProxyBoth)) {
2828
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 opt_proxy_list_ = proxy_list;
2829 }
2830 contains_direct =
2831
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 StripDirect(opt_proxy_fallback_list_, &set_proxy_fallback_list);
2832
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 if (contains_direct) {
2833 LogCvmfs(kLogDownload, kLogSyslogWarn | kLogDebug,
2834 "(manager '%s') fallback proxies do not support DIRECT, removing",
2835 name_.c_str());
2836 }
2837
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 if (set_proxy_fallback_list == "") {
2838
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 set_proxy_list = opt_proxy_list_;
2839 } else {
2840 bool contains_direct = StripDirect(opt_proxy_list_, &set_proxy_list);
2841 if (contains_direct) {
2842 LogCvmfs(kLogDownload, kLogSyslog | kLogDebug,
2843 "(manager '%s') skipping DIRECT proxy to use fallback proxy",
2844 name_.c_str());
2845 }
2846 }
2847
2848 // From this point on, use set_proxy_list and set_fallback_proxy_list as
2849 // effective proxy lists!
2850
2851 67 opt_proxy_map_.clear();
2852
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 35 times.
67 delete opt_proxy_groups_;
2853
2/6
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 67 times.
67 if ((set_proxy_list == "") && (set_proxy_fallback_list == "")) {
2854 opt_proxy_groups_ = NULL;
2855 opt_proxy_groups_current_ = 0;
2856 opt_proxy_groups_current_burned_ = 0;
2857 opt_proxy_groups_fallback_ = 0;
2858 opt_num_proxies_ = 0;
2859 return;
2860 }
2861
2862 // Determine number of regular proxy groups (== first fallback proxy group)
2863 67 opt_proxy_groups_fallback_ = 0;
2864
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 if (set_proxy_list != "") {
2865
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 opt_proxy_groups_fallback_ = SplitString(set_proxy_list, ';').size();
2866 }
2867
1/2
✓ Branch 2 taken 67 times.
✗ Branch 3 not taken.
67 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s') "
2868 "first fallback proxy group %u",
2869 name_.c_str(), opt_proxy_groups_fallback_);
2870
2871 // Concatenate regular proxies and fallback proxies, both of which can be
2872 // empty.
2873
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 string all_proxy_list = set_proxy_list;
2874
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
67 if (set_proxy_fallback_list != "") {
2875 if (all_proxy_list != "")
2876 all_proxy_list += ";";
2877 all_proxy_list += set_proxy_fallback_list;
2878 }
2879
1/2
✓ Branch 3 taken 67 times.
✗ Branch 4 not taken.
67 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s') full proxy list %s",
2880 name_.c_str(), all_proxy_list.c_str());
2881
2882 // Resolve server names in provided urls
2883 67 vector<string> hostnames; // All encountered hostnames
2884 67 vector<string> proxy_groups;
2885
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 if (all_proxy_list != "")
2886
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 proxy_groups = SplitString(all_proxy_list, ';');
2887
2/2
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 67 times.
135 for (unsigned i = 0; i < proxy_groups.size(); ++i) {
2888
1/2
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
68 vector<string> this_group = SplitString(proxy_groups[i], '|');
2889
2/2
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 68 times.
136 for (unsigned j = 0; j < this_group.size(); ++j) {
2890
1/2
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
68 this_group[j] = dns::AddDefaultScheme(this_group[j]);
2891 // Note: DIRECT strings will be "extracted" to an empty string.
2892
1/2
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
68 string hostname = dns::ExtractHost(this_group[j]);
2893 // Save the hostname. Leave empty (DIRECT) names so indexes will
2894 // match later.
2895
1/2
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
68 hostnames.push_back(hostname);
2896 68 }
2897 68 }
2898 67 vector<dns::Host> hosts;
2899
1/2
✓ Branch 3 taken 67 times.
✗ Branch 4 not taken.
67 LogCvmfs(kLogDownload, kLogDebug, "(manager '%s') "
2900 "resolving %lu proxy addresses",
2901 name_.c_str(), hostnames.size());
2902
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 resolver_->ResolveMany(hostnames, &hosts);
2903
2904 // Construct opt_proxy_groups_: traverse proxy list in same order and expand
2905 // names to resolved IP addresses.
2906
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 opt_proxy_groups_ = new vector< vector<ProxyInfo> >();
2907 67 opt_num_proxies_ = 0;
2908 67 unsigned num_proxy = 0; // Combined i, j counter
2909
2/2
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 67 times.
135 for (unsigned i = 0; i < proxy_groups.size(); ++i) {
2910
1/2
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
68 vector<string> this_group = SplitString(proxy_groups[i], '|');
2911 // Construct ProxyInfo objects from proxy string and DNS resolver result for
2912 // every proxy in this_group. One URL can result in multiple ProxyInfo
2913 // objects, one for each IP address.
2914 68 vector<ProxyInfo> infos;
2915
2/2
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 68 times.
136 for (unsigned j = 0; j < this_group.size(); ++j, ++num_proxy) {
2916
1/2
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
68 this_group[j] = dns::AddDefaultScheme(this_group[j]);
2917
2/2
✓ Branch 2 taken 64 times.
✓ Branch 3 taken 4 times.
68 if (this_group[j] == "DIRECT") {
2918
3/6
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 64 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 64 times.
✗ Branch 9 not taken.
64 infos.push_back(ProxyInfo("DIRECT"));
2919 64 continue;
2920 }
2921
2922
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (hosts[num_proxy].status() != dns::kFailOk) {
2923 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn, "(manager '%s') "
2924 "failed to resolve IP addresses for %s (%d - %s)", name_.c_str(),
2925 hosts[num_proxy].name().c_str(), hosts[num_proxy].status(),
2926 dns::Code2Ascii(hosts[num_proxy].status()));
2927 dns::Host failed_host =
2928 dns::Host::ExtendDeadline(hosts[num_proxy], resolver_->min_ttl());
2929 infos.push_back(ProxyInfo(failed_host, this_group[j]));
2930 continue;
2931 }
2932
2933 // IPv4 addresses have precedence
2934 set<string> best_addresses =
2935
2/4
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
4 hosts[num_proxy].ViewBestAddresses(opt_ip_preference_);
2936 4 set<string>::const_iterator iter_ips = best_addresses.begin();
2937
2/2
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
8 for (; iter_ips != best_addresses.end(); ++iter_ips) {
2938
1/2
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
4 string url_ip = dns::RewriteUrl(this_group[j], *iter_ips);
2939
2/4
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
4 infos.push_back(ProxyInfo(hosts[num_proxy], url_ip));
2940
2941
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if (sharding_policy_.UseCount() > 0) {
2942 sharding_policy_->AddProxy(url_ip);
2943 }
2944 4 }
2945 4 }
2946
1/2
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
68 opt_proxy_groups_->push_back(infos);
2947 68 opt_num_proxies_ += infos.size();
2948 68 }
2949
1/2
✓ Branch 2 taken 67 times.
✗ Branch 3 not taken.
67 LogCvmfs(kLogDownload, kLogDebug,
2950 "(manager '%s') installed %u proxies in %lu load-balance groups",
2951 67 name_.c_str(), opt_num_proxies_, opt_proxy_groups_->size());
2952 67 opt_proxy_groups_current_ = 0;
2953 67 opt_proxy_groups_current_burned_ = 0;
2954
2955 // Select random start proxy from the first group.
2956
1/2
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
67 if (opt_proxy_groups_->size() > 0) {
2957 // Select random start proxy from the first group.
2958
2/4
✓ Branch 2 taken 67 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 67 times.
✗ Branch 6 not taken.
67 UpdateProxiesUnlocked("set random start proxy from the first proxy group");
2959 }
2960
3/6
✓ Branch 5 taken 67 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 67 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 67 times.
✗ Branch 12 not taken.
67 }
2961
2962
2963 /**
2964 * Retrieves the proxy chain, optionally the currently active load-balancing
2965 * group, and optionally the index of the first fallback proxy group.
2966 * If there are no fallback proxies, the index will equal the size of
2967 * the proxy chain.
2968 */
2969 void DownloadManager::GetProxyInfo(vector< vector<ProxyInfo> > *proxy_chain,
2970 unsigned *current_group,
2971 unsigned *fallback_group)
2972 {
2973 assert(proxy_chain != NULL);
2974 MutexLockGuard m(lock_options_);
2975
2976
2977 if (!opt_proxy_groups_) {
2978 vector< vector<ProxyInfo> > empty_chain;
2979 *proxy_chain = empty_chain;
2980 if (current_group != NULL)
2981 *current_group = 0;
2982 if (fallback_group != NULL)
2983 *fallback_group = 0;
2984 return;
2985 }
2986
2987 *proxy_chain = *opt_proxy_groups_;
2988 if (current_group != NULL)
2989 *current_group = opt_proxy_groups_current_;
2990 if (fallback_group != NULL)
2991 *fallback_group = opt_proxy_groups_fallback_;
2992 }
2993
2994 string DownloadManager::GetProxyList() {
2995 return opt_proxy_list_;
2996 }
2997
2998 string DownloadManager::GetFallbackProxyList() {
2999 return opt_proxy_fallback_list_;
3000 }
3001
3002 /**
3003 * Choose proxy
3004 */
3005 DownloadManager::ProxyInfo *
3006 206 DownloadManager::ChooseProxyUnlocked(const shash::Any *hash) {
3007
2/2
✓ Branch 0 taken 121 times.
✓ Branch 1 taken 85 times.
206 if (!opt_proxy_groups_)
3008 121 return NULL;
3009
3010
2/2
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 44 times.
85 uint32_t key = (hash ? hash->Partial32() : 0);
3011
1/2
✓ Branch 1 taken 85 times.
✗ Branch 2 not taken.
85 map<uint32_t, ProxyInfo *>::iterator it = opt_proxy_map_.lower_bound(key);
3012 85 ProxyInfo *proxy = it->second;
3013
3014 85 return proxy;
3015 }
3016
3017 /**
3018 * Update currently selected proxy
3019 */
3020 100 void DownloadManager::UpdateProxiesUnlocked(const string &reason) {
3021
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (!opt_proxy_groups_)
3022 return;
3023
3024 // Identify number of non-burned proxies within the current group
3025 100 vector<ProxyInfo> *group = current_proxy_group();
3026 100 unsigned num_alive = (group->size() - opt_proxy_groups_current_burned_);
3027
2/4
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 100 times.
✗ Branch 6 not taken.
200 string old_proxy = JoinStrings(opt_proxies_, "|");
3028
3029 // Rebuild proxy map and URL list
3030 100 opt_proxy_map_.clear();
3031 100 opt_proxies_.clear();
3032 100 const uint32_t max_key = 0xffffffffUL;
3033
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 100 times.
100 if (opt_proxy_shard_) {
3034 // Build a consistent map with multiple entries for each proxy
3035 for (unsigned i = 0; i < num_alive; ++i) {
3036 ProxyInfo *proxy = &(*group)[i];
3037 shash::Any proxy_hash(shash::kSha1);
3038 HashString(proxy->url, &proxy_hash);
3039 Prng prng;
3040 prng.InitSeed(proxy_hash.Partial32());
3041 for (unsigned j = 0; j < kProxyMapScale; ++j) {
3042 const std::pair<uint32_t, ProxyInfo *> entry(prng.Next(max_key), proxy);
3043 opt_proxy_map_.insert(entry);
3044 }
3045 std::string proxy_name = proxy->host.name().empty() ?
3046 "" : " (" + proxy->host.name() + ")";
3047 opt_proxies_.push_back(proxy->url + proxy_name);
3048 }
3049 // Ensure lower_bound() finds a value for all keys
3050 ProxyInfo *first_proxy = opt_proxy_map_.begin()->second;
3051 const std::pair<uint32_t, ProxyInfo *> last_entry(max_key, first_proxy);
3052 opt_proxy_map_.insert(last_entry);
3053 } else {
3054 // Build a map with a single entry for one randomly selected proxy
3055 100 unsigned select = prng_.Next(num_alive);
3056 100 ProxyInfo *proxy = &(*group)[select];
3057 100 const std::pair<uint32_t, ProxyInfo *> entry(max_key, proxy);
3058
1/2
✓ Branch 1 taken 100 times.
✗ Branch 2 not taken.
100 opt_proxy_map_.insert(entry);
3059 100 std::string proxy_name = proxy->host.name().empty() ?
3060
9/18
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 4 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 4 times.
✓ Branch 14 taken 96 times.
✓ Branch 15 taken 96 times.
✓ Branch 16 taken 4 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
104 "" : " (" + proxy->host.name() + ")";
3061
2/4
✓ Branch 1 taken 100 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 100 times.
✗ Branch 5 not taken.
100 opt_proxies_.push_back(proxy->url + proxy_name);
3062 100 }
3063
1/2
✓ Branch 3 taken 100 times.
✗ Branch 4 not taken.
100 sort(opt_proxies_.begin(), opt_proxies_.end());
3064
3065 // Report any change in proxy usage
3066
2/4
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 100 times.
✗ Branch 6 not taken.
200 string new_proxy = JoinStrings(opt_proxies_, "|");
3067
6/12
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 4 times.
✓ Branch 3 taken 96 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 4 times.
✓ Branch 10 taken 96 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
296 const string curr_host = "Current host: " + (opt_host_.chain ?
3068
1/2
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
196 (*opt_host_.chain)[opt_host_.current] : "");
3069
2/2
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 32 times.
100 if (new_proxy != old_proxy) {
3070
4/9
✗ Branch 2 not taken.
✓ Branch 3 taken 68 times.
✓ Branch 4 taken 67 times.
✓ Branch 5 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 68 times.
✗ Branch 9 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
205 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogWarn,
3071 "(manager '%s') switching proxy from %s to %s. Reason: %s [%s]",
3072 69 name_.c_str(), (old_proxy.empty() ? "(none)" : old_proxy.c_str()),
3073 136 (new_proxy.empty() ? "(none)" : new_proxy.c_str()),
3074 reason.c_str(), curr_host.c_str());
3075 }
3076 100 }
3077
3078 /**
3079 * Enable proxy sharding
3080 */
3081 void DownloadManager::ShardProxies() {
3082 opt_proxy_shard_ = true;
3083 RebalanceProxiesUnlocked("enable sharding");
3084 }
3085
3086 /**
3087 * Selects a new random proxy in the current load-balancing group. Resets the
3088 * "burned" counter.
3089 */
3090 void DownloadManager::RebalanceProxiesUnlocked(const string &reason) {
3091 if (!opt_proxy_groups_)
3092 return;
3093
3094 opt_timestamp_failover_proxies_ = 0;
3095 opt_proxy_groups_current_burned_ = 0;
3096 UpdateProxiesUnlocked(reason);
3097 }
3098
3099
3100 void DownloadManager::RebalanceProxies() {
3101 MutexLockGuard m(lock_options_);
3102 RebalanceProxiesUnlocked("rebalance invoked manually");
3103 }
3104
3105
3106 /**
3107 * Switches to the next load-balancing group of proxy servers.
3108 */
3109 void DownloadManager::SwitchProxyGroup() {
3110 MutexLockGuard m(lock_options_);
3111
3112 if (!opt_proxy_groups_ || (opt_proxy_groups_->size() < 2)) {
3113 return;
3114 }
3115
3116 opt_proxy_groups_current_ = (opt_proxy_groups_current_ + 1) %
3117 opt_proxy_groups_->size();
3118 opt_timestamp_backup_proxies_ = time(NULL);
3119
3120 std::string msg = "switch to proxy group " +
3121 StringifyUint(opt_proxy_groups_current_);
3122 RebalanceProxiesUnlocked(msg);
3123 }
3124
3125
3126 void DownloadManager::SetProxyGroupResetDelay(const unsigned seconds) {
3127 MutexLockGuard m(lock_options_);
3128 opt_proxy_groups_reset_after_ = seconds;
3129 if (opt_proxy_groups_reset_after_ == 0) {
3130 opt_timestamp_backup_proxies_ = 0;
3131 opt_timestamp_failover_proxies_ = 0;
3132 }
3133 }
3134
3135
3136 void DownloadManager::SetMetalinkResetDelay(const unsigned seconds)
3137 {
3138 const MutexLockGuard m(lock_options_);
3139 opt_metalink_.reset_after = seconds;
3140 if (opt_metalink_.reset_after == 0)
3141 opt_metalink_.timestamp_backup = 0;
3142 }
3143
3144
3145 void DownloadManager::SetHostResetDelay(const unsigned seconds)
3146 {
3147 MutexLockGuard m(lock_options_);
3148 opt_host_.reset_after = seconds;
3149 if (opt_host_.reset_after == 0)
3150 opt_host_.timestamp_backup = 0;
3151 }
3152
3153
3154 63 void DownloadManager::SetRetryParameters(const unsigned max_retries,
3155 const unsigned backoff_init_ms,
3156 const unsigned backoff_max_ms)
3157 {
3158 63 MutexLockGuard m(lock_options_);
3159 63 opt_max_retries_ = max_retries;
3160 63 opt_backoff_init_ms_ = backoff_init_ms;
3161 63 opt_backoff_max_ms_ = backoff_max_ms;
3162 63 }
3163
3164
3165 34 void DownloadManager::SetMaxIpaddrPerProxy(unsigned limit) {
3166 34 MutexLockGuard m(lock_options_);
3167 34 resolver_->set_throttle(limit);
3168 34 }
3169
3170
3171 39 void DownloadManager::SetProxyTemplates(
3172 const std::string &direct,
3173 const std::string &forced)
3174 {
3175 39 MutexLockGuard m(lock_options_);
3176
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 proxy_template_direct_ = direct;
3177
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 proxy_template_forced_ = forced;
3178 39 }
3179
3180
3181 void DownloadManager::EnableInfoHeader() {
3182 enable_info_header_ = true;
3183 }
3184
3185
3186 27 void DownloadManager::EnableRedirects() {
3187 27 follow_redirects_ = true;
3188 27 }
3189
3190 void DownloadManager::EnableIgnoreSignatureFailures() {
3191 ignore_signature_failures_ = true;
3192 }
3193
3194 void DownloadManager::EnableHTTPTracing() {
3195 enable_http_tracing_ = true;
3196 }
3197
3198 void DownloadManager::AddHTTPTracingHeader(const std::string &header) {
3199 http_tracing_headers_.push_back(header);
3200 }
3201
3202 24 void DownloadManager::UseSystemCertificatePath() {
3203 24 ssl_certificate_store_.UseSystemCertificatePath();
3204 24 }
3205
3206 bool DownloadManager::SetShardingPolicy(const ShardingPolicySelector type) {
3207 bool success = false;
3208 switch (type) {
3209 default:
3210 LogCvmfs(kLogDownload, kLogDebug | kLogSyslogErr, "(manager '%s') "
3211 "Proposed sharding policy does not exist. Falling back to default",
3212 name_.c_str());
3213 }
3214 return success;
3215 }
3216
3217 void DownloadManager::SetFailoverIndefinitely() {
3218 failover_indefinitely_ = true;
3219 }
3220
3221 /**
3222 * Creates a copy of the existing download manager. Must only be called in
3223 * single-threaded stage because it calls curl_global_init().
3224 */
3225 34 DownloadManager *DownloadManager::Clone(
3226 const perf::StatisticsTemplate &statistics, const std::string &cloned_name)
3227 {
3228 DownloadManager *clone = new DownloadManager(pool_max_handles_, statistics,
3229
1/2
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
34 cloned_name);
3230
3231 34 clone->SetDnsParameters(resolver_->retries(), resolver_->timeout_ms());
3232 34 clone->SetDnsTtlLimits(resolver_->min_ttl(), resolver_->max_ttl());
3233 34 clone->SetMaxIpaddrPerProxy(resolver_->throttle());
3234
3235
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
34 if (!opt_dns_server_.empty())
3236 clone->SetDnsServer(opt_dns_server_);
3237 34 clone->opt_timeout_proxy_ = opt_timeout_proxy_;
3238 34 clone->opt_timeout_direct_ = opt_timeout_direct_;
3239 34 clone->opt_low_speed_limit_ = opt_low_speed_limit_;
3240 34 clone->opt_max_retries_ = opt_max_retries_;
3241 34 clone->opt_backoff_init_ms_ = opt_backoff_init_ms_;
3242 34 clone->opt_backoff_max_ms_ = opt_backoff_max_ms_;
3243 34 clone->enable_info_header_ = enable_info_header_;
3244 34 clone->enable_http_tracing_ = enable_http_tracing_;
3245 34 clone->http_tracing_headers_ = http_tracing_headers_;
3246 34 clone->follow_redirects_ = follow_redirects_;
3247 34 clone->ignore_signature_failures_ = ignore_signature_failures_;
3248
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 2 times.
34 if (opt_host_.chain) {
3249
1/2
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
32 clone->opt_host_.chain = new vector<string>(*opt_host_.chain);
3250
1/2
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
32 clone->opt_host_chain_rtt_ = new vector<int>(*opt_host_chain_rtt_);
3251 }
3252
3253 34 CloneProxyConfig(clone);
3254 34 clone->opt_ip_preference_ = opt_ip_preference_;
3255 34 clone->proxy_template_direct_ = proxy_template_direct_;
3256 34 clone->proxy_template_forced_ = proxy_template_forced_;
3257 34 clone->opt_proxy_groups_reset_after_ = opt_proxy_groups_reset_after_;
3258 34 clone->opt_metalink_.reset_after = opt_metalink_.reset_after;
3259 34 clone->opt_host_.reset_after = opt_host_.reset_after;
3260 34 clone->credentials_attachment_ = credentials_attachment_;
3261 34 clone->ssl_certificate_store_ = ssl_certificate_store_;
3262
3263 34 clone->health_check_ = health_check_;
3264 34 clone->sharding_policy_ = sharding_policy_;
3265 34 clone->failover_indefinitely_ = failover_indefinitely_;
3266 34 clone->fqrn_ = fqrn_;
3267
3268 34 return clone;
3269 }
3270
3271
3272 34 void DownloadManager::CloneProxyConfig(DownloadManager *clone) {
3273 34 clone->opt_proxy_groups_current_ = opt_proxy_groups_current_;
3274 34 clone->opt_proxy_groups_current_burned_ = opt_proxy_groups_current_burned_;
3275 34 clone->opt_proxy_groups_fallback_ = opt_proxy_groups_fallback_;
3276 34 clone->opt_num_proxies_ = opt_num_proxies_;
3277 34 clone->opt_proxy_shard_ = opt_proxy_shard_;
3278 34 clone->opt_proxy_list_ = opt_proxy_list_;
3279 34 clone->opt_proxy_fallback_list_ = opt_proxy_fallback_list_;
3280
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 32 times.
34 if (opt_proxy_groups_ == NULL)
3281 2 return;
3282
3283 32 clone->opt_proxy_groups_ = new vector< vector<ProxyInfo> >(
3284
1/2
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
32 *opt_proxy_groups_);
3285
2/4
✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 32 times.
✗ Branch 6 not taken.
32 clone->UpdateProxiesUnlocked("cloned");
3286 }
3287
3288 } // namespace download
3289