Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
TNetFile.cxx
Go to the documentation of this file.
1 // @(#)root/net:$Id$
2 // Author: Fons Rademakers 14/08/97
3 
4 /*************************************************************************
5  * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. *
6  * All rights reserved. *
7  * *
8  * For the licensing terms see $ROOTSYS/LICENSE. *
9  * For the list of contributors see $ROOTSYS/README/CREDITS. *
10  *************************************************************************/
11 
12 //////////////////////////////////////////////////////////////////////////
13 // //
14 // TNetFile //
15 // //
16 // A TNetFile is like a normal TFile except that it reads and writes //
17 // its data via a rootd server (for more on the rootd daemon see the //
18 // source files root/rootd/src/*.cxx). TNetFile file names are in //
19 // standard URL format with protocol "root" or "roots". The following //
20 // are valid TNetFile URL's: //
21 // //
22 // root://hpbrun.cern.ch/root/hsimple.root //
23 // root://pcna49a:5151/~na49/data/run821.root //
24 // root://pcna49d.cern.ch:5050//v1/data/run810.root //
25 // //
26 // The only difference with the well known httpd URL's is that the root //
27 // of the remote file tree is the user's home directory. Therefore an //
28 // absolute pathname requires a // after the host or port specifier //
29 // (see last example). Further the expansion of the standard shell //
30 // characters, like ~, $, .., are handled as expected. //
31 // TNetFile (actually TUrl) uses 1094 as default port for rootd. //
32 // //
33 // Connecting to a rootd requires the remote user id and password. //
34 // TNetFile allows three ways for you to provide your login: //
35 // 1) Setting it globally via the static functions: //
36 // TAuthenticate::SetGlobalUser() and //
37 // TAuthenticate::SetGlobalPasswd() //
38 // 2) Getting it from the ~/.netrc file (same file as used by ftp) //
39 // 3) Command line prompt //
40 // The different methods will be tried in the order given above. //
41 // On machines with AFS rootd will authenticate using AFS (if it was //
42 // compiled with AFS support). //
43 // //
44 // If the protocol is specified as "rootk" kerberos5 will be used for //
45 // authentication. //
46 // //
47 // The rootd daemon lives in the directory $ROOTSYS/bin. It can be //
48 // started either via inetd or by hand from the command line (no need //
49 // to be super user). For more info about rootd see the web page: //
50 // Begin_Html <a href=http://root.cern.ch/root/NetFile.html>NetFile</a> //
51 // End_Html //
52 // //
53 //////////////////////////////////////////////////////////////////////////
54 
55 #include <errno.h>
56 
57 #include "Bytes.h"
58 #include "NetErrors.h"
59 #include "TApplication.h"
60 #include "TEnv.h"
61 #include "TNetFile.h"
62 #include "TPSocket.h"
63 #include "TROOT.h"
64 #include "TSysEvtHandler.h"
65 #include "TSystem.h"
66 #include "TTimeStamp.h"
67 #include "TVirtualPerfStats.h"
68 
69 // fgClientProtocol is now in TAuthenticate
70 
71 ClassImp(TNetFile);
72 ClassImp(TNetSystem);
73 
74 ////////////////////////////////////////////////////////////////////////////////
75 /// Create a TNetFile object. This is actually done inside Create(), so
76 /// for a description of the options and other arguments see Create().
77 /// Normally a TNetFile is created via TFile::Open().
78 
79 TNetFile::TNetFile(const char *url, Option_t *option, const char *ftitle,
80  Int_t compress, Int_t netopt)
81  : TFile(url, "NET", ftitle, compress), fEndpointUrl(url)
82 {
83  fSocket = 0;
84  Create(url, option, netopt);
85 }
86 
87 ////////////////////////////////////////////////////////////////////////////////
88 /// Create a TNetFile object. To be used by derived classes, that need
89 /// to initialize the TFile base class but not open a connection at this
90 /// moment.
91 
92 TNetFile::TNetFile(const char *url, const char *ftitle, Int_t compress, Bool_t)
93  : TFile(url, "NET", ftitle, compress), fEndpointUrl(url)
94 {
95  fSocket = 0;
96  fProtocol = 0;
97  fErrorCode = 0;
98  fNetopt = 0;
99 }
100 
101 ////////////////////////////////////////////////////////////////////////////////
102 /// TNetFile dtor. Send close message and close socket.
103 
104 TNetFile::~TNetFile()
105 {
106  Close();
107 }
108 
109 ////////////////////////////////////////////////////////////////////////////////
110 /// Open a remote file. Requires fOption to be set correctly.
111 
112 Int_t TNetFile::SysOpen(const char * /*file*/, Int_t /*flags*/, UInt_t /*mode*/)
113 {
114  if (!fSocket) {
115 
116  Create(fUrl.GetUrl(), fOption, fNetopt);
117  if (!fSocket) return -1;
118 
119  } else {
120 
121  if (fProtocol > 15) {
122  fSocket->Send(Form("%s %s", fUrl.GetFile(), ToLower(fOption).Data()),
123  kROOTD_OPEN);
124  } else {
125  // Old daemon versions expect an additional slash at beginning
126  fSocket->Send(Form("/%s %s", fUrl.GetFile(), ToLower(fOption).Data()),
127  kROOTD_OPEN);
128  }
129 
130  EMessageTypes kind;
131  int stat;
132  Recv(stat, kind);
133 
134  if (kind == kROOTD_ERR) {
135  PrintError("SysOpen", stat);
136  return -1;
137  }
138  }
139 
140  // This means ok for net files
141  return -2; // set as fD in ReOpen
142 }
143 
144 ////////////////////////////////////////////////////////////////////////////////
145 /// Close currently open file.
146 
147 Int_t TNetFile::SysClose(Int_t /*fd*/)
148 {
149  if (fSocket)
150  fSocket->Send(kROOTD_CLOSE);
151 
152  return 0;
153 }
154 
155 ////////////////////////////////////////////////////////////////////////////////
156 /// Return file stat information. The interface and return value is
157 /// identical to TSystem::GetPathInfo().
158 
159 Int_t TNetFile::SysStat(Int_t, Long_t *id, Long64_t *size, Long_t *flags, Long_t *modtime)
160 {
161  if (fProtocol < 3) return 1;
162 
163  if (!fSocket) return 1;
164 
165  fSocket->Send(kROOTD_FSTAT);
166 
167  char msg[1024];
168  Int_t kind;
169  fSocket->Recv(msg, sizeof(msg), kind);
170 
171  Int_t mode, uid, gid, islink;
172  Long_t dev, ino;
173 
174  if (fProtocol > 12) {
175 #ifdef R__WIN32
176  sscanf(msg, "%ld %ld %d %d %d %I64d %ld %d", &dev, &ino, &mode,
177  &uid, &gid, size, modtime, &islink);
178 #else
179  sscanf(msg, "%ld %ld %d %d %d %lld %ld %d", &dev, &ino, &mode,
180  &uid, &gid, size, modtime, &islink);
181 #endif
182  if (dev == -1)
183  return 1;
184  if (id)
185  *id = (dev << 24) + ino;
186  if (flags) {
187  *flags = 0;
188  if (mode & (kS_IXUSR|kS_IXGRP|kS_IXOTH))
189  *flags |= 1;
190  if (R_ISDIR(mode))
191  *flags |= 2;
192  if (!R_ISREG(mode) && !R_ISDIR(mode))
193  *flags |= 4;
194  }
195  } else {
196 #ifdef R__WIN32
197  sscanf(msg, "%ld %I64d %ld %ld", id, size, flags, modtime);
198 #else
199  sscanf(msg, "%ld %lld %ld %ld", id, size, flags, modtime);
200 #endif
201  if (*id == -1)
202  return 1;
203  }
204 
205  return 0;
206 }
207 
208 ////////////////////////////////////////////////////////////////////////////////
209 /// Close remote file.
210 
211 void TNetFile::Close(Option_t *opt)
212 {
213  if (!fSocket) return;
214 
215  TFile::Close(opt);
216 
217  if (fProtocol > 6)
218  fSocket->Send(kROOTD_BYE);
219 
220  SafeDelete(fSocket);
221 
222  fD = -1; // so TFile::IsOpen() returns false when in TFile::~TFile
223 }
224 
225 ////////////////////////////////////////////////////////////////////////////////
226 /// Flush file to disk.
227 
228 void TNetFile::Flush()
229 {
230  FlushWriteCache();
231 
232  if (fSocket && fWritable)
233  fSocket->Send(kROOTD_FLUSH);
234 }
235 
236 ////////////////////////////////////////////////////////////////////////////////
237 /// Initialize a TNetFile object.
238 
239 void TNetFile::Init(Bool_t create)
240 {
241  Seek(0);
242 
243  TFile::Init(create);
244  fD = -2; // so TFile::IsOpen() returns true when in TFile::~TFile
245 }
246 
247 ////////////////////////////////////////////////////////////////////////////////
248 /// Retruns kTRUE if file is open, kFALSE otherwise.
249 
250 Bool_t TNetFile::IsOpen() const
251 {
252  return fSocket == 0 ? kFALSE : kTRUE;
253 }
254 
255 ////////////////////////////////////////////////////////////////////////////////
256 /// Print some info about the net file.
257 
258 void TNetFile::Print(Option_t *) const
259 {
260  const char *fname = fUrl.GetFile();
261  Printf("URL: %s", ((TUrl*)&fUrl)->GetUrl());
262  Printf("Remote file: %s", &fname[1]);
263  Printf("Remote user: %s", fUser.Data());
264  Printf("Title: %s", fTitle.Data());
265  Printf("Option: %s", fOption.Data());
266  Printf("Bytes written: %lld", fBytesWrite);
267  Printf("Bytes read: %lld", fBytesRead);
268 }
269 
270 ////////////////////////////////////////////////////////////////////////////////
271 /// Print error string depending on error code.
272 
273 void TNetFile::PrintError(const char *where, Int_t err)
274 {
275  fErrorCode = err;
276  Error(where, "%s", gRootdErrStr[err]);
277 }
278 
279 ////////////////////////////////////////////////////////////////////////////////
280 /// Reopen a file with a different access mode, like from READ to
281 /// UPDATE or from NEW, CREATE, RECREATE, UPDATE to READ. Thus the
282 /// mode argument can be either "READ" or "UPDATE". The method returns
283 /// 0 in case the mode was successfully modified, 1 in case the mode
284 /// did not change (was already as requested or wrong input arguments)
285 /// and -1 in case of failure, in which case the file cannot be used
286 /// anymore.
287 
288 Int_t TNetFile::ReOpen(Option_t *mode)
289 {
290  if (fProtocol < 7) {
291  Error("ReOpen", "operation not supported by remote rootd (protocol = %d)",
292  fProtocol);
293  return 1;
294  }
295 
296  return TFile::ReOpen(mode);
297 }
298 
299 ////////////////////////////////////////////////////////////////////////////////
300 /// Read specified byte range from remote file via rootd daemon.
301 /// Returns kTRUE in case of error.
302 
303 Bool_t TNetFile::ReadBuffer(char *buf, Int_t len)
304 {
305  if (!fSocket) return kTRUE;
306  if (len == 0)
307  return kFALSE;
308 
309  Bool_t result = kFALSE;
310 
311  Int_t st;
312  if ((st = ReadBufferViaCache(buf, len))) {
313  if (st == 2)
314  return kTRUE;
315  return kFALSE;
316  }
317 
318  if (gApplication && gApplication->GetSignalHandler())
319  gApplication->GetSignalHandler()->Delay();
320 
321  Double_t start = 0;
322  if (gPerfStats) start = TTimeStamp();
323 
324  if (fSocket->Send(Form("%lld %d", fOffset, len), kROOTD_GET) < 0) {
325  Error("ReadBuffer", "error sending kROOTD_GET command");
326  result = kTRUE;
327  goto end;
328  }
329 
330  Int_t stat, n;
331  EMessageTypes kind;
332 
333  fErrorCode = -1;
334  if (Recv(stat, kind) < 0 || kind == kROOTD_ERR) {
335  PrintError("ReadBuffer", stat);
336  result = kTRUE;
337  goto end;
338  }
339 
340  while ((n = fSocket->RecvRaw(buf, len)) < 0 && TSystem::GetErrno() == EINTR)
341  TSystem::ResetErrno();
342 
343  if (n != len) {
344  Error("ReadBuffer", "error receiving buffer of length %d, got %d", len, n);
345  result = kTRUE;
346  goto end;
347  }
348 
349  fOffset += len;
350 
351  fBytesRead += len;
352  fReadCalls++;
353 #ifdef R__WIN32
354  SetFileBytesRead(GetFileBytesRead() + len);
355  SetFileReadCalls(GetFileReadCalls() + 1);
356 #else
357  fgBytesRead += len;
358  fgReadCalls++;
359 #endif
360 
361 end:
362 
363  if (gPerfStats)
364  gPerfStats->FileReadEvent(this, len, start);
365 
366  if (gApplication && gApplication->GetSignalHandler())
367  gApplication->GetSignalHandler()->HandleDelayedSignal();
368 
369  return result;
370 }
371 
372 ////////////////////////////////////////////////////////////////////////////////
373 /// Read specified byte range from remote file via rootd daemon.
374 /// Returns kTRUE in case of error.
375 
376 Bool_t TNetFile::ReadBuffer(char *buf, Long64_t pos, Int_t len)
377 {
378  SetOffset(pos);
379  return ReadBuffer(buf, len);
380 }
381 
382 ////////////////////////////////////////////////////////////////////////////////
383 /// Read a list of buffers given in pos[] and len[] and return it in a single
384 /// buffer.
385 /// Returns kTRUE in case of error.
386 
387 Bool_t TNetFile::ReadBuffers(char *buf, Long64_t *pos, Int_t *len, Int_t nbuf)
388 {
389  if (!fSocket) return kTRUE;
390 
391  // If it's an old version of the protocol try the default TFile::ReadBuffers
392  if (fProtocol < 17)
393  return TFile::ReadBuffers(buf, pos, len, nbuf);
394 
395  Int_t stat;
396  Int_t blockSize = 262144; //Let's say we transfer 256KB at the time
397  Bool_t result = kFALSE;
398  EMessageTypes kind;
399  TString data_buf; // buf to put the info
400 
401  if (gApplication && gApplication->GetSignalHandler())
402  gApplication->GetSignalHandler()->Delay();
403 
404  Double_t start = 0;
405  if (gPerfStats) start = TTimeStamp();
406 
407  // Make the string with a list of offsets and lengths
408  Long64_t total_len = 0;
409  Long64_t actual_pos;
410  for(Int_t i = 0; i < nbuf; i++) {
411  data_buf += pos[i] + fArchiveOffset;
412  data_buf += "-";
413  data_buf += len[i];
414  data_buf += "/";
415  total_len += len[i];
416  }
417 
418  // Send the command with the length of the info and number of buffers
419  if (fSocket->Send(Form("%d %d %d", nbuf, data_buf.Length(), blockSize),
420  kROOTD_GETS) < 0) {
421  Error("ReadBuffers", "error sending kROOTD_GETS command");
422  result = kTRUE;
423  goto end;
424  }
425  // Send buffer with the list of offsets and lengths
426  if (fSocket->SendRaw(data_buf, data_buf.Length()) < 0) {
427  Error("ReadBuffers", "error sending buffer");
428  result = kTRUE;
429  goto end;
430  }
431 
432  fErrorCode = -1;
433  if (Recv(stat, kind) < 0 || kind == kROOTD_ERR) {
434  PrintError("ReadBuffers", stat);
435  result = kTRUE;
436  goto end;
437  }
438 
439  actual_pos = 0;
440  while (actual_pos < total_len) {
441  Long64_t left = total_len - actual_pos;
442  if (left > blockSize)
443  left = blockSize;
444 
445  Int_t n;
446  while ((n = fSocket->RecvRaw(buf + actual_pos, Int_t(left))) < 0 &&
447  TSystem::GetErrno() == EINTR)
448  TSystem::ResetErrno();
449 
450  if (n != Int_t(left)) {
451  Error("GetBuffers", "error receiving buffer of length %d, got %d",
452  Int_t(left), n);
453  result = kTRUE ;
454  goto end;
455  }
456  actual_pos += left;
457  }
458 
459  fBytesRead += total_len;
460  fReadCalls++;
461 #ifdef R__WIN32
462  SetFileBytesRead(GetFileBytesRead() + total_len);
463  SetFileReadCalls(GetFileReadCalls() + 1);
464 #else
465  fgBytesRead += total_len;
466  fgReadCalls++;
467 #endif
468 
469 end:
470 
471  if (gPerfStats)
472  gPerfStats->FileReadEvent(this, total_len, start);
473 
474  if (gApplication && gApplication->GetSignalHandler())
475  gApplication->GetSignalHandler()->HandleDelayedSignal();
476 
477  // If found problems try the generic implementation
478  if (result) {
479  if (gDebug > 0)
480  Info("ReadBuffers", "Couldnt use the specific implementation, calling TFile::ReadBuffers");
481  return TFile::ReadBuffers(buf, pos, len, nbuf);
482  }
483 
484  return result;
485 }
486 
487 ////////////////////////////////////////////////////////////////////////////////
488 /// Write specified byte range to remote file via rootd daemon.
489 /// Returns kTRUE in case of error.
490 
491 Bool_t TNetFile::WriteBuffer(const char *buf, Int_t len)
492 {
493  if (!fSocket || !fWritable) return kTRUE;
494 
495  Bool_t result = kFALSE;
496 
497  Int_t st;
498  if ((st = WriteBufferViaCache(buf, len))) {
499  if (st == 2)
500  return kTRUE;
501  return kFALSE;
502  }
503 
504  gSystem->IgnoreInterrupt();
505 
506  if (fSocket->Send(Form("%lld %d", fOffset, len), kROOTD_PUT) < 0) {
507  SetBit(kWriteError);
508  Error("WriteBuffer", "error sending kROOTD_PUT command");
509  result = kTRUE;
510  goto end;
511  }
512  if (fSocket->SendRaw(buf, len) < 0) {
513  SetBit(kWriteError);
514  Error("WriteBuffer", "error sending buffer");
515  result = kTRUE;
516  goto end;
517  }
518 
519  Int_t stat;
520  EMessageTypes kind;
521 
522  fErrorCode = -1;
523  if (Recv(stat, kind) < 0 || kind == kROOTD_ERR) {
524  SetBit(kWriteError);
525  PrintError("WriteBuffer", stat);
526  result = kTRUE;
527  goto end;
528  }
529 
530  fOffset += len;
531 
532  fBytesWrite += len;
533 #ifdef R__WIN32
534  SetFileBytesWritten(GetFileBytesWritten() + len);
535 #else
536  fgBytesWrite += len;
537 #endif
538 
539 end:
540  gSystem->IgnoreInterrupt(kFALSE);
541 
542  return result;
543 }
544 
545 ////////////////////////////////////////////////////////////////////////////////
546 /// Return status from rootd server and message kind. Returns -1 in
547 /// case of error otherwise 8 (sizeof 2 words, status and kind).
548 
549 Int_t TNetFile::Recv(Int_t &status, EMessageTypes &kind)
550 {
551  kind = kROOTD_ERR;
552  status = 0;
553 
554  if (!fSocket) return -1;
555 
556  Int_t what;
557  Int_t n = fSocket->Recv(status, what);
558  kind = (EMessageTypes) what;
559  return n;
560 }
561 
562 ////////////////////////////////////////////////////////////////////////////////
563 /// Set position from where to start reading.
564 
565 void TNetFile::Seek(Long64_t offset, ERelativeTo pos)
566 {
567  SetOffset(offset, pos);
568 }
569 
570 ////////////////////////////////////////////////////////////////////////////////
571 /// Connect to remote rootd server.
572 
573 void TNetFile::ConnectServer(Int_t *stat, EMessageTypes *kind, Int_t netopt,
574  Int_t tcpwindowsize, Bool_t forceOpen,
575  Bool_t forceRead)
576 {
577  TString fn = fUrl.GetFile();
578 
579  // Create Authenticated socket
580  Int_t sSize = netopt < -1 ? -netopt : 1;
581  TString url(fUrl.GetProtocol());
582  if (url.Contains("root")) {
583  url.Insert(4,"dp");
584  } else {
585  url = "rootdp";
586  }
587  url += TString(Form("://%s@%s:%d",
588  fUrl.GetUser(), fUrl.GetHost(), fUrl.GetPort()));
589  fSocket = TSocket::CreateAuthSocket(url, sSize, tcpwindowsize, fSocket, stat);
590  if (!fSocket || (fSocket && !fSocket->IsAuthenticated())) {
591  if (sSize > 1)
592  Error("TNetFile", "can't open %d-stream connection to rootd on "
593  "host %s at port %d", sSize, fUrl.GetHost(), fUrl.GetPort());
594  else
595  Error("TNetFile", "can't open connection to rootd on "
596  "host %s at port %d", fUrl.GetHost(), fUrl.GetPort());
597  *kind = kROOTD_ERR;
598  goto zombie;
599  }
600 
601  // Check if rootd supports new options
602  fProtocol = fSocket->GetRemoteProtocol();
603  if (forceRead && fProtocol < 5) {
604  Warning("ConnectServer", "rootd does not support \"+read\" option");
605  forceRead = kFALSE;
606  }
607 
608  // Open the file
609  if (fProtocol < 16)
610  // For backward compatibility we need to add a '/' at the beginning
611  fn.Insert(0,"/");
612  if (forceOpen)
613  fSocket->Send(Form("%s %s", fn.Data(),
614  ToLower("f"+fOption).Data()), kROOTD_OPEN);
615  else if (forceRead)
616  fSocket->Send(Form("%s %s", fn.Data(), "+read"), kROOTD_OPEN);
617  else
618  fSocket->Send(Form("%s %s", fn.Data(),
619  ToLower(fOption).Data()), kROOTD_OPEN);
620 
621  EMessageTypes tmpkind;
622  int tmpstat;
623  Recv(tmpstat, tmpkind);
624  *stat = tmpstat;
625  *kind = tmpkind;
626 
627  return;
628 
629 zombie:
630  // error in file opening occured, make this object a zombie
631  MakeZombie();
632  SafeDelete(fSocket);
633  gDirectory = gROOT;
634 }
635 
636 ////////////////////////////////////////////////////////////////////////////////
637 /// Create a NetFile object. A net file is the same as a TFile
638 /// except that it is being accessed via a rootd server. The url
639 /// argument must be of the form: root[k]://host.dom.ain/file.root.
640 /// When protocol is "rootk" try using kerberos5 authentication.
641 /// If the file specified in the URL does not exist, is not accessable
642 /// or can not be created the kZombie bit will be set in the TNetFile
643 /// object. Use IsZombie() to see if the file is accessable.
644 /// If the remote daemon thinks the file is still connected, while you are
645 /// sure this is not the case you can force open the file by preceding the
646 /// option argument with an "-", e.g.: "-recreate". Do this only
647 /// in cases when you are very sure nobody else is using the file.
648 /// To bypass the writelock on a file, to allow the reading of a file
649 /// that is being written by another process, explicitly specify the
650 /// "+read" option ("read" being the default option).
651 /// The netopt argument can be used to specify the size of the tcp window in
652 /// bytes (for more info see: http://www.psc.edu/networking/perf_tune.html).
653 /// The default and minimum tcp window size is 65535 bytes.
654 /// If netopt < -1 then |netopt| is the number of parallel sockets that will
655 /// be used to connect to rootd. This option should be used on fat pipes
656 /// (i.e. high bandwidth, high latency links). The ideal number of parallel
657 /// sockets depends on the bandwidth*delay product. Generally 5-7 is a good
658 /// number.
659 /// For a description of the option and other arguments see the TFile ctor.
660 /// The preferred interface to this constructor is via TFile::Open().
661 
662 void TNetFile::Create(const char * /*url*/, Option_t *option, Int_t netopt)
663 {
664  Int_t tcpwindowsize = 65535;
665 
666  fErrorCode = -1;
667  fNetopt = netopt;
668  fOption = option;
669 
670  Bool_t forceOpen = kFALSE;
671  if (option[0] == '-') {
672  fOption = &option[1];
673  forceOpen = kTRUE;
674  }
675  // accept 'f', like 'frecreate' still for backward compatibility
676  if (option[0] == 'F' || option[0] == 'f') {
677  fOption = &option[1];
678  forceOpen = kTRUE;
679  }
680 
681  Bool_t forceRead = kFALSE;
682  if (!strcasecmp(option, "+read")) {
683  fOption = &option[1];
684  forceRead = kTRUE;
685  }
686 
687  fOption.ToUpper();
688 
689  if (fOption == "NEW")
690  fOption = "CREATE";
691 
692  Bool_t create = (fOption == "CREATE") ? kTRUE : kFALSE;
693  Bool_t recreate = (fOption == "RECREATE") ? kTRUE : kFALSE;
694  Bool_t update = (fOption == "UPDATE") ? kTRUE : kFALSE;
695  Bool_t read = (fOption == "READ") ? kTRUE : kFALSE;
696  if (!create && !recreate && !update && !read) {
697  read = kTRUE;
698  fOption = "READ";
699  }
700 
701  if (!fUrl.IsValid()) {
702  Error("Create", "invalid URL specified: %s", fUrl.GetUrl());
703  goto zombie;
704  }
705 
706  if (netopt > tcpwindowsize)
707  tcpwindowsize = netopt;
708 
709  // Open connection to remote rootd server
710  EMessageTypes kind;
711  Int_t stat;
712  ConnectServer(&stat, &kind, netopt, tcpwindowsize, forceOpen, forceRead);
713  if (gDebug > 2) Info("Create", "got from host %d %d", stat, kind);
714 
715  if (kind == kROOTD_ERR) {
716  PrintError("Create", stat);
717  Error("Create", "failing on file %s", fUrl.GetUrl());
718  goto zombie;
719  }
720 
721  if (recreate) {
722  recreate = kFALSE;
723  create = kTRUE;
724  fOption = "CREATE";
725  }
726 
727  if (update && stat > 1) {
728  update = kFALSE;
729  create = kTRUE;
730  stat = 1;
731  }
732 
733  if (stat == 1)
734  fWritable = kTRUE;
735  else
736  fWritable = kFALSE;
737 
738  Init(create);
739  return;
740 
741 zombie:
742  // error in file opening occured, make this object a zombie
743  MakeZombie();
744  SafeDelete(fSocket);
745  gDirectory = gROOT;
746 }
747 
748 ////////////////////////////////////////////////////////////////////////////////
749 /// Create a NetFile object using an existing connection (socket s).
750 /// Provided for use in TXNetFile.
751 /// See:
752 /// TNetFile::Create(const char *url, Option_t *option, Int_t netopt)
753 /// for details about the arguments.
754 
755 void TNetFile::Create(TSocket *s, Option_t *option, Int_t netopt)
756 {
757  // Import socket
758  fSocket = s;
759 
760  // Create the connection
761  Create(s->GetUrl(), option, netopt);
762 }
763 
764 ////////////////////////////////////////////////////////////////////////////////
765 /// Return kTRUE if 'url' matches the coordinates of this file.
766 /// Check the full URL, including port and FQDN.
767 
768 Bool_t TNetFile::Matches(const char *url)
769 {
770  // Run standard check on fUrl, first
771  Bool_t rc = TFile::Matches(url);
772  if (rc)
773  // Ok, standard check enough
774  return kTRUE;
775 
776  // Check also endpoint URL
777  TUrl u(url);
778  if (!strcmp(u.GetFile(),fEndpointUrl.GetFile())) {
779  // Candidate info
780  TString fqdn = u.GetHostFQDN();
781 
782  // Check ports
783  if (u.GetPort() == fEndpointUrl.GetPort()) {
784  TString fqdnref = fEndpointUrl.GetHostFQDN();
785  if (fqdn == fqdnref)
786  // Ok, coordinates match
787  return kTRUE;
788  }
789  }
790 
791  // Default is not matching
792  return kFALSE;
793 }
794 
795 //
796 // TNetSystem: the directory handler for net files
797 //
798 
799 ////////////////////////////////////////////////////////////////////////////////
800 /// Create helper class that allows directory access via rootd.
801 /// Use ftpowner = TRUE (default) if this instance is responsible
802 /// for cleaning of the underlying TFTP connection; this allows
803 /// to have control on the order of the final cleaning.
804 
805 TNetSystem::TNetSystem(Bool_t ftpowner)
806  : TSystem("-root", "Net file Helper System")
807 {
808  // name must start with '-' to bypass the TSystem singleton check
809  SetName("root");
810 
811  fDir = kFALSE;
812  fDirp = 0;
813  fFTP = 0;
814  fFTPOwner = ftpowner;
815  fUser = "";
816  fHost = "";
817  fPort = -1;
818  fIsLocal = kFALSE;
819 }
820 
821 ////////////////////////////////////////////////////////////////////////////////
822 /// Create helper class that allows directory access via rootd.
823 /// Use ftpowner = TRUE (default) if this instance is responsible
824 /// for cleaning of the underlying TFTP connection; this allows
825 /// to have control on the order of the final cleaning.
826 
827 TNetSystem::TNetSystem(const char *url, Bool_t ftpowner)
828  : TSystem("-root", "Net file Helper System")
829 {
830  // name must start with '-' to bypass the TSystem singleton check
831  SetName("root");
832 
833  fFTPOwner = ftpowner;
834  fIsLocal = kFALSE;
835  Create(url);
836 }
837 
838 ////////////////////////////////////////////////////////////////////////////////
839 /// Parse and save coordinates of the remote entity (user, host, port, ...)
840 
841 void TNetSystem::InitRemoteEntity(const char *url)
842 {
843  TUrl turl(url);
844 
845  // Remote username: local as default
846  fUser = turl.GetUser();
847  if (!fUser.Length()) {
848  UserGroup_t *u = gSystem->GetUserInfo();
849  if (u)
850  fUser = u->fUser;
851  delete u;
852  }
853 
854  // Check and save the host FQDN ...
855  fHost = turl.GetHostFQDN();
856 
857  // Remote port: the default should be 1094 because we are here
858  // only if the protocol is "root://"
859  fPort = turl.GetPort();
860 }
861 
862 ////////////////////////////////////////////////////////////////////////////////
863 /// Create a TNetSystem object.
864 
865 void TNetSystem::Create(const char *url, TSocket *sock)
866 {
867  // If we got here protocol must be at least its short form "^root.*:" :
868  // make sure that it is in the full form to avoid problems in TFTP
869  TString surl(url);
870  if (!surl.Contains("://")) {
871  surl.Insert(surl.Index(":")+1,"//");
872  }
873  TUrl turl(surl);
874 
875  fDir = kFALSE;
876  fDirp = 0;
877  fFTP = 0;
878 
879  // Check locality, taking into account possible prefixes
880  fLocalPrefix = "";
881  fIsLocal = kFALSE;
882  // We may have been asked explicitly to go through the daemon
883  Bool_t forceRemote = gEnv->GetValue("Path.ForceRemote", 0);
884  TString opts = TUrl(url).GetOptions();
885  if (opts.Contains("remote=1"))
886  forceRemote = kTRUE;
887  else if (opts.Contains("remote=0"))
888  forceRemote = kFALSE;
889  if (!forceRemote) {
890  if ((fIsLocal = TSystem::IsPathLocal(url))) {
891  fLocalPrefix = gEnv->GetValue("Path.Localroot","");
892  // Nothing more to do
893  return;
894  }
895  }
896 
897  // Fill in user, host, port
898  InitRemoteEntity(surl);
899 
900  // Build a TFTP url
901  if (fHost.Length()) {
902  TString eurl = "";
903  // First the protocol
904  if (strlen(turl.GetProtocol())) {
905  eurl = turl.GetProtocol();
906  eurl += "://";
907  } else
908  eurl = "root://";
909  // Add user, if specified
910  if (strlen(turl.GetUser())) {
911  eurl += turl.GetUser();
912  eurl += "@";
913  }
914  // Add host
915  eurl += fHost;
916  // Add port
917  eurl += ":";
918  eurl += turl.GetPort();
919 
920  fFTP = new TFTP(eurl, 1, TFTP::kDfltWindowSize, sock);
921  if (fFTP && fFTP->IsOpen()) {
922  if (fFTP->GetSocket()->GetRemoteProtocol() < 12) {
923  Error("Create",
924  "remote daemon does not support 'system' functionality");
925  fFTP->Close();
926  delete fFTP;
927  } else {
928  fUser = fFTP->GetSocket()->GetSecContext()->GetUser();
929  fHost = fFTP->GetSocket()->GetSecContext()->GetHost();
930  // If responsible for the TFTP connection, remove it from the
931  // socket global list to avoid problems with double deletion
932  // at final cleanup
933  if (fFTPOwner)
934  gROOT->GetListOfSockets()->Remove(fFTP);
935  }
936  }
937  }
938 }
939 
940 ////////////////////////////////////////////////////////////////////////////////
941 /// Destructor
942 
943 TNetSystem::~TNetSystem()
944 {
945  // Close FTP connection
946  if (fFTPOwner) {
947  if (fFTP) {
948  if (fFTP->IsOpen()) {
949 
950  // Close remote directory if still open
951  if (fDir) {
952  fFTP->FreeDirectory(kFALSE);
953  fDir = kFALSE;
954  }
955  fFTP->Close();
956  }
957  delete fFTP;
958  }
959  }
960  fDirp = 0;
961  fFTP = 0;
962 }
963 
964 ////////////////////////////////////////////////////////////////////////////////
965 /// Make a directory via rootd.
966 
967 Int_t TNetSystem::MakeDirectory(const char *dir)
968 {
969  // If local, use the local TSystem
970  if (fIsLocal) {
971  TString edir = TUrl(dir).GetFile();
972  if (!fLocalPrefix.IsNull())
973  edir.Insert(0, fLocalPrefix);
974  return gSystem->MakeDirectory(edir);
975  }
976 
977  if (fFTP && fFTP->IsOpen()) {
978  // Extract the directory name
979  TString edir = TUrl(dir).GetFile();
980  return fFTP->MakeDirectory(edir,kFALSE);
981  }
982  return -1;
983 }
984 
985 ////////////////////////////////////////////////////////////////////////////////
986 /// Open a directory and return an opaque pointer to a dir structure.
987 /// Returns nullptr in case of error.
988 
989 void *TNetSystem::OpenDirectory(const char *dir)
990 {
991  // If local, use the local TSystem
992  if (fIsLocal) {
993  TString edir = TUrl(dir).GetFile();
994  if (!fLocalPrefix.IsNull())
995  edir.Insert(0, fLocalPrefix);
996  return gSystem->OpenDirectory(edir);
997  }
998 
999  if (!fFTP || !fFTP->IsOpen())
1000  return nullptr;
1001 
1002  if (fDir) {
1003  if (gDebug > 0)
1004  Info("OpenDirectory", "a directory is already open: close it first");
1005  fFTP->FreeDirectory(kFALSE);
1006  fDir = kFALSE;
1007  }
1008 
1009  // Extract the directory name
1010  TString edir = TUrl(dir).GetFile();
1011 
1012  if (fFTP->OpenDirectory(edir,kFALSE)) {
1013  fDir = kTRUE;
1014  fDirp = (void *)&fDir;
1015  return fDirp;
1016  } else
1017  return nullptr;
1018 }
1019 
1020 ////////////////////////////////////////////////////////////////////////////////
1021 /// Free directory via rootd.
1022 
1023 void TNetSystem::FreeDirectory(void *dirp)
1024 {
1025  // If local, use the local TSystem
1026  if (fIsLocal) {
1027  gSystem->FreeDirectory(dirp);
1028  return;
1029  }
1030 
1031  if (dirp != fDirp) {
1032  Error("FreeDirectory", "invalid directory pointer (should never happen)");
1033  return;
1034  }
1035 
1036  if (fFTP && fFTP->IsOpen()) {
1037  if (fDir) {
1038  fFTP->FreeDirectory(kFALSE);
1039  fDir = kFALSE;
1040  fDirp = 0;
1041  }
1042  }
1043 }
1044 
1045 ////////////////////////////////////////////////////////////////////////////////
1046 /// Get directory entry via rootd. Returns 0 in case no more entries.
1047 
1048 const char *TNetSystem::GetDirEntry(void *dirp)
1049 {
1050  // If local, use the local TSystem
1051  if (fIsLocal) {
1052  return gSystem->GetDirEntry(dirp);
1053  }
1054 
1055  if (dirp != fDirp) {
1056  Error("GetDirEntry", "invalid directory pointer (should never happen)");
1057  return 0;
1058  }
1059 
1060  if (fFTP && fFTP->IsOpen() && fDir) {
1061  return fFTP->GetDirEntry(kFALSE);
1062  }
1063  return 0;
1064 }
1065 
1066 ////////////////////////////////////////////////////////////////////////////////
1067 /// Get info about a file. Info is returned in the form of a FileStat_t
1068 /// structure (see TSystem.h).
1069 /// The function returns 0 in case of success and 1 if the file could
1070 /// not be stat'ed.
1071 
1072 Int_t TNetSystem::GetPathInfo(const char *path, FileStat_t &buf)
1073 {
1074  // If local, use the local TSystem
1075  if (fIsLocal) {
1076  TString epath = TUrl(path).GetFile();
1077  if (!fLocalPrefix.IsNull())
1078  epath.Insert(0, fLocalPrefix);
1079  return gSystem->GetPathInfo(epath, buf);
1080  }
1081 
1082  if (fFTP && fFTP->IsOpen()) {
1083  // Extract the directory name
1084  TString epath = TUrl(path).GetFile();
1085  fFTP->GetPathInfo(epath, buf, kFALSE);
1086  return 0;
1087  }
1088  return 1;
1089 }
1090 
1091 ////////////////////////////////////////////////////////////////////////////////
1092 /// Returns FALSE if one can access a file using the specified access mode.
1093 /// Mode is the same as for the Unix access(2) function.
1094 /// Attention, bizarre convention of return value!!
1095 
1096 Bool_t TNetSystem::AccessPathName(const char *path, EAccessMode mode)
1097 {
1098  // If local, use the local TSystem
1099  if (fIsLocal) {
1100  TString epath = TUrl(path).GetFile();
1101  if (!fLocalPrefix.IsNull())
1102  epath.Insert(0, fLocalPrefix);
1103  return gSystem->AccessPathName(epath, mode);
1104  }
1105 
1106  if (fFTP && fFTP->IsOpen()) {
1107  // Extract the directory name
1108  TString epath = TUrl(path).GetFile();
1109  return fFTP->AccessPathName(epath, mode, kFALSE);
1110  }
1111  return kTRUE;
1112 }
1113 
1114 ////////////////////////////////////////////////////////////////////////////////
1115 /// Check consistency of this helper with the one required
1116 /// by 'path' or 'dirptr'.
1117 
1118 Bool_t TNetSystem::ConsistentWith(const char *path, void *dirptr)
1119 {
1120  // Standard check: only the protocol part of 'path' is required to match
1121  Bool_t checkstd = TSystem::ConsistentWith(path, dirptr);
1122  if (!checkstd) return kFALSE;
1123 
1124  // Require match of 'user' and 'host'
1125  Bool_t checknet = path ? kFALSE : kTRUE;
1126  if (path && strlen(path)) {
1127 
1128  // Get user name
1129  TUrl url(path);
1130  TString user = url.GetUser();
1131  if (user.IsNull() && !fUser.IsNull()) {
1132  UserGroup_t *u = gSystem->GetUserInfo();
1133  if (u)
1134  user = u->fUser;
1135  delete u;
1136  }
1137 
1138  // Get host name
1139  TString host = url.GetHostFQDN();
1140 
1141  // Get port
1142  Int_t port = url.GetPort();
1143  if (gDebug > 1)
1144  Info("ConsistentWith", "fUser:'%s' (%s), fHost:'%s' (%s), fPort:%d (%d)",
1145  fUser.Data(), user.Data(), fHost.Data(), host.Data(),
1146  fPort, port);
1147 
1148  if (user == fUser && host == fHost && port == fPort)
1149  checknet = kTRUE;
1150  }
1151 
1152  return (checkstd && checknet);
1153 }
1154 
1155 ////////////////////////////////////////////////////////////////////////////////
1156 /// Remove a path
1157 
1158 Int_t TNetSystem::Unlink(const char *path)
1159 {
1160  // If local, use the local TSystem
1161  if (fIsLocal) {
1162  TString epath = TUrl(path).GetFile();
1163  if (!fLocalPrefix.IsNull())
1164  epath.Insert(0, fLocalPrefix);
1165  return gSystem->Unlink(epath);
1166  }
1167 
1168  // Not implemented for rootd
1169  Warning("Unlink", "functionality not implemented - ignored (path: %s)", path);
1170  return -1;
1171 }