XRootD
XrdHttpTpcTPC.cc
Go to the documentation of this file.
2 #include "XrdNet/XrdNetAddr.hh"
3 #include "XrdNet/XrdNetUtils.hh"
4 #include "XrdOuc/XrdOucEnv.hh"
5 #include "XrdSec/XrdSecEntity.hh"
8 #include "XrdSys/XrdSysFD.hh"
9 #include "XrdVersion.hh"
10 
12 #include "XrdOuc/XrdOucTUtils.hh"
14 
15 #include <curl/curl.h>
16 
17 #include <dlfcn.h>
18 #include <fcntl.h>
19 
20 #include <algorithm>
21 #include <memory>
22 #include <sstream>
23 #include <stdexcept>
24 #include <thread>
25 #include <iostream> // Delete later!!!
26 
27 #include "XrdHttpTpcState.hh"
28 #include "XrdHttpTpcStream.hh"
29 #include "XrdHttpTpcTPC.hh"
30 #include <fstream>
31 
32 using namespace TPC;
33 
34 XrdXrootdTpcMon* TPCHandler::TPCLogRecord::tpcMonitor = 0;
35 
36 uint64_t TPCHandler::m_monid{0};
37 int TPCHandler::m_marker_period = 5;
38 size_t TPCHandler::m_block_size = 16*1024*1024;
39 size_t TPCHandler::m_small_block_size = 1*1024*1024;
40 XrdSysMutex TPCHandler::m_monid_mutex;
41 
43 
44 /******************************************************************************/
45 /* T P C H a n d l e r : : T P C L o g R e c o r d D e s t r u c t o r */
46 /******************************************************************************/
47 
48 TPCHandler::TPCLogRecord::~TPCLogRecord()
49 {
50 // Record monitoring data is enabled
51 //
52  if (tpcMonitor)
53  {XrdXrootdTpcMon::TpcInfo monInfo;
54 
55  monInfo.clID = clID.c_str();
56  monInfo.begT = begT;
57  gettimeofday(&monInfo.endT, 0);
58 
59  if (mTpcType == TpcType::Pull)
60  {monInfo.dstURL = local.c_str();
61  monInfo.srcURL = remote.c_str();
62  } else {
63  monInfo.dstURL = remote.c_str();
64  monInfo.srcURL = local.c_str();
66  }
67 
68  if (!status) monInfo.endRC = 0;
69  else if (tpc_status > 0) monInfo.endRC = tpc_status;
70  else monInfo.endRC = 1;
71  monInfo.strm = static_cast<unsigned char>(streams);
72  monInfo.fSize = (bytes_transferred < 0 ? 0 : bytes_transferred);
73  if (!isIPv6) monInfo.opts |= XrdXrootdTpcMon::TpcInfo::isIPv4;
74 
75  tpcMonitor->Report(monInfo);
76  }
77 }
78 
79 /******************************************************************************/
80 /* C u r l D e l e t e r : : o p e r a t o r ( ) */
81 /******************************************************************************/
82 
84 {
85  if (curl) curl_easy_cleanup(curl);
86 }
87 
88 /******************************************************************************/
89 /* s o c k o p t _ s e t c l o e x e c _ c a l l b a c k */
90 /******************************************************************************/
91 
100 int TPCHandler::sockopt_callback(void *clientp, curl_socket_t curlfd, curlsocktype purpose) {
101  TPCLogRecord * rec = (TPCLogRecord *)clientp;
102  if (purpose == CURLSOCKTYPE_IPCXN && rec && rec->pmarkManager.isEnabled()) {
103  // We will not reach this callback if the corresponding socket could not have been connected
104  // the socket is already connected only if the packet marking is enabled
105  return CURL_SOCKOPT_ALREADY_CONNECTED;
106  }
107  return CURL_SOCKOPT_OK;
108 }
109 
110 /******************************************************************************/
111 /* o p e n s o c k e t _ c a l l b a c k */
112 /******************************************************************************/
113 
114 
119 int TPCHandler::opensocket_callback(void *clientp,
120  curlsocktype purpose,
121  struct curl_sockaddr *aInfo)
122 {
123  //Return a socket file descriptor (note the clo_exec flag will be set).
124  int fd = XrdSysFD_Socket(aInfo->family, aInfo->socktype, aInfo->protocol);
125  // See what kind of address will be used to connect
126  //
127  if(fd < 0) {
128  return CURL_SOCKET_BAD;
129  }
130  TPCLogRecord * rec = (TPCLogRecord *)clientp;
131  if (purpose == CURLSOCKTYPE_IPCXN && clientp)
132  {XrdNetAddr thePeer(&(aInfo->addr));
133  rec->isIPv6 = (thePeer.isIPType(XrdNetAddrInfo::IPv6)
134  && !thePeer.isMapped());
135  std::stringstream connectErrMsg;
136 
137  if(!rec->pmarkManager.connect(fd, &(aInfo->addr), aInfo->addrlen, CONNECT_TIMEOUT, connectErrMsg)) {
138  rec->m_log->Emsg(rec->log_prefix.c_str(),"Unable to connect socket:", connectErrMsg.str().c_str());
139  return CURL_SOCKET_BAD;
140  }
141  }
142 
143  return fd;
144 }
145 
146 int TPCHandler::closesocket_callback(void *clientp, curl_socket_t fd) {
147  TPCLogRecord * rec = (TPCLogRecord *)clientp;
148 
149  // Destroy the PMark handle associated to the file descriptor before closing it.
150  // Otherwise, we would lose the socket usage information if the socket is closed before
151  // the PMark handle is closed.
152  rec->pmarkManager.endPmark(fd);
153 
154  return close(fd);
155 }
156 
157 /******************************************************************************/
158 /* p r e p a r e U R L */
159 /******************************************************************************/
160 
161 // See XrdHttpTpcUtils::prepareOpenURL() documentation
162 std::string TPCHandler::prepareURL(XrdHttpExtReq &req) {
163  return XrdHttpTpcUtils::prepareOpenURL(req.resource, req.headers,hdr2cgimap);
164 }
165 
166 /******************************************************************************/
167 /* e n c o d e _ x r o o t d _ o p a q u e _ t o _ u r i */
168 /******************************************************************************/
169 
170 // When processing a redirection from the filesystem layer, it is permitted to return
171 // some xrootd opaque data. The quoting rules for xrootd opaque data are significantly
172 // more permissive than a URI (basically, only '&' and '=' are disallowed while some
173 // URI parsers may dislike characters like '"'). This function takes an opaque string
174 // (e.g., foo=1&bar=2&baz=") and makes it safe for all URI parsers.
175 std::string encode_xrootd_opaque_to_uri(CURL *curl, const std::string &opaque)
176 {
177  std::stringstream parser(opaque);
178  std::string sequence;
179  std::stringstream output;
180  bool first = true;
181  while (getline(parser, sequence, '&')) {
182  if (sequence.empty()) {continue;}
183  size_t equal_pos = sequence.find('=');
184  char *val = NULL;
185  if (equal_pos != std::string::npos)
186  val = curl_easy_escape(curl, sequence.c_str() + equal_pos + 1, sequence.size() - equal_pos - 1);
187  // Do not emit parameter if value exists and escaping failed.
188  if (!val && equal_pos != std::string::npos) {continue;}
189 
190  if (!first) output << "&";
191  first = false;
192  output << sequence.substr(0, equal_pos);
193  if (val) {
194  output << "=" << val;
195  curl_free(val);
196  }
197  }
198  return output.str();
199 }
200 
201 /******************************************************************************/
202 /* T P C H a n d l e r : : C o n f i g u r e C u r l C A */
203 /******************************************************************************/
204 
205 void
206 TPCHandler::ConfigureCurlCA(CURL *curl)
207 {
208  auto ca_filename = m_ca_file ? m_ca_file->CAFilename() : "";
209  auto crl_filename = m_ca_file ? m_ca_file->CRLFilename() : "";
210  if (!ca_filename.empty() && !crl_filename.empty()) {
211  curl_easy_setopt(curl, CURLOPT_CAINFO, ca_filename.c_str());
212  //Check that the CRL file contains at least one entry before setting this option to curl
213  //Indeed, an empty CRL file will make curl unhappy and therefore will fail
214  //all HTTP TPC transfers (https://github.com/xrootd/xrootd/issues/1543)
215  std::ifstream in(crl_filename, std::ifstream::ate | std::ifstream::binary);
216  if(in.tellg() > 0 && m_ca_file->atLeastOneValidCRLFound()){
217  curl_easy_setopt(curl, CURLOPT_CRLFILE, crl_filename.c_str());
218  } else {
219  std::ostringstream oss;
220  oss << "No valid CRL file has been found in the file " << crl_filename << ". Disabling CRL checking.";
221  m_log.Log(Warning,"TpcHandler",oss.str().c_str());
222  }
223  }
224  else if (!m_cadir.empty()) {
225  curl_easy_setopt(curl, CURLOPT_CAPATH, m_cadir.c_str());
226  }
227  if (!m_cafile.empty()) {
228  curl_easy_setopt(curl, CURLOPT_CAINFO, m_cafile.c_str());
229  }
230 }
231 
232 
233 bool TPCHandler::MatchesPath(const char *verb, const char *path) {
234  return !strcmp(verb, "COPY") || !strcmp(verb, "OPTIONS");
235 }
236 
237 /******************************************************************************/
238 /* P r e p a r e U R L */
239 /******************************************************************************/
240 
241 static std::string PrepareURL(const std::string &input) {
242  if (!strncmp(input.c_str(), "davs://", 7)) {
243  return "https://" + input.substr(7);
244  }
245  return input;
246 }
247 
248 /******************************************************************************/
249 /* T P C H a n d l e r : : P r o c e s s R e q */
250 /******************************************************************************/
251 
253  if (req.verb == "OPTIONS") {
254  return ProcessOptionsReq(req);
255  }
256  auto header = XrdOucTUtils::caseInsensitiveFind(req.headers,"credential");
257  if (header != req.headers.end()) {
258  if (header->second != "none") {
259  m_log.Emsg("ProcessReq", "COPY requested an unsupported credential type: ", header->second.c_str());
260  return req.SendSimpleResp(400, NULL, NULL, "COPY requestd an unsupported Credential type", 0);
261  }
262  }
263  header = XrdOucTUtils::caseInsensitiveFind(req.headers,"source");
264  if (header != req.headers.end()) {
265  std::string src = PrepareURL(header->second);
266  return ProcessPullReq(src, req);
267  }
268  header = XrdOucTUtils::caseInsensitiveFind(req.headers,"destination");
269  if (header != req.headers.end()) {
270  return ProcessPushReq(header->second, req);
271  }
272  m_log.Emsg("ProcessReq", "COPY verb requested but no source or destination specified.");
273  return req.SendSimpleResp(400, NULL, NULL, "No Source or Destination specified", 0);
274 }
275 
276 /******************************************************************************/
277 /* T P C H a n d l e r D e s t r u c t o r */
278 /******************************************************************************/
279 
281  m_sfs = NULL;
282 }
283 
284 /******************************************************************************/
285 /* T P C H a n d l e r C o n s t r u c t o r */
286 /******************************************************************************/
287 
288 TPCHandler::TPCHandler(XrdSysError *log, const char *config, XrdOucEnv *myEnv) :
289  m_desthttps(false),
290  m_fixed_route(false),
291  m_timeout(60),
292  m_first_timeout(120),
293  m_log(log->logger(), "TPC_"),
294  m_sfs(NULL)
295 {
296  if (!Configure(config, myEnv)) {
297  throw std::runtime_error("Failed to configure the HTTP third-party-copy handler.");
298  }
299 
300 // Extract out the TPC monitoring object (we share it with xrootd).
301 //
302  XrdXrootdGStream *gs = (XrdXrootdGStream*)myEnv->GetPtr("Tpc.gStream*");
303  if (gs)
304  TPCLogRecord::tpcMonitor = new XrdXrootdTpcMon("http",log->logger(),*gs);
305 }
306 
307 /******************************************************************************/
308 /* T P C H a n d l e r : : P r o c e s s O p t i o n s R e q */
309 /******************************************************************************/
310 
314 int TPCHandler::ProcessOptionsReq(XrdHttpExtReq &req) {
315  return req.SendSimpleResp(200, NULL, (char *) "DAV: 1\r\nDAV: <http://apache.org/dav/propset/fs/1>\r\nAllow: HEAD,GET,PUT,PROPFIND,DELETE,OPTIONS,COPY", NULL, 0);
316 }
317 
318 /******************************************************************************/
319 /* T P C H a n d l e r : : G e t A u t h z */
320 /******************************************************************************/
321 
322 std::string TPCHandler::GetAuthz(XrdHttpExtReq &req) {
323  std::string authz;
324  auto authz_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"authorization");
325  if (authz_header != req.headers.end()) {
326  std::stringstream ss;
327  ss << "authz=" << encode_str(authz_header->second);
328  authz += ss.str();
329  }
330  return authz;
331 }
332 
333 /******************************************************************************/
334 /* T P C H a n d l e r : : R e d i r e c t T r a n s f e r */
335 /******************************************************************************/
336 
337 int TPCHandler::RedirectTransfer(CURL *curl, const std::string &redirect_resource,
338  XrdHttpExtReq &req, XrdOucErrInfo &error, TPCLogRecord &rec)
339 {
340  int port;
341  const char *ptr = error.getErrText(port);
342  if ((ptr == NULL) || (*ptr == '\0') || (port == 0)) {
343  rec.status = 500;
344  std::stringstream ss;
345  ss << "Internal error: redirect without hostname";
346  logTransferEvent(LogMask::Error, rec, "REDIRECT_INTERNAL_ERROR", ss.str());
347  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
348  }
349 
350  // Construct redirection URL taking into consideration any opaque info
351  std::string rdr_info = ptr;
352  std::string host, opaque;
353  size_t pos = rdr_info.find('?');
354  host = rdr_info.substr(0, pos);
355 
356  if (pos != std::string::npos) {
357  opaque = rdr_info.substr(pos + 1);
358  }
359 
360  std::stringstream ss;
361  ss << "Location: http" << (m_desthttps ? "s" : "") << "://" << host << ":" << port << "/" << redirect_resource;
362 
363  if (!opaque.empty()) {
364  ss << "?" << encode_xrootd_opaque_to_uri(curl, opaque);
365  }
366 
367  rec.status = 307;
368  logTransferEvent(LogMask::Info, rec, "REDIRECT", ss.str());
369  return req.SendSimpleResp(rec.status, NULL, const_cast<char *>(ss.str().c_str()),
370  NULL, 0);
371 }
372 
373 /******************************************************************************/
374 /* T P C H a n d l e r : : O p e n W a i t S t a l l */
375 /******************************************************************************/
376 
377 int TPCHandler::OpenWaitStall(XrdSfsFile &fh, const std::string &resource,
378  int mode, int openMode, const XrdSecEntity &sec,
379  const std::string &authz)
380 {
381  int open_result;
382  while (1) {
383  int orig_ucap = fh.error.getUCap();
384  fh.error.setUCap(orig_ucap | XrdOucEI::uIPv64);
385  std::string opaque;
386  size_t pos = resource.find('?');
387  // Extract the path and opaque info from the resource
388  std::string path = resource.substr(0, pos);
389 
390  if (pos != std::string::npos) {
391  opaque = resource.substr(pos + 1);
392  }
393 
394  // Append the authz information if there are some
395  if(!authz.empty()) {
396  opaque += (opaque.empty() ? "" : "&");
397  opaque += authz;
398  }
399  open_result = fh.open(path.c_str(), mode, openMode, &sec, opaque.c_str());
400 
401  if ((open_result == SFS_STALL) || (open_result == SFS_STARTED)) {
402  int secs_to_stall = fh.error.getErrInfo();
403  if (open_result == SFS_STARTED) {secs_to_stall = secs_to_stall/2 + 5;}
404  std::this_thread::sleep_for (std::chrono::seconds(secs_to_stall));
405  }
406  break;
407  }
408  return open_result;
409 }
410 
411 /******************************************************************************/
412 /* T P C H a n d l e r : : D e t e r m i n e X f e r S i z e */
413 /******************************************************************************/
414 
415 
416 
420 int TPCHandler::DetermineXferSize(CURL *curl, XrdHttpExtReq &req, State &state,
421  bool &success, TPCLogRecord &rec, bool shouldReturnErrorToClient) {
422  success = false;
423  curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
424  // Set a custom timeout of 60 seconds (= CONNECT_TIMEOUT for convenience) for the HEAD request
425  curl_easy_setopt(curl, CURLOPT_TIMEOUT, CONNECT_TIMEOUT);
426  CURLcode res;
427  res = curl_easy_perform(curl);
428  //Immediately set the CURLOPT_NOBODY flag to 0 as we anyway
429  //don't want the next curl call to do be a HEAD request
430  curl_easy_setopt(curl, CURLOPT_NOBODY, 0);
431  // Reset the CURLOPT_TIMEOUT to no timeout (default)
432  curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);
433  if (res == CURLE_HTTP_RETURNED_ERROR) {
434  std::stringstream ss;
435  ss << "Remote server failed request while fetching remote size";
436  std::stringstream ss2;
437  ss2 << ss.str() << ": " << curl_easy_strerror(res);
438  rec.status = 500;
439  logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss2.str());
440  return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec, res).c_str(), 0) : -1;
441  } else if (state.GetStatusCode() >= 400) {
442  std::stringstream ss;
443  ss << "Remote side " << req.clienthost << " failed with status code " << state.GetStatusCode() << " while fetching remote size";
444  rec.status = 500;
445  logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss.str());
446  return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0) : -1;
447  } else if (res) {
448  std::stringstream ss;
449  ss << "Internal transfer failure while fetching remote size";
450  std::stringstream ss2;
451  ss2 << ss.str() << " - HTTP library failed: " << curl_easy_strerror(res);
452  rec.status = 500;
453  logTransferEvent(LogMask::Error, rec, "SIZE_FAIL", ss2.str());
454  return shouldReturnErrorToClient ? req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec, res).c_str(), 0) : -1;
455  }
456  std::stringstream ss;
457  ss << "Successfully determined remote size for pull request: "
458  << state.GetContentLength();
459  logTransferEvent(LogMask::Debug, rec, "SIZE_SUCCESS", ss.str());
460  success = true;
461  return 0;
462 }
463 
464 int TPCHandler::GetContentLengthTPCPull(CURL *curl, XrdHttpExtReq &req, uint64_t &contentLength, bool & success, TPCLogRecord &rec) {
465  State state(curl,req.tpcForwardCreds);
466  //Don't forget to copy the headers of the client's request before doing the HEAD call. Otherwise, if there is a need for authentication,
467  //it will fail
468  state.SetupHeaders(req);
469  int result;
470  //In case we cannot get the content length, we return the error to the client
471  if ((result = DetermineXferSize(curl, req, state, success, rec)) || !success) {
472  return result;
473  }
474  contentLength = state.GetContentLength();
475  return result;
476 }
477 
478 /******************************************************************************/
479 /* T P C H a n d l e r : : S e n d P e r f M a r k e r */
480 /******************************************************************************/
481 
482 int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, TPC::State &state) {
483  std::stringstream ss;
484  const std::string crlf = "\n";
485  ss << "Perf Marker" << crlf;
486  ss << "Timestamp: " << time(NULL) << crlf;
487  ss << "Stripe Index: 0" << crlf;
488  ss << "Stripe Bytes Transferred: " << state.BytesTransferred() << crlf;
489  ss << "Total Stripe Count: 1" << crlf;
490  // Include the TCP connection associated with this transfer; used by
491  // the TPC client for monitoring purposes.
492  std::string desc = state.GetConnectionDescription();
493  if (!desc.empty())
494  ss << "RemoteConnections: " << desc << crlf;
495  ss << "End" << crlf;
496  rec.bytes_transferred = state.BytesTransferred();
497  logTransferEvent(LogMask::Debug, rec, "PERF_MARKER");
498 
499  return req.ChunkResp(ss.str().c_str(), 0);
500 }
501 
502 /******************************************************************************/
503 /* T P C H a n d l e r : : S e n d P e r f M a r k e r */
504 /******************************************************************************/
505 
506 int TPCHandler::SendPerfMarker(XrdHttpExtReq &req, TPCLogRecord &rec, std::vector<State*> &state,
507  off_t bytes_transferred)
508 {
509  // The 'performance marker' format is largely derived from how GridFTP works
510  // (e.g., the concept of `Stripe` is not quite so relevant here). See:
511  // https://twiki.cern.ch/twiki/bin/view/LCG/HttpTpcTechnical
512  // Example marker:
513  // Perf Marker\n
514  // Timestamp: 1537788010\n
515  // Stripe Index: 0\n
516  // Stripe Bytes Transferred: 238745\n
517  // Total Stripe Count: 1\n
518  // RemoteConnections: tcp:129.93.3.4:1234,tcp:[2600:900:6:1301:268a:7ff:fef6:a590]:2345\n
519  // End\n
520  //
521  std::stringstream ss;
522  const std::string crlf = "\n";
523  ss << "Perf Marker" << crlf;
524  ss << "Timestamp: " << time(NULL) << crlf;
525  ss << "Stripe Index: 0" << crlf;
526  ss << "Stripe Bytes Transferred: " << bytes_transferred << crlf;
527  ss << "Total Stripe Count: 1" << crlf;
528  // Build a list of TCP connections associated with this transfer; used by
529  // the TPC client for monitoring purposes.
530  bool first = true;
531  std::stringstream ss2;
532  for (std::vector<State*>::const_iterator iter = state.begin();
533  iter != state.end(); iter++)
534  {
535  std::string desc = (*iter)->GetConnectionDescription();
536  if (!desc.empty()) {
537  ss2 << (first ? "" : ",") << desc;
538  first = false;
539  }
540  }
541  if (!first)
542  ss << "RemoteConnections: " << ss2.str() << crlf;
543  ss << "End" << crlf;
544  rec.bytes_transferred = bytes_transferred;
545  logTransferEvent(LogMask::Debug, rec, "PERF_MARKER");
546 
547  return req.ChunkResp(ss.str().c_str(), 0);
548 }
549 
550 /******************************************************************************/
551 /* T P C H a n d l e r : : R u n C u r l W i t h U p d a t e s */
552 /******************************************************************************/
553 
554 int TPCHandler::RunCurlWithUpdates(CURL *curl, XrdHttpExtReq &req, State &state,
555  TPCLogRecord &rec)
556 {
557  // Create the multi-handle and add in the current transfer to it.
558  CURLM *multi_handle = curl_multi_init();
559  if (!multi_handle) {
560  rec.status = 500;
561  logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL",
562  "Failed to initialize a libcurl multi-handle");
563  std::stringstream ss;
564  ss << "Failed to initialize internal server memory";
565  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
566  }
567 
568  //curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 128*1024);
569 
570  CURLMcode mres;
571  mres = curl_multi_add_handle(multi_handle, curl);
572  if (mres) {
573  rec.status = 500;
574  std::stringstream ss;
575  ss << "Failed to add transfer to libcurl multi-handle: HTTP library failure=" << curl_multi_strerror(mres);
576  logTransferEvent(LogMask::Error, rec, "CURL_INIT_FAIL", ss.str());
577  curl_multi_cleanup(multi_handle);
578  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
579  }
580 
581  // Start response to client prior to the first call to curl_multi_perform
582  int retval = req.StartChunkedResp(201, "Created", "Content-Type: text/plain");
583  if (retval) {
584  curl_multi_cleanup(multi_handle);
585  logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
586  "Failed to send the initial response to the TPC client");
587  return retval;
588  } else {
589  logTransferEvent(LogMask::Debug, rec, "RESPONSE_START",
590  "Initial transfer response sent to the TPC client");
591  }
592 
593  // Transfer loop: use curl to actually run the transfer, but periodically
594  // interrupt things to send back performance updates to the client.
595  int running_handles = 1;
596  time_t last_marker = 0;
597  // Track how long it's been since the last time we recorded more bytes being transferred.
598  off_t last_advance_bytes = 0;
599  time_t last_advance_time = time(NULL);
600  time_t transfer_start = last_advance_time;
601  CURLcode res = static_cast<CURLcode>(-1);
602  do {
603  time_t now = time(NULL);
604  time_t next_marker = last_marker + m_marker_period;
605  if (now >= next_marker) {
606  off_t bytes_xfer = state.BytesTransferred();
607  if (bytes_xfer > last_advance_bytes) {
608  last_advance_bytes = bytes_xfer;
609  last_advance_time = now;
610  }
611  if (SendPerfMarker(req, rec, state)) {
612  curl_multi_remove_handle(multi_handle, curl);
613  curl_multi_cleanup(multi_handle);
614  logTransferEvent(LogMask::Error, rec, "PERFMARKER_FAIL",
615  "Failed to send a perf marker to the TPC client");
616  return -1;
617  }
618  int timeout = (transfer_start == last_advance_time) ? m_first_timeout : m_timeout;
619  if (now > last_advance_time + timeout) {
620  const char *log_prefix = rec.log_prefix.c_str();
621  bool tpc_pull = strncmp("Pull", log_prefix, 4) == 0;
622 
623  state.SetErrorCode(10);
624  std::stringstream ss;
625  ss << "Transfer failed because no bytes have been "
626  << (tpc_pull ? "received from the source (pull mode) in "
627  : "transmitted to the destination (push mode) in ") << timeout << " seconds.";
628  state.SetErrorMessage(ss.str());
629  curl_multi_remove_handle(multi_handle, curl);
630  curl_multi_cleanup(multi_handle);
631  break;
632  }
633  last_marker = now;
634  }
635  // The transfer will start after this point, notify the packet marking manager
636  rec.pmarkManager.startTransfer();
637  mres = curl_multi_perform(multi_handle, &running_handles);
638  if (mres == CURLM_CALL_MULTI_PERFORM) {
639  // curl_multi_perform should be called again immediately. On newer
640  // versions of curl, this is no longer used.
641  continue;
642  } else if (mres != CURLM_OK) {
643  break;
644  } else if (running_handles == 0) {
645  break;
646  }
647 
648  rec.pmarkManager.beginPMarks();
649  //printf("There are %d running handles\n", running_handles);
650 
651  // Harvest any messages, looking for CURLMSG_DONE.
652  CURLMsg *msg;
653  do {
654  int msgq = 0;
655  msg = curl_multi_info_read(multi_handle, &msgq);
656  if (msg && (msg->msg == CURLMSG_DONE)) {
657  CURL *easy_handle = msg->easy_handle;
658  res = msg->data.result;
659  curl_multi_remove_handle(multi_handle, easy_handle);
660  }
661  } while (msg);
662 
663  int64_t max_sleep_time = next_marker - time(NULL);
664  if (max_sleep_time <= 0) {
665  continue;
666  }
667  int fd_count;
668  mres = curl_multi_wait(multi_handle, NULL, 0, max_sleep_time*1000, &fd_count);
669  if (mres != CURLM_OK) {
670  break;
671  }
672  } while (running_handles);
673 
674  if (mres != CURLM_OK) {
675  std::stringstream ss;
676  ss << "Internal libcurl multi-handle error: HTTP library failure=" << curl_multi_strerror(mres);
677  logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str());
678 
679  curl_multi_remove_handle(multi_handle, curl);
680  curl_multi_cleanup(multi_handle);
681 
682  if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
683  logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
684  "Failed to send error message to the TPC client");
685  return retval;
686  }
687  return req.ChunkResp(NULL, 0);
688  }
689 
690  // Harvest any messages, looking for CURLMSG_DONE.
691  CURLMsg *msg;
692  do {
693  int msgq = 0;
694  msg = curl_multi_info_read(multi_handle, &msgq);
695  if (msg && (msg->msg == CURLMSG_DONE)) {
696  CURL *easy_handle = msg->easy_handle;
697  res = msg->data.result;
698  curl_multi_remove_handle(multi_handle, easy_handle);
699  }
700  } while (msg);
701 
702  if (!state.GetErrorCode() && res == static_cast<CURLcode>(-1)) { // No transfers returned?!?
703  curl_multi_remove_handle(multi_handle, curl);
704  curl_multi_cleanup(multi_handle);
705  std::stringstream ss;
706  ss << "Internal state error in libcurl";
707  logTransferEvent(LogMask::Error, rec, "TRANSFER_CURL_ERROR", ss.str());
708 
709  if ((retval = req.ChunkResp(generateClientErr(ss, rec).c_str(), 0))) {
710  logTransferEvent(LogMask::Error, rec, "RESPONSE_FAIL",
711  "Failed to send error message to the TPC client");
712  return retval;
713  }
714  return req.ChunkResp(NULL, 0);
715  }
716  curl_multi_cleanup(multi_handle);
717 
718  state.Flush();
719 
720  rec.bytes_transferred = state.BytesTransferred();
721  rec.tpc_status = state.GetStatusCode();
722 
723  // Explicitly finalize the stream (which will close the underlying file
724  // handle) before the response is sent. In some cases, subsequent HTTP
725  // requests can occur before the filesystem is done closing the handle -
726  // and those requests may occur against partial data.
727  state.Finalize();
728 
729  // Generate the final response back to the client.
730  std::stringstream ss;
731  bool success = false;
732  if (state.GetStatusCode() >= 400) {
733  std::string err = state.GetErrorMessage();
734  std::stringstream ss2;
735  ss2 << "Remote side failed with status code " << state.GetStatusCode();
736  if (!err.empty()) {
737  std::replace(err.begin(), err.end(), '\n', ' ');
738  ss2 << "; error message: \"" << err << "\"";
739  }
740  logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
741  ss << generateClientErr(ss2, rec);
742  } else if (state.GetErrorCode()) {
743  std::string err = state.GetErrorMessage();
744  if (err.empty()) {err = "(no error message provided)";}
745  else {std::replace(err.begin(), err.end(), '\n', ' ');}
746  std::stringstream ss2;
747  ss2 << "Error when interacting with local filesystem: " << err;
748  logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss2.str());
749  ss << generateClientErr(ss2, rec);
750  } else if (res != CURLE_OK) {
751  std::stringstream ss2;
752  ss2 << "Internal transfer failure";
753  std::stringstream ss3;
754  ss3 << ss2.str() << ": " << curl_easy_strerror(res);
755  logTransferEvent(LogMask::Error, rec, "TRANSFER_FAIL", ss3.str());
756  ss << generateClientErr(ss2, rec, res);
757  } else {
758  ss << "success: Created";
759  success = true;
760  }
761 
762  if ((retval = req.ChunkResp(ss.str().c_str(), 0))) {
763  logTransferEvent(LogMask::Error, rec, "TRANSFER_ERROR",
764  "Failed to send last update to remote client");
765  return retval;
766  } else if (success) {
767  logTransferEvent(LogMask::Info, rec, "TRANSFER_SUCCESS");
768  rec.status = 0;
769  }
770  return req.ChunkResp(NULL, 0);
771 }
772 
773 /******************************************************************************/
774 /* T P C H a n d l e r : : P r o c e s s P u s h R e q */
775 /******************************************************************************/
776 
777 int TPCHandler::ProcessPushReq(const std::string & resource, XrdHttpExtReq &req) {
778  TPCLogRecord rec(req, TpcType::Push);
779  rec.log_prefix = "PushRequest";
780  rec.local = req.resource;
781  rec.remote = resource;
782  rec.m_log = &m_log;
783  char *name = req.GetSecEntity().name;
784  req.GetClientID(rec.clID);
785  if (name) rec.name = name;
786  logTransferEvent(LogMask::Info, rec, "PUSH_START", "Starting a push request");
787 
788  ManagedCurlHandle curlPtr(curl_easy_init());
789  auto curl = curlPtr.get();
790  if (!curl) {
791  std::stringstream ss;
792  ss << "Failed to initialize internal transfer resources";
793  rec.status = 500;
794  logTransferEvent(LogMask::Error, rec, "PUSH_FAIL", ss.str());
795  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
796  }
797  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
798  curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1);
799 // curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_setcloexec_callback);
800 
801  curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
802  curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
803  curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
804  curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
805  curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
806  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
807  auto query_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"xrd-http-fullresource");
808  std::string redirect_resource = req.resource;
809  if (query_header != req.headers.end()) {
810  redirect_resource = query_header->second;
811  }
812 
813  AtomicBeg(m_monid_mutex);
814  uint64_t file_monid = AtomicInc(m_monid);
815  AtomicEnd(m_monid_mutex);
816  std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, file_monid));
817  if (!fh.get()) {
818  rec.status = 500;
819  std::stringstream ss;
820  ss << "Failed to initialize internal transfer file handle";
821  logTransferEvent(LogMask::Error, rec, "OPEN_FAIL",
822  ss.str());
823  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
824  }
825  std::string full_url = prepareURL(req);
826 
827  std::string authz = GetAuthz(req);
828 
829  int open_results = OpenWaitStall(*fh, full_url, SFS_O_RDONLY, 0644,
830  req.GetSecEntity(), authz);
831  if (SFS_REDIRECT == open_results) {
832  int result = RedirectTransfer(curl, redirect_resource, req, fh->error, rec);
833  return result;
834  } else if (SFS_OK != open_results) {
835  int code;
836  std::stringstream ss;
837  const char *msg = fh->error.getErrText(code);
838  if (msg == NULL) ss << "Failed to open local resource";
839  else ss << msg;
840  rec.status = 400;
841  if (code == EACCES) rec.status = 401;
842  else if (code == EEXIST) rec.status = 412;
843  logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", msg);
844  int resp_result = req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
845  fh->close();
846  return resp_result;
847  }
848  ConfigureCurlCA(curl);
849  curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
850 
851  Stream stream(std::move(fh), 0, 0, m_log);
852  State state(0, stream, curl, true, req.tpcForwardCreds);
853  state.SetupHeaders(req);
854 
855  return RunCurlWithUpdates(curl, req, state, rec);
856 }
857 
858 /******************************************************************************/
859 /* T P C H a n d l e r : : P r o c e s s P u l l R e q */
860 /******************************************************************************/
861 
862 int TPCHandler::ProcessPullReq(const std::string &resource, XrdHttpExtReq &req) {
863  TPCLogRecord rec(req,TpcType::Pull);
864  rec.log_prefix = "PullRequest";
865  rec.local = req.resource;
866  rec.remote = resource;
867  rec.m_log = &m_log;
868  char *name = req.GetSecEntity().name;
869  req.GetClientID(rec.clID);
870  if (name) rec.name = name;
871  logTransferEvent(LogMask::Info, rec, "PULL_START", "Starting a pull request");
872 
873  ManagedCurlHandle curlPtr(curl_easy_init());
874  auto curl = curlPtr.get();
875  if (!curl) {
876  std::stringstream ss;
877  ss << "Failed to initialize internal transfer resources";
878  rec.status = 500;
879  logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
880  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
881  }
882  // ddavila 2023-01-05:
883  // The following change was required by the Rucio/SENSE project where
884  // multiple IP addresses, each from a different subnet, are assigned to a
885  // single server and routed differently by SENSE.
886  // The above requires the server to utilize the same IP, that was used to
887  // start the TPC, for the resolution of the given TPC instead of
888  // using any of the IPs available.
889  if (m_fixed_route){
890  XrdNetAddr *nP;
891  int numIP = 0;
892  char buff[1024];
893  char * ip;
894 
895  // Get the hostname used to contact the server from the http header
896  auto host_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"host");
897  std::string host_used;
898  if (host_header != req.headers.end()) {
899  host_used = host_header->second;
900  }
901 
902  // Get the IP addresses associated with the above hostname
903  XrdNetUtils::GetAddrs(host_used.c_str(), &nP, numIP, XrdNetUtils::prefAuto, 0);
904  int ip_size = nP[0].Format(buff, 1024, XrdNetAddrInfo::fmtAddr,XrdNetAddrInfo::noPort);
905  ip = (char *)malloc(ip_size-1);
906 
907  // Substring to get only the address, remove brackets and garbage
908  memcpy(ip, buff+1, ip_size-2);
909  ip[ip_size-2]='\0';
910  logTransferEvent(LogMask::Info, rec, "LOCAL IP", ip);
911 
912  curl_easy_setopt(curl, CURLOPT_INTERFACE, ip);
913  }
914  curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
915  curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1);
916 // curl_easy_setopt(curl,CURLOPT_SOCKOPTFUNCTION,sockopt_setcloexec_callback);
917  curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);
918  curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &rec);
919  curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
920  curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA , &rec);
921  curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closesocket_callback);
922  curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &rec);
923  curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, CONNECT_TIMEOUT);
924  std::unique_ptr<XrdSfsFile> fh(m_sfs->newFile(name, m_monid++));
925  if (!fh.get()) {
926  std::stringstream ss;
927  ss << "Failed to initialize internal transfer file handle";
928  rec.status = 500;
929  logTransferEvent(LogMask::Error, rec, "PULL_FAIL", ss.str());
930  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
931  }
932  auto query_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"xrd-http-fullresource");
933  std::string redirect_resource = req.resource;
934  if (query_header != req.headers.end()) {
935  redirect_resource = query_header->second;
936  }
938  auto overwrite_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"overwrite");
939  if ((overwrite_header == req.headers.end()) || (overwrite_header->second == "T")) {
940  if (! usingEC) mode = SFS_O_TRUNC;
941  }
942  int streams = 1;
943  {
944  auto streams_header = XrdOucTUtils::caseInsensitiveFind(req.headers,"x-number-of-streams");
945  if (streams_header != req.headers.end()) {
946  int stream_req = -1;
947  try {
948  stream_req = std::stol(streams_header->second);
949  } catch (...) { // Handled below
950  }
951  if (stream_req < 0 || stream_req > 100) {
952  std::stringstream ss;
953  ss << "Invalid request for number of streams";
954  rec.status = 400;
955  logTransferEvent(LogMask::Info, rec, "INVALID_REQUEST", ss.str());
956  return req.SendSimpleResp(rec.status, NULL, NULL, generateClientErr(ss, rec).c_str(), 0);
957  }
958  streams = stream_req == 0 ? 1 : stream_req;
959  }
960  }
961  rec.streams = streams;
962  std::string full_url = prepareURL(req);
963  std::string authz = GetAuthz(req);
964  curl_easy_setopt(curl, CURLOPT_URL, resource.c_str());
965  ConfigureCurlCA(curl);
966  uint64_t sourceFileContentLength = 0;
967  {
968  //Get the content-length of the source file and pass it to the OSS layer
969  //during the open
970  bool success;
971  GetContentLengthTPCPull(curl, req, sourceFileContentLength, success, rec);
972  if(success) {
973  //In the case we cannot get the information from the source server (offline or other error)
974  //we just don't add the size information to the opaque of the local file to open
975  full_url += "&oss.asize=" + std::to_string(sourceFileContentLength);
976  } else {
977  // In the case the GetContentLength is not successful, an error will be returned to the client
978  // just exit here so we don't open the file!
979  return 0;
980  }
981  }
982  int open_result = OpenWaitStall(*fh, full_url, mode|SFS_O_WRONLY,
983  0644 | SFS_O_MKPTH,
984  req.GetSecEntity(), authz);
985  if (SFS_REDIRECT == open_result) {
986  int result = RedirectTransfer(curl, redirect_resource, req, fh->error, rec);
987  return result;
988  } else if (SFS_OK != open_result) {
989  int code;
990  std::stringstream ss;
991  const char *msg = fh->error.getErrText(code);
992  if ((msg == NULL) || (*msg == '\0')) ss << "Failed to open local resource";
993  else ss << msg;
994  rec.status = 400;
995  if (code == EACCES) rec.status = 401;
996  else if (code == EEXIST) rec.status = 412;
997  logTransferEvent(LogMask::Error, rec, "OPEN_FAIL", ss.str());
998  int resp_result = req.SendSimpleResp(rec.status, NULL, NULL,
999  generateClientErr(ss, rec).c_str(), 0);
1000  fh->close();
1001  return resp_result;
1002  }
1003  Stream stream(std::move(fh), streams * m_pipelining_multiplier, streams > 1 ? m_block_size : m_small_block_size, m_log);
1004  State state(0, stream, curl, false, req.tpcForwardCreds);
1005  state.SetupHeaders(req);
1006  state.SetContentLength(sourceFileContentLength);
1007 
1008  if (streams > 1) {
1009  return RunCurlWithStreams(req, state, streams, rec);
1010  } else {
1011  return RunCurlWithUpdates(curl, req, state, rec);
1012  }
1013 }
1014 
1015 /******************************************************************************/
1016 /* T P C H a n d l e r : : l o g T r a n s f e r E v e n t */
1017 /******************************************************************************/
1018 
1019 void TPCHandler::logTransferEvent(LogMask mask, const TPCLogRecord &rec,
1020  const std::string &event, const std::string &message)
1021 {
1022  if (!(m_log.getMsgMask() & mask)) {return;}
1023 
1024  std::stringstream ss;
1025  ss << "event=" << event << ", local=" << rec.local << ", remote=" << rec.remote;
1026  if (rec.name.empty())
1027  ss << ", user=(anonymous)";
1028  else
1029  ss << ", user=" << rec.name;
1030  if (rec.streams != 1)
1031  ss << ", streams=" << rec.streams;
1032  if (rec.bytes_transferred >= 0)
1033  ss << ", bytes_transferred=" << rec.bytes_transferred;
1034  if (rec.status >= 0)
1035  ss << ", status=" << rec.status;
1036  if (rec.tpc_status >= 0)
1037  ss << ", tpc_status=" << rec.tpc_status;
1038  if (!message.empty())
1039  ss << "; " << message;
1040  m_log.Log(mask, rec.log_prefix.c_str(), ss.str().c_str());
1041 }
1042 
1043 std::string TPCHandler::generateClientErr(std::stringstream &err_ss, const TPCLogRecord &rec, CURLcode cCode) {
1044  std::stringstream ssret;
1045  ssret << "failure: " << err_ss.str() << ", local=" << rec.local <<", remote=" << rec.remote;
1046  if(cCode != CURLcode::CURLE_OK) {
1047  ssret << ", HTTP library failure=" << curl_easy_strerror(cCode);
1048  }
1049  return ssret.str();
1050 }
1051 /******************************************************************************/
1052 /* X r d H t t p G e t E x t H a n d l e r */
1053 /******************************************************************************/
1054 
1055 extern "C" {
1056 
1057 XrdHttpExtHandler *XrdHttpGetExtHandler(XrdSysError *log, const char * config, const char * /*parms*/, XrdOucEnv *myEnv) {
1058  if (curl_global_init(CURL_GLOBAL_DEFAULT)) {
1059  log->Emsg("TPCInitialize", "libcurl failed to initialize");
1060  return NULL;
1061  }
1062 
1063  TPCHandler *retval{NULL};
1064  if (!config) {
1065  log->Emsg("TPCInitialize", "TPC handler requires a config filename in order to load");
1066  return NULL;
1067  }
1068  try {
1069  log->Emsg("TPCInitialize", "Will load configuration for the TPC handler from", config);
1070  retval = new TPCHandler(log, config, myEnv);
1071  } catch (std::runtime_error &re) {
1072  log->Emsg("TPCInitialize", "Encountered a runtime failure when loading ", re.what());
1073  //printf("Provided env vars: %p, XrdInet*: %p\n", myEnv, myEnv->GetPtr("XrdInet*"));
1074  }
1075  return retval;
1076 }
1077 
1078 }
void CURL
static std::string PrepareURL(const std::string &input)
XrdVERSIONINFO(XrdHttpGetExtHandler, HttpTPC)
XrdHttpExtHandler * XrdHttpGetExtHandler(XrdSysError *log, const char *config, const char *, XrdOucEnv *myEnv)
std::string encode_xrootd_opaque_to_uri(CURL *curl, const std::string &opaque)
std::string encode_str(const std::string &str)
#define close(a)
Definition: XrdPosix.hh:48
bool Debug
void getline(uchar *buff, int blen)
#define SFS_REDIRECT
#define SFS_O_MKPTH
#define SFS_STALL
#define SFS_O_RDONLY
#define SFS_STARTED
#define SFS_O_WRONLY
#define SFS_O_CREAT
int XrdSfsFileOpenMode
#define SFS_OK
#define SFS_O_TRUNC
#define AtomicInc(x)
#define AtomicBeg(Mtx)
#define AtomicEnd(Mtx)
@ Error
int GetStatusCode() const
off_t BytesTransferred() const
void SetErrorMessage(const std::string &error_msg)
int GetErrorCode() const
std::string GetErrorMessage() const
std::string GetConnectionDescription()
void SetupHeaders(XrdHttpExtReq &req)
void SetContentLength(const off_t content_length)
off_t GetContentLength() const
void SetErrorCode(int error_code)
TPCHandler(XrdSysError *log, const char *config, XrdOucEnv *myEnv)
virtual int ProcessReq(XrdHttpExtReq &req)
virtual ~TPCHandler()
virtual bool MatchesPath(const char *verb, const char *path)
Tells if the incoming path is recognized as one of the paths that have to be processed.
std::string clienthost
int ChunkResp(const char *body, long long bodylen)
Send a (potentially partial) body in a chunked response; invoking with NULL body.
void GetClientID(std::string &clid)
std::map< std::string, std::string > & headers
std::string resource
std::string verb
int StartChunkedResp(int code, const char *desc, const char *header_to_add)
Starts a chunked response; body of request is sent over multiple parts using the SendChunkResp.
const XrdSecEntity & GetSecEntity() const
int SendSimpleResp(int code, const char *desc, const char *header_to_add, const char *body, long long bodylen)
Sends a basic response. If the length is < 0 then it is calculated internally.
static std::string prepareOpenURL(const std::string &reqResource, std::map< std::string, std::string > &reqHeaders, const std::map< std::string, std::string > &hdr2cgimap)
static const int noPort
Do not add port number.
int Format(char *bAddr, int bLen, fmtUse fmtType=fmtAuto, int fmtOpts=0)
@ fmtAddr
Address using suitable ipv4 or ipv6 format.
static const char * GetAddrs(const char *hSpec, XrdNetAddr *aListP[], int &aListN, AddrOpts opts=allIPMap, int pNum=PortInSpec)
Definition: XrdNetUtils.cc:239
void * GetPtr(const char *varname)
Definition: XrdOucEnv.cc:263
const char * getErrText()
void setUCap(int ucval)
Set user capabilties.
static std::map< std::string, T >::const_iterator caseInsensitiveFind(const std::map< std::string, T > &m, const std::string &lowerCaseSearchKey)
Definition: XrdOucTUtils.hh:79
char * name
Entity's name.
Definition: XrdSecEntity.hh:69
virtual XrdSfsFile * newFile(char *user=0, int MonID=0)=0
XrdOucErrInfo & error
virtual int open(const char *fileName, XrdSfsFileOpenMode openMode, mode_t createMode, const XrdSecEntity *client=0, const char *opaque=0)=0
virtual int close()=0
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:95
XrdSysLogger * logger(XrdSysLogger *lp=0)
Definition: XrdSysError.hh:141
int getMsgMask()
Definition: XrdSysError.hh:156
void Log(int mask, const char *esfx, const char *text1, const char *text2=0, const char *text3=0)
Definition: XrdSysError.hh:133
std::unique_ptr< CURL, CurlDeleter > ManagedCurlHandle
@ Warning
void operator()(CURL *curl)
static const int uIPv64
ucap: Supports only IPv4 info
static const int isaPush