// @(#)root/castor:$Id$ // Author: Fons Rademakers + Jean-Damien Durand 17/09/2003 + Ben Couturier 31/05/2005 // + Giulia Taurelli 26/04/2006 /************************************************************************* * Copyright (C) 1995-2006, Rene Brun and Fons Rademakers. * * All rights reserved. * * * * For the licensing terms see $ROOTSYS/LICENSE. * * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ /** \class TCastorFile TCastorFile.cxx \ingroup IO A TNetFile interfaced to the Castor storage backend A TCastorFile is like a normal TNetFile except that it obtains the remote node (disk server) via the CASTOR API, once the disk server and the local file path are determined, the file will be accessed via the rootd daemon. File names have to be specified like: ~~~{.bash} castor:/castor/cern.ch/user/r/rdm/bla.root ~~~ If Castor 2.1 is used the file names can also be specified in the following ways: ~~~{.bash} castor://stager_host:stager_port/?path=/castor/cern.ch/user/r/rdm/bla.root&svcClass=MYSVCLASS&castorVersion=MYCASTORVERSION castor://stager_host/?path=/castor/cern.ch/user/r/rdm/bla.root&svcClass=MYSVCLASS&castorVersion=MYCASTORVERSION castor:///castor?path=/castor/cern.ch/user/r/rdm/bla.root&svcClass=MYSVCLASS&castorVersion=MYCASTORVERSION ~~~ The path is mandatory as parameter but all the other ones are optional. Use "&rootAuth=" in the option field to force the specified authentication protocol when contacting the server, e.g. ~~~{.bash} castor:///castor?path=/castor/cern.ch/user/r/rdm/bla.root&svcClass=MYSVCLASS&castorVersion=MYCASTORVERSION&rootAuth=3 ~~~ will try first the globus/GSI protocol; available protocols are -# passwd -# srp -# krb5 -# globus -# ssh -# uidgid The default is taken from the env ROOTCASTORAUTH. */ #include "NetErrors.h" #include "TCastorFile.h" #include "TError.h" #include #include #ifdef _WIN32 #include #include #endif #ifdef R__CASTOR2 #include // For the new CASTOR 2 Stager #endif #define RFIO_KERNEL // Get access to extra symbols in the headers #include // Dial with CASTOR stager #include // Util routines from rfio #include // Dial with CASTOR Name Server #include #include #define RFIO_USE_CASTOR_V2 "RFIO_USE_CASTOR_V2" #define RFIO_HSM_BASETYPE 0x0 #define RFIO_HSM_CNS RFIO_HSM_BASETYPE+1 extern "C" { int rfio_HsmIf_reqtoput (char *); } extern "C" { int rfio_parse(char *, char **, char **); } extern "C" { int rfio_HsmIf_IsHsmFile (const char *); } extern "C" { char *getconfent(char *, char *, int); } #ifdef R__CASTOR2 extern int tStageHostKey; extern int tStagePortKey; extern int tSvcClassKey; extern int tCastorVersionKey; extern "C" { int use_castor2_api(); } /// Function that checks whether we should use the old or new stager API. static int UseCastor2API() { int version = use_castor2_api(); return version; } #else // Function that checks whether we should use the old or new stager API. static int UseCastor2API() { char *p; if (((p = getenv(RFIO_USE_CASTOR_V2)) == 0) && ((p = getconfent("RFIO","USE_CASTOR_V2",0)) == 0)) { // Variable not set: compat mode return 0; } if ((strcmp(p,"YES") == 0) || (strcmp(p,"yes") == 0) || (atoi(p) == 1)) { // Variable set to yes or 1 but old CASTOR 1: compat mode + warning static int once = 0; if (!once) { ::Warning("UseCastor2API", "asked to use CASTOR 2, but linked with CASTOR 1"); once = 1; } return 0; } // Variable set but not to 1 : compat mode return 0; } #endif /// Determine the authentication protocol to be tried first from the url /// string or from defaults. The auth option, if any, is removed from 'url'. static const char *GetAuthProto(TString &url) { const Int_t rootNumSec = 6; const char *protoSec[rootNumSec] = {"rootup", "roots", "rootk", "rootg", "rooth", "rootug" }; TString p = url; Int_t ii = p.Index("&rootAuth="); if (ii != kNPOS) { Int_t jj = p.Index("&", ii+1); if (jj != kNPOS) p.Remove(jj); p.Remove(0,ii); url.ReplaceAll(p, ""); p.ReplaceAll("&rootAuth=", ""); } if (p.Length() <= 0) // Use defaults p = getenv("ROOTCASTORAUTH"); Int_t sec = -1; if (p.Length() > 0 && p.IsDigit()) { sec = p.Atoi(); if (sec < 0 || sec > (rootNumSec - 1)) sec = -1; } // Done return ((sec > -1 && sec < rootNumSec) ? protoSec[sec] : "root"); } ClassImp(TCastorFile) /////////////////////////////////////////////////////////////////////////////// /// Create a TCastorFile. A TCastorFile is like a normal TNetFile except /// that it obtains the remote node (disk server) via the CASTOR API, once /// the disk server and the local file path are determined, the file will /// be accessed via the rootd daemon. File names have to be specified like: /// castor:/castor/cern.ch/user/r/rdm/bla.root. /// The other arguments are the same as for TNetFile and TFile. TCastorFile::TCastorFile(const char *url, Option_t *option, const char *ftitle, Int_t compress, Int_t netopt) : TNetFile(url, ftitle, compress, kFALSE) { fIsCastor = kFALSE; fWrittenTo = kFALSE; // Extract the authentication info, if any; removing it from the options TString u(url); fAuthProto = GetAuthProto(u); fUrl.SetUrl(u); if (gDebug > 0) Info("TCastorFile","fAuthProto = %s, u: %s", fAuthProto.Data(), u.Data()); // file is always created by stage_out_hsm() and therefore // exists when opened by rootd TString opt = option; opt.ToUpper(); if (opt == "NEW" || opt == "CREATE") opt = "RECREATE"; Create(url, opt, netopt); } /////////////////////////////////////////////////////////////////////////////// /// Find the CASTOR disk server and internal file path. void TCastorFile::FindServerAndPath() { // just call rfio_parse and no extra parsing is added here to that TString castorturl; char *host=0; char *name=0; // to be able to use the turl starting with castor: if (!strcmp(fUrl.GetProtocol(),"castor")) castorturl.Form("%s://%s", "rfio", fUrl.GetFile()); else castorturl.Form("%s://%s", fUrl.GetProtocol(), fUrl.GetFile()); if (strlen(fUrl.GetOptions()) > 0) castorturl += TString::Format("?%s", fUrl.GetOptions()); // the complete turl in fname TString fname = castorturl; // for compatibility with rfio_parse interface if (::rfio_parse((char *)fname.Data(), &host, &name)>=0) { castorturl.Form("%s",(!name || !strstr(name,"/castor"))?fname.Data():name); fname = castorturl.Data(); } else { Error("FindServerAndPath", "error parsing %s", fUrl.GetUrl()); return; } if (!UseCastor2API()) { struct stgcat_entry *stcp_output = 0; if (rfio_HsmIf_IsHsmFile(fUrl.GetFile()) == RFIO_HSM_CNS) { // This is a CASTOR file int flags = O_RDONLY; struct Cns_filestat st; int rc; char stageoutbuf[1025]; char stageerrbuf[1025]; // Check with internal stage limits - preventing overflow afterwards if (strlen(fUrl.GetFile()) > STAGE_MAX_HSMLENGTH) { serrno = ENAMETOOLONG; Error("FindServerAndPath", "can't open %s, error %d (%s)", fUrl.GetFile(), serrno, sstrerror(serrno)); return; } // Prepare the flags if (fOption == "CREATE" || fOption == "RECREATE" || fOption == "UPDATE") flags |= O_RDWR; if (fOption == "CREATE" || fOption == "RECREATE") flags |= O_CREAT | O_TRUNC; // Check if an existing file is going to be updated memset(&st, 0, sizeof(st)); rc = Cns_stat(fUrl.GetFile(), &st); // Make sure that filesize is 0 if file doesn't exist // or that we will create (stage_out) if O_TRUNC. if (rc == -1 || ((flags & O_TRUNC) != 0)) st.filesize = 0; // Makes sure stage api does not write automatically to stdout/stderr if (stage_setoutbuf(stageoutbuf, 1024) != 0) { Error("FindServerAndPath", "can't open %s, stage_setoutbuf, error %d (%s)", fUrl.GetFile(), serrno, sstrerror(serrno)); return; } if (stage_seterrbuf(stageerrbuf, 1024) != 0) { Error("FindServerAndPath", "can't open %s, stage_seterrbuf, error %d (%s)", fUrl.GetFile(), serrno, sstrerror(serrno)); return; } struct stgcat_entry stcp_input; int nstcp_output; memset(&stcp_input, 0, sizeof(struct stgcat_entry)); strlcpy(stcp_input.u1.h.xfile, fUrl.GetFile(), sizeof(stcp_input.u1.h.xfile)); if (flags == O_RDONLY || st.filesize > 0) { // Do a recall if (stage_in_hsm((u_signed64) 0, // Ebusy is possible... (int) flags, // open flags (char *) 0, // hostname (char *) 0, // pooluser (int) 1, // nstcp_input (struct stgcat_entry *) &stcp_input, // stcp_input (int *) &nstcp_output, // nstcp_output (struct stgcat_entry **) &stcp_output, // stcp_output (int) 0, // nstpp_input (struct stgpath_entry *) 0 // stpp_input ) != 0) { Error("FindServerAndPath", "can't open %s, stage_in_hsm error %d (%s)", fUrl.GetFile(), serrno, sstrerror(serrno)); return; } } else { // Do a creation if (stage_out_hsm((u_signed64) 0, // Ebusy is possible... (int) flags, // open flags (mode_t) 0666, // open mode (c.f. also umask) // Note: This is S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH, c.f. fopen(2) (char *) 0, // hostname (char *) 0, // pooluser (int) 1, // nstcp_input (struct stgcat_entry *) &stcp_input, // stcp_input (int *) &nstcp_output, // nstcp_output (struct stgcat_entry **) &stcp_output, // stcp_output (int) 0, // nstpp_input (struct stgpath_entry *) 0 // stpp_input ) != 0) { Error("FindServerAndPath", "can't open %s, stage_out_hsm error %d (%s)", fUrl.GetFile(), serrno, sstrerror(serrno)); return; } } if ((nstcp_output != 1) || (stcp_output == 0) || (*(stcp_output->ipath) == '\0')) { // Impossible serrno = SEINTERNAL; if (stcp_output != 0) free(stcp_output); Error("FindServerAndPath", "can't open %s, error %d (%s)", fUrl.GetFile(), serrno, sstrerror(serrno)); return; } // Parse orig string to get disk server host char *filename; char *realhost = 0; rfio_parse(stcp_output->ipath, &realhost, &filename); if (realhost == 0) { serrno = SEINTERNAL; Error("FindServerAndPath", "can't open %s, get disk server hostname from %s error %d (%s)", fUrl.GetFile(), stcp_output->ipath, errno, sstrerror(serrno)); free(stcp_output); return; } // Save real host and internal path fDiskServer = realhost; if (filename[0] != '/') { // Make file 'local' to the host fInternalPath = "/"; fInternalPath += filename; } else { fInternalPath = filename; } if (st.filesize == 0) { // Will force notification to stage when the file is closed fWrittenTo = kTRUE; } } // Set the protocol prefix for TNetFile. // For the cern.ch domain we set the default authentication // method to UidGid, i.e. as for rfiod, unless there is a specific // request (from options or envs); for this we need // the full FQDN or address in "nnn.mmm.iii.jjj" form TString r = fAuthProto; if (fAuthProto == "root") { TString fqdn; TInetAddress addr = gSystem->GetHostByName(fDiskServer); if (addr.IsValid()) { fqdn = addr.GetHostName(); if (fqdn.EndsWith(".cern.ch") || fqdn.BeginsWith("137.138.")) r = "rootug://"; else r = "root://"; } else r = "root://"; } else { // Fix the format r += "://"; } // Update fUrl with new path r += fDiskServer + "/"; r += fInternalPath; TUrl rurl(r); fUrl = rurl; if (gDebug > 0) Info("FindServerAndPath"," fDiskServer: %s, r: %s", fDiskServer.Data(), r.Data()); // Now ipath is not null and contains the real internal path on the disk // server 'host', e.g. it is fDiskServer:fInternalPath fInternalPath = stcp_output==0?0:stcp_output->ipath; if (stcp_output) free(stcp_output); } else { #ifdef R__CASTOR2 // We use the new stager API // I use fname which has the Turl already parsed correctly int flags = O_RDONLY; int rc; struct stage_io_fileresp *response = 0; char *requestId = 0, *url = 0; char stageerrbuf[1025]; // Prepare the flags if (fOption == "CREATE" || fOption == "RECREATE" || fOption == "UPDATE") flags |= O_RDWR; if (fOption == "CREATE" || fOption == "RECREATE") flags |= O_CREAT | O_TRUNC; stage_seterrbuf(stageerrbuf, 1024); int* auxVal; char ** auxPoint; struct stage_options opts; opts.stage_host=0; opts.stage_port=0; opts.service_class=0; opts.stage_version=0; void *ptrPoint = &auxPoint; void *ptrVal = &auxVal; int ret=Cglobals_get(& tStageHostKey, (void**)ptrPoint,sizeof(void*)); if(ret==0){ opts.stage_host=*auxPoint; } ret=Cglobals_get(& tStagePortKey, (void**)ptrVal,sizeof(int)); if(ret==0){ opts.stage_port=*auxVal; } opts.stage_version=2; ret=Cglobals_get(& tSvcClassKey, (void**)ptrPoint,sizeof(void*)); if (ret==0){ opts.service_class=*auxPoint; } // in stage_open I use the fname which is the result of the rfio_parsing rc = stage_open(0, MOVER_PROTOCOL_ROOT, (char *)fname.Data(), flags, (mode_t) 0666, 0, &response, &requestId, &opts); // global values used as options if (rc != 0) { Error("FindServerAndPath", "stage_open failed: %s (%s)", sstrerror(serrno), stageerrbuf); if (response) free(response); if (requestId) free(requestId); return; } if (response == 0) { Error("FindServerAndPath", "response was null for %s (Request %s) %d/%s", fname.Data(), requestId, serrno, sstrerror(serrno)); if (requestId) free(requestId); return; } if (response->errorCode != 0) { serrno = response->errorCode; Error("FindServerAndPath", "error getting file %s (Request %s) %d/%s", fname.Data(), requestId, serrno, sstrerror(serrno)); free(response); if (requestId) free(requestId); return; } url = stage_geturl(response); if (url == 0) { Error("FindServerAndPath", "error getting file %s (Request %s) %d/%s", fname.Data(), requestId, serrno, sstrerror(serrno)); free(response); if (requestId) free(requestId); return; } TUrl rurl(url); // Set the protocol prefix for TNetFile. // For the cern.ch domain we set the default authentication // method to UidGid, i.e. as for rfiod, unless there is a specific // request (from options or envs); for this we need // the full FQDN or address in "nnn.mmm.iii.jjj" form TString p = fAuthProto; if (fAuthProto == "root") { TString fqdn = rurl.GetHostFQDN(); if (fqdn.EndsWith(".cern.ch") || fqdn.BeginsWith("137.138.")) fAuthProto = "rootug"; } // Update protocol and fUrl rurl.SetProtocol(fAuthProto); fUrl = rurl; if (response) free(response); if (url) free(url); if (requestId) free(requestId); #endif } fIsCastor = kTRUE; } /////////////////////////////////////////////////////////////////////////////// /// Close currently open file. Int_t TCastorFile::SysClose(Int_t fd) { Int_t r = TNetFile::SysClose(fd); if (!UseCastor2API()) { if (fIsCastor && fWrittenTo) { #ifndef R__CASTOR2 // CASTOR file was created or modified rfio_HsmIf_reqtoput((char *)fInternalPath.Data()); #endif fWrittenTo = kFALSE; } } return r; } /////////////////////////////////////////////////////////////////////////////// /// Write specified byte range to remote file via rootd daemon. /// Returns kTRUE in case of error. Bool_t TCastorFile::WriteBuffer(const char *buf, Int_t len) { if (TNetFile::WriteBuffer(buf, len)) return kTRUE; if (!UseCastor2API()) { if (fIsCastor && !fWrittenTo && len > 0) { stage_hsm_t hsmfile; // Change status of file in stage catalog from STAGED to STAGEOUT memset(&hsmfile, 0, sizeof(hsmfile)); hsmfile.upath = StrDup(fInternalPath); if (stage_updc_filchg(0, &hsmfile) < 0) { Error("WriteBuffer", "error calling stage_updc_filchg"); delete [] hsmfile.upath; return kTRUE; } delete [] hsmfile.upath; fWrittenTo = kTRUE; } } return kFALSE; } /////////////////////////////////////////////////////////////////////////////// /// Connect to remote rootd server on CASTOR disk server. void TCastorFile::ConnectServer(Int_t *stat, EMessageTypes *kind, Int_t netopt, Int_t tcpwindowsize, Bool_t forceOpen, Bool_t forceRead) { FindServerAndPath(); // Continue only if successful if (fIsCastor) { TNetFile::ConnectServer(stat, kind, netopt, tcpwindowsize, forceOpen, forceRead); } else { // Failure: fill these to signal it to TNetFile *stat = kErrFileOpen; *kind = kROOTD_ERR; } }